fix: 乐读派作品元数据本地同步与创作流程路由修复
- 后端:PUT 代理成功码兼容 0/200;syncWork 在状态未变时同步元数据;请求体覆盖本地 t_ugc_work - 前端:EditInfo 强制拉详情、workId 字符串化、副标题/简介完整提交 - 评委管理:联系方式增加手机号校验 - Dubbing:仅未编目完成时按状态跳转,避免已配音作品从编辑页进配音被立即打回 Made-with: Cursor
This commit is contained in:
parent
b11cb4b9d7
commit
eff55b6f7b
@ -160,40 +160,48 @@ public class LeaiProxyController {
|
||||
body.putAll(requestBody);
|
||||
log.info("[乐读派代理] 编辑作品, workId={}", id);
|
||||
String responseBody = leaiApiClient.proxyPut("/update/work/" + id, body);
|
||||
syncLocalAfterLeaiUpdate(id, responseBody);
|
||||
syncLocalAfterLeaiUpdate(id, responseBody, requestBody);
|
||||
return jsonOk(responseBody);
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析乐读派 update 响应并同步本地 t_ugc_work,避免仅依赖 Webhook 导致发布前状态滞后
|
||||
* 解析乐读派 update 响应并同步本地 t_ugc_work,避免仅依赖 Webhook 导致发布前状态滞后;
|
||||
* 成功后额外用本次请求体覆盖本地元数据(与乐读派查询结果解耦,避免远端滞后)。
|
||||
*/
|
||||
private void syncLocalAfterLeaiUpdate(String remoteWorkId, String responseJson) {
|
||||
if (responseJson == null || responseJson.isEmpty()) {
|
||||
return;
|
||||
private void syncLocalAfterLeaiUpdate(String remoteWorkId, String responseJson, Map<String, Object> proxyRequestBody) {
|
||||
if (responseJson != null && !responseJson.isEmpty()) {
|
||||
try {
|
||||
Map<String, Object> root = objectMapper.readValue(responseJson, new TypeReference<Map<String, Object>>() {});
|
||||
boolean ok = true;
|
||||
if (root.containsKey("code")) {
|
||||
int code = LeaiUtil.toInt(root.get("code"), 200);
|
||||
// 乐读派部分接口成功为 0,与前端 publicApi 拦截器一致
|
||||
if (code != 200 && code != 0) {
|
||||
ok = false;
|
||||
log.debug("[乐读派代理] update 响应非成功,跳过远端拉取同步: code={}", code);
|
||||
}
|
||||
}
|
||||
if (ok) {
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, Object> data = (Map<String, Object>) root.get("data");
|
||||
if (data == null) {
|
||||
data = root;
|
||||
}
|
||||
if (data.get("status") == null) {
|
||||
data = leaiApiClient.fetchWorkDetail(remoteWorkId);
|
||||
}
|
||||
if (data != null) {
|
||||
leaiSyncService.syncWork(remoteWorkId, data, "Proxy[update.work]");
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.warn("[乐读派代理] 编辑作品后同步本地失败: remoteWorkId={}", remoteWorkId, e);
|
||||
}
|
||||
}
|
||||
try {
|
||||
Map<String, Object> root = objectMapper.readValue(responseJson, new TypeReference<Map<String, Object>>() {});
|
||||
if (root.containsKey("code")) {
|
||||
int code = LeaiUtil.toInt(root.get("code"), 200);
|
||||
if (code != 200) {
|
||||
log.debug("[乐读派代理] update 响应非成功,跳过本地同步: code={}", code);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, Object> data = (Map<String, Object>) root.get("data");
|
||||
if (data == null) {
|
||||
data = root;
|
||||
}
|
||||
if (data.get("status") == null) {
|
||||
data = leaiApiClient.fetchWorkDetail(remoteWorkId);
|
||||
if (data == null) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
leaiSyncService.syncWork(remoteWorkId, data, "Proxy[update.work]");
|
||||
leaiSyncService.applyLocalMetadataFromProxyPut(remoteWorkId, proxyRequestBody);
|
||||
} catch (Exception e) {
|
||||
log.warn("[乐读派代理] 编辑作品后同步本地失败: remoteWorkId={}", remoteWorkId, e);
|
||||
log.warn("[乐读派代理] 本地元数据覆盖失败: remoteWorkId={}", remoteWorkId, e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -23,4 +23,12 @@ public interface ILeaiSyncService {
|
||||
* @param source 来源标识(用于日志,如 "Webhook[work.status_changed]")
|
||||
*/
|
||||
void syncWork(String remoteWorkId, Map<String, Object> remoteData, String source);
|
||||
|
||||
/**
|
||||
* PUT 代理成功后,用本次请求体中的元数据直接覆盖本地 t_ugc_work(与乐读派查询解耦,避免远端滞后)。
|
||||
*
|
||||
* @param remoteWorkId 乐读派作品 ID
|
||||
* @param proxyRequestBody 前端传入的 JSON(通常含 author、subtitle、intro、tags 等,不含 orgId/phone)
|
||||
*/
|
||||
void applyLocalMetadataFromProxyPut(String remoteWorkId, Map<String, Object> proxyRequestBody);
|
||||
}
|
||||
|
||||
@ -29,6 +29,8 @@ import java.util.*;
|
||||
@RequiredArgsConstructor
|
||||
public class LeaiSyncService implements ILeaiSyncService {
|
||||
|
||||
private static final String SOURCE_PROXY_UPDATE = "Proxy[update.work]";
|
||||
|
||||
private final UgcWorkMapper ugcWorkMapper;
|
||||
private final UgcWorkPageMapper ugcWorkPageMapper;
|
||||
private final LeaiApiClient leaiApiClient;
|
||||
@ -90,6 +92,16 @@ public class LeaiSyncService implements ILeaiSyncService {
|
||||
return;
|
||||
}
|
||||
|
||||
// PUT /update/work 仅改作者/副标题/简介时乐读派 status 不变,不走「状态前进」;需单独同步元数据
|
||||
if (remoteStatus >= LeaiCreationStatus.CATALOGED
|
||||
&& remoteStatus == localLeaiStatus
|
||||
&& source != null
|
||||
&& source.contains(SOURCE_PROXY_UPDATE)) {
|
||||
applyMetadataFromRemote(localWork, remoteData);
|
||||
log.info("[{}] 元数据同步(状态未变) remoteWorkId={}", source, remoteWorkId);
|
||||
return;
|
||||
}
|
||||
|
||||
// 旧数据或重复推送,忽略状态更新
|
||||
// 但如果 remoteStatus >= 3 且本地缺少页面数据,需要补充拉取
|
||||
if (remoteStatus >= LeaiCreationStatus.COMPLETED && !hasPages(localWork.getId())) {
|
||||
@ -370,6 +382,94 @@ public class LeaiSyncService implements ILeaiSyncService {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 乐读派编辑元数据后 status 不变时,将 title/author/副标题与简介 写入本地(无 leaiStatus CAS)
|
||||
*/
|
||||
private void applyMetadataFromRemote(UgcWork work, Map<String, Object> remoteData) {
|
||||
LambdaUpdateWrapper<UgcWork> wrapper = new LambdaUpdateWrapper<>();
|
||||
wrapper.eq(UgcWork::getId, work.getId());
|
||||
boolean any = false;
|
||||
if (remoteData.containsKey("title")) {
|
||||
wrapper.set(UgcWork::getTitle, LeaiUtil.toString(remoteData.get("title"), null));
|
||||
any = true;
|
||||
}
|
||||
if (remoteData.containsKey("author")) {
|
||||
wrapper.set(UgcWork::getAuthorName, LeaiUtil.toString(remoteData.get("author"), null));
|
||||
any = true;
|
||||
}
|
||||
if (remoteData.containsKey("intro") || remoteData.containsKey("subtitle")) {
|
||||
String sub = remoteData.containsKey("subtitle")
|
||||
? LeaiUtil.toString(remoteData.get("subtitle"), "")
|
||||
: "";
|
||||
String intro = remoteData.containsKey("intro")
|
||||
? LeaiUtil.toString(remoteData.get("intro"), "")
|
||||
: "";
|
||||
String desc;
|
||||
if (sub.isEmpty()) {
|
||||
desc = intro;
|
||||
} else if (intro.isEmpty()) {
|
||||
desc = sub;
|
||||
} else {
|
||||
desc = sub + "\n" + intro;
|
||||
}
|
||||
desc = desc.trim();
|
||||
wrapper.set(UgcWork::getDescription, desc.isEmpty() ? null : desc);
|
||||
any = true;
|
||||
}
|
||||
if (!any) {
|
||||
return;
|
||||
}
|
||||
wrapper.set(UgcWork::getModifyTime, LocalDateTime.now());
|
||||
ugcWorkMapper.update(null, wrapper);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void applyLocalMetadataFromProxyPut(String remoteWorkId, Map<String, Object> proxyRequestBody) {
|
||||
if (remoteWorkId == null || remoteWorkId.isEmpty() || proxyRequestBody == null || proxyRequestBody.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
UgcWork work = findByRemoteWorkId(remoteWorkId);
|
||||
if (work == null) {
|
||||
return;
|
||||
}
|
||||
LambdaUpdateWrapper<UgcWork> wrapper = new LambdaUpdateWrapper<>();
|
||||
wrapper.eq(UgcWork::getId, work.getId());
|
||||
boolean any = false;
|
||||
if (proxyRequestBody.containsKey("author")) {
|
||||
wrapper.set(UgcWork::getAuthorName, LeaiUtil.toString(proxyRequestBody.get("author"), null));
|
||||
any = true;
|
||||
}
|
||||
if (proxyRequestBody.containsKey("title")) {
|
||||
wrapper.set(UgcWork::getTitle, LeaiUtil.toString(proxyRequestBody.get("title"), null));
|
||||
any = true;
|
||||
}
|
||||
if (proxyRequestBody.containsKey("intro") || proxyRequestBody.containsKey("subtitle")) {
|
||||
String sub = proxyRequestBody.containsKey("subtitle")
|
||||
? LeaiUtil.toString(proxyRequestBody.get("subtitle"), "")
|
||||
: "";
|
||||
String intro = proxyRequestBody.containsKey("intro")
|
||||
? LeaiUtil.toString(proxyRequestBody.get("intro"), "")
|
||||
: "";
|
||||
String desc;
|
||||
if (sub.isEmpty()) {
|
||||
desc = intro;
|
||||
} else if (intro.isEmpty()) {
|
||||
desc = sub;
|
||||
} else {
|
||||
desc = sub + "\n" + intro;
|
||||
}
|
||||
desc = desc.trim();
|
||||
wrapper.set(UgcWork::getDescription, desc.isEmpty() ? null : desc);
|
||||
any = true;
|
||||
}
|
||||
if (!any) {
|
||||
return;
|
||||
}
|
||||
wrapper.set(UgcWork::getModifyTime, LocalDateTime.now());
|
||||
ugcWorkMapper.update(null, wrapper);
|
||||
log.info("[ProxyPut] 本地元数据已覆盖 remoteWorkId={}", remoteWorkId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过 remoteWorkId 查找本地作品
|
||||
*/
|
||||
|
||||
@ -4,34 +4,35 @@
|
||||
<template #title>评委管理</template>
|
||||
<template #extra>
|
||||
<a-space>
|
||||
<a-button
|
||||
v-permission="'judge:create'"
|
||||
type="primary"
|
||||
@click="handleAdd"
|
||||
>
|
||||
<template #icon><PlusOutlined /></template>
|
||||
<a-button v-permission="'judge:create'" type="primary" @click="handleAdd">
|
||||
<template #icon>
|
||||
<PlusOutlined />
|
||||
</template>
|
||||
新增
|
||||
</a-button>
|
||||
<a-tooltip title="功能开发中,敬请期待">
|
||||
<a-button v-permission="'judge:create'" disabled>
|
||||
<template #icon><UploadOutlined /></template>
|
||||
<template #icon>
|
||||
<UploadOutlined />
|
||||
</template>
|
||||
导入
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
<a-tooltip title="功能开发中,敬请期待">
|
||||
<a-button v-permission="'judge:read'" disabled>
|
||||
<template #icon><DownloadOutlined /></template>
|
||||
<template #icon>
|
||||
<DownloadOutlined />
|
||||
</template>
|
||||
导出
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
<a-popconfirm
|
||||
v-permission="'judge:delete'"
|
||||
title="确定要删除选中的评委吗?"
|
||||
<a-popconfirm v-permission="'judge:delete'" title="确定要删除选中的评委吗?"
|
||||
:disabled="selectedRowKeys.length === 0 || selectedRows.every(r => r.isPlatform)"
|
||||
@confirm="handleBatchDelete"
|
||||
>
|
||||
@confirm="handleBatchDelete">
|
||||
<a-button danger :disabled="selectedRowKeys.length === 0 || selectedRows.every(r => r.isPlatform)">
|
||||
<template #icon><DeleteOutlined /></template>
|
||||
<template #icon>
|
||||
<DeleteOutlined />
|
||||
</template>
|
||||
删除
|
||||
</a-button>
|
||||
</a-popconfirm>
|
||||
@ -40,70 +41,42 @@
|
||||
</a-card>
|
||||
|
||||
<!-- 搜索表单 -->
|
||||
<a-form
|
||||
:model="searchParams"
|
||||
layout="inline"
|
||||
class="search-form"
|
||||
@finish="handleSearch"
|
||||
>
|
||||
<a-form :model="searchParams" layout="inline" class="search-form" @finish="handleSearch">
|
||||
<a-form-item label="所属单位">
|
||||
<a-input
|
||||
v-model:value="searchParams.organization"
|
||||
placeholder="请输入所属单位"
|
||||
allow-clear
|
||||
style="width: 200px"
|
||||
/>
|
||||
<a-input v-model:value="searchParams.organization" placeholder="请输入所属单位" allow-clear style="width: 200px" />
|
||||
</a-form-item>
|
||||
<a-form-item label="姓名">
|
||||
<a-input
|
||||
v-model:value="searchParams.nickname"
|
||||
placeholder="请输入姓名"
|
||||
allow-clear
|
||||
style="width: 150px"
|
||||
/>
|
||||
<a-input v-model:value="searchParams.nickname" placeholder="请输入姓名" allow-clear style="width: 150px" />
|
||||
</a-form-item>
|
||||
<a-form-item label="账号">
|
||||
<a-input
|
||||
v-model:value="searchParams.username"
|
||||
placeholder="请输入账号"
|
||||
allow-clear
|
||||
style="width: 150px"
|
||||
/>
|
||||
<a-input v-model:value="searchParams.username" placeholder="请输入账号" allow-clear style="width: 150px" />
|
||||
</a-form-item>
|
||||
<a-form-item label="状态">
|
||||
<a-select
|
||||
v-model:value="searchParams.status"
|
||||
placeholder="请选择状态"
|
||||
allow-clear
|
||||
style="width: 120px"
|
||||
@change="handleSearch"
|
||||
>
|
||||
<a-select v-model:value="searchParams.status" placeholder="请选择状态" allow-clear style="width: 120px"
|
||||
@change="handleSearch">
|
||||
<a-select-option value="disabled">停用</a-select-option>
|
||||
<a-select-option value="enabled">启用</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item>
|
||||
<a-button type="primary" html-type="submit">
|
||||
<template #icon><SearchOutlined /></template>
|
||||
<template #icon>
|
||||
<SearchOutlined />
|
||||
</template>
|
||||
搜索
|
||||
</a-button>
|
||||
<a-button style="margin-left: 8px" @click="handleReset">
|
||||
<template #icon><ReloadOutlined /></template>
|
||||
<template #icon>
|
||||
<ReloadOutlined />
|
||||
</template>
|
||||
重置
|
||||
</a-button>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
|
||||
<!-- 数据表格 -->
|
||||
<a-table
|
||||
:columns="columns"
|
||||
:data-source="dataSource"
|
||||
:loading="loading"
|
||||
:pagination="pagination"
|
||||
:row-selection="rowSelection"
|
||||
row-key="id"
|
||||
@change="handleTableChange"
|
||||
>
|
||||
<a-table :columns="columns" :data-source="dataSource" :loading="loading" :pagination="pagination"
|
||||
:row-selection="rowSelection" row-key="id" @change="handleTableChange">
|
||||
<template #bodyCell="{ column, record, index }">
|
||||
<template v-if="column.key === 'index'">
|
||||
{{ (pagination.current - 1) * pagination.pageSize + index + 1 }}
|
||||
@ -126,9 +99,7 @@
|
||||
</a-tag>
|
||||
</template>
|
||||
<template v-else-if="column.key === 'ongoingContests'">
|
||||
<template
|
||||
v-if="record.contestJudges && record.contestJudges.length > 0"
|
||||
>
|
||||
<template v-if="record.contestJudges && record.contestJudges.length > 0">
|
||||
<a-tooltip>
|
||||
<template #title>
|
||||
<div v-for="cj in record.contestJudges" :key="cj.contest.id">
|
||||
@ -145,31 +116,17 @@
|
||||
<template v-else-if="column.key === 'action'">
|
||||
<a-space>
|
||||
<template v-if="!record.isPlatform">
|
||||
<a-button
|
||||
v-permission="'judge:update'"
|
||||
type="link"
|
||||
size="small"
|
||||
@click="handleToggleStatus(record)"
|
||||
>
|
||||
<a-button v-permission="'judge:update'" type="link" size="small" @click="handleToggleStatus(record)">
|
||||
{{ record.status === "enabled" ? "冻结" : "解冻" }}
|
||||
</a-button>
|
||||
</template>
|
||||
<template v-if="!record.isPlatform">
|
||||
<a-button
|
||||
v-permission="'judge:update'"
|
||||
type="link"
|
||||
size="small"
|
||||
@click="handleEdit(record)"
|
||||
>
|
||||
<a-button v-permission="'judge:update'" type="link" size="small" @click="handleEdit(record)">
|
||||
编辑
|
||||
</a-button>
|
||||
</template>
|
||||
<a-popconfirm
|
||||
v-if="!record.isPlatform"
|
||||
v-permission="'judge:delete'"
|
||||
title="确定要删除这个评委吗?"
|
||||
@confirm="handleDelete(record.id)"
|
||||
>
|
||||
<a-popconfirm v-if="!record.isPlatform" v-permission="'judge:delete'" title="确定要删除这个评委吗?"
|
||||
@confirm="handleDelete(record.id)">
|
||||
<a-button type="link" danger size="small">删除</a-button>
|
||||
</a-popconfirm>
|
||||
</a-space>
|
||||
@ -178,34 +135,14 @@
|
||||
</a-table>
|
||||
|
||||
<!-- 新增/编辑评委抽屉 -->
|
||||
<a-drawer
|
||||
v-model:open="drawerVisible"
|
||||
:title="isEditing ? '编辑评委' : '新增评委'"
|
||||
placement="right"
|
||||
width="500px"
|
||||
:footer-style="{ textAlign: 'right' }"
|
||||
>
|
||||
<a-form
|
||||
ref="formRef"
|
||||
:model="form"
|
||||
:rules="rules"
|
||||
:label-col="{ span: 6 }"
|
||||
:wrapper-col="{ span: 18 }"
|
||||
>
|
||||
<a-drawer v-model:open="drawerVisible" :title="isEditing ? '编辑评委' : '新增评委'" placement="right" width="500px"
|
||||
:footer-style="{ textAlign: 'right' }">
|
||||
<a-form ref="formRef" :model="form" :rules="rules" :label-col="{ span: 6 }" :wrapper-col="{ span: 18 }">
|
||||
<a-form-item label="账号" name="username">
|
||||
<a-input
|
||||
v-model:value="form.username"
|
||||
placeholder="请输入账号"
|
||||
:maxlength="50"
|
||||
:disabled="isEditing"
|
||||
/>
|
||||
<a-input v-model:value="form.username" placeholder="请输入账号" :maxlength="50" :disabled="isEditing" />
|
||||
</a-form-item>
|
||||
<a-form-item label="姓名" name="nickname">
|
||||
<a-input
|
||||
v-model:value="form.nickname"
|
||||
placeholder="请输入姓名"
|
||||
:maxlength="50"
|
||||
/>
|
||||
<a-input v-model:value="form.nickname" placeholder="请输入姓名" :maxlength="50" />
|
||||
</a-form-item>
|
||||
<a-form-item label="性别" name="gender">
|
||||
<a-radio-group v-model:value="form.gender">
|
||||
@ -214,31 +151,18 @@
|
||||
</a-radio-group>
|
||||
</a-form-item>
|
||||
<a-form-item label="所属单位" name="organization">
|
||||
<a-input
|
||||
v-model:value="form.organization"
|
||||
placeholder="请输入所属单位"
|
||||
:maxlength="100"
|
||||
/>
|
||||
<a-input v-model:value="form.organization" placeholder="请输入所属单位" :maxlength="100" />
|
||||
</a-form-item>
|
||||
<a-form-item label="联系方式" name="phone">
|
||||
<a-input
|
||||
v-model:value="form.phone"
|
||||
placeholder="请输入手机号"
|
||||
:maxlength="11"
|
||||
/>
|
||||
<a-input v-model:value="form.phone" placeholder="请输入手机号" :maxlength="11" />
|
||||
</a-form-item>
|
||||
<a-form-item label="初始密码" name="password">
|
||||
<a-input-password
|
||||
v-model:value="form.password"
|
||||
:placeholder="isEditing ? '请输入新密码' : '请输入初始密码'"
|
||||
:maxlength="50"
|
||||
/>
|
||||
<a-input-password v-model:value="form.password" :placeholder="isEditing ? '请输入新密码' : '请输入初始密码'"
|
||||
:maxlength="50" />
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
<template #footer>
|
||||
<a-button style="margin-right: 8px" @click="handleCancel"
|
||||
>取消</a-button
|
||||
>
|
||||
<a-button style="margin-right: 8px" @click="handleCancel">取消</a-button>
|
||||
<a-button type="primary" :loading="submitLoading" @click="handleSubmit">
|
||||
确定
|
||||
</a-button>
|
||||
@ -562,7 +486,7 @@ const handleSubmit = async () => {
|
||||
}
|
||||
message.error(
|
||||
error?.response?.data?.message ||
|
||||
(isEditing.value ? "编辑失败" : "创建失败")
|
||||
(isEditing.value ? "编辑失败" : "创建失败")
|
||||
)
|
||||
} finally {
|
||||
submitLoading.value = false
|
||||
@ -592,9 +516,16 @@ $primary: #6366f1;
|
||||
|
||||
.ant-card-head {
|
||||
border-bottom: none;
|
||||
.ant-card-head-title { font-size: 18px; font-weight: 600; }
|
||||
|
||||
.ant-card-head-title {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
|
||||
.ant-card-body {
|
||||
padding: 0;
|
||||
}
|
||||
.ant-card-body { padding: 0; }
|
||||
}
|
||||
|
||||
:deep(.ant-table-wrapper) {
|
||||
@ -603,9 +534,19 @@ $primary: #6366f1;
|
||||
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.06);
|
||||
overflow: hidden;
|
||||
|
||||
.ant-table-thead > tr > th { background: #fafafa; font-weight: 600; }
|
||||
.ant-table-tbody > tr:hover > td { background: rgba($primary, 0.03); }
|
||||
.ant-table-pagination { padding: 16px; margin: 0; }
|
||||
.ant-table-thead>tr>th {
|
||||
background: #fafafa;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.ant-table-tbody>tr:hover>td {
|
||||
background: rgba($primary, 0.03);
|
||||
}
|
||||
|
||||
.ant-table-pagination {
|
||||
padding: 16px;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -517,8 +517,11 @@ async function loadWork() {
|
||||
}
|
||||
const w = store.workDetail
|
||||
|
||||
if (w.status >= STATUS.DUBBED) {
|
||||
const nextRoute = getRouteByStatus(w.status, w.workId)
|
||||
// 仅当尚未编目完成(CATALOGED)时按流程跳转(如 COMPLETED→预览);已 DUBBED 仍允许停留本页,
|
||||
// 否则从编辑页点「去配音」会立刻被 getRouteByStatus(DUBBED→EditInfo) 打回当前页。
|
||||
if (w.status < STATUS.CATALOGED) {
|
||||
const wid = String(w.workId ?? workId.value ?? '')
|
||||
const nextRoute = getRouteByStatus(w.status, wid)
|
||||
if (nextRoute) { router.replace(nextRoute); return }
|
||||
}
|
||||
|
||||
|
||||
@ -115,7 +115,7 @@ export default { name: 'EditInfoView' }
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, onActivated, nextTick } from 'vue'
|
||||
import { ref, computed, onActivated, nextTick, watch } from 'vue'
|
||||
import { useRouter, useRoute } from 'vue-router'
|
||||
import {
|
||||
LoadingOutlined,
|
||||
@ -142,6 +142,13 @@ const route = useRoute()
|
||||
const store = useAicreateStore()
|
||||
const workId = computed(() => route.params.workId || store.workId)
|
||||
|
||||
/** 乐读派 workId 可能超过 JS 安全整数,请求与比较一律用路由字符串 */
|
||||
function resolvedWorkIdStr() {
|
||||
const id = workId.value
|
||||
if (id == null || id === '') return ''
|
||||
return String(id)
|
||||
}
|
||||
|
||||
const loading = ref(true)
|
||||
const processing = ref(false)
|
||||
const coverUrl = ref('')
|
||||
@ -182,19 +189,22 @@ function confirmAddTag() {
|
||||
}
|
||||
|
||||
async function loadWork() {
|
||||
const id = resolvedWorkIdStr()
|
||||
if (!id) {
|
||||
loading.value = false
|
||||
return
|
||||
}
|
||||
loading.value = true
|
||||
try {
|
||||
// 缓存不匹配当前 workId 时重新请求
|
||||
if (!store.workDetail || store.workDetail.workId !== workId.value) {
|
||||
store.workDetail = null
|
||||
const res = await getWorkDetail(workId.value)
|
||||
store.workDetail = res
|
||||
}
|
||||
// 必须每次拉取详情:创作壳 keep-alive 会缓存组件,仅靠 store 命中会跳过请求,二次进入表单仍是旧值
|
||||
store.workDetail = null
|
||||
const res = await getWorkDetail(id)
|
||||
store.workDetail = res
|
||||
const w = store.workDetail
|
||||
|
||||
// 已配音(DUBBED)仍可在本页编辑元数据/发布;仅当状态高于当前流程终态时再按 status 跳转
|
||||
if (w.status > STATUS.DUBBED) {
|
||||
const nextRoute = getRouteByStatus(w.status, w.workId)
|
||||
const nextRoute = getRouteByStatus(w.status, id)
|
||||
if (nextRoute) { router.replace(nextRoute); return }
|
||||
}
|
||||
|
||||
@ -227,24 +237,29 @@ function validate() {
|
||||
*/
|
||||
async function saveFormToServer() {
|
||||
try {
|
||||
const id = resolvedWorkIdStr()
|
||||
if (!id) {
|
||||
message.error('作品 ID 无效')
|
||||
return false
|
||||
}
|
||||
const data = { tags: selectedTags.value }
|
||||
data.author = form.value.author.trim()
|
||||
if (form.value.subtitle.trim()) data.subtitle = form.value.subtitle.trim()
|
||||
if (form.value.intro.trim()) data.intro = form.value.intro.trim()
|
||||
data.subtitle = form.value.subtitle.trim()
|
||||
data.intro = form.value.intro.trim()
|
||||
|
||||
await updateWork(workId.value, data)
|
||||
await updateWork(id, data)
|
||||
|
||||
if (store.workDetail) {
|
||||
store.workDetail.author = data.author
|
||||
if (data.subtitle) store.workDetail.subtitle = data.subtitle
|
||||
if (data.intro) store.workDetail.intro = data.intro
|
||||
store.workDetail.subtitle = data.subtitle
|
||||
store.workDetail.intro = data.intro
|
||||
store.workDetail.tags = [...selectedTags.value]
|
||||
}
|
||||
return true
|
||||
} catch (e) {
|
||||
// 容错:保存报错时检查实际状态,可能已经成功但重试导致 CAS 失败
|
||||
try {
|
||||
const check = await getWorkDetail(workId.value)
|
||||
const check = await getWorkDetail(resolvedWorkIdStr())
|
||||
if (check?.status >= 4) return true
|
||||
} catch { /* ignore */ }
|
||||
message.error(e.message || '保存失败,请重试')
|
||||
@ -261,7 +276,7 @@ async function handleSave() {
|
||||
store.workDetail = null
|
||||
router.push({
|
||||
name: 'PublicCreateSaveSuccess',
|
||||
params: { workId: String(workId.value || '') },
|
||||
params: { workId: resolvedWorkIdStr() },
|
||||
})
|
||||
}
|
||||
} finally {
|
||||
@ -276,7 +291,7 @@ async function handleGoDubbing() {
|
||||
try {
|
||||
if (await saveFormToServer()) {
|
||||
store.workDetail = null
|
||||
router.push(`/p/create/dubbing/${workId.value}`)
|
||||
router.push(`/p/create/dubbing/${resolvedWorkIdStr()}`)
|
||||
}
|
||||
} finally {
|
||||
processing.value = false
|
||||
@ -308,7 +323,7 @@ async function handlePublish() {
|
||||
try {
|
||||
if (!(await saveFormToServer())) return
|
||||
|
||||
const rw = String(workId.value || '').trim()
|
||||
const rw = resolvedWorkIdStr().trim()
|
||||
let ugcId = await findUgcIdByRemoteWorkId(rw)
|
||||
if (ugcId == null) {
|
||||
message.error('作品库尚未同步该绘本,请稍后重试或到作品详情页点击「公开发布」')
|
||||
@ -341,6 +356,15 @@ async function handlePublish() {
|
||||
}
|
||||
}
|
||||
|
||||
// 同组件仅路由参数切换作品时(keep-alive 复用实例)需重新拉详情
|
||||
watch(
|
||||
() => String(route.params.workId || ''),
|
||||
(newId, oldId) => {
|
||||
if (!newId) return
|
||||
if (oldId !== undefined && newId !== oldId) loadWork()
|
||||
},
|
||||
)
|
||||
|
||||
onActivated(() => {
|
||||
clearExtractDraft()
|
||||
loadWork()
|
||||
|
||||
Loading…
Reference in New Issue
Block a user