feat: 乐读派作品本库表单读写接口与创作壳数据流调整
- 新增 PublicLeaiWorkController:GET/PUT /public/leai-works/{remoteWorkId}/work-form
- PublicLeaiWorkFormService:编目元数据、分页快照、本库详情查询
- LeaiSyncService 抽取 applyCatalogMetadata、savePagesFromLeaiPageList 供复用
- 前端 public API:saveLeaiWorkForm、getLeaiWorkFormDetail;编目/配音/欢迎/生成页对接
- 补充 docs/design/public/ugc-work-status-redesign.md 说明
Made-with: Cursor
This commit is contained in:
parent
ee9a519d57
commit
1542d0bd71
@ -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 语义,仅补充「创作壳保存」路径。
|
||||
|
||||
@ -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<String, Object> proxyRequestBody);
|
||||
|
||||
/**
|
||||
* 将编目字段写入本地作品(与 {@link #applyLocalMetadataFromProxyPut} 同字段逻辑)。
|
||||
* 调用方须已校验作品归属。
|
||||
*
|
||||
* @return 是否至少更新了一列业务字段(不含仅 modifyTime)
|
||||
*/
|
||||
boolean applyCatalogMetadata(UgcWork work, Map<String, Object> body);
|
||||
|
||||
/**
|
||||
* 用乐读派 pageList 形态的数据覆盖本地 t_ugc_work_page(先删后插),并同步首图到 cover_url。
|
||||
*/
|
||||
void savePagesFromLeaiPageList(Long workId, List<Map<String, Object>> pageList);
|
||||
|
||||
/**
|
||||
* 将本地 DB 中已保存的元数据(author、description)合并到 LeAI B2 响应 JSON 中,
|
||||
* 避免二次编目后 LeAI 返回旧数据覆盖用户的本地编辑。
|
||||
|
||||
@ -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<String, Object> body) {
|
||||
if (work == null || body == null || body.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
LambdaUpdateWrapper<UgcWork> wrapper = new LambdaUpdateWrapper<>();
|
||||
wrapper.eq(UgcWork::getId, work.getId());
|
||||
boolean any = false;
|
||||
|
||||
// 用于存储 subtitle/intro/tags 到 ai_meta,供 mergeLocalMetadata 精确读取
|
||||
Map<String, Object> aiMetaMap = new HashMap<>();
|
||||
if (work.getAiMeta() instanceof Map) {
|
||||
aiMetaMap.putAll((Map<String, Object>) 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<Map<String, Object>> pageList) {
|
||||
if (pageList == null || pageList.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
savePageList(workId, pageList);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -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<Map<String, Object>> getWorkForm(@PathVariable String remoteWorkId) {
|
||||
Long userId = SecurityUtil.getCurrentUserId();
|
||||
return Result.success(publicLeaiWorkFormService.getWorkFormDetail(userId, remoteWorkId));
|
||||
}
|
||||
|
||||
@PutMapping("/{remoteWorkId}/work-form")
|
||||
@Operation(summary = "保存编目/分页快照(仅本库,不经乐读派 PUT)")
|
||||
public Result<Void> saveWorkForm(
|
||||
@PathVariable String remoteWorkId,
|
||||
@RequestBody Map<String, Object> body) {
|
||||
Long userId = SecurityUtil.getCurrentUserId();
|
||||
publicLeaiWorkFormService.saveWorkForm(userId, remoteWorkId, body);
|
||||
return Result.success();
|
||||
}
|
||||
}
|
||||
@ -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<String, Object> 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<String, Object> 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<UgcWork> 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<Map<String, Object>> pageList = (List<Map<String, Object>>) body.get("pageList");
|
||||
if (pageList != null && !pageList.isEmpty()) {
|
||||
leaiSyncService.savePagesFromLeaiPageList(work.getId(), pageList);
|
||||
if (allPagesHaveNonBlankAudio(pageList)) {
|
||||
LambdaUpdateWrapper<UgcWork> 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<String, Object> getWorkFormDetail(Long userId, String remoteWorkId) {
|
||||
UgcWork work = findOwnedWork(userId, remoteWorkId);
|
||||
Map<String, Object> 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<UgcWorkPage> pq = new LambdaQueryWrapper<>();
|
||||
pq.eq(UgcWorkPage::getWorkId, work.getId()).orderByAsc(UgcWorkPage::getPageNo);
|
||||
List<UgcWorkPage> rows = ugcWorkPageMapper.selectList(pq);
|
||||
List<Map<String, Object>> pageList = new ArrayList<>(rows.size());
|
||||
for (UgcWorkPage p : rows) {
|
||||
Map<String, Object> 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<Object> tags = new ArrayList<>();
|
||||
|
||||
if (localWork.getAiMeta() instanceof Map) {
|
||||
Map<String, Object> metaMap = (Map<String, Object>) 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<Object>) 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<Object> tags) {}
|
||||
|
||||
private Map<String, Object> filterCatalogKeys(Map<String, Object> body) {
|
||||
Map<String, Object> 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<UgcWork> 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<Map<String, Object>> pageList) {
|
||||
for (Map<String, Object> 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;
|
||||
}
|
||||
}
|
||||
@ -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<string, unknown>,
|
||||
): Promise<void> {
|
||||
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<Record<string, unknown>> {
|
||||
const id = encodeURIComponent(remoteWorkId);
|
||||
return publicApi.get(`/public/leai-works/${id}/work-form`);
|
||||
}
|
||||
|
||||
// ==================== AI 创作流程 ====================
|
||||
|
||||
export const publicCreationApi = {
|
||||
|
||||
@ -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 = '正在查询创作进度…'
|
||||
|
||||
@ -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<string, unknown>
|
||||
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
|
||||
|
||||
@ -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() })
|
||||
|
||||
@ -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 {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user