feat(p3.3): 应用内审批前端——flow-approval 组件族本地化 + /todo 我的审批页 + 详情审批卡片
build / build (push) Successful in 2m12s
build / build (push) Successful in 2m12s
- 从 Notice/front 复制 flow-approval 六件并本地修复(漂移登记组件 README,Notice 不回改):
· P0-04:四个写调用点(List 通过/驳回 + Card 通过/驳回)全部实际携带本行 bizId,
选中 bizId 随弹窗生命周期锁定/清理
· P0-05:删 useElementHeight/yh-* 平台布局依赖,本地 fa-* 样式(theme-base.css --sh-* 令牌)
· P0-06:types.ts 本地契约——增 my(行=ApplyVo 子集/FlowPage 完整元数据/状态 0=待提交
禁止当全部,全部用 -1 哨兵)、删 instances(不代理全校视角)、approve/reject 增 bizId 参
· P1-05:卡片轨迹不自取(详情页把 /progress 转换 TraceVo 经 props 传入);节点摘要取
active steps 节点名;MULTI_TASK 逐条列出本人待办各带动作,不默认 tasks[0];
未进流程(flowInstanceId 空)详情页不渲染卡片(显式决策)
· P1-09:FlowTaskEmbed 消息四条件(打开中+严格 origin+本 iframe+taskId/bizId 上下文一致),
换址重置高度;宿主换址请求带代际号,关闭/换件废弃在途响应并清上下文;
open-biz 只用与当前办件一致的 bizId 构造 /apply-detail?id=,不透传任意 URL
- ApprovalApi 类挂 svc.ts(todo/my/byBiz/approve/reject/recordUrl/taskPageUrl,写面
taskId/applyId 查询参数+opinion/edgeName JSON body 两形态区分)+ approvalAdapter.ts 唯一映射
- /todo 路由 + 顶部导航第三链接「我的审批」+ MyApprovals.vue(双页签+记录抽屉+完整办理)
- ApplyDetail 挂审批卡片(审批操作区)+ TraceVo 转换器
- 小屏基础响应式(drawer/modal 宽度自适应、列表横向滚动;1200px 全量适配不在本批)
- 门禁:vue-tsc(ts4.9 外挂兼容组合)错误数 HEAD=工作区=18 零新增(仓内 vue-tsc1.8+ts5.9
崩溃系历史遗留);vite build 绿(MyApprovals/approvalAdapter chunk 产出);
preview 冒烟:/todo 渲染完整(导航/页签/表格空态/API 失败优雅降级)、详情页无崩溃(Stage S4/P3.3)
This commit is contained in:
@@ -0,0 +1,130 @@
|
||||
<template>
|
||||
<Drawer :model-value="modelValue" :title="title" :width="drawerWidth" :transfer="false"
|
||||
:styles="{ body: { padding: '0' } }" @update:model-value="close">
|
||||
<div class="fa-embed-region">
|
||||
<div v-if="error" class="fa-embed-error">
|
||||
<Alert type="error" show-icon>{{ error }}</Alert>
|
||||
<Button size="small" @click="load">重试</Button>
|
||||
</div>
|
||||
<!-- iframe 一旦给出就是只读嵌入页:本组件不放任何通过/驳回入口 -->
|
||||
<iframe v-else-if="pageUrl" :src="pageUrl" :title="title" class="fa-embed-frame" />
|
||||
<div v-else class="fa-embed-loading">{{ loading ? "加载中…" : "" }}</div>
|
||||
</div>
|
||||
</Drawer>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
/**
|
||||
* 审批记录抽屉(hall 本地版,源自 Notice FlowRecordDrawer;漂移登记见本目录 README)。
|
||||
* iframe 嵌 flow-center 的 **token 只读页**(instanceId 形态换址,后端代理)。
|
||||
*
|
||||
* 三条纪律写死在组件里,不给调用方犯错的机会:
|
||||
* 1. pageUrl **每次打开现取**(token 10 分钟短时效),关闭/换件即清空——不缓存、不进 storage;
|
||||
* 2. 只读:组件内不出现通过/驳回,也不接收这类回调;
|
||||
* 3. 换址失败(未进入流程 / 无权限 / 过期)原文提示 + 重试,不白屏。
|
||||
* P1-09 加严:换办件(bizId 变化)时立即废弃旧地址与在途请求结果——晚到响应只写当前 bizId。
|
||||
*/
|
||||
import { computed, ref, watch } from "vue";
|
||||
import type { FlowApprovalApi } from "./types";
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
modelValue: boolean;
|
||||
api: FlowApprovalApi;
|
||||
bizId: string;
|
||||
title?: string;
|
||||
width?: number;
|
||||
}>(),
|
||||
{ title: "审批记录", width: 740 }
|
||||
);
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: "update:modelValue", v: boolean): void;
|
||||
}>();
|
||||
|
||||
/** 小屏自适应(基础响应式:1200px 全量适配不在本批) */
|
||||
const drawerWidth = computed(() => (typeof window !== "undefined" && window.innerWidth < 768
|
||||
? Math.min(props.width, window.innerWidth - 24)
|
||||
: props.width));
|
||||
|
||||
const pageUrl = ref("");
|
||||
const loading = ref(false);
|
||||
const error = ref("");
|
||||
|
||||
async function load() {
|
||||
const bizId = String(props.bizId);
|
||||
loading.value = true;
|
||||
error.value = "";
|
||||
pageUrl.value = "";
|
||||
try {
|
||||
const res: any = await props.api.recordUrl(bizId);
|
||||
// 晚到防护:请求期间已换件/已关闭,丢弃本次结果(P1-09)
|
||||
if (bizId !== String(props.bizId) || !props.modelValue) return;
|
||||
const url = res?.pageUrl || "";
|
||||
if (!url) {
|
||||
error.value = "审批记录地址获取失败,请稍后重试";
|
||||
return;
|
||||
}
|
||||
pageUrl.value = url;
|
||||
} catch (e: any) {
|
||||
if (bizId !== String(props.bizId)) return;
|
||||
// 后端信封 msg 原文透传("办件尚未进入流程,暂无审批记录"/"办件不存在"等)
|
||||
error.value = e?.msg || e?.message || String(e) || "审批记录打开失败";
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
(open) => {
|
||||
if (open) load();
|
||||
else {
|
||||
// 关闭即丢地址:token 不进缓存,下次打开重新签发
|
||||
pageUrl.value = "";
|
||||
error.value = "";
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// 换件即清(抽屉开着连续换办件的场景)
|
||||
watch(
|
||||
() => props.bizId,
|
||||
() => {
|
||||
pageUrl.value = "";
|
||||
error.value = "";
|
||||
if (props.modelValue) load();
|
||||
}
|
||||
);
|
||||
|
||||
function close(v: boolean) {
|
||||
emit("update:modelValue", v);
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.fa-embed-region {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.fa-embed-frame {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border: 0;
|
||||
}
|
||||
.fa-embed-loading {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.fa-embed-error {
|
||||
padding: 16px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
align-items: flex-start;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user