/** * AI 创作全局状态(Pinia Store) * * 敏感信息(phone/orgId/appSecret)不再存储在 localStorage * orgId 仅存 sessionStorage(会话级),sessionToken 判断是否已初始化 */ import { defineStore } from 'pinia' import { ref } from 'vue' 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([]) const selectedCharacter = ref(null) const selectedStyle = ref('') const storyData = ref(null) const workId = ref('') const workDetail = ref(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 = '' workDetail.value = null lastCreateRoute.value = '' // 只清除创作流程数据,保留认证信息 localStorage.removeItem('le_workId') // 清除 sessionStorage 中的恢复数据 sessionStorage.removeItem('le_recovery') } 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( `` + `` + `` + `` + `` + `` + `` ) 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( `` + `` + `` + `` + `` + `` + `` ) // 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, workDetail, reset, saveRecoveryState, restoreRecoveryState, // 开发模式 fillMockData, fillMockWorkDetail, // Tab 切换状态 lastCreateRoute, setLastCreateRoute, clearLastCreateRoute, } })