2026-04-08 18:09:05 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* AI 创作全局状态(Pinia Store)
|
|
|
|
|
|
*
|
2026-04-09 21:31:25 +08:00
|
|
|
|
* 敏感信息(phone/orgId/appSecret)不再存储在 localStorage
|
|
|
|
|
|
* orgId 仅存 sessionStorage(会话级),sessionToken 判断是否已初始化
|
2026-04-08 18:09:05 +08:00
|
|
|
|
*/
|
|
|
|
|
|
import { defineStore } from 'pinia'
|
2026-04-09 12:52:39 +08:00
|
|
|
|
import { ref } from 'vue'
|
2026-04-08 18:09:05 +08:00
|
|
|
|
|
|
|
|
|
|
export const useAicreateStore = defineStore('aicreate', () => {
|
2026-04-09 21:31:25 +08:00
|
|
|
|
// ─── 认证信息(不再存储敏感信息到 localStorage) ───
|
|
|
|
|
|
const orgId = ref(sessionStorage.getItem('le_orgId') || '')
|
2026-04-08 18:09:05 +08:00
|
|
|
|
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('')
|
|
|
|
|
|
const workDetail = ref<any>(null)
|
|
|
|
|
|
|
2026-04-08 22:58:07 +08:00
|
|
|
|
// ─── Tab 切换状态保存 ───
|
|
|
|
|
|
const lastCreateRoute = ref('')
|
|
|
|
|
|
|
2026-04-08 18:09:05 +08:00
|
|
|
|
// ─── 方法 ───
|
|
|
|
|
|
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 = ''
|
2026-04-09 21:31:25 +08:00
|
|
|
|
orgId.value = ''
|
2026-04-08 18:09:05 +08:00
|
|
|
|
sessionStorage.removeItem('le_sessionToken')
|
2026-04-09 21:31:25 +08:00
|
|
|
|
sessionStorage.removeItem('le_orgId')
|
2026-04-08 18:09:05 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-08 22:58:07 +08:00
|
|
|
|
function setLastCreateRoute(path: string) {
|
|
|
|
|
|
lastCreateRoute.value = path
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function clearLastCreateRoute() {
|
|
|
|
|
|
lastCreateRoute.value = ''
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-08 18:09:05 +08:00
|
|
|
|
function reset() {
|
|
|
|
|
|
imageUrl.value = ''
|
|
|
|
|
|
extractId.value = ''
|
|
|
|
|
|
characters.value = []
|
|
|
|
|
|
selectedCharacter.value = null
|
|
|
|
|
|
selectedStyle.value = ''
|
|
|
|
|
|
storyData.value = null
|
|
|
|
|
|
workId.value = ''
|
|
|
|
|
|
workDetail.value = null
|
2026-04-08 22:58:07 +08:00
|
|
|
|
lastCreateRoute.value = ''
|
2026-04-09 21:31:25 +08:00
|
|
|
|
// 只清除创作流程数据,保留认证信息
|
2026-04-08 18:09:05 +08:00
|
|
|
|
localStorage.removeItem('le_workId')
|
2026-04-09 12:52:39 +08:00
|
|
|
|
// 清除 sessionStorage 中的恢复数据
|
|
|
|
|
|
sessionStorage.removeItem('le_recovery')
|
2026-04-08 18:09:05 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
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))
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-09 18:14:26 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 开发模式:填充一份 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),
|
|
|
|
|
|
})),
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-08 18:09:05 +08:00
|
|
|
|
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 {
|
|
|
|
|
|
// 认证
|
2026-04-09 21:31:25 +08:00
|
|
|
|
orgId, sessionToken,
|
|
|
|
|
|
setSession, clearSession,
|
2026-04-08 18:09:05 +08:00
|
|
|
|
// 创作流程
|
|
|
|
|
|
imageUrl, extractId, characters, selectedCharacter,
|
|
|
|
|
|
selectedStyle, storyData, workId, workDetail,
|
|
|
|
|
|
reset, saveRecoveryState, restoreRecoveryState,
|
2026-04-09 18:14:26 +08:00
|
|
|
|
// 开发模式
|
|
|
|
|
|
fillMockData,
|
|
|
|
|
|
fillMockWorkDetail,
|
2026-04-08 22:58:07 +08:00
|
|
|
|
// Tab 切换状态
|
|
|
|
|
|
lastCreateRoute, setLastCreateRoute, clearLastCreateRoute,
|
2026-04-08 18:09:05 +08:00
|
|
|
|
}
|
|
|
|
|
|
})
|