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

@@ -28,6 +28,7 @@ export interface ActionButton {
permission?: string;
placement?: ActionPlacement;
tone?: ActionTone;
visible?: ((row: any) => boolean) | boolean;
onClick: (row: any) => void;
}
@@ -77,6 +78,13 @@ function hasPermission(permission?: string) {
return !permission || hasAccessByCodes([permission]);
}
function isActionVisible(action: ActionButton, row: any) {
if (typeof action.visible === 'function') {
return action.visible(row);
}
return action.visible !== false;
}
const resolvedPrimaryAction = computed(() => {
if (!props.primaryAction || !hasPermission(props.primaryAction.permission)) {
return undefined;
@@ -109,24 +117,6 @@ const resolvedActions = computed<ResolvedActionButton[]>(() => {
}));
});
const inlineActions = computed(() => {
return resolvedActions.value.filter(
(action) => action.placement === 'inline',
);
});
const menuActions = computed(() => {
return resolvedActions.value.filter((action) => action.placement === 'menu');
});
const showFooter = computed(() => {
return Boolean(
resolvedPrimaryAction.value ||
inlineActions.value.length > 0 ||
menuActions.value.length > 0,
);
});
function handlePrimaryAction(item: any) {
resolvedPrimaryAction.value?.onClick(item);
}
@@ -139,6 +129,24 @@ function handleActionClick(event: Event, action: ActionButton, item: any) {
function resolveActionText(action: ActionButton, item: any) {
return typeof action.text === 'function' ? action.text(item) : action.text;
}
function resolveInlineActions(item: any) {
return resolvedActions.value.filter(
(action) => action.placement === 'inline' && isActionVisible(action, item),
);
}
function resolveMenuActions(item: any) {
return resolvedActions.value.filter(
(action) => action.placement === 'menu' && isActionVisible(action, item),
);
}
function hasVisibleActions(item: any) {
return (
resolveInlineActions(item).length > 0 || resolveMenuActions(item).length > 0
);
}
</script>
<template>
@@ -198,7 +206,7 @@ function resolveActionText(action: ActionButton, item: any) {
</div>
</div>
<template v-if="showFooter" #footer>
<template v-if="resolvedPrimaryAction || hasVisibleActions(item)" #footer>
<div class="card-footer">
<div v-if="resolvedPrimaryAction" class="card-primary-hint">
<div class="primary-label">
@@ -214,12 +222,12 @@ function resolveActionText(action: ActionButton, item: any) {
</div>
<div
v-if="inlineActions.length > 0 || menuActions.length > 0"
v-if="hasVisibleActions(item)"
class="card-actions"
@click.stop
>
<ElButton
v-for="(action, actionIndex) in inlineActions"
v-for="(action, actionIndex) in resolveInlineActions(item)"
:key="`${item.id ?? index}-inline-${actionIndex}`"
:icon="typeof action.icon === 'string' ? undefined : action.icon"
size="small"
@@ -235,7 +243,7 @@ function resolveActionText(action: ActionButton, item: any) {
</ElButton>
<ElDropdown
v-if="menuActions.length > 0"
v-if="resolveMenuActions(item).length > 0"
trigger="click"
placement="bottom-end"
>
@@ -248,7 +256,7 @@ function resolveActionText(action: ActionButton, item: any) {
<template #dropdown>
<ElDropdownMenu>
<ElDropdownItem
v-for="(action, actionIndex) in menuActions"
v-for="(action, actionIndex) in resolveMenuActions(item)"
:key="`${item.id ?? index}-menu-${actionIndex}`"
:class="{
'card-menu-item--danger': action.tone === 'danger',