diff --git a/docs/design/public/ugc-work-status-redesign.md b/docs/design/public/ugc-work-status-redesign.md index d53fd16..91a4bae 100644 --- a/docs/design/public/ugc-work-status-redesign.md +++ b/docs/design/public/ugc-work-status-redesign.md @@ -417,3 +417,19 @@ CREATE INDEX idx_ugc_work_leai_status ON t_ugc_work(leai_status); - `unpublished` 强调"还没发布"的暂时性,跟"已发布 published"形成对立 - 跟 Medium 的 Draft / Unlisted / Published 三态命名风格一致 - 本地化为"未发布"也是中文用户最直观的理解 + +## 附录 C:创作壳编目/分页本库保存(2026-04-15) + +**背景**:乐读派 `PUT /update/work` 在部分远端环境下仅允许 status 3/4 编辑元数据,已配音(5)等场景会与「本库需保存编目/分页快照」冲突。 + +**约定**: + +- **公众端创作壳**(`EditInfoView`、`DubbingView` 完成配音后)保存编目字段与分页(含各页 `audioUrl`)时,使用 **`PUT /api/public/leai-works/{remoteWorkId}/work-form`**,直写 `t_ugc_work` / `t_ugc_work_page`,**不经过** `PUT /leai-proxy/work/{id}`。 +- **鉴权**:当前登录用户须为 `t_ugc_work.user_id` 对应作品;`remote_work_id` 与路径参数一致。 +- **字段**:编目与 `updateWork` 请求体对齐(`author`、`title`、`subtitle`、`intro`、`tags`);可选 `pageList`(乐读派 `pageList` 形态:`imageUrl`、`text`、`audioUrl` 等)。配音仍通过现有 **`/leai-proxy/voice`、`/leai-proxy/batch-audio`** 等与乐读派交互,仅**本库快照**走上述接口。 +- **状态**:编目写入成功且本地 `leai_status >= 3` 时,若发布状态仍为 `draft`,可升为 `unpublished`;`pageList` 中**每一页**均有非空 `audioUrl` 时,可将本地 `leai_status` 置为 `5`(DUBBED)。 + +**与《企业同步创作数据 核心三步 V4.0》的关系**: + +- Webhook、`GET /leai-proxy/work/{id}`(B2)仍是**远端乐读派状态与 pageList** 的同步通道;本接口解决的是**本库元数据与分页快照**的权威写入,避免依赖乐读派 PUT 编目。 +- 远端状态前进仍以 V4.0 规则(`remote_status > local_status` 等)为准;本接口不改变 Webhook 语义,仅补充「创作壳保存」路径。 diff --git a/lesingle-creation-backend/src/main/java/com/lesingle/modules/leai/service/ILeaiSyncService.java b/lesingle-creation-backend/src/main/java/com/lesingle/modules/leai/service/ILeaiSyncService.java index 25adddf..4ce9286 100644 --- a/lesingle-creation-backend/src/main/java/com/lesingle/modules/leai/service/ILeaiSyncService.java +++ b/lesingle-creation-backend/src/main/java/com/lesingle/modules/leai/service/ILeaiSyncService.java @@ -1,5 +1,8 @@ package com.lesingle.modules.leai.service; +import com.lesingle.modules.ugc.entity.UgcWork; + +import java.util.List; import java.util.Map; /** @@ -32,6 +35,19 @@ public interface ILeaiSyncService { */ void applyLocalMetadataFromProxyPut(String remoteWorkId, Map proxyRequestBody); + /** + * 将编目字段写入本地作品(与 {@link #applyLocalMetadataFromProxyPut} 同字段逻辑)。 + * 调用方须已校验作品归属。 + * + * @return 是否至少更新了一列业务字段(不含仅 modifyTime) + */ + boolean applyCatalogMetadata(UgcWork work, Map body); + + /** + * 用乐读派 pageList 形态的数据覆盖本地 t_ugc_work_page(先删后插),并同步首图到 cover_url。 + */ + void savePagesFromLeaiPageList(Long workId, List> pageList); + /** * 将本地 DB 中已保存的元数据(author、description)合并到 LeAI B2 响应 JSON 中, * 避免二次编目后 LeAI 返回旧数据覆盖用户的本地编辑。 diff --git a/lesingle-creation-backend/src/main/java/com/lesingle/modules/leai/service/LeaiSyncService.java b/lesingle-creation-backend/src/main/java/com/lesingle/modules/leai/service/LeaiSyncService.java index 8ec8e8c..82feb61 100644 --- a/lesingle-creation-backend/src/main/java/com/lesingle/modules/leai/service/LeaiSyncService.java +++ b/lesingle-creation-backend/src/main/java/com/lesingle/modules/leai/service/LeaiSyncService.java @@ -436,30 +436,40 @@ public class LeaiSyncService implements ILeaiSyncService { if (work == null) { return; } + if (applyCatalogMetadata(work, proxyRequestBody)) { + log.info("[ProxyPut] 本地元数据已覆盖 remoteWorkId={}", remoteWorkId); + } + } + + @Override + @SuppressWarnings("unchecked") + public boolean applyCatalogMetadata(UgcWork work, Map body) { + if (work == null || body == null || body.isEmpty()) { + return false; + } LambdaUpdateWrapper wrapper = new LambdaUpdateWrapper<>(); wrapper.eq(UgcWork::getId, work.getId()); boolean any = false; - // 用于存储 subtitle/intro/tags 到 ai_meta,供 mergeLocalMetadata 精确读取 Map aiMetaMap = new HashMap<>(); if (work.getAiMeta() instanceof Map) { aiMetaMap.putAll((Map) work.getAiMeta()); } - if (proxyRequestBody.containsKey("author")) { - wrapper.set(UgcWork::getAuthorName, LeaiUtil.toString(proxyRequestBody.get("author"), null)); + if (body.containsKey("author")) { + wrapper.set(UgcWork::getAuthorName, LeaiUtil.toString(body.get("author"), null)); any = true; } - if (proxyRequestBody.containsKey("title")) { - wrapper.set(UgcWork::getTitle, LeaiUtil.toString(proxyRequestBody.get("title"), null)); + if (body.containsKey("title")) { + wrapper.set(UgcWork::getTitle, LeaiUtil.toString(body.get("title"), null)); any = true; } - if (proxyRequestBody.containsKey("intro") || proxyRequestBody.containsKey("subtitle")) { - String sub = proxyRequestBody.containsKey("subtitle") - ? LeaiUtil.toString(proxyRequestBody.get("subtitle"), "") + if (body.containsKey("intro") || body.containsKey("subtitle")) { + String sub = body.containsKey("subtitle") + ? LeaiUtil.toString(body.get("subtitle"), "") : ""; - String intro = proxyRequestBody.containsKey("intro") - ? LeaiUtil.toString(proxyRequestBody.get("intro"), "") + String intro = body.containsKey("intro") + ? LeaiUtil.toString(body.get("intro"), "") : ""; String desc; if (sub.isEmpty()) { @@ -471,31 +481,36 @@ public class LeaiSyncService implements ILeaiSyncService { } desc = desc.trim(); wrapper.set(UgcWork::getDescription, desc.isEmpty() ? null : desc); - // 精确存储 subtitle/intro 到 ai_meta,避免 description 反拆分丢失信息 aiMetaMap.put("_subtitle", sub); aiMetaMap.put("_intro", intro); any = true; } - if (proxyRequestBody.containsKey("tags")) { - Object tags = proxyRequestBody.get("tags"); + if (body.containsKey("tags")) { + Object tags = body.get("tags"); if (tags instanceof List) { aiMetaMap.put("_tags", tags); any = true; } } if (!any) { - return; + return false; } - // LambdaUpdateWrapper.set() 不会自动应用 JacksonTypeHandler, - // 需要手动序列化为 JSON 字符串,否则 Map 会以 Java toString 格式存入 DB try { wrapper.set(UgcWork::getAiMeta, objectMapper.writeValueAsString(aiMetaMap)); } catch (Exception e) { - log.warn("[ProxyPut] ai_meta 序列化失败,跳过 ai_meta 写入: remoteWorkId={}", remoteWorkId, e); + log.warn("[Catalog] ai_meta 序列化失败,跳过 ai_meta 写入: workId={}", work.getId(), e); } wrapper.set(UgcWork::getModifyTime, LocalDateTime.now()); ugcWorkMapper.update(null, wrapper); - log.info("[ProxyPut] 本地元数据已覆盖 remoteWorkId={}", remoteWorkId); + return true; + } + + @Override + public void savePagesFromLeaiPageList(Long workId, List> pageList) { + if (pageList == null || pageList.isEmpty()) { + return; + } + savePageList(workId, pageList); } /** diff --git a/lesingle-creation-backend/src/main/java/com/lesingle/modules/pub/controller/PublicLeaiWorkController.java b/lesingle-creation-backend/src/main/java/com/lesingle/modules/pub/controller/PublicLeaiWorkController.java new file mode 100644 index 0000000..66d3390 --- /dev/null +++ b/lesingle-creation-backend/src/main/java/com/lesingle/modules/pub/controller/PublicLeaiWorkController.java @@ -0,0 +1,40 @@ +package com.lesingle.modules.pub.controller; + +import com.lesingle.common.result.Result; +import com.lesingle.common.util.SecurityUtil; +import com.lesingle.modules.pub.service.PublicLeaiWorkFormService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.*; + +import java.util.Map; + +/** + * 公众端乐读派创作作品表单(编目/分页)直写本库 + */ +@Tag(name = "公众端-乐读派作品表单") +@RestController +@RequestMapping("/public/leai-works") +@RequiredArgsConstructor +public class PublicLeaiWorkController { + + private final PublicLeaiWorkFormService publicLeaiWorkFormService; + + @GetMapping("/{remoteWorkId}/work-form") + @Operation(summary = "查询本库作品详情(编目/分页快照,不经乐读派 B2 GET)") + public Result> getWorkForm(@PathVariable String remoteWorkId) { + Long userId = SecurityUtil.getCurrentUserId(); + return Result.success(publicLeaiWorkFormService.getWorkFormDetail(userId, remoteWorkId)); + } + + @PutMapping("/{remoteWorkId}/work-form") + @Operation(summary = "保存编目/分页快照(仅本库,不经乐读派 PUT)") + public Result saveWorkForm( + @PathVariable String remoteWorkId, + @RequestBody Map body) { + Long userId = SecurityUtil.getCurrentUserId(); + publicLeaiWorkFormService.saveWorkForm(userId, remoteWorkId, body); + return Result.success(); + } +} diff --git a/lesingle-creation-backend/src/main/java/com/lesingle/modules/pub/service/PublicLeaiWorkFormService.java b/lesingle-creation-backend/src/main/java/com/lesingle/modules/pub/service/PublicLeaiWorkFormService.java new file mode 100644 index 0000000..7e4756c --- /dev/null +++ b/lesingle-creation-backend/src/main/java/com/lesingle/modules/pub/service/PublicLeaiWorkFormService.java @@ -0,0 +1,201 @@ +package com.lesingle.modules.pub.service; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; +import com.lesingle.common.exception.BusinessException; +import com.lesingle.modules.leai.enums.LeaiCreationStatus; +import com.lesingle.modules.leai.service.ILeaiSyncService; +import com.lesingle.modules.leai.util.LeaiUtil; +import com.lesingle.modules.ugc.entity.UgcWork; +import com.lesingle.modules.ugc.entity.UgcWorkPage; +import com.lesingle.modules.ugc.enums.WorkPublishStatus; +import com.lesingle.modules.ugc.mapper.UgcWorkMapper; +import com.lesingle.modules.ugc.mapper.UgcWorkPageMapper; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +/** + * 公众端创作壳:编目/分页快照直写本库,不经过乐读派 PUT /update/work。 + */ +@Slf4j +@Service +@RequiredArgsConstructor +public class PublicLeaiWorkFormService { + + private final UgcWorkMapper ugcWorkMapper; + private final UgcWorkPageMapper ugcWorkPageMapper; + private final ILeaiSyncService leaiSyncService; + + /** + * @param body 可含 author、title、subtitle、intro、tags、pageList(乐读派 pageList 形态) + */ + @Transactional(rollbackFor = Exception.class) + public void saveWorkForm(Long userId, String remoteWorkId, Map body) { + if (remoteWorkId == null || remoteWorkId.isBlank()) { + throw new BusinessException(400, "remoteWorkId 无效"); + } + if (body == null || body.isEmpty()) { + throw new BusinessException(400, "请求体不能为空"); + } + + UgcWork work = findOwnedWork(userId, remoteWorkId); + boolean catalogTouched = false; + + Map catalogOnly = filterCatalogKeys(body); + if (!catalogOnly.isEmpty()) { + catalogTouched = leaiSyncService.applyCatalogMetadata(work, catalogOnly); + } + + if (catalogTouched) { + UgcWork fresh = ugcWorkMapper.selectById(work.getId()); + if (fresh != null + && WorkPublishStatus.DRAFT.getValue().equals(fresh.getStatus()) + && fresh.getLeaiStatus() != null + && fresh.getLeaiStatus() >= LeaiCreationStatus.COMPLETED) { + LambdaUpdateWrapper uw = new LambdaUpdateWrapper<>(); + uw.eq(UgcWork::getId, work.getId()) + .set(UgcWork::getStatus, WorkPublishStatus.UNPUBLISHED.getValue()) + .set(UgcWork::getModifyTime, LocalDateTime.now()); + ugcWorkMapper.update(null, uw); + log.info("[WorkForm] draft→unpublished workId={}, remoteWorkId={}", work.getId(), remoteWorkId); + } + } + + @SuppressWarnings("unchecked") + List> pageList = (List>) body.get("pageList"); + if (pageList != null && !pageList.isEmpty()) { + leaiSyncService.savePagesFromLeaiPageList(work.getId(), pageList); + if (allPagesHaveNonBlankAudio(pageList)) { + LambdaUpdateWrapper uw = new LambdaUpdateWrapper<>(); + uw.eq(UgcWork::getId, work.getId()) + .set(UgcWork::getLeaiStatus, LeaiCreationStatus.DUBBED) + .set(UgcWork::getModifyTime, LocalDateTime.now()); + ugcWorkMapper.update(null, uw); + log.info("[WorkForm] leai_status→DUBBED workId={}, remoteWorkId={}", work.getId(), remoteWorkId); + } + } + } + + /** + * 本库作品详情(乐读派详情形态):编目/分页均以本地为准,不经 B2 GET。 + * 字段与前端创作壳对 {@code getWorkDetail} 的用法对齐:workId、status、title、author、subtitle、intro、tags、pageList、coverUrl。 + */ + public Map getWorkFormDetail(Long userId, String remoteWorkId) { + UgcWork work = findOwnedWork(userId, remoteWorkId); + Map out = new LinkedHashMap<>(); + out.put("workId", work.getRemoteWorkId()); + + int status = work.getLeaiStatus() != null ? work.getLeaiStatus() : LeaiCreationStatus.PENDING; + out.put("status", status); + + out.put("title", work.getTitle()); + out.put("author", work.getAuthorName()); + out.put("coverUrl", work.getCoverUrl()); + + CatalogView cv = extractCatalogView(work); + out.put("subtitle", cv.subtitle != null ? cv.subtitle : ""); + out.put("intro", cv.intro != null ? cv.intro : ""); + out.put("tags", cv.tags); + + LambdaQueryWrapper pq = new LambdaQueryWrapper<>(); + pq.eq(UgcWorkPage::getWorkId, work.getId()).orderByAsc(UgcWorkPage::getPageNo); + List rows = ugcWorkPageMapper.selectList(pq); + List> pageList = new ArrayList<>(rows.size()); + for (UgcWorkPage p : rows) { + Map pm = new LinkedHashMap<>(); + pm.put("pageNum", p.getPageNo()); + pm.put("imageUrl", p.getImageUrl()); + pm.put("text", p.getText()); + pm.put("audioUrl", p.getAudioUrl()); + pageList.add(pm); + } + out.put("pageList", pageList); + + return out; + } + + /** 与 {@link com.lesingle.modules.leai.service.LeaiSyncService#mergeLocalMetadata} 读取本地副标题/简介/标签规则一致 */ + @SuppressWarnings("unchecked") + private CatalogView extractCatalogView(UgcWork localWork) { + String localSubtitle = null; + String localIntro = null; + List tags = new ArrayList<>(); + + if (localWork.getAiMeta() instanceof Map) { + Map metaMap = (Map) localWork.getAiMeta(); + if (metaMap.containsKey("_subtitle")) { + localSubtitle = LeaiUtil.toString(metaMap.get("_subtitle"), ""); + } + if (metaMap.containsKey("_intro")) { + localIntro = LeaiUtil.toString(metaMap.get("_intro"), ""); + } + if (metaMap.containsKey("_tags") && metaMap.get("_tags") instanceof List) { + for (Object o : (List) metaMap.get("_tags")) { + tags.add(o); + } + } + } + if (localSubtitle == null && localIntro == null + && localWork.getDescription() != null && !localWork.getDescription().isEmpty()) { + String desc = localWork.getDescription(); + int newlineIdx = desc.indexOf('\n'); + if (newlineIdx >= 0) { + localSubtitle = desc.substring(0, newlineIdx); + localIntro = desc.substring(newlineIdx + 1); + } else { + localIntro = desc; + } + } + return new CatalogView(localSubtitle, localIntro, tags); + } + + private record CatalogView(String subtitle, String intro, List tags) {} + + private Map filterCatalogKeys(Map body) { + Map m = new HashMap<>(); + for (String k : List.of("author", "title", "subtitle", "intro", "tags")) { + if (body.containsKey(k)) { + m.put(k, body.get(k)); + } + } + return m; + } + + private UgcWork findOwnedWork(Long userId, String remoteWorkId) { + LambdaQueryWrapper q = new LambdaQueryWrapper<>(); + q.eq(UgcWork::getRemoteWorkId, remoteWorkId) + .eq(UgcWork::getUserId, userId) + .eq(UgcWork::getIsDeleted, 0); + UgcWork work = ugcWorkMapper.selectOne(q); + if (work == null) { + throw new BusinessException(404, "作品不存在或无权操作"); + } + return work; + } + + /** + * 每一页均有非空白 audioUrl 时视为配音完成快照。 + */ + private boolean allPagesHaveNonBlankAudio(List> pageList) { + for (Map p : pageList) { + if (p == null) { + return false; + } + Object au = p.get("audioUrl"); + String s = au != null ? au.toString().trim() : ""; + if (s.isEmpty()) { + return false; + } + } + return true; + } +} diff --git a/lesingle-creation-frontend/src/api/public.ts b/lesingle-creation-frontend/src/api/public.ts index ca328bd..2b8c281 100644 --- a/lesingle-creation-frontend/src/api/public.ts +++ b/lesingle-creation-frontend/src/api/public.ts @@ -598,6 +598,25 @@ export const publicUserWorksApi = { ) => publicApi.post(`/public/works/${id}/pages`, { pages }), }; +/** + * 乐读派 remoteWorkId:编目/分页快照直写本库(不经 PUT /leai-proxy/work) + */ +export function saveLeaiWorkForm( + remoteWorkId: string, + body: Record, +): Promise { + const id = encodeURIComponent(remoteWorkId); + return publicApi.put(`/public/leai-works/${id}/work-form`, body); +} + +/** 本库作品详情(与乐读派 B2 详情字段对齐),不经 GET /leai-proxy/work */ +export function getLeaiWorkFormDetail( + remoteWorkId: string, +): Promise> { + const id = encodeURIComponent(remoteWorkId); + return publicApi.get(`/public/leai-works/${id}/work-form`); +} + // ==================== AI 创作流程 ==================== export const publicCreationApi = { diff --git a/lesingle-creation-frontend/src/views/public/create/views/CreatingView.vue b/lesingle-creation-frontend/src/views/public/create/views/CreatingView.vue index b9f87da..728d518 100644 --- a/lesingle-creation-frontend/src/views/public/create/views/CreatingView.vue +++ b/lesingle-creation-frontend/src/views/public/create/views/CreatingView.vue @@ -162,7 +162,10 @@ function friendlyStage(pct: number, msg: string): string { function saveWorkId(id: string) { store.workId = id if (id) { - localStorage.setItem('le_workId', id) + const urlWorkId = new URLSearchParams(window.location.search).get('workId'); + if (!urlWorkId) { + localStorage.setItem('le_workId', id) + } } else { localStorage.removeItem('le_workId') } @@ -380,17 +383,15 @@ onMounted(() => { // 恢复 workId const urlWorkId = new URLSearchParams(window.location.search).get('workId') console.log('store.workId', urlWorkId, window.location.search) - if (urlWorkId) { - saveWorkId(urlWorkId) - } else { + if (!urlWorkId) { restoreWorkId() } - try { - getWorkDetailApi(store.workId) - } catch (error) { - console.log('error', error); - } if (store.workId) { + try { + getWorkDetailApi(store.workId) + } catch (error) { + console.log('error', error); + } submitted = true progress.value = 0 stage.value = '正在查询创作进度…' diff --git a/lesingle-creation-frontend/src/views/public/create/views/DubbingView.vue b/lesingle-creation-frontend/src/views/public/create/views/DubbingView.vue index e999ed7..45270d9 100644 --- a/lesingle-creation-frontend/src/views/public/create/views/DubbingView.vue +++ b/lesingle-creation-frontend/src/views/public/create/views/DubbingView.vue @@ -166,7 +166,8 @@ import { ArrowRightOutlined, } from '@ant-design/icons-vue' import PageHeader from '@/components/aicreate/PageHeader.vue' -import { getWorkDetail, voicePage, ossUpload, batchUpdateAudio, finishDubbing } from '@/api/aicreate' +import { voicePage, ossUpload, batchUpdateAudio, finishDubbing } from '@/api/aicreate' +import { getLeaiWorkFormDetail, saveLeaiWorkForm } from '@/api/public' import { useAicreateStore } from '@/stores/aicreate' import { STATUS, getRouteByStatus } from '@/utils/aicreate/status' @@ -449,6 +450,26 @@ async function voiceAllConfirm() { } } +/** 乐读派分页形态 + 可选编目字段,写入本库 t_ugc_work / t_ugc_work_page */ +function buildWorkFormPayload() { + const pageList = pages.value.map(p => ({ + pageNum: p.pageNum, + imageUrl: p.imageUrl, + text: p.text, + audioUrl: p.audioUrl || undefined, + })) + const wd = store.workDetail + const body = { pageList } as Record + if (wd) { + if (wd.author) body.author = wd.author + if (wd.subtitle != null && wd.subtitle !== '') body.subtitle = wd.subtitle + if (wd.intro != null && wd.intro !== '') body.intro = wd.intro + if (Array.isArray(wd.tags) && wd.tags.length) body.tags = [...wd.tags] + if (wd.title) body.title = wd.title + } + return body +} + // --- 完成配音 --- async function finish() { submitting.value = true @@ -473,6 +494,7 @@ async function finish() { await finishDubbing(workId.value) } + await saveLeaiWorkForm(String(workId.value || ''), buildWorkFormPayload()) store.workDetail = null showToast('配音完成') setTimeout( @@ -485,8 +507,13 @@ async function finish() { ) } catch (e: any) { try { - const check = await getWorkDetail(workId.value) + const check = await getLeaiWorkFormDetail(String(workId.value || '')) if (check?.status >= 5) { + try { + await saveLeaiWorkForm(String(workId.value || ''), buildWorkFormPayload()) + } catch { + /* 本库无记录时忽略 */ + } store.workDetail = null showToast('配音已完成') setTimeout( @@ -512,7 +539,7 @@ async function loadWork() { try { if (!store.workDetail || store.workDetail.workId !== workId.value) { store.workDetail = null - const res = await getWorkDetail(workId.value) + const res = await getLeaiWorkFormDetail(String(workId.value || '')) store.workDetail = res } const w = store.workDetail diff --git a/lesingle-creation-frontend/src/views/public/create/views/EditInfoView.vue b/lesingle-creation-frontend/src/views/public/create/views/EditInfoView.vue index ac03897..6e8cc63 100644 --- a/lesingle-creation-frontend/src/views/public/create/views/EditInfoView.vue +++ b/lesingle-creation-frontend/src/views/public/create/views/EditInfoView.vue @@ -131,8 +131,7 @@ import { } from '@ant-design/icons-vue' import { message } from 'ant-design-vue' import PageHeader from '@/components/aicreate/PageHeader.vue' -import { getWorkDetail, updateWork } from '@/api/aicreate' -import { publicUserWorksApi } from '@/api/public' +import { getLeaiWorkFormDetail, publicUserWorksApi, saveLeaiWorkForm } from '@/api/public' import { useAicreateStore } from '@/stores/aicreate' import { clearExtractDraft } from '@/utils/aicreate/extractDraft' import { STATUS, getRouteByStatus } from '@/utils/aicreate/status' @@ -198,7 +197,7 @@ async function loadWork() { try { // 必须每次拉取详情:创作壳 keep-alive 会缓存组件,仅靠 store 命中会跳过请求,二次进入表单仍是旧值 store.workDetail = null - const res = await getWorkDetail(id) + const res = await getLeaiWorkFormDetail(id) store.workDetail = res const w = store.workDetail @@ -212,7 +211,7 @@ async function loadWork() { form.value.subtitle = w.subtitle || '' form.value.intro = w.intro || '' selectedTags.value = Array.isArray(w.tags) && w.tags.length ? [...w.tags] : ['冒险'] - coverUrl.value = w.pageList?.[0]?.imageUrl || '' + coverUrl.value = w.coverUrl || w.pageList?.[0]?.imageUrl || '' } catch (e) { // fallback: proceed with empty form } finally { @@ -247,7 +246,7 @@ async function saveFormToServer() { data.subtitle = form.value.subtitle.trim() data.intro = form.value.intro.trim() - await updateWork(id, data) + await saveLeaiWorkForm(id, data) if (store.workDetail) { store.workDetail.author = data.author @@ -361,6 +360,7 @@ watch( ) onActivated(() => { + store.reset(); clearExtractDraft() loadWork() nextTick(() => { if (tagInput.value) tagInput.value.focus() }) diff --git a/lesingle-creation-frontend/src/views/public/create/views/WelcomeView.vue b/lesingle-creation-frontend/src/views/public/create/views/WelcomeView.vue index c55acba..9af961d 100644 --- a/lesingle-creation-frontend/src/views/public/create/views/WelcomeView.vue +++ b/lesingle-creation-frontend/src/views/public/create/views/WelcomeView.vue @@ -207,10 +207,8 @@ async function runWelcomeEntry() { return } - // 3) 本地 le_workId:乐读派已生成作品,按状态继续 - const storedWid = localStorage.getItem('le_workId') - if (storedWid && store.sessionToken) { - const ok = await resumeLeaiWorkFromApi(storedWid, router, store) + if (store.sessionToken) { + const ok = await resumeLeaiWorkFromApi(store.workId, router, store) if (ok) return } @@ -225,7 +223,6 @@ async function runWelcomeEntry() { store.selectedStyle = '' store.workId = '' store.workDetail = null - localStorage.removeItem('le_workId') router.replace('/p/create/characters') } } finally {