@@ -9,8 +9,8 @@
< div class = "left-panel-content" >
< CreationTabs active = "novel-comic" / >
< div class = "form-card" >
< h3 class = "form-card -title" > 创作设置 < / h3 >
< div >
< h3 class = "form-section -title" > 创作设置 < / h3 >
<!-- 主题 ( 选填 ) -- >
< div class = "form-group" >
@@ -22,7 +22,7 @@
v -model :value = "form.theme"
placeholder = "例如:都市奇幻、校园恋爱、科幻冒险..."
:maxlength = "100"
show -count
@input ="checkThemeLength"
/ >
< / div >
@@ -37,7 +37,7 @@
placeholder = "描述故事发生的世界观、时代背景、环境设定等..."
:rows = "4"
:maxlength = "2000"
show -count
@input ="checkStoryBackgroundLength"
/ >
< / div >
@@ -84,7 +84,7 @@
placeholder = "输入完整的故事文案/剧本内容, AI 将基于此生成漫剧..."
:rows = "8"
:maxlength = "10000"
show -count
@input ="checkStoryScriptLength"
/ >
< / div >
@@ -163,35 +163,64 @@
< / div >
<!-- 历史记录 -- >
< div class = "history-card" v-if = "historyList.length > 0" >
< div class = "history-card" >
< h3 class = "form-card-title" > 历史记录 < / h3 >
< div class = "history-list" >
< div class = "history-list" v-if = "historyList.length > 0" >
< div
v-for = "item in historyList"
: key = "item.id || item.taskId"
class = "history-item"
@click ="viewHistoryItem(item)"
>
< div class = "history-item-top" >
< span class = "history-status" :class = "getHistoryStatusClass(item.status)" >
{ { getHistoryStatusText ( item . status ) } }
< / span >
< span class = "history-date" > { { formatDate ( item . createdAt ) } } < / span >
< span class = "history-date" > { { formatFriendly Date ( item . createdAt ) } } < / span >
< / div >
< div class = "history-theme" v-if = "item.theme" > {{ item.theme }} < / div >
< div class = "history-preview-text" >
{ { truncateText ( item . storyBackground || item . storyScript || '' , 80 ) } }
< / div >
<!-- 展开详情 -- >
< div class = "history-expand" v-if = "expandedItemId === item.id" >
< div class = "history-detail-section" >
< div class = "history-detail-label" > 故事背景 < / div >
< div class = "history-detail-content" > { { item . storyBackground || '-' } } < / div >
< / div >
< div class = "history-detail-section" >
< div class = "history-detail-label" > 故事文案 < / div >
< div class = "history-detail-content" > { { item . storyScript || '-' } } < / div >
< / div >
< / div >
< div class = "history-item-actions" >
< a-button size = "small" @click.stop ="toggleExpand(item)" >
{{ expandedItemId = = = item.id ? ' 收起 ' : ' 展开 ' }}
< / a -button >
< a-button size = "small" @click.stop ="reuseHistory(item)" > 复用设置 < / a -button >
< a-button size = "small" type = "primary" v-if = "item.status === 'SUCCESS' || item.status === 'COMPLETED'" @click.stop="viewHistoryItem(item)" >
< CopyOutlined / > 查看结果
< / a-button >
< a-button size = "small" danger @click.stop ="handleDeleteHistory(item)" >
删除
< / a -button >
< / div >
< / div >
< / div >
<!-- 空状态 -- >
< div class = "history-empty" v-else-if = "!loadingHistory" >
< div class = "empty-illustration" > 📝 < / div >
< p class = "empty-title" > 暂无历史记录 < / p >
< p class = "empty-desc" > 完成创作后 , 记录将显示在这里 < / p >
< / div >
<!-- 加载中 -- >
< div class = "history-loading" v-else >
< LoadingOutlined class = "is-loading" / >
< span > 加载中 ... < / span >
< / div >
<!-- 加载更多 -- >
< div class = "history-load-more" v-if = "hasMoreHistory" >
< div class = "history-load-more" v-if = "hasMoreHistory && historyList.length > 0 " >
< a -button size = "small" :loading = "loadingHistory" @click ="loadMoreHistory" > 加载更多 < / a -button >
< / div >
< / div >
@@ -225,11 +254,11 @@
< script setup >
import { ref , computed , onMounted } from 'vue'
import { message } from 'ant-design-vue'
import { message , Modal } from 'ant-design-vue'
import { useRouter } from 'vue-router'
import { LoadingOutlined , CopyOutlined , CustomerServiceOutlined , CloseOutlined } from '@ant-design/icons-vue'
import { useUserStore } from '@/stores/user'
import { createNovelComicTask , getNovelComicTaskStatus , getNovelComicHistory } from '@/api/novelComic'
import { createNovelComicTask , getNovelComicTaskStatus , getNovelComicHistory , deleteNovelComicHistory } from '@/api/novelComic'
import { getUserSubscriptionInfo } from '@/api/payments'
import CreationHeader from '@/components/CreationHeader.vue'
import CreationTabs from '@/components/CreationTabs.vue'
@@ -362,6 +391,8 @@ const handleSubmit = async () => {
// 异步任务:开始轮询状态
polling . value = true
startPolling ( taskId )
// 刷新历史记录
loadHistory ( )
} else {
taskResult . value = {
status : 'FAILED' ,
@@ -469,11 +500,35 @@ const resetForm = () => {
taskResult . value = null
}
// 字数限制检查
const THEME _MAX _LENGTH = 100
const STORY _BACKGROUND _MAX _LENGTH = 2000
const STORY _SCRIPT _MAX _LENGTH = 10000
const checkThemeLength = ( e ) => {
if ( e . target . value . length >= THEME _MAX _LENGTH ) {
message . warning ( ` 主题最多 ${ THEME _MAX _LENGTH } 个字 ` )
}
}
const checkStoryBackgroundLength = ( e ) => {
if ( e . target . value . length >= STORY _BACKGROUND _MAX _LENGTH ) {
message . warning ( ` 故事背景最多 ${ STORY _BACKGROUND _MAX _LENGTH } 个字 ` )
}
}
const checkStoryScriptLength = ( e ) => {
if ( e . target . value . length >= STORY _SCRIPT _MAX _LENGTH ) {
message . warning ( ` 故事文案最多 ${ STORY _SCRIPT _MAX _LENGTH } 个字 ` )
}
}
// ===== 历史记录 =====
const historyList = ref ( [ ] )
const loadingHistory = ref ( false )
const historyPage = ref ( 0 )
const hasMoreHistory = ref ( false )
const expandedItemId = ref ( null )
const loadHistory = async ( page = 0 ) => {
loadingHistory . value = true
@@ -488,7 +543,6 @@ const loadHistory = async (page = 0) => {
historyList . value . push ( ... items )
}
historyPage . value = page
// 判断是否还有更多
const totalElements = data . data . totalElements || 0
hasMoreHistory . value = historyList . value . length < totalElements
}
@@ -530,11 +584,47 @@ const formatDate = (dateStr) => {
return ` ${ month } - ${ day } ${ hour } : ${ min } `
}
const formatFriendlyDate = ( dateStr ) => {
if ( ! dateStr ) return ''
const date = new Date ( dateStr )
const now = new Date ( )
const diff = now - date
const oneDay = 24 * 60 * 60 * 1000
const oneHour = 60 * 60 * 1000
const oneMinute = 60 * 1000
if ( diff < oneMinute ) {
return '刚刚'
} else if ( diff < oneHour ) {
const minutes = Math . floor ( diff / oneMinute )
return ` ${ minutes } 分钟前 `
} else if ( diff < oneDay ) {
const hours = Math . floor ( diff / oneHour )
return ` ${ hours } 小时前 `
} else if ( diff < 2 * oneDay ) {
return '昨天'
} else if ( diff < 7 * oneDay ) {
const days = Math . floor ( diff / oneDay )
return ` ${ days } 天前 `
} else {
return formatDate ( dateStr )
}
}
const truncateText = ( text , maxLen ) => {
if ( ! text ) return '暂无描述'
return text . length > maxLen ? text . slice ( 0 , maxLen ) + '...' : text
}
// 展开/收起历史记录
const toggleExpand = ( item ) => {
if ( expandedItemId . value === item . id ) {
expandedItemId . value = null
} else {
expandedItemId . value = item . id
}
}
// 查看历史结果
const viewHistoryItem = ( item ) => {
const s = ( item . status || '' ) . toUpperCase ( )
@@ -560,6 +650,34 @@ const reuseHistory = (item) => {
message . success ( '已填充历史设置' )
}
// 删除历史记录
const handleDeleteHistory = ( item ) => {
Modal . confirm ( {
title : '确认删除' ,
content : ` 确定要删除这条历史记录吗? ` ,
okText : '删除' ,
cancelText : '取消' ,
okType : 'danger' ,
onOk : async ( ) => {
try {
const response = await deleteNovelComicHistory ( item . id )
if ( response . data ? . success ) {
message . success ( '删除成功' )
historyList . value = historyList . value . filter ( i => i . id !== item . id )
if ( expandedItemId . value === item . id ) {
expandedItemId . value = null
}
} else {
message . error ( response . data ? . message || '删除失败' )
}
} catch ( error ) {
console . error ( '删除历史记录失败:' , error )
message . error ( '删除失败,请重试' )
}
}
} )
}
onMounted ( ( ) => {
checkMembership ( )
loadHistory ( )
@@ -640,7 +758,6 @@ onMounted(() => {
gap : 20 px ;
}
. form - card ,
. result - card {
background : var ( -- bg - surface ) ;
border - radius : 20 px ;
@@ -649,7 +766,7 @@ onMounted(() => {
box - shadow : 0 2 px 12 px rgba ( 124 , 58 , 237 , 0.04 ) ;
}
. form - card - title {
. form - section - title {
font - size : 1.1 rem ;
font - weight : 700 ;
margin : 0 0 24 px 0 ;
@@ -658,7 +775,7 @@ onMounted(() => {
padding - bottom : 12 px ;
}
. form - card - title : : after {
. form - section - title : : after {
content : '' ;
position : absolute ;
bottom : 0 ;
@@ -1101,6 +1218,75 @@ onMounted(() => {
. history - item - actions {
display : flex ;
gap : 8 px ;
margin - top : 12 px ;
}
/* 展开区域 */
. history - expand {
margin - top : 12 px ;
padding - top : 12 px ;
border - top : 1 px dashed var ( -- border - subtle ) ;
}
. history - detail - section {
margin - bottom : 12 px ;
}
. history - detail - section : last - child {
margin - bottom : 0 ;
}
. history - detail - label {
font - size : 12 px ;
font - weight : 600 ;
color : var ( -- text - tertiary ) ;
margin - bottom : 4 px ;
}
. history - detail - content {
font - size : 13 px ;
color : var ( -- text - secondary ) ;
line - height : 1.6 ;
background : var ( -- bg - base ) ;
padding : 10 px 12 px ;
border - radius : 10 px ;
word - break : break - word ;
}
/* 空状态和加载中 */
. history - empty ,
. history - loading {
text - align : center ;
padding : 40 px 20 px ;
display : flex ;
flex - direction : column ;
align - items : center ;
gap : 12 px ;
}
. history - empty . empty - illustration ,
. history - loading . empty - illustration {
font - size : 48 px ;
opacity : 0.6 ;
}
. history - empty . empty - title ,
. history - loading . empty - title {
font - size : 16 px ;
font - weight : 600 ;
color : var ( -- text - secondary ) ;
margin : 0 ;
}
. history - empty . empty - desc ,
. history - loading . empty - desc {
font - size : 13 px ;
color : var ( -- text - tertiary ) ;
margin : 0 ;
}
. history - loading . is - loading {
font - size : 28 px ;
}
. history - load - more {