203 lines
7.5 KiB
TypeScript
203 lines
7.5 KiB
TypeScript
/**
|
||
* AI 创作全局状态(Pinia Store)
|
||
*
|
||
* 敏感信息(phone/orgId/appSecret)不再存储在 localStorage
|
||
* orgId 仅存 sessionStorage(会话级),sessionToken 判断是否已初始化
|
||
*/
|
||
import { defineStore } from 'pinia'
|
||
import { ref } from 'vue'
|
||
import { clearExtractDraft } from '@/utils/aicreate/extractDraft'
|
||
|
||
export const useAicreateStore = defineStore('aicreate', () => {
|
||
// ─── 认证信息(不再存储敏感信息到 localStorage) ───
|
||
const orgId = ref(sessionStorage.getItem('le_orgId') || '')
|
||
const sessionToken = ref(sessionStorage.getItem('le_sessionToken') || '')
|
||
|
||
// ─── 创作流程数据 ───
|
||
const imageUrl = ref('')
|
||
const extractId = ref('')
|
||
const characters = ref<any[]>([])
|
||
const selectedCharacter = ref<any>(null)
|
||
const selectedStyle = ref('')
|
||
const storyData = ref<any>(null)
|
||
const workId = ref('')
|
||
/** extract 接口可能返回的 workId,供下游使用 */
|
||
const originalWorkId = ref('')
|
||
const workDetail = ref<any>(null)
|
||
|
||
// ─── Tab 切换状态保存 ───
|
||
const lastCreateRoute = ref('')
|
||
|
||
// ─── 方法 ───
|
||
function setSession(id: string, token: string) {
|
||
orgId.value = id
|
||
sessionToken.value = token
|
||
sessionStorage.setItem('le_orgId', id)
|
||
sessionStorage.setItem('le_sessionToken', token)
|
||
}
|
||
|
||
function clearSession() {
|
||
sessionToken.value = ''
|
||
orgId.value = ''
|
||
sessionStorage.removeItem('le_sessionToken')
|
||
sessionStorage.removeItem('le_orgId')
|
||
}
|
||
|
||
function setLastCreateRoute(path: string) {
|
||
lastCreateRoute.value = path
|
||
}
|
||
|
||
function clearLastCreateRoute() {
|
||
lastCreateRoute.value = ''
|
||
}
|
||
|
||
function reset() {
|
||
imageUrl.value = ''
|
||
extractId.value = ''
|
||
characters.value = []
|
||
selectedCharacter.value = null
|
||
selectedStyle.value = ''
|
||
storyData.value = null
|
||
workId.value = ''
|
||
originalWorkId.value = ''
|
||
workDetail.value = null
|
||
lastCreateRoute.value = ''
|
||
// 只清除创作流程数据,保留认证信息
|
||
localStorage.removeItem('le_workId')
|
||
// 清除 sessionStorage 中的恢复数据
|
||
sessionStorage.removeItem('le_recovery')
|
||
clearExtractDraft()
|
||
}
|
||
|
||
function saveRecoveryState() {
|
||
const recovery = {
|
||
path: window.location.pathname || '/',
|
||
workId: workId.value || localStorage.getItem('le_workId') || '',
|
||
imageUrl: imageUrl.value || '',
|
||
extractId: extractId.value || '',
|
||
selectedStyle: selectedStyle.value || '',
|
||
savedAt: Date.now()
|
||
}
|
||
sessionStorage.setItem('le_recovery', JSON.stringify(recovery))
|
||
}
|
||
|
||
/**
|
||
* 开发模式:填充一份 mock 数据,用于跳过真实后端调用走通 UI 流程
|
||
* 仅供开发期 UI 调试使用,不要在生产逻辑中调用
|
||
* @param count 要 mock 的角色数量(1-3),默认 3
|
||
*/
|
||
function fillMockData(count: number = 3) {
|
||
// 纯渐变占位图(不带文字,模拟真实 AI 抠图返回的角色形象)
|
||
const mockSvg = (hue: number) =>
|
||
'data:image/svg+xml;charset=utf-8,' + encodeURIComponent(
|
||
`<svg xmlns="http://www.w3.org/2000/svg" width="240" height="240" viewBox="0 0 240 240">` +
|
||
`<defs><linearGradient id="g" x1="0" y1="0" x2="1" y2="1">` +
|
||
`<stop offset="0" stop-color="hsl(${hue},75%,72%)"/>` +
|
||
`<stop offset="1" stop-color="hsl(${(hue + 35) % 360},80%,58%)"/>` +
|
||
`</linearGradient></defs>` +
|
||
`<rect width="240" height="240" fill="url(#g)"/>` +
|
||
`</svg>`
|
||
)
|
||
|
||
imageUrl.value = mockSvg(250)
|
||
extractId.value = 'mock-extract-' + Date.now()
|
||
selectedCharacter.value = null
|
||
|
||
// 注意:真实 AI 接口不返回 name 字段,mock 数据也不写 name,由用户在 StoryInputView 自己起名
|
||
const allChars = [
|
||
{ charId: 'mock-c1', type: 'HERO', originalCropUrl: mockSvg(280) },
|
||
{ charId: 'mock-c2', type: 'SIDEKICK', originalCropUrl: mockSvg(30) },
|
||
{ charId: 'mock-c3', type: 'SIDEKICK', originalCropUrl: mockSvg(100) },
|
||
]
|
||
const n = Math.max(1, Math.min(count, allChars.length))
|
||
characters.value = allChars.slice(0, n)
|
||
}
|
||
|
||
/**
|
||
* 开发模式:填充一份完整的 mock 作品数据,用于跳过真实 AI 生成走通预览/编辑/发布等下游 UI
|
||
* 仅供开发期 UI 调试使用
|
||
*/
|
||
function fillMockWorkDetail() {
|
||
// 16:9 渐变占位图(800x450),模拟真实绘本插画
|
||
const mockPage = (hue: number) =>
|
||
'data:image/svg+xml;charset=utf-8,' + encodeURIComponent(
|
||
`<svg xmlns="http://www.w3.org/2000/svg" width="800" height="450" viewBox="0 0 800 450">` +
|
||
`<defs><linearGradient id="g" x1="0" y1="0" x2="1" y2="1">` +
|
||
`<stop offset="0" stop-color="hsl(${hue},70%,75%)"/>` +
|
||
`<stop offset="1" stop-color="hsl(${(hue + 45) % 360},75%,55%)"/>` +
|
||
`</linearGradient></defs>` +
|
||
`<rect width="800" height="450" fill="url(#g)"/>` +
|
||
`</svg>`
|
||
)
|
||
|
||
// 13 页(封面 + 12 内页),模拟真实绘本规模,便于测试横向胶卷式缩略图
|
||
const pageTexts = [
|
||
'', // 封面
|
||
'一个阳光明媚的早晨,小主角推开窗,看见远处的森林闪着金光。',
|
||
'它沿着小路走啊走,遇到了一只迷路的小鸟,羽毛湿漉漉的。',
|
||
'小主角轻轻抱起小鸟,决定送它回家。',
|
||
'路过一片野花田时,一只小狐狸从草丛里跳出来打招呼。',
|
||
'小狐狸说它认识森林里所有的小路,愿意做大家的向导。',
|
||
'三个朋友来到一条清澈的溪水边,溪里有一群闪闪发光的小鱼。',
|
||
'小鱼们告诉他们,那棵会发光的大树就在前方不远处。',
|
||
'森林深处真的有一棵会发光的大树,挂满了亮晶晶的果实。',
|
||
'原来这就是小鸟的家,妈妈正在树枝上焦急地张望。',
|
||
'小鸟欢快地飞回妈妈身边,鸟妈妈给大家每人一颗果实。',
|
||
'夕阳下,小主角和小狐狸告别,小狐狸送他们到森林边缘。',
|
||
'小主角带着这份美好回到家,心里也开出了一朵花。',
|
||
]
|
||
|
||
const wid = 'mock-work-' + Date.now()
|
||
workId.value = wid
|
||
workDetail.value = {
|
||
workId: wid,
|
||
status: 3, // COMPLETED
|
||
title: storyData.value?.title || '森林大冒险',
|
||
subtitle: '',
|
||
author: '',
|
||
coverUrl: mockPage(280),
|
||
pageList: pageTexts.map((text, i) => ({
|
||
pageNum: i,
|
||
text,
|
||
imageUrl: mockPage((280 + i * 27) % 360),
|
||
})),
|
||
}
|
||
}
|
||
|
||
function restoreRecoveryState() {
|
||
const raw = sessionStorage.getItem('le_recovery')
|
||
if (!raw) return null
|
||
try {
|
||
const recovery = JSON.parse(raw)
|
||
if (Date.now() - recovery.savedAt > 30 * 60 * 1000) {
|
||
sessionStorage.removeItem('le_recovery')
|
||
return null
|
||
}
|
||
if (recovery.workId) workId.value = recovery.workId
|
||
if (recovery.imageUrl) imageUrl.value = recovery.imageUrl
|
||
if (recovery.extractId) extractId.value = recovery.extractId
|
||
if (recovery.selectedStyle) selectedStyle.value = recovery.selectedStyle
|
||
sessionStorage.removeItem('le_recovery')
|
||
return recovery
|
||
} catch {
|
||
sessionStorage.removeItem('le_recovery')
|
||
return null
|
||
}
|
||
}
|
||
|
||
return {
|
||
// 认证
|
||
orgId, sessionToken,
|
||
setSession, clearSession,
|
||
// 创作流程
|
||
imageUrl, extractId, characters, selectedCharacter,
|
||
selectedStyle, storyData, workId, originalWorkId, workDetail,
|
||
reset, saveRecoveryState, restoreRecoveryState,
|
||
// 开发模式
|
||
fillMockData,
|
||
fillMockWorkDetail,
|
||
// Tab 切换状态
|
||
lastCreateRoute, setLastCreateRoute, clearLastCreateRoute,
|
||
}
|
||
})
|