This commit is contained in:
2026-02-23 22:29:43 +08:00
parent e1627eb48c
commit 07388db632
6 changed files with 175 additions and 81 deletions

View File

@@ -47,18 +47,6 @@
<span class="loading-text">加载中...</span>
</div>
<!-- 空状态 -->
<div v-else-if="!loading && allPrompts.length === 0" class="prompt-empty-state">
<div class="prompt-empty-icon">
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect>
<circle cx="8.5" cy="8.5" r="1.5"></circle>
<polyline points="21 15 16 10 5 21"></polyline>
</svg>
</div>
<p class="prompt-empty-text">没有找到文案风格</p>
</div>
<!-- 更多提示词弹窗 -->
<div v-if="showAllPromptsModal" class="prompt-modal-mask" @click.self="showAllPromptsModal = false">
<div class="prompt-modal">

View File

@@ -1,12 +1,13 @@
<script setup>
import { ref, reactive, computed, onMounted } from 'vue'
import { onMounted, reactive, ref } from 'vue'
import { message } from 'ant-design-vue'
import { CommonService } from '@/api/common'
import { UserPromptApi } from '@/api/userPrompt'
import { useUserStore } from '@/stores/user'
import PromptSelector from '@/components/PromptSelector.vue'
import { getVoiceText } from '@gold/hooks/web/useVoiceText'
import TikhubService, { InterfaceType, MethodType, ParamType } from '@/api/tikhub'
import { UserPromptApi } from '@/api/userPrompt'
import PromptSelector from '@/components/PromptSelector.vue'
import { useUserStore } from '@/stores/user'
import { getVoiceText } from '@gold/hooks/web/useVoiceText'
defineOptions({ name: 'ForecastView' })
@@ -45,31 +46,40 @@ const loadingPrompts = ref(false)
const promptSearchKeyword = ref('')
// 工具函数
const formatNumber = (num) => {
function formatNumber(num) {
if (!num) return '0'
return num >= 10000 ? `${(num / 10000).toFixed(1)}w` : num.toString()
return num >= 10000 ? `${(num / 10000).toFixed(1)}w` : String(num)
}
const truncateTitle = (title, maxLength = 28) => {
function truncateTitle(title, maxLength = 28) {
if (!title) return ''
return title.length <= maxLength ? title : `${title.substring(0, maxLength)}...`
}
const handleImageError = (event) => {
function handleImageError(event) {
event.target.style.display = 'none'
}
const openVideo = (topic, event) => {
function openVideo(topic, event) {
event.stopPropagation()
if (topic.videoUrl) window.open(topic.videoUrl, '_blank')
}
const handleSearchKeypress = (event) => {
function handleSearchKeypress(event) {
if (event.key === 'Enter' && !isLoading.value) {
handleSearch()
}
}
async function copyContent() {
try {
await navigator.clipboard.writeText(generatedContent.value)
message.success('已复制')
} catch {
message.error('复制失败')
}
}
// 提示词管理
async function loadUserPrompts() {
if (!userStore.userId) {
@@ -177,22 +187,12 @@ async function handleGenerate() {
const ctrl = new AbortController()
let fullText = ''
let errorOccurred = false
let isResolved = false
let completed = false
await new Promise((resolve, reject) => {
let timeout = null
const cleanup = () => {
if (timeout) {
clearTimeout(timeout)
timeout = null
}
}
timeout = setTimeout(() => {
if (!isResolved) {
cleanup()
const timeout = setTimeout(() => {
if (!completed) {
completed = true
ctrl.abort()
reject(new Error('请求超时'))
}
@@ -202,25 +202,21 @@ async function handleGenerate() {
data: requestData,
ctrl,
onMessage: (event) => {
if (errorOccurred || !event?.data) return
if (completed || !event?.data) return
const dataStr = event.data.trim()
if (dataStr === '[DONE]') {
cleanup()
if (!isResolved) {
isResolved = true
resolve()
}
completed = true
clearTimeout(timeout)
resolve()
return
}
if (dataStr.startsWith('[TIMEOUT]')) {
cleanup()
if (!isResolved) {
errorOccurred = true
isResolved = true
reject(new Error(dataStr.replace('[TIMEOUT]', '').trim() || '请求超时'))
}
completed = true
clearTimeout(timeout)
reject(new Error(dataStr.replace('[TIMEOUT]', '').trim() || '请求超时'))
return
}
@@ -231,7 +227,7 @@ async function handleGenerate() {
fullText += piece
generatedContent.value = fullText
}
} catch (e) {
} catch {
if (event.data && !event.data.startsWith('[')) {
fullText += event.data
generatedContent.value = fullText
@@ -239,20 +235,18 @@ async function handleGenerate() {
}
},
onError: (err) => {
cleanup()
if (!isResolved) {
errorOccurred = true
isResolved = true
if (!completed) {
completed = true
clearTimeout(timeout)
ctrl.abort()
const errorMsg = err?.message || '网络请求失败'
message.error(errorMsg)
reject(new Error(errorMsg))
message.error(err?.message || '网络请求失败')
reject(new Error(err?.message || '网络请求失败'))
}
},
onClose: () => {
cleanup()
if (!isResolved) {
isResolved = true
if (!completed) {
completed = true
clearTimeout(timeout)
resolve()
}
}
@@ -273,11 +267,11 @@ async function handleGenerate() {
function extractAudioUrl(video) {
const urlList = video?.play_addr?.url_list
if (Array.isArray(urlList) && urlList.length > 0) {
const lastUrl = urlList[urlList.length - 1]
const firstUrl = urlList[0]
return (lastUrl && lastUrl.trim()) || (firstUrl && firstUrl.trim()) || ''
const lastUrl = urlList[urlList.length - 1]?.trim()
const firstUrl = urlList[0]?.trim()
return lastUrl || firstUrl || ''
}
return (video?.play_addr?.url && video.play_addr.url.trim()) || ''
return video?.play_addr?.url?.trim() || ''
}
function extractCover(video) {
@@ -369,12 +363,12 @@ async function handleSearch() {
}
// 初始化
onMounted(async () => {
onMounted(() => {
if (userStore.userId) {
await loadUserPrompts()
loadUserPrompts()
} else if (userStore.isLoggedIn) {
setTimeout(async () => {
if (userStore.userId) await loadUserPrompts()
setTimeout(() => {
if (userStore.userId) loadUserPrompts()
}, 500)
}
})
@@ -608,7 +602,7 @@ onMounted(async () => {
<span class="result-label">生成结果</span>
<button
class="copy-btn"
@click="navigator.clipboard.writeText(generatedContent); message.success('已复制')"
@click="copyContent"
>
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<rect x="9" y="9" width="13" height="13" rx="2"/>