[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:
+145
-23
@@ -47,6 +47,18 @@ const showTransfer = ref(false);
|
||||
const transferUser = 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 raw = detail.value?.bizSnapshot;
|
||||
if (!raw) return [];
|
||||
@@ -56,10 +68,22 @@ const snapshotRows = computed(() => {
|
||||
} catch {
|
||||
return [{ k: "摘要", v: String(raw) }];
|
||||
}
|
||||
if (obj && typeof obj === "object") {
|
||||
return Object.entries(obj).map(([k, v]) => ({ k, v: v == null ? "-" : String(v) }));
|
||||
if (!obj || typeof obj !== "object") return [];
|
||||
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 逐键值只读渲染;数组值按文件名/项连接;解析失败降级一行原文 */
|
||||
@@ -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() {
|
||||
const url = detail.value?.bizUrl;
|
||||
if (!url) return;
|
||||
// bare:由父页面决定怎么开(它才知道自家业务详情的路由),iframe 里 window.open 会白跳一个中心站地址
|
||||
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;
|
||||
}
|
||||
window.open(url, "_blank");
|
||||
if (!bizUrlAbs.value) {
|
||||
Message.warning("业务详情地址暂不可用,请在业务系统内查看");
|
||||
return;
|
||||
}
|
||||
window.open(bizUrlAbs.value, "_blank");
|
||||
}
|
||||
|
||||
/** 流程图仍要能看,但 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() {
|
||||
if (outlets.value.length > 1 && !chosenEdge.value) {
|
||||
Message.warning("该节点有多个办理去向,请先选择");
|
||||
return;
|
||||
}
|
||||
if (isBare.value) {
|
||||
confirming.value = true;
|
||||
return;
|
||||
}
|
||||
Modal.confirm({
|
||||
title: "同意该任务?",
|
||||
onOk: () => submit(true),
|
||||
});
|
||||
}
|
||||
|
||||
function doApprove() {
|
||||
confirming.value = false;
|
||||
submit(true);
|
||||
}
|
||||
|
||||
// ===== 退回 =====
|
||||
function openReturn() {
|
||||
const targets = detail.value?.returnTargets || [];
|
||||
@@ -266,17 +313,18 @@ onBeforeUnmount(() => {
|
||||
<Tag v-if="myTask && myTask.status === 0" color="warning">待本人办理</Tag>
|
||||
</div>
|
||||
|
||||
<!-- 业务摘要区块(M1 办理页核心;富文本正文留在业务方,经 bizUrl 查看) -->
|
||||
<!-- 业务信息区块(M1 办理页核心):只渲染子应用给的 summaryFields 中文标签行;
|
||||
富文本正文留在业务方,经 bizUrl 查看 -->
|
||||
<Card class="block" :bordered="false" dis-hover>
|
||||
<template #title>业务摘要</template>
|
||||
<div v-if="snapshotRows.length === 0" class="muted">(业务方未提供摘要)</div>
|
||||
<template #title>业务信息</template>
|
||||
<div v-if="snapshotRows.length === 0" class="muted">(业务方未提供业务信息)</div>
|
||||
<div v-else class="snapshot">
|
||||
<div v-for="row in snapshotRows" :key="row.k" class="row">
|
||||
<span class="k">{{ row.k }}</span>
|
||||
<span class="v">{{ row.v }}</span>
|
||||
</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>
|
||||
</Card>
|
||||
@@ -306,16 +354,49 @@ onBeforeUnmount(() => {
|
||||
<Input v-model="opinion" type="textarea" :rows="3" maxlength="500" show-word-limit
|
||||
placeholder="同意可选填意见;拒绝必须填写意见" />
|
||||
<div class="actions" v-if="myTaskId">
|
||||
<Button type="primary" :loading="submitting" @click="confirmApprove">同意</Button>
|
||||
<Button :loading="submitting" @click="openReturn">退回</Button>
|
||||
<Button :loading="submitting" @click="showTransfer = true">移交</Button>
|
||||
<Button type="error" :loading="submitting" @click="submit(false)">拒绝</Button>
|
||||
<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 :loading="submitting" @click="openReturn">退回</Button>
|
||||
<Button :loading="submitting" @click="showTransfer = true">移交</Button>
|
||||
<Button type="error" :loading="submitting" @click="submit(false)">拒绝</Button>
|
||||
</template>
|
||||
</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 class="block" :bordered="false" dis-hover>
|
||||
<!-- 流程轨迹(独立打开):中心侧唯一上下文,常显 -->
|
||||
<Card v-if="!isBare" class="block" :bordered="false" dis-hover>
|
||||
<template #title>
|
||||
流程轨迹
|
||||
<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>
|
||||
</Card>
|
||||
|
||||
<!-- 退回弹窗 -->
|
||||
<Modal v-model="showReturn" title="退回" :width="480">
|
||||
<!-- 流程轨迹(bare 态):父页面已有"审批进度"卡片,同一份 trace 不再整块铺开——
|
||||
默认收起,需要核对明细(路由判断/抄送/退回留痕)时再展开 -->
|
||||
<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">
|
||||
<span class="modal-label">退回至:</span>
|
||||
<Select v-model="returnTarget" size="small" style="width: 240px"
|
||||
@@ -345,8 +445,8 @@ onBeforeUnmount(() => {
|
||||
</template>
|
||||
</Modal>
|
||||
|
||||
<!-- 移交弹窗 -->
|
||||
<Modal v-model="showTransfer" title="移交" :width="480">
|
||||
<!-- 移交弹窗(独立打开;bare 态走行内面板) -->
|
||||
<Modal v-if="!isBare" v-model="showTransfer" title="移交" :width="480">
|
||||
<div class="modal-row">
|
||||
<span class="modal-label">移交对象工号:</span>
|
||||
<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 .v { white-space: pre-wrap; word-break: break-all; }
|
||||
.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; }
|
||||
.outlet { margin-bottom: 8px; display: flex; align-items: center; }
|
||||
.outlet-label { color: #515a6e; margin-right: 6px; font-size: 13px; }
|
||||
@@ -384,4 +492,18 @@ onBeforeUnmount(() => {
|
||||
.opinion { color: #666; }
|
||||
.time { color: #aaa; font-size: 12px; }
|
||||
.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>
|
||||
|
||||
@@ -108,6 +108,15 @@ const formRows = computed(() => {
|
||||
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 的 KIND_TAG) */
|
||||
@@ -356,9 +365,9 @@ onMounted(async () => {
|
||||
<div class="embed-meta-k">{{ m.k }}</div>
|
||||
<div class="embed-meta-v" :title="m.v">{{ m.v }}</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-v"><a :href="detail.bizUrl" target="_blank">查看业务详情</a></div>
|
||||
<div class="embed-meta-v"><a :href="bizUrlAbs" target="_blank">查看业务详情</a></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user