feat: 收敛AI资源发布审批生命周期
- 统一工作流、知识库、聊天助手的发布、重新发布、下线与删除链路 - 收敛审批编排、生命周期状态机与展示态,补齐审批管理和快照预览 - 调整审批管理权限模型为单入口页面加内部按钮权限
This commit is contained in:
@@ -36,6 +36,7 @@ const resourceLabelMap: Record<string, string> = {
|
||||
|
||||
const actionLabelMap: Record<string, string> = {
|
||||
DELETE: $t('approval.action.delete'),
|
||||
OFFLINE: $t('approval.action.offline'),
|
||||
PUBLISH: $t('approval.action.publish'),
|
||||
};
|
||||
|
||||
@@ -138,18 +139,28 @@ function formatPayload(payload: Record<string, any>) {
|
||||
return JSON.stringify(payload || {}, null, 2);
|
||||
}
|
||||
|
||||
function formatAccountDisplay(name?: string, id?: null | number | string) {
|
||||
if (name && id) {
|
||||
return `${name}(${id})`;
|
||||
function formatAccountDisplay(
|
||||
name?: string,
|
||||
account?: null | string,
|
||||
fallbackId?: null | number | string,
|
||||
) {
|
||||
if (name && account) {
|
||||
return `${name}(${account})`;
|
||||
}
|
||||
if (name) {
|
||||
return name;
|
||||
}
|
||||
return id || '-';
|
||||
if (account) {
|
||||
return account;
|
||||
}
|
||||
return fallbackId || '-';
|
||||
}
|
||||
|
||||
function formatOperatorId(id?: null | number | string) {
|
||||
return id || '-';
|
||||
function formatOperatorId(
|
||||
account?: null | string,
|
||||
fallbackId?: null | number | string,
|
||||
) {
|
||||
return account || fallbackId || '-';
|
||||
}
|
||||
|
||||
function formatOperatorName(name?: null | string) {
|
||||
@@ -276,7 +287,13 @@ function formatEventInfo(row: Record<string, any>) {
|
||||
{{ detail.id || '-' }}
|
||||
</ElDescriptionsItem>
|
||||
<ElDescriptionsItem :label="$t('approval.fields.applicant')">
|
||||
{{ formatAccountDisplay(detail.applicantName, detail.applicantId) }}
|
||||
{{
|
||||
formatAccountDisplay(
|
||||
detail.applicantName,
|
||||
detail.applicantAccount,
|
||||
detail.applicantId,
|
||||
)
|
||||
}}
|
||||
</ElDescriptionsItem>
|
||||
<ElDescriptionsItem :label="$t('approval.fields.submittedAt')">
|
||||
{{ detail.submittedAt || '-' }}
|
||||
@@ -347,7 +364,7 @@ function formatEventInfo(row: Record<string, any>) {
|
||||
</ElTableColumn>
|
||||
<ElTableColumn :label="$t('approval.fields.operatorId')" width="140">
|
||||
<template #default="{ row }">
|
||||
{{ formatOperatorId(row.operatorId) }}
|
||||
{{ formatOperatorId(row.operatorAccount, row.operatorId) }}
|
||||
</template>
|
||||
</ElTableColumn>
|
||||
<ElTableColumn
|
||||
|
||||
@@ -31,7 +31,7 @@ defineExpose({
|
||||
});
|
||||
|
||||
type ResourceType = '' | 'BOT' | 'KNOWLEDGE' | 'WORKFLOW';
|
||||
type ActionType = '' | 'DELETE' | 'PUBLISH';
|
||||
type ActionType = '' | 'DELETE' | 'OFFLINE' | 'PUBLISH';
|
||||
type AssigneeType = 'ROLE' | 'USER';
|
||||
type ScopeType = 'CATEGORY' | 'DEPT';
|
||||
type FlowStatus = 'DISABLED' | 'ENABLED';
|
||||
@@ -77,6 +77,7 @@ const RESOURCE_OPTIONS = [
|
||||
|
||||
const ACTION_OPTIONS = [
|
||||
{ label: $t('approval.action.publish'), value: 'PUBLISH' },
|
||||
{ label: $t('approval.action.offline'), value: 'OFFLINE' },
|
||||
{ label: $t('approval.action.delete'), value: 'DELETE' },
|
||||
];
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@ import {
|
||||
} from 'element-plus';
|
||||
|
||||
import { api } from '#/api/request';
|
||||
import { hasPermission } from '#/api/common/hasPermission';
|
||||
import ListPageShell from '#/components/page/ListPageShell.vue';
|
||||
import PageData from '#/components/page/PageData.vue';
|
||||
import { $t } from '#/locales';
|
||||
@@ -40,6 +41,7 @@ const RESOURCE_OPTIONS = [
|
||||
|
||||
const ACTION_OPTIONS = [
|
||||
{ label: $t('approval.action.publish'), value: 'PUBLISH' },
|
||||
{ label: $t('approval.action.offline'), value: 'OFFLINE' },
|
||||
{ label: $t('approval.action.delete'), value: 'DELETE' },
|
||||
];
|
||||
|
||||
@@ -66,21 +68,25 @@ const TAB_CONFIG = [
|
||||
label: $t('approval.tab.flow'),
|
||||
name: 'flow',
|
||||
path: '/sys/approval/flow',
|
||||
permission: '/page/approval/flow',
|
||||
},
|
||||
{
|
||||
label: $t('approval.tab.pending'),
|
||||
name: 'pending',
|
||||
path: '/sys/approval/pending',
|
||||
permission: '/page/approval/pending',
|
||||
},
|
||||
{
|
||||
label: $t('approval.tab.processed'),
|
||||
name: 'processed',
|
||||
path: '/sys/approval/processed',
|
||||
permission: '/page/approval/processed',
|
||||
},
|
||||
{
|
||||
label: $t('approval.tab.initiated'),
|
||||
name: 'initiated',
|
||||
path: '/sys/approval/initiated',
|
||||
permission: '/page/approval/initiated',
|
||||
},
|
||||
] as const;
|
||||
|
||||
@@ -115,14 +121,23 @@ const pendingBadgeText = computed(() =>
|
||||
const hasApprovalRootMenu = computed(
|
||||
() => !!accessStore.getMenuByPath('/sys/approval'),
|
||||
);
|
||||
const hasLegacyTabMenus = computed(() =>
|
||||
TAB_CONFIG.some((item) => !!accessStore.getMenuByPath(item.path)),
|
||||
);
|
||||
const hasButtonTabPermissions = computed(() =>
|
||||
TAB_CONFIG.some((item) => hasPermission([item.permission])),
|
||||
);
|
||||
const visibleTabs = computed(() => {
|
||||
const matchedTabs = TAB_CONFIG.filter((item) =>
|
||||
accessStore.getMenuByPath(item.path),
|
||||
);
|
||||
if (matchedTabs.length > 0) {
|
||||
return matchedTabs;
|
||||
if (!hasApprovalRootMenu.value) {
|
||||
return [];
|
||||
}
|
||||
return hasApprovalRootMenu.value ? [...TAB_CONFIG] : [];
|
||||
if (hasButtonTabPermissions.value) {
|
||||
return TAB_CONFIG.filter((item) => hasPermission([item.permission]));
|
||||
}
|
||||
if (hasLegacyTabMenus.value) {
|
||||
return TAB_CONFIG.filter((item) => accessStore.getMenuByPath(item.path));
|
||||
}
|
||||
return [];
|
||||
});
|
||||
const visibleTabNames = computed(() =>
|
||||
visibleTabs.value.map((item) => item.name),
|
||||
@@ -139,7 +154,7 @@ const instanceStatusOptions = computed(() => {
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
void syncRouteTab(route.path);
|
||||
void syncRouteTab();
|
||||
});
|
||||
|
||||
watch(activeTab, () => {
|
||||
@@ -153,9 +168,9 @@ watch(activeTab, () => {
|
||||
}
|
||||
});
|
||||
watch(
|
||||
[() => route.path, visibleTabNames],
|
||||
async ([path]) => {
|
||||
await syncRouteTab(path);
|
||||
[() => route.fullPath, visibleTabNames],
|
||||
async () => {
|
||||
await syncRouteTab();
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
@@ -177,19 +192,55 @@ function resolveTabNameByPath(path: string): ApprovalTabName {
|
||||
return 'flow';
|
||||
}
|
||||
|
||||
async function syncRouteTab(path: string) {
|
||||
function resolveTabNameByRoute() {
|
||||
const queryTab = String(route.query.tab || '');
|
||||
if (
|
||||
queryTab === 'flow' ||
|
||||
queryTab === 'pending' ||
|
||||
queryTab === 'processed' ||
|
||||
queryTab === 'initiated'
|
||||
) {
|
||||
return queryTab as ApprovalTabName;
|
||||
}
|
||||
return resolveTabNameByPath(route.path);
|
||||
}
|
||||
|
||||
async function syncRouteTab() {
|
||||
if (visibleTabs.value.length === 0) {
|
||||
activeTab.value = 'flow';
|
||||
pendingBadgeCount.value = 0;
|
||||
return;
|
||||
}
|
||||
const expectedTab = resolveTabNameByPath(path);
|
||||
const fallbackTab = visibleTabs.value[0];
|
||||
const queryTab = String(route.query.tab || '');
|
||||
|
||||
// 进入审批管理根路径时,默认落到当前可见页签中排序第一个的页签。
|
||||
if (route.path === '/sys/approval' && !queryTab && fallbackTab) {
|
||||
activeTab.value = fallbackTab.name;
|
||||
if (fallbackTab.name !== 'flow') {
|
||||
await router.replace({
|
||||
path: '/sys/approval',
|
||||
query: { tab: fallbackTab.name },
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const expectedTab = resolveTabNameByRoute();
|
||||
if (visibleTabNames.value.includes(expectedTab)) {
|
||||
activeTab.value = expectedTab;
|
||||
} else {
|
||||
if (fallbackTab && path !== fallbackTab.path) {
|
||||
await router.replace(fallbackTab.path);
|
||||
const fallbackQuery =
|
||||
fallbackTab?.name === 'flow' ? {} : { tab: fallbackTab?.name };
|
||||
const currentQueryTab = String(route.query.tab || '');
|
||||
if (
|
||||
fallbackTab &&
|
||||
(route.path !== '/sys/approval' || currentQueryTab !== String(fallbackTab.name))
|
||||
) {
|
||||
await router.replace({
|
||||
path: '/sys/approval',
|
||||
query: fallbackQuery,
|
||||
});
|
||||
return;
|
||||
}
|
||||
activeTab.value = fallbackTab?.name || 'flow';
|
||||
@@ -206,10 +257,16 @@ function handleTabChange(name: number | string) {
|
||||
return;
|
||||
}
|
||||
const target = visibleTabs.value.find((item) => item.name === name);
|
||||
if (!target || route.path === target.path) {
|
||||
const nextQuery = name === 'flow' ? {} : { tab: name };
|
||||
const currentQueryTab = String(route.query.tab || '');
|
||||
const currentTab = currentQueryTab || 'flow';
|
||||
if (!target || currentTab === name) {
|
||||
return;
|
||||
}
|
||||
router.push(target.path);
|
||||
router.replace({
|
||||
path: '/sys/approval',
|
||||
query: nextQuery,
|
||||
});
|
||||
}
|
||||
|
||||
function reloadCurrentTab() {
|
||||
|
||||
Reference in New Issue
Block a user