mirror of
https://gitee.com/bootx/dax-pay-ui
synced 2026-08-11 15:15:34 +08:00
feat(admin): 用户协议版本新建支持继承上一版内容
新建草稿默认继承同语言上一版,已发布/归档行支持复制为草稿,并补充多语种文案。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -18,6 +18,13 @@ export const UserProtocolVersionApi = {
|
||||
add(data: UserProtocolVersion): Promise<Result<void>> {
|
||||
return defHttp.post({ url: '/user/protocol/version/add', data });
|
||||
},
|
||||
/** 查询新建草稿可继承的源版本(已发布优先, 否则最新归档) */
|
||||
findInheritSource(protocolId: string, language: string): Promise<Result<UserProtocolVersion | null>> {
|
||||
return defHttp.get({
|
||||
url: '/user/protocol/version/find-inherit-source',
|
||||
params: { protocolId, language },
|
||||
});
|
||||
},
|
||||
/** 编辑草稿内容(仅草稿可编辑) */
|
||||
update(data: UserProtocolVersion): Promise<Result<void>> {
|
||||
return defHttp.post({ url: '/user/protocol/version/update', data });
|
||||
|
||||
@@ -48,6 +48,9 @@
|
||||
"confirmArchive": "Archive this version?",
|
||||
"confirmDelete": "Are you sure to delete this draft?",
|
||||
"confirmClose": "The content has been changed. Close anyway? Unsaved changes will be lost.",
|
||||
"createFromThis": "Copy as draft",
|
||||
"confirmReplaceByLanguage": "Switching language will replace the current content. Continue?",
|
||||
"inheritedTip": "Content loaded from the previous version. Edit and save as a new draft.",
|
||||
"statusDraft": "Draft",
|
||||
"statusPublished": "Published",
|
||||
"statusArchived": "Archived"
|
||||
|
||||
@@ -48,6 +48,9 @@
|
||||
"confirmArchive": "Arsipkan versi ini?",
|
||||
"confirmDelete": "Apakah Anda yakin akan menghapus draf ini?",
|
||||
"confirmClose": "Kontennya telah diubah. Tutup saja? Perubahan yang belum disimpan akan hilang.",
|
||||
"createFromThis": "Salin sebagai draf",
|
||||
"confirmReplaceByLanguage": "Mengganti bahasa akan menimpa konten saat ini. Lanjutkan?",
|
||||
"inheritedTip": "Konten dimuat dari versi sebelumnya. Edit lalu simpan sebagai draf baru.",
|
||||
"statusDraft": "Draf",
|
||||
"statusPublished": "Diterbitkan",
|
||||
"statusArchived": "Diarsipkan"
|
||||
|
||||
@@ -48,6 +48,9 @@
|
||||
"confirmArchive": "このバージョンをアーカイブしますか?",
|
||||
"confirmDelete": "この下書きを削除してもよろしいですか?",
|
||||
"confirmClose": "内容が変更になりました。とにかく閉じますか?保存されていない変更は失われます。",
|
||||
"createFromThis": "下書きにコピー",
|
||||
"confirmReplaceByLanguage": "言語を切り替えると現在の本文が上書きされます。続けますか?",
|
||||
"inheritedTip": "前のバージョンから内容を読み込みました。編集して新しい下書きとして保存できます",
|
||||
"statusDraft": "下書き",
|
||||
"statusPublished": "発行済み",
|
||||
"statusArchived": "アーカイブ済み"
|
||||
|
||||
@@ -48,6 +48,9 @@
|
||||
"confirmArchive": "이 버전을 보관처리하시겠습니까?",
|
||||
"confirmDelete": "이 초안을 삭제하시겠습니까?",
|
||||
"confirmClose": "내용이 변경되었습니다. 그래도 닫으시겠어요? 저장되지 않은 변경사항은 손실됩니다.",
|
||||
"createFromThis": "초안으로 복사",
|
||||
"confirmReplaceByLanguage": "언어를 바꾸면 현재 본문이 덮어씌워집니다. 계속할까요?",
|
||||
"inheritedTip": "이전 버전에서 내용을 불러왔습니다. 수정 후 새 초안으로 저장하세요",
|
||||
"statusDraft": "초안",
|
||||
"statusPublished": "게시됨",
|
||||
"statusArchived": "보관됨"
|
||||
|
||||
@@ -48,6 +48,9 @@
|
||||
"confirmArchive": "Arkibkan versi ini?",
|
||||
"confirmDelete": "Adakah anda pasti akan memadamkan draf ini?",
|
||||
"confirmClose": "Kandungan telah diubah. Tutup pula? Perubahan yang tidak disimpan akan hilang.",
|
||||
"createFromThis": "Salin sebagai draf",
|
||||
"confirmReplaceByLanguage": "Menukar bahasa akan menggantikan kandungan semasa. Teruskan?",
|
||||
"inheritedTip": "Kandungan dimuatkan dari versi sebelumnya. Edit lalu simpan sebagai draf baharu.",
|
||||
"statusDraft": "Draf",
|
||||
"statusPublished": "Diterbitkan",
|
||||
"statusArchived": "Diarkibkan"
|
||||
|
||||
@@ -48,6 +48,9 @@
|
||||
"confirmArchive": "เก็บเวอร์ชันนี้ไหม",
|
||||
"confirmDelete": "คุณแน่ใจหรือว่าจะลบฉบับร่างนี้",
|
||||
"confirmClose": "เนื้อหามีการเปลี่ยนแปลง ปิดรึยังคะ? การเปลี่ยนแปลงที่ไม่ได้บันทึกจะสูญหาย",
|
||||
"createFromThis": "คัดลอกเป็นร่าง",
|
||||
"confirmReplaceByLanguage": "เปลี่ยนภาษาจะเขียนทับเนื้อหาปัจจุบัน ดำเนินการต่อหรือไม่?",
|
||||
"inheritedTip": "โหลดเนื้อหาจากรุ่นก่อนแล้ว แก้ไขแล้วบันทึกเป็นร่างใหม่ได้",
|
||||
"statusDraft": "ร่าง",
|
||||
"statusPublished": "ที่ตีพิมพ์",
|
||||
"statusArchived": "เก็บถาวรแล้ว"
|
||||
|
||||
@@ -48,6 +48,9 @@
|
||||
"confirmArchive": "Lưu trữ phiên bản này?",
|
||||
"confirmDelete": "Bạn có chắc chắn xóa bản nháp này không?",
|
||||
"confirmClose": "Nội dung đã được thay đổi. Vẫn đóng? Những thay đổi chưa được lưu sẽ bị mất.",
|
||||
"createFromThis": "Sao chép thành bản nháp",
|
||||
"confirmReplaceByLanguage": "Đổi ngôn ngữ sẽ ghi đè nội dung hiện tại. Tiếp tục?",
|
||||
"inheritedTip": "Đã tải nội dung từ phiên bản trước. Chỉnh sửa rồi lưu thành bản nháp mới.",
|
||||
"statusDraft": "Bản nháp",
|
||||
"statusPublished": "Đã xuất bản",
|
||||
"statusArchived": "Đã lưu trữ"
|
||||
|
||||
@@ -48,6 +48,9 @@
|
||||
"confirmArchive": "确认归档该版本吗?",
|
||||
"confirmDelete": "确认删除该草稿吗?",
|
||||
"confirmClose": "协议内容已有改动,确认关闭吗?关闭后未保存的内容将丢失。",
|
||||
"createFromThis": "复制为草稿",
|
||||
"confirmReplaceByLanguage": "切换语言将覆盖当前正文,是否继续?",
|
||||
"inheritedTip": "已从上一版本载入内容,可直接修改后保存为新草稿",
|
||||
"statusDraft": "草稿",
|
||||
"statusPublished": "已发布",
|
||||
"statusArchived": "已归档"
|
||||
|
||||
@@ -48,6 +48,9 @@
|
||||
"confirmArchive": "確認歸檔該版本嗎?",
|
||||
"confirmDelete": "確認刪除該草稿嗎?",
|
||||
"confirmClose": "協議內容已有改動,確認關閉嗎?關閉後未保存的內容將丟失。",
|
||||
"createFromThis": "複製為草稿",
|
||||
"confirmReplaceByLanguage": "切換語言將覆蓋當前正文,是否繼續?",
|
||||
"inheritedTip": "已從上一版本載入內容,可直接修改後保存為新草稿",
|
||||
"statusDraft": "草稿",
|
||||
"statusPublished": "已發佈",
|
||||
"statusArchived": "已歸檔"
|
||||
|
||||
@@ -48,6 +48,9 @@
|
||||
"confirmArchive": "確認歸檔該版本嗎?",
|
||||
"confirmDelete": "確認刪除該草稿嗎?",
|
||||
"confirmClose": "協議內容已有改動,確認關閉嗎?關閉後未儲存的內容將丟失。",
|
||||
"createFromThis": "複製為草稿",
|
||||
"confirmReplaceByLanguage": "切換語言將覆蓋當前正文,是否繼續?",
|
||||
"inheritedTip": "已從上一版本載入內容,可直接修改後儲存為新草稿",
|
||||
"statusDraft": "草稿",
|
||||
"statusPublished": "已釋出",
|
||||
"statusArchived": "已歸檔"
|
||||
|
||||
@@ -30,6 +30,12 @@
|
||||
});
|
||||
// 协议正文初始内容(用于关闭时检测是否有未保存修改)
|
||||
const loadedContent = ref('');
|
||||
// 最近一次继承载入的正文(用于语言切换时判断是否可自动覆盖)
|
||||
const inheritedContent = ref('');
|
||||
// 是否已从上一版本载入内容(展示提示)
|
||||
const inherited = ref(false);
|
||||
// 语言切换时用于回滚的上一次语言
|
||||
const previousLanguage = ref('zh-CN');
|
||||
|
||||
// 语言选项(与 SUPPORT_LANGUAGES 对齐)
|
||||
const languageOptions = [
|
||||
@@ -53,27 +59,132 @@
|
||||
content: [{ required: true, message: $t('system.protocol.version.inputContent') }],
|
||||
};
|
||||
|
||||
/** 入口 */
|
||||
function init(protocolId: string, id: string | undefined, editType: FormEditType) {
|
||||
/**
|
||||
* 入口
|
||||
* @param protocolId 协议ID
|
||||
* @param id 版本ID(编辑/查看时)
|
||||
* @param editType 表单模式
|
||||
* @param sourceVersionId 基于指定版本新建时传入源版本ID
|
||||
*/
|
||||
function init(protocolId: string, id: string | undefined, editType: FormEditType, sourceVersionId?: string) {
|
||||
initFormEditType(editType);
|
||||
resetForm();
|
||||
form.value.protocolId = protocolId;
|
||||
getInfo(id, editType);
|
||||
previousLanguage.value = form.value.language ?? 'zh-CN';
|
||||
getInfo(id, editType, sourceVersionId);
|
||||
}
|
||||
|
||||
/** 将源版本字段预填到新建草稿表单(不带 id/状态/版本号, summary 清空) */
|
||||
function applyInheritSource(source: UserProtocolVersion, protocolId: string) {
|
||||
const content = source.content ?? '';
|
||||
form.value = {
|
||||
protocolId,
|
||||
language: source.language || 'zh-CN',
|
||||
contentFormat: source.contentFormat || 'MARKDOWN',
|
||||
versionLabel: source.versionLabel,
|
||||
title: source.title,
|
||||
content,
|
||||
contentHtml: source.contentHtml ?? '',
|
||||
// 变更说明需重写
|
||||
summary: '',
|
||||
};
|
||||
inheritedContent.value = content;
|
||||
loadedContent.value = content;
|
||||
inherited.value = true;
|
||||
previousLanguage.value = form.value.language ?? 'zh-CN';
|
||||
}
|
||||
|
||||
/** 按协议+语言拉取可继承源版本并预填; 无源则保持空白 */
|
||||
async function loadInheritByLanguage(protocolId: string, language: string) {
|
||||
const { data } = await UserProtocolVersionApi.findInheritSource(protocolId, language);
|
||||
if (data) {
|
||||
applyInheritSource(data, protocolId);
|
||||
// 保持用户刚切换的目标语言(源可能同语言)
|
||||
form.value.language = language;
|
||||
previousLanguage.value = language;
|
||||
} else {
|
||||
form.value.language = language;
|
||||
form.value.title = undefined;
|
||||
form.value.versionLabel = undefined;
|
||||
form.value.content = '';
|
||||
form.value.contentHtml = '';
|
||||
form.value.summary = '';
|
||||
form.value.contentFormat = 'MARKDOWN';
|
||||
inheritedContent.value = '';
|
||||
loadedContent.value = '';
|
||||
inherited.value = false;
|
||||
previousLanguage.value = language;
|
||||
}
|
||||
}
|
||||
|
||||
/** 获取信息 */
|
||||
function getInfo(id: string | undefined, editType: FormEditType) {
|
||||
function getInfo(id: string | undefined, editType: FormEditType, sourceVersionId?: string) {
|
||||
if ([FormEditType.Edit, FormEditType.Show].includes(editType) && id) {
|
||||
confirmLoading.value = true;
|
||||
UserProtocolVersionApi.findById(id).then(({ data }) => {
|
||||
form.value = data;
|
||||
// 记录正文初始值, 用于关闭时检测变动
|
||||
loadedContent.value = data.content ?? '';
|
||||
UserProtocolVersionApi.findById(id)
|
||||
.then(({ data }) => {
|
||||
form.value = data;
|
||||
// 记录正文初始值, 用于关闭时检测变动
|
||||
loadedContent.value = data.content ?? '';
|
||||
inheritedContent.value = '';
|
||||
inherited.value = false;
|
||||
previousLanguage.value = data.language ?? 'zh-CN';
|
||||
})
|
||||
.finally(() => {
|
||||
confirmLoading.value = false;
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// 新建: 基于指定版本 或 默认继承同语言上一版
|
||||
if (editType === FormEditType.Add) {
|
||||
const protocolId = form.value.protocolId!;
|
||||
confirmLoading.value = true;
|
||||
const loadPromise = sourceVersionId
|
||||
? UserProtocolVersionApi.findById(sourceVersionId).then(({ data }) => {
|
||||
applyInheritSource(data, protocolId);
|
||||
})
|
||||
: loadInheritByLanguage(protocolId, form.value.language || 'zh-CN');
|
||||
loadPromise.finally(() => {
|
||||
confirmLoading.value = false;
|
||||
});
|
||||
} else {
|
||||
confirmLoading.value = false;
|
||||
return;
|
||||
}
|
||||
|
||||
confirmLoading.value = false;
|
||||
}
|
||||
|
||||
/** 新建模式下切换语言: 未改过继承正文则自动覆盖, 否则二次确认 */
|
||||
function handleLanguageChange(language: string) {
|
||||
if (formEditType.value !== FormEditType.Add || !form.value.protocolId) {
|
||||
previousLanguage.value = language;
|
||||
return;
|
||||
}
|
||||
const content = form.value.content ?? '';
|
||||
const canAutoReplace = !content || content === inheritedContent.value;
|
||||
if (canAutoReplace) {
|
||||
confirmLoading.value = true;
|
||||
loadInheritByLanguage(form.value.protocolId, language).finally(() => {
|
||||
confirmLoading.value = false;
|
||||
});
|
||||
return;
|
||||
}
|
||||
// 正文已手动改过, 确认后再覆盖
|
||||
confirm({
|
||||
title: $t('common.warning'),
|
||||
// 切换语言将覆盖当前正文
|
||||
content: $t('system.protocol.version.confirmReplaceByLanguage'),
|
||||
onOk: () => {
|
||||
confirmLoading.value = true;
|
||||
return loadInheritByLanguage(form.value.protocolId!, language).finally(() => {
|
||||
confirmLoading.value = false;
|
||||
});
|
||||
},
|
||||
onCancel: () => {
|
||||
// 取消时回滚语言选择
|
||||
form.value.language = previousLanguage.value;
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/** 重置表单 */
|
||||
@@ -88,6 +199,9 @@
|
||||
contentHtml: '',
|
||||
};
|
||||
loadedContent.value = '';
|
||||
inheritedContent.value = '';
|
||||
inherited.value = false;
|
||||
previousLanguage.value = 'zh-CN';
|
||||
}
|
||||
|
||||
/** Markdown 渲染后的HTML */
|
||||
@@ -148,6 +262,10 @@
|
||||
</a-space>
|
||||
</template>
|
||||
<a-spin :spinning="confirmLoading">
|
||||
<!-- 已从上一版本载入提示 -->
|
||||
<div v-if="inherited && !showable" class="mb-4">
|
||||
<a-alert type="info" show-icon :message="$t('system.protocol.version.inheritedTip')" />
|
||||
</div>
|
||||
<a-form ref="formRef" :model="form" :rules="rules" :label-col="{ span: 4 }" :wrapper-col="{ span: 18 }">
|
||||
<!-- 主键 -->
|
||||
<a-form-item label="ID" name="id" :hidden="true">
|
||||
@@ -160,6 +278,7 @@
|
||||
:disabled="showable"
|
||||
:options="languageOptions"
|
||||
:placeholder="$t('common.pleaseSelect')"
|
||||
@change="handleLanguageChange"
|
||||
/>
|
||||
</a-form-item>
|
||||
<!-- 版本标签 -->
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
<script lang="ts" setup>
|
||||
import type { MenuProps } from 'antdv-next';
|
||||
import type { VxeTableInstance, VxeToolbarInstance } from 'vxe-table';
|
||||
|
||||
import { ref } from 'vue';
|
||||
|
||||
import { IconifyIcon } from '@vben-core/icons';
|
||||
import { $t } from '@vben/locales';
|
||||
|
||||
import { UserProtocolVersionApi } from '#/api/system/protocol/user-protocol-version.api';
|
||||
@@ -82,11 +84,16 @@
|
||||
queryPage();
|
||||
}
|
||||
|
||||
/** 新建草稿 */
|
||||
/** 新建草稿(默认继承同语言上一版) */
|
||||
function handleAdd() {
|
||||
versionEdit.value.init(protocolId.value, undefined, FormEditType.Add);
|
||||
}
|
||||
|
||||
/** 基于指定已发布/归档版本新建草稿 */
|
||||
function handleCreateFrom(row: any) {
|
||||
versionEdit.value.init(protocolId.value, undefined, FormEditType.Add, row.id);
|
||||
}
|
||||
|
||||
/** 编辑草稿 */
|
||||
function handleEdit(row: any) {
|
||||
versionEdit.value.init(protocolId.value, row.id, FormEditType.Edit);
|
||||
@@ -145,6 +152,45 @@
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 更多操作菜单
|
||||
* 草稿: 删除; 已发布: 归档
|
||||
*/
|
||||
function getActionMenu(row: any): MenuProps {
|
||||
const items: NonNullable<MenuProps['items']> = [];
|
||||
if (row.status === 'DRAFT') {
|
||||
items.push({
|
||||
key: 'delete',
|
||||
// 删除草稿
|
||||
label: $t('common.delete'),
|
||||
danger: true,
|
||||
});
|
||||
}
|
||||
if (row.status === 'PUBLISHED') {
|
||||
items.push({
|
||||
key: 'archive',
|
||||
// 归档
|
||||
label: $t('system.protocol.version.archive'),
|
||||
});
|
||||
}
|
||||
return {
|
||||
items,
|
||||
onClick: ({ key }: { key: string }) => {
|
||||
if (key === 'delete') {
|
||||
handleDelete(row);
|
||||
}
|
||||
if (key === 'archive') {
|
||||
handleArchive(row);
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/** 是否展示更多菜单 */
|
||||
function hasMoreActions(row: any) {
|
||||
return row.status === 'DRAFT' || row.status === 'PUBLISHED';
|
||||
}
|
||||
|
||||
/** 分页变化 */
|
||||
function handlePageChange({ currentPage, pageSize }: any) {
|
||||
pageConfig.value.currentPage = currentPage;
|
||||
@@ -246,7 +292,7 @@
|
||||
<!-- 变更说明 -->
|
||||
<vxe-column field="summary" :title="$t('system.protocol.version.summary')" :min-width="160" />
|
||||
<!-- 操作 -->
|
||||
<vxe-column fixed="right" width="240" :show-overflow="false" :title="$t('common.operation')">
|
||||
<vxe-column fixed="right" :width="220" :show-overflow="false" :title="$t('common.operation')">
|
||||
<template #default="{ row }">
|
||||
<a-space :size="2">
|
||||
<template #separator>
|
||||
@@ -256,22 +302,30 @@
|
||||
<a-button v-if="row.status === 'DRAFT'" type="link" size="small" @click="handleEdit(row)">{{
|
||||
$t('common.edit')
|
||||
}}</a-button>
|
||||
<!-- 查看(非草稿) -->
|
||||
<a-button v-if="row.status !== 'DRAFT'" type="link" size="small" @click="handleView(row)">{{
|
||||
$t('common.view')
|
||||
}}</a-button>
|
||||
<!-- 发布(仅草稿) -->
|
||||
<a-button v-if="row.status === 'DRAFT'" type="link" size="small" @click="handlePublish(row)">{{
|
||||
$t('system.protocol.version.publish')
|
||||
}}</a-button>
|
||||
<!-- 归档(仅已发布) -->
|
||||
<a-button v-if="row.status === 'PUBLISHED'" type="link" size="small" @click="handleArchive(row)">{{
|
||||
$t('system.protocol.version.archive')
|
||||
}}</a-button>
|
||||
<!-- 删除(仅草稿) -->
|
||||
<a-button v-if="row.status === 'DRAFT'" type="link" size="small" danger @click="handleDelete(row)">{{
|
||||
$t('common.delete')
|
||||
<!-- 查看(非草稿) -->
|
||||
<a-button v-if="row.status !== 'DRAFT'" type="link" size="small" @click="handleView(row)">{{
|
||||
$t('common.view')
|
||||
}}</a-button>
|
||||
<!-- 基于此版本新建(已发布/归档) -->
|
||||
<a-button
|
||||
v-if="row.status === 'PUBLISHED' || row.status === 'ARCHIVED'"
|
||||
type="link"
|
||||
size="small"
|
||||
@click="handleCreateFrom(row)"
|
||||
>
|
||||
{{ $t('system.protocol.version.createFromThis') }}
|
||||
</a-button>
|
||||
<!-- 更多: 草稿删除 / 已发布归档 -->
|
||||
<a-dropdown v-if="hasMoreActions(row)" :menu="getActionMenu(row)">
|
||||
<a-button type="link" size="small">
|
||||
{{ $t('common.more') }}
|
||||
<IconifyIcon icon="ant-design:down-outlined" class="inline" />
|
||||
</a-button>
|
||||
</a-dropdown>
|
||||
</a-space>
|
||||
</template>
|
||||
</vxe-column>
|
||||
|
||||
Reference in New Issue
Block a user