[fix] 办理页联调修复:业务信息只认 summaryFields / 业务详情用 bizUrlAbs / bare 态不叠遮罩层
build / build (push) Successful in 3m50s
build / build (push) Successful in 3m50s
- 业务摘要 → 业务信息:只渲染 bizSnapshot.summaryFields(label+value,口径同 InstanceBody), 不再平铺英文键(summaryFields 曾渲染成 [object Object]);老数据仅回退 标题/摘要/附件 - 查看业务详情:独立打开改用后端解析的 bizUrlAbs(裸路径不再落到本应用 origin),拿不到不渲染 - bare 态(embed=1):同意改行内确认条、退回/移交改行内面板(Modal 在 iframe 内会与父页面弹窗套娃) - bare 态流程轨迹默认折叠(父页面已有审批进度,同一份 trace 不同屏铺开两次)
This commit is contained in:
+141
-19
@@ -47,6 +47,18 @@ const showTransfer = ref(false);
|
|||||||
const transferUser = ref("");
|
const transferUser = ref("");
|
||||||
const transferName = ref("");
|
const transferName = ref("");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 业务信息行:唯一内容 = 约定键 bizSnapshot.summaryFields(子应用决定展示哪些字段、用什么中文 label,
|
||||||
|
* 口径同 InstanceBody「业务信息」)。不再逐字平铺 bizSnapshot 原始键——那会把 title/summaryFields 这类
|
||||||
|
* 英文 key 直接摆给办理人看,对象值还会渲染成 [object Object]。
|
||||||
|
* 无 summaryFields(老数据/未接入约定的子应用)只回退已知的人类可读键,未知键一律不外露。
|
||||||
|
*/
|
||||||
|
const SUMMARY_FALLBACK_LABELS: Array<[string, string]> = [
|
||||||
|
["title", "标题"],
|
||||||
|
["summary", "摘要"],
|
||||||
|
["attachmentNames", "附件"],
|
||||||
|
];
|
||||||
|
|
||||||
const snapshotRows = computed(() => {
|
const snapshotRows = computed(() => {
|
||||||
const raw = detail.value?.bizSnapshot;
|
const raw = detail.value?.bizSnapshot;
|
||||||
if (!raw) return [];
|
if (!raw) return [];
|
||||||
@@ -56,10 +68,22 @@ const snapshotRows = computed(() => {
|
|||||||
} catch {
|
} catch {
|
||||||
return [{ k: "摘要", v: String(raw) }];
|
return [{ k: "摘要", v: String(raw) }];
|
||||||
}
|
}
|
||||||
if (obj && typeof obj === "object") {
|
if (!obj || typeof obj !== "object") return [];
|
||||||
return Object.entries(obj).map(([k, v]) => ({ k, v: v == null ? "-" : String(v) }));
|
const sf = obj.summaryFields;
|
||||||
|
if (Array.isArray(sf) && sf.length) {
|
||||||
|
return sf
|
||||||
|
.filter((x: any) => x && x.label !== null && x.label !== undefined)
|
||||||
|
.slice(0, 6)
|
||||||
|
.map((x: any) => ({
|
||||||
|
k: String(x.label),
|
||||||
|
v: Array.isArray(x.value)
|
||||||
|
? (x.value.join("、") || "-")
|
||||||
|
: (x.value === null || x.value === undefined || x.value === "" ? "-" : String(x.value)),
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
return [];
|
return SUMMARY_FALLBACK_LABELS
|
||||||
|
.filter(([key]) => obj[key] !== null && obj[key] !== undefined && obj[key] !== "")
|
||||||
|
.map(([key, label]) => ({ k: label, v: formValueText(obj[key]) }));
|
||||||
});
|
});
|
||||||
|
|
||||||
/** 业务表单数据(§3.4):formDataJson 逐键值只读渲染;数组值按文件名/项连接;解析失败降级一行原文 */
|
/** 业务表单数据(§3.4):formDataJson 逐键值只读渲染;数组值按文件名/项连接;解析失败降级一行原文 */
|
||||||
@@ -100,15 +124,22 @@ async function load() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 业务详情绝对地址(后端按 bizUrl + applyAppCode 查应用注册表 pc_url 拼出)。
|
||||||
|
* bizUrl 本身是裸相对路径,流内直开时没有宿主页面可借基址,直接 window.open 会落到本应用 origin 上。 */
|
||||||
|
const bizUrlAbs = computed(() => detail.value?.bizUrlAbs || "");
|
||||||
|
/** 入口可见性:bare 由父页面按自己路由接管(原样传裸路径),独立打开必须拿到绝对地址 */
|
||||||
|
const canOpenBiz = computed(() => !!detail.value?.bizUrl && (isBare.value || !!bizUrlAbs.value));
|
||||||
|
|
||||||
function openBizUrl() {
|
function openBizUrl() {
|
||||||
const url = detail.value?.bizUrl;
|
|
||||||
if (!url) return;
|
|
||||||
// bare:由父页面决定怎么开(它才知道自家业务详情的路由),iframe 里 window.open 会白跳一个中心站地址
|
|
||||||
if (isBare.value) {
|
if (isBare.value) {
|
||||||
postToParent({ type: "open-biz", bizId: detail.value?.bizId ?? null, bizUrl: url });
|
postToParent({ type: "open-biz", bizId: detail.value?.bizId ?? null, bizUrl: detail.value?.bizUrl || "" });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
window.open(url, "_blank");
|
if (!bizUrlAbs.value) {
|
||||||
|
Message.warning("业务详情地址暂不可用,请在业务系统内查看");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
window.open(bizUrlAbs.value, "_blank");
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 流程图仍要能看,但 bare 下不能用 router.push——/diagram 不是 /task/ 前缀,会在 iframe 里长出菜单壳 */
|
/** 流程图仍要能看,但 bare 下不能用 router.push——/diagram 不是 /task/ 前缀,会在 iframe 里长出菜单壳 */
|
||||||
@@ -162,17 +193,33 @@ async function submit(approved: boolean) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 二次确认:独立打开用 Modal.confirm(原生体验);bare 态一律行内确认。
|
||||||
|
* iframe 内弹 Modal 会在父页面弹窗之上再叠一层遮罩——父页遮罩盖不到 iframe,视觉上是"弹窗套弹窗",
|
||||||
|
* 且弹出位置只在本页顶部(长表单下离按钮很远)。行内确认条没有这个问题。
|
||||||
|
*/
|
||||||
|
const confirming = ref(false);
|
||||||
|
|
||||||
function confirmApprove() {
|
function confirmApprove() {
|
||||||
if (outlets.value.length > 1 && !chosenEdge.value) {
|
if (outlets.value.length > 1 && !chosenEdge.value) {
|
||||||
Message.warning("该节点有多个办理去向,请先选择");
|
Message.warning("该节点有多个办理去向,请先选择");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (isBare.value) {
|
||||||
|
confirming.value = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
Modal.confirm({
|
Modal.confirm({
|
||||||
title: "同意该任务?",
|
title: "同意该任务?",
|
||||||
onOk: () => submit(true),
|
onOk: () => submit(true),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function doApprove() {
|
||||||
|
confirming.value = false;
|
||||||
|
submit(true);
|
||||||
|
}
|
||||||
|
|
||||||
// ===== 退回 =====
|
// ===== 退回 =====
|
||||||
function openReturn() {
|
function openReturn() {
|
||||||
const targets = detail.value?.returnTargets || [];
|
const targets = detail.value?.returnTargets || [];
|
||||||
@@ -266,17 +313,18 @@ onBeforeUnmount(() => {
|
|||||||
<Tag v-if="myTask && myTask.status === 0" color="warning">待本人办理</Tag>
|
<Tag v-if="myTask && myTask.status === 0" color="warning">待本人办理</Tag>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 业务摘要区块(M1 办理页核心;富文本正文留在业务方,经 bizUrl 查看) -->
|
<!-- 业务信息区块(M1 办理页核心):只渲染子应用给的 summaryFields 中文标签行;
|
||||||
|
富文本正文留在业务方,经 bizUrl 查看 -->
|
||||||
<Card class="block" :bordered="false" dis-hover>
|
<Card class="block" :bordered="false" dis-hover>
|
||||||
<template #title>业务摘要</template>
|
<template #title>业务信息</template>
|
||||||
<div v-if="snapshotRows.length === 0" class="muted">(业务方未提供摘要)</div>
|
<div v-if="snapshotRows.length === 0" class="muted">(业务方未提供业务信息)</div>
|
||||||
<div v-else class="snapshot">
|
<div v-else class="snapshot">
|
||||||
<div v-for="row in snapshotRows" :key="row.k" class="row">
|
<div v-for="row in snapshotRows" :key="row.k" class="row">
|
||||||
<span class="k">{{ row.k }}</span>
|
<span class="k">{{ row.k }}</span>
|
||||||
<span class="v">{{ row.v }}</span>
|
<span class="v">{{ row.v }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Button v-if="detail.bizUrl" size="small" type="primary" ghost class="biz-link" @click="openBizUrl">
|
<Button v-if="canOpenBiz" size="small" type="primary" ghost class="biz-link" @click="openBizUrl">
|
||||||
查看业务详情
|
查看业务详情
|
||||||
</Button>
|
</Button>
|
||||||
</Card>
|
</Card>
|
||||||
@@ -306,16 +354,49 @@ onBeforeUnmount(() => {
|
|||||||
<Input v-model="opinion" type="textarea" :rows="3" maxlength="500" show-word-limit
|
<Input v-model="opinion" type="textarea" :rows="3" maxlength="500" show-word-limit
|
||||||
placeholder="同意可选填意见;拒绝必须填写意见" />
|
placeholder="同意可选填意见;拒绝必须填写意见" />
|
||||||
<div class="actions" v-if="myTaskId">
|
<div class="actions" v-if="myTaskId">
|
||||||
|
<template v-if="confirming">
|
||||||
|
<span class="confirm-tip">同意该任务?</span>
|
||||||
|
<Button type="primary" :loading="submitting" @click="doApprove">确定</Button>
|
||||||
|
<Button @click="confirming = false">取消</Button>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
<Button type="primary" :loading="submitting" @click="confirmApprove">同意</Button>
|
<Button type="primary" :loading="submitting" @click="confirmApprove">同意</Button>
|
||||||
<Button :loading="submitting" @click="openReturn">退回</Button>
|
<Button :loading="submitting" @click="openReturn">退回</Button>
|
||||||
<Button :loading="submitting" @click="showTransfer = true">移交</Button>
|
<Button :loading="submitting" @click="showTransfer = true">移交</Button>
|
||||||
<Button type="error" :loading="submitting" @click="submit(false)">拒绝</Button>
|
<Button type="error" :loading="submitting" @click="submit(false)">拒绝</Button>
|
||||||
|
</template>
|
||||||
</div>
|
</div>
|
||||||
<div v-else class="muted">当前任务不可办理(不存在/非本人/已办理)</div>
|
<div v-else class="muted">当前任务不可办理(不存在/非本人/已办理)</div>
|
||||||
|
|
||||||
|
<!-- bare 态退回/移交:行内面板(iframe 里再弹 Modal 会与父页面弹窗叠成三层) -->
|
||||||
|
<div v-if="isBare && showReturn" class="inline-panel">
|
||||||
|
<div class="modal-row">
|
||||||
|
<span class="modal-label">退回至:</span>
|
||||||
|
<Select v-model="returnTarget" size="small" style="width: 240px"
|
||||||
|
placeholder="缺省=最近一个已办节点">
|
||||||
|
<Option value="">(最近一个已办节点)</Option>
|
||||||
|
<Option v-for="t in detail.returnTargets || []" :key="t.nodeId" :value="t.nodeId"
|
||||||
|
:label="t.nodeName || t.nodeId" />
|
||||||
|
</Select>
|
||||||
|
<Button size="small" type="primary" :loading="submitting" @click="doReturn">确认退回</Button>
|
||||||
|
<Button size="small" @click="showReturn = false">取消</Button>
|
||||||
|
</div>
|
||||||
|
<div class="muted modal-tip">退回后目标节点重新生成待办;并行分支内节点不可作为退回目标。</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="isBare && showTransfer" class="inline-panel">
|
||||||
|
<div class="modal-row">
|
||||||
|
<span class="modal-label">移交对象工号:</span>
|
||||||
|
<Input v-model="transferUser" size="small" style="width: 200px" placeholder="对方工号(数字)" />
|
||||||
|
<Button size="small" type="primary" :loading="submitting" @click="doTransfer">确认移交</Button>
|
||||||
|
<Button size="small" @click="showTransfer = false">取消</Button>
|
||||||
|
</div>
|
||||||
|
<div class="muted modal-tip">移交后由对方接手本任务办理;意见可在下方填写交接说明。</div>
|
||||||
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
<!-- 流程轨迹 -->
|
<!-- 流程轨迹(独立打开):中心侧唯一上下文,常显 -->
|
||||||
<Card class="block" :bordered="false" dis-hover>
|
<Card v-if="!isBare" class="block" :bordered="false" dis-hover>
|
||||||
<template #title>
|
<template #title>
|
||||||
流程轨迹
|
流程轨迹
|
||||||
<RadioGroup class="trace-mode" :model-value="mode" size="small" type="button" @on-change="switchMode">
|
<RadioGroup class="trace-mode" :model-value="mode" size="small" type="button" @on-change="switchMode">
|
||||||
@@ -327,8 +408,27 @@ onBeforeUnmount(() => {
|
|||||||
<Button size="small" ghost type="primary" @click="openDiagram">查看流程图</Button>
|
<Button size="small" ghost type="primary" @click="openDiagram">查看流程图</Button>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
<!-- 退回弹窗 -->
|
<!-- 流程轨迹(bare 态):父页面已有"审批进度"卡片,同一份 trace 不再整块铺开——
|
||||||
<Modal v-model="showReturn" title="退回" :width="480">
|
默认收起,需要核对明细(路由判断/抄送/退回留痕)时再展开 -->
|
||||||
|
<Card v-else class="block" :bordered="false" dis-hover>
|
||||||
|
<details class="trace-fold">
|
||||||
|
<summary>
|
||||||
|
<span class="trace-fold-title">流程轨迹({{ (detail.trace || []).length }} 条)</span>
|
||||||
|
<RadioGroup class="trace-mode" :model-value="mode" size="small" type="button"
|
||||||
|
@click.stop @on-change="switchMode">
|
||||||
|
<Radio label="user">按人员</Radio>
|
||||||
|
<Radio label="dept">按科室</Radio>
|
||||||
|
</RadioGroup>
|
||||||
|
</summary>
|
||||||
|
<div class="trace-fold-b">
|
||||||
|
<TraceTimeline :trace="detail.trace || []" :mode="mode" />
|
||||||
|
<Button size="small" ghost type="primary" @click="openDiagram">查看流程图</Button>
|
||||||
|
</div>
|
||||||
|
</details>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
<!-- 退回弹窗(独立打开;bare 态走行内面板,见办理区) -->
|
||||||
|
<Modal v-if="!isBare" v-model="showReturn" title="退回" :width="480">
|
||||||
<div class="modal-row">
|
<div class="modal-row">
|
||||||
<span class="modal-label">退回至:</span>
|
<span class="modal-label">退回至:</span>
|
||||||
<Select v-model="returnTarget" size="small" style="width: 240px"
|
<Select v-model="returnTarget" size="small" style="width: 240px"
|
||||||
@@ -345,8 +445,8 @@ onBeforeUnmount(() => {
|
|||||||
</template>
|
</template>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|
||||||
<!-- 移交弹窗 -->
|
<!-- 移交弹窗(独立打开;bare 态走行内面板) -->
|
||||||
<Modal v-model="showTransfer" title="移交" :width="480">
|
<Modal v-if="!isBare" v-model="showTransfer" title="移交" :width="480">
|
||||||
<div class="modal-row">
|
<div class="modal-row">
|
||||||
<span class="modal-label">移交对象工号:</span>
|
<span class="modal-label">移交对象工号:</span>
|
||||||
<Input v-model="transferUser" size="small" style="width: 200px" placeholder="对方工号(数字)" />
|
<Input v-model="transferUser" size="small" style="width: 200px" placeholder="对方工号(数字)" />
|
||||||
@@ -373,7 +473,15 @@ onBeforeUnmount(() => {
|
|||||||
.snapshot .k { width: 140px; color: #888; flex-shrink: 0; }
|
.snapshot .k { width: 140px; color: #888; flex-shrink: 0; }
|
||||||
.snapshot .v { white-space: pre-wrap; word-break: break-all; }
|
.snapshot .v { white-space: pre-wrap; word-break: break-all; }
|
||||||
.biz-link { margin-top: 8px; }
|
.biz-link { margin-top: 8px; }
|
||||||
.actions { margin-top: 10px; display: flex; gap: 8px; }
|
.actions { margin-top: 10px; display: flex; gap: 8px; align-items: center; }
|
||||||
|
.confirm-tip { color: #515a6e; font-size: 13px; }
|
||||||
|
/* bare 态行内动作面板:与按钮行同宽,浅底描边,不遮挡父页面 */
|
||||||
|
.inline-panel {
|
||||||
|
margin-top: 10px; padding: 10px 12px;
|
||||||
|
border: 1px solid #e8eaec; border-radius: 6px; background: #fafbfc;
|
||||||
|
}
|
||||||
|
.inline-panel .modal-row { margin-bottom: 4px; }
|
||||||
|
.inline-panel .modal-row .ivu-btn { margin-left: 8px; }
|
||||||
.muted { color: #999; }
|
.muted { color: #999; }
|
||||||
.outlet { margin-bottom: 8px; display: flex; align-items: center; }
|
.outlet { margin-bottom: 8px; display: flex; align-items: center; }
|
||||||
.outlet-label { color: #515a6e; margin-right: 6px; font-size: 13px; }
|
.outlet-label { color: #515a6e; margin-right: 6px; font-size: 13px; }
|
||||||
@@ -384,4 +492,18 @@ onBeforeUnmount(() => {
|
|||||||
.opinion { color: #666; }
|
.opinion { color: #666; }
|
||||||
.time { color: #aaa; font-size: 12px; }
|
.time { color: #aaa; font-size: 12px; }
|
||||||
.trace-mode { float: right; font-weight: normal; margin-right: 16px; }
|
.trace-mode { float: right; font-weight: normal; margin-right: 16px; }
|
||||||
|
/* bare 态轨迹折叠:summary 自绘(去默认三角),展开后内容与独立页一致 */
|
||||||
|
.trace-fold > summary {
|
||||||
|
cursor: pointer; list-style: none; display: flex; align-items: center; gap: 10px;
|
||||||
|
font-size: 14px; font-weight: 600; color: #17233d;
|
||||||
|
}
|
||||||
|
.trace-fold > summary::-webkit-details-marker { display: none; }
|
||||||
|
.trace-fold > summary::after { content: "▾"; margin-left: auto; color: #808695; font-size: 12px; transition: .2s; }
|
||||||
|
.trace-fold[open] > summary::after { transform: rotate(180deg); }
|
||||||
|
.trace-fold-title { position: relative; padding-left: 9px; }
|
||||||
|
.trace-fold-title::before {
|
||||||
|
content: ""; position: absolute; left: 0; top: 50%; transform: translateY(-50%);
|
||||||
|
width: 3px; height: 13px; border-radius: 2px; background: var(--brand);
|
||||||
|
}
|
||||||
|
.trace-fold-b { padding-top: 12px; }
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -108,6 +108,15 @@ const formRows = computed(() => {
|
|||||||
return [{ label: "表单原文", value: String(raw) }];
|
return [{ label: "表单原文", value: String(raw) }];
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/** 业务详情链接(仅管理面):bizUrl 是裸相对路径,必须用后端解析好的绝对地址(bizUrlAbs),
|
||||||
|
* 否则会落到流程中心自己的 origin 上;解析不出就不给入口,不给打不开的链接。 */
|
||||||
|
const bizUrlAbs = computed(() => {
|
||||||
|
const abs = props.detail?.bizUrlAbs;
|
||||||
|
if (abs) return abs;
|
||||||
|
const raw = props.detail?.bizUrl;
|
||||||
|
return /^https?:\/\//i.test(raw || "") ? raw : "";
|
||||||
|
});
|
||||||
|
|
||||||
// ===== 事件类型口径(与 TraceTimeline.vue 同源:改一处必须同步另一处) =====
|
// ===== 事件类型口径(与 TraceTimeline.vue 同源:改一处必须同步另一处) =====
|
||||||
|
|
||||||
/** 事件类型 → 动作标签主题色(逐字照抄 TraceTimeline.vue 的 KIND_TAG) */
|
/** 事件类型 → 动作标签主题色(逐字照抄 TraceTimeline.vue 的 KIND_TAG) */
|
||||||
@@ -356,9 +365,9 @@ onMounted(async () => {
|
|||||||
<div class="embed-meta-k">{{ m.k }}</div>
|
<div class="embed-meta-k">{{ m.k }}</div>
|
||||||
<div class="embed-meta-v" :title="m.v">{{ m.v }}</div>
|
<div class="embed-meta-v" :title="m.v">{{ m.v }}</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="full && detail.bizUrl" class="embed-meta-i">
|
<div v-if="full && bizUrlAbs" class="embed-meta-i">
|
||||||
<div class="embed-meta-k">业务详情</div>
|
<div class="embed-meta-k">业务详情</div>
|
||||||
<div class="embed-meta-v"><a :href="detail.bizUrl" target="_blank">查看业务详情</a></div>
|
<div class="embed-meta-v"><a :href="bizUrlAbs" target="_blank">查看业务详情</a></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user