feat: 归档L03与L09审批发布能力

- 新增统一审批中心与审批管理页面,支持流程配置、审批详情与角色/用户审批对象

- 接入聊天助手、知识库、工作流的发布与删除审批,并补齐发布态校验与快照展示
This commit is contained in:
2026-04-07 14:41:52 +08:00
parent 7e7c236c2a
commit 3f128e977a
138 changed files with 13035 additions and 346 deletions

View File

@@ -5,15 +5,30 @@ import { useRoute } from 'vue-router';
import { usePreferences } from '@easyflow/preferences';
import { getOptions, sortNodes } from '@easyflow/utils';
import { ArrowLeft, CircleCheck, Close } from '@element-plus/icons-vue';
import {
ArrowLeft,
CircleCheck,
Close,
Promotion,
} from '@element-plus/icons-vue';
import { Tinyflow } from '@tinyflow-ai/vue';
import { ElButton, ElDrawer, ElMessage, ElSkeleton } from 'element-plus';
import {
ElButton,
ElDrawer,
ElMessage,
ElMessageBox,
ElSkeleton,
} from 'element-plus';
import { api } from '#/api/request';
import CommonSelectDataModal from '#/components/commonSelectModal/CommonSelectDataModal.vue';
import { $t } from '#/locales';
import { router } from '#/router';
import { getIconByValue } from '#/views/ai/model/modelUtils/defaultIcon';
import {
isAiResourceApprovalPending,
normalizeAiPublishStatus,
} from '#/views/ai/shared/publish-status';
import ExecResult from '#/views/ai/workflow/components/ExecResult.vue';
import SingleRun from '#/views/ai/workflow/components/SingleRun.vue';
import WorkflowForm from '#/views/ai/workflow/components/WorkflowForm.vue';
@@ -135,6 +150,7 @@ const customNode = ref();
const showTinyFlow = ref(false);
const saveLoading = ref(false);
const checkLoading = ref(false);
const publishLoading = ref(false);
const checkIssuesVisible = ref(false);
const checkResult = ref<any>(null);
const checkContentSnapshot = ref<any>(null);
@@ -234,6 +250,30 @@ const pluginSelectRef = ref();
const updatePluginNode = ref<any>(null);
const pageLoading = ref(false);
const chainInfo = ref<any>(null);
const publishActionText = computed(() => {
switch (normalizeAiPublishStatus(workflowInfo.value?.publishStatus)) {
case 'DELETE_PENDING': {
return $t('aiWorkflow.publishStatusDeletePending');
}
case 'PUBLISH_PENDING': {
return $t('aiWorkflow.publishStatusPublishPending');
}
case 'PUBLISHED': {
return `${$t('aiWorkflow.publishStatusPublished')} · ${$t('button.republish')}`;
}
default: {
return `${$t('aiWorkflow.publishStatusDraft')} · ${$t('button.submitPublishApproval')}`;
}
}
});
const publishActionDisabled = computed(
() =>
!workflowId.value ||
saveLoading.value ||
checkLoading.value ||
publishLoading.value ||
isAiResourceApprovalPending(workflowInfo.value?.publishStatus),
);
function syncNavTitle(title: string) {
if (!title) {
@@ -458,6 +498,44 @@ function closeCheckIssues() {
async function handleCheck() {
await runCheck('PRE_EXECUTE');
}
async function handlePublish() {
if (publishLoading.value) {
return;
}
if (isAiResourceApprovalPending(workflowInfo.value?.publishStatus)) {
ElMessage.warning($t('aiWorkflow.publishPendingHint'));
return;
}
try {
await ElMessageBox.confirm(
$t('aiWorkflow.submitPublishApprovalConfirm'),
$t('message.noticeTitle'),
{
confirmButtonText: $t('button.confirm'),
cancelButtonText: $t('button.cancel'),
type: 'info',
},
);
} catch {
return;
}
const saved = await handleSave();
if (!saved) {
return;
}
publishLoading.value = true;
try {
const res = await api.post('/api/v1/workflow/submitPublishApproval', {
id: workflowId.value,
});
if (res.errorCode === 0) {
ElMessage.success(res.message || $t('message.saveOkMessage'));
await getWorkflowInfo(workflowId.value);
}
} finally {
publishLoading.value = false;
}
}
function onSubmit() {
initState.value = !initState.value;
}
@@ -584,7 +662,7 @@ function onAsyncExecute(info: any) {
<div class="workflow-head-actions">
<ElButton
:loading="checkLoading"
:disabled="saveLoading"
:disabled="saveLoading || publishLoading"
:icon="CircleCheck"
@click="handleCheck"
>
@@ -592,11 +670,21 @@ function onAsyncExecute(info: any) {
</ElButton>
<ElButton
type="primary"
:disabled="saveLoading || checkLoading"
:disabled="saveLoading || checkLoading || publishLoading"
@click="handleSave(true)"
>
{{ $t('button.save') }}(ctrl+s)
</ElButton>
<ElButton
:icon="Promotion"
:loading="publishLoading"
:disabled="publishActionDisabled"
class="workflow-publish-button"
:class="`workflow-publish-button--${normalizeAiPublishStatus(workflowInfo?.publishStatus)}`"
@click="handlePublish"
>
{{ publishActionText }}
</ElButton>
</div>
</div>
<Tinyflow
@@ -695,8 +783,42 @@ function onAsyncExecute(info: any) {
.workflow-head-actions {
display: flex;
flex-wrap: wrap;
gap: 8px;
align-items: center;
justify-content: flex-end;
}
:deep(.workflow-publish-button.el-button) {
box-shadow: none;
}
:deep(.workflow-publish-button.el-button:not(.is-disabled)) {
background: transparent;
}
:deep(.workflow-publish-button--DRAFT.el-button) {
color: hsl(var(--foreground) / 72%);
background: hsl(var(--muted) / 68%);
border-color: hsl(var(--foreground) / 14%);
}
:deep(.workflow-publish-button--PUBLISH_PENDING.el-button) {
color: hsl(var(--warning));
background: hsl(var(--warning) / 18%);
border-color: hsl(var(--warning) / 24%);
}
:deep(.workflow-publish-button--PUBLISHED.el-button) {
color: hsl(var(--success));
background: hsl(var(--success) / 18%);
border-color: hsl(var(--success) / 24%);
}
:deep(.workflow-publish-button--DELETE_PENDING.el-button) {
color: hsl(var(--destructive));
background: hsl(var(--destructive) / 16%);
border-color: hsl(var(--destructive) / 24%);
}
.tiny-flow-container {