feat(flow): 流程图入口/失败提示/指派预览/线级停发闭环/配置分层/选人宿主
build / build (push) Successful in 1m28s

- 修复办理页「查看流程图」双 hash URL:改 router.resolve + toAppHashUrl 单 hash
- 修复管理端办件抽屉「查看大图」域名尾点(origin + BASE_URL("./") 拼接)
- 新增 flowError 口径层与办理页内联失败条,审批失败提示可读、不泄漏堆栈
- 审批节点抽屉新增「预览候选人」真入口 + AssigneePreviewModal(只读,不写回定义)
- 线级停发/恢复失败改为单条错误面,避免同错双 toast
- 审批抽屉核心区/高级设置分层;插入菜单常用/高级分组
- 新增 FlowAssignEmbed 选人 iframe 父侧宿主(origin/source/信封/context 四校验)
- 修复 /diagram、/assign-preview 面包屑误显示「流程编辑器」
This commit is contained in:
chenxing
2026-10-28 03:18:54 +08:00
parent f2841e2740
commit c93313cdb7
26 changed files with 1602 additions and 36 deletions
@@ -0,0 +1,91 @@
<script setup lang="ts">
/**
* 审批节点候选人预览弹窗(编辑器内入口,验收报告 P2-1)。
* 只读展示 resolveNodePreview 的结果:草稿解析 + 已发布版本对账 + 明确的不可预览原因。
* 关闭不做任何写回——它只接收抽屉里的本地表单快照。
*/
import { ref } from "vue";
import { Modal, Alert, Tag, Button } from "view-ui-plus";
import { resolveNodePreview, type PreviewApi, type PreviewDept, type PreviewNodeConfig } from "../assignPreview";
import { flowApi } from "@/api/module/flow";
const api: PreviewApi = {
deptMembers: (deptId) => flowApi.deptMembers(deptId).send() as Promise<any[]>,
lookupUser: (userId) => flowApi.mapLookupUser(userId).send() as Promise<any>,
applyPreview: (flowGuid) => flowApi.applyPreview(flowGuid).send() as Promise<any>,
};
const visible = ref(false);
const loading = ref(false);
const title = ref("预览候选人");
const result = ref<Awaited<ReturnType<typeof resolveNodePreview>> | null>(null);
async function show(cfg: PreviewNodeConfig, depts: PreviewDept[], flowGuid = ""): Promise<void> {
visible.value = true;
loading.value = true;
result.value = null;
title.value = `预览候选人 · ${cfg.name || "未命名节点"}`;
try {
result.value = await resolveNodePreview(cfg, depts, api, flowGuid);
} finally {
loading.value = false;
}
}
defineExpose({ show });
</script>
<template>
<Modal v-model="visible" :title="title" :width="620" :footer-hide="true">
<div v-if="loading" class="muted" style="padding: 24px 0; text-align: center">正在解析候选人…</div>
<template v-else-if="result">
<Alert v-if="result.blocked" type="warning" show-icon class="pv-block">{{ result.blocked }}</Alert>
<div class="pv-headline">
<b>{{ result.headline }}</b>
<span class="pv-source">数据源:{{ result.rows.length ? "FL 科室映射 / UserBase 既有接口" : "未取到候选" }}</span>
</div>
<div v-if="result.rows.length" class="pv-rows">
<div v-for="r in result.rows" :key="r.userId" class="pv-row">
<Tag :color="r.warn ? 'warning' : 'blue'">{{ r.userName }}</Tag>
<span class="pv-id">#{{ r.userId }}</span>
<span v-if="r.warn" class="pv-warn">{{ r.warn }}</span>
</div>
</div>
<div v-else class="muted">(本节点当前没有可展示的候选人)</div>
<div class="pv-pub" v-if="result.published">
<div class="pv-pub-title">已发布版本对账</div>
<div class="pv-pub-body">
<Tag :color="result.published.state === 'ok' ? 'success' : result.published.state === 'missing' ? 'default' : 'warning'">
{{ result.published.state === "ok" ? "可比对" : result.published.state === "missing" ? "草稿节点未发布" : "不可比对" }}
</Tag>
<span>{{ result.published.text }}</span>
</div>
<div v-if="result.published.state === 'ok' && result.published.rows.length" class="pv-rows">
<Tag v-for="p in result.published.rows" :key="p.userId" color="green">{{ p.userName }}</Tag>
</div>
</div>
<ul class="pv-notes">
<li v-for="(n, i) in result.notes" :key="i">{{ n }}</li>
</ul>
</template>
<div style="margin-top: 12px; text-align: right">
<Button @click="visible = false">关闭</Button>
</div>
</Modal>
</template>
<style scoped>
.pv-block { margin-bottom: 10px; }
.pv-headline { display: flex; align-items: baseline; gap: 10px; margin-bottom: 10px; }
.pv-source { color: #808695; font-size: 12px; }
.pv-rows { display: flex; flex-wrap: wrap; gap: 6px 0; margin-bottom: 12px; }
.pv-row { display: flex; align-items: center; gap: 6px; width: 100%; }
.pv-id { color: #c5c8ce; font-size: 12px; }
.pv-warn { color: #ff9900; font-size: 12px; }
.pv-pub { border-top: 1px dashed #e8eaec; padding-top: 10px; margin-bottom: 10px; }
.pv-pub-title { font-weight: 600; font-size: 13px; margin-bottom: 6px; }
.pv-pub-body { display: flex; align-items: flex-start; gap: 8px; font-size: 13px; }
.pv-notes { margin: 0; padding-left: 18px; color: #808695; font-size: 12px; line-height: 1.7; }
</style>