feat: 收敛AI资源发布审批生命周期

- 统一工作流、知识库、聊天助手的发布、重新发布、下线与删除链路

- 收敛审批编排、生命周期状态机与展示态,补齐审批管理和快照预览

- 调整审批管理权限模型为单入口页面加内部按钮权限
This commit is contained in:
2026-04-09 17:13:54 +08:00
parent 81125ce55c
commit 4e565aef99
68 changed files with 3859 additions and 817 deletions

View File

@@ -46,9 +46,16 @@ import PageSide from '#/components/page/PageSide.vue';
import DocumentCollectionModal from '#/views/ai/documentCollection/DocumentCollectionModal.vue';
import AiResourceCornerMeta from '#/views/ai/shared/AiResourceCornerMeta.vue';
import {
buildOfflineImpactMessage,
type OfflineImpactCheck,
} from '#/views/ai/shared/offline-impact';
import {
canAiResourceDelete,
canAiResourceOffline,
canAiResourcePublish,
canAiResourceRepublish,
isAiResourceApprovalPending,
isAiResourcePublished,
normalizeAiPublishStatus,
resolveAiResourceDisplayStatus,
} from '#/views/ai/shared/publish-status';
const router = useRouter();
@@ -186,24 +193,41 @@ const actions: ActionButton[] = [
{
icon: Promotion,
text: (row) =>
isAiResourcePublished(row.publishStatus)
canAiResourceRepublish(row.displayPublishStatus, row.publishStatus)
? $t('button.republish')
: $t('button.submitPublishApproval'),
: $t('button.publish'),
permission: '/api/v1/documentCollection/save',
placement: 'inline',
visible: (row) =>
canAiResourcePublish(row.displayPublishStatus, row.publishStatus) ||
canAiResourceRepublish(row.displayPublishStatus, row.publishStatus),
onClick(row) {
if (!ensureManageKnowledgeItem(row)) {
return;
}
submitPublishApproval(row);
submitPublishAction(row);
},
},
{
text: $t('button.submitDeleteApproval'),
icon: Promotion,
text: $t('button.offline'),
permission: '/api/v1/documentCollection/save',
placement: 'menu',
visible: (row) => canAiResourceOffline(row.displayPublishStatus, row.publishStatus),
onClick(row) {
if (!ensureManageKnowledgeItem(row)) {
return;
}
submitOfflineAction(row);
},
},
{
text: $t('button.delete'),
icon: Delete,
tone: 'danger',
permission: '/api/v1/documentCollection/remove',
placement: 'menu',
visible: (row) => canAiResourceDelete(row.displayPublishStatus, row.publishStatus),
onClick(row) {
if (!ensureManageKnowledgeItem(row)) {
return;
@@ -216,14 +240,22 @@ const actions: ActionButton[] = [
onMounted(() => {
getCategoryList();
});
const submitPublishApproval = async (item: any) => {
if (isAiResourceApprovalPending(item.publishStatus)) {
function isRepublishAction(item: any) {
return canAiResourceRepublish(item.displayPublishStatus, item.publishStatus);
}
const submitPublishAction = async (item: any) => {
if (
isAiResourceApprovalPending(item.displayPublishStatus, item.publishStatus)
) {
ElMessage.warning($t('documentCollection.publishPendingHint'));
return;
}
try {
await ElMessageBox.confirm(
$t('documentCollection.submitPublishApprovalConfirm'),
isRepublishAction(item)
? $t('documentCollection.submitRepublishApprovalConfirm')
: $t('documentCollection.submitPublishApprovalConfirm'),
$t('message.noticeTitle'),
{
confirmButtonText: $t('button.confirm'),
@@ -245,8 +277,71 @@ const submitPublishApproval = async (item: any) => {
reloadKnowledgeList();
}
};
const submitOfflineAction = async (item: any) => {
if (
isAiResourceApprovalPending(item.displayPublishStatus, item.publishStatus)
) {
ElMessage.warning($t('documentCollection.publishPendingHint'));
return;
}
const impactRes = await api.get<{
data: OfflineImpactCheck;
errorCode: number;
}>(
'/api/v1/documentCollection/offlineImpactCheck',
{
params: { id: item.id },
},
);
if (impactRes.errorCode !== 0) {
return;
}
if (impactRes.data?.hasWorkflowUsages) {
await ElMessageBox.alert(
buildOfflineImpactMessage(
$t('documentCollection.offlineImpactWorkflowBlockedIntro'),
impactRes.data.workflowUsages,
$t('documentCollection.offlineImpactWorkflowBlockedFooter'),
),
$t('message.noticeTitle'),
{
confirmButtonText: $t('button.confirm'),
type: 'warning',
},
);
return;
}
try {
await ElMessageBox.confirm(
impactRes.data?.hasBotBindings
? buildOfflineImpactMessage(
$t('documentCollection.offlineImpactBoundBotsIntro'),
impactRes.data.botBindings,
$t('documentCollection.offlineImpactBoundBotsFooter'),
)
: $t('documentCollection.submitOfflineApprovalConfirm'),
$t('message.noticeTitle'),
{
confirmButtonText: $t('button.confirm'),
cancelButtonText: $t('button.cancel'),
type: 'warning',
},
);
} catch {
return;
}
const res = await api.post('/api/v1/documentCollection/submitOfflineApproval', {
id: item.id,
});
if (res.errorCode === 0) {
ElMessage.success(res.message || $t('message.saveOkMessage'));
reloadKnowledgeList();
}
};
const submitDeleteApproval = async (item: any) => {
if (isAiResourceApprovalPending(item.publishStatus)) {
if (
isAiResourceApprovalPending(item.displayPublishStatus, item.publishStatus)
) {
ElMessage.warning($t('documentCollection.deletePendingHint'));
return;
}
@@ -274,14 +369,29 @@ const submitDeleteApproval = async (item: any) => {
reloadKnowledgeList();
}
};
function resolvePublishStatusMeta(status?: string) {
switch (normalizeAiPublishStatus(status)) {
function resolvePublishStatusMeta(
displayPublishStatus?: string,
publishStatus?: string,
) {
switch (resolveAiResourceDisplayStatus(displayPublishStatus, publishStatus)) {
case 'DELETE_PENDING': {
return {
label: $t('documentCollection.publishStatusDeletePending'),
tone: 'danger',
};
}
case 'OFFLINE_PENDING': {
return {
label: $t('documentCollection.publishStatusOfflinePending'),
tone: 'pending',
};
}
case 'OFFLINE': {
return {
label: $t('documentCollection.publishStatusOffline'),
tone: 'draft',
};
}
case 'PUBLISH_PENDING': {
return {
label: $t('documentCollection.publishStatusPublishPending'),
@@ -553,11 +663,11 @@ function changeCategory(category: any) {
<template #publish>
<div
class="knowledge-publish-chip"
:class="`knowledge-publish-chip--${resolvePublishStatusMeta(item.publishStatus).tone}`"
:class="`knowledge-publish-chip--${resolvePublishStatusMeta(item.displayPublishStatus, item.publishStatus).tone}`"
>
<span class="knowledge-publish-chip__dot"></span>
<span>{{
resolvePublishStatusMeta(item.publishStatus).label
resolvePublishStatusMeta(item.displayPublishStatus, item.publishStatus).label
}}</span>
</div>
</template>