release: FL 2.0 前端候选(流程线停发/发起页指派预览/测试夹具)
build / build (push) Successful in 1m33s

配套 service 候选 500a9a2:
- flow.ts 增加 admin/def/offlineLine、enableLine(T03 线级停发/恢复)
- Defs.vue 接线级停发开关与状态展示;lineOffline.ts 收敛判定与文案
- AssignPreview.vue + assignEmbed.ts 发起时指派预览(T02 契约)
- def-editor 表单坐标绑定微调
- 新增 vitest 组件测试与 line-offline 契约测试(npm test 可跑)
This commit is contained in:
chenxing
2026-10-27 12:07:40 +08:00
parent cdea9c559b
commit f2841e2740
13 changed files with 2517 additions and 171 deletions
+103
View File
@@ -0,0 +1,103 @@
<template>
<div class="assign-preview-page">
<a-alert v-if="error" type="error" show-icon :message="error" />
<a-alert v-else-if="!flowGuid" type="warning" show-icon message="缺少 flowGuid 查询参数" />
<template v-else>
<FlowAssignSelector ref="selectorRef" :flow-guid="flowGuid" />
<div class="actions">
<Button type="primary" :loading="submitting" @click="onConfirm">确认选人</Button>
<Button @click="onCancel">取消</Button>
</div>
</template>
</div>
</template>
<script lang="ts" setup>
/**
* 托管选人 iframe 页(2026-10-25 T07,F6 正式接线):
* 业务页面以 iframe 打开本页(query: flowGuid & contextId),本页 CAS 会话预览候选;
* 确认=只向父页回传最终选择+版本锚(postMessage,精确 origin),<b>不调用 apply、
* 不建实例、不建临时流程</b>;取消=只通知关闭。正式提交由父页所在业务按 apply 终审。
* 重新确认以最近一次为准(父页内存覆盖;本页每次确认即时回传)。
*/
import { computed, onMounted, ref } from 'vue';
import { useRoute } from 'vue-router';
import { Button } from 'view-ui-plus';
import FlowAssignSelector from '@/components/FlowAssignSelector.vue';
import { flowApi } from '@/api/module/flow';
import { AssignEmbedMessage, buildMessage, parentOrigin, ASSIGN_EMBED_PROTO, ASSIGN_EMBED_VERSION } from '@/utils/assignEmbed';
const route = useRoute();
const flowGuid = computed(() => String(route.query.flowGuid || ''));
/** 本次打开的关联标识(父页生成);迟到/异 context 消息按它失效 */
const contextId = computed(() => String(route.query.contextId || ''));
const lockedOrigin = parentOrigin();
const selectorRef = ref();
const error = ref('');
const submitting = ref(false);
onMounted(() => {
if (!contextId.value) {
error.value = '缺少 contextId(父页必须生成本次打开的关联标识)';
}
});
async function onConfirm(): Promise<void> {
const selector = selectorRef.value;
if (!selector) {
return;
}
submitting.value = true;
try {
const invalid = selector.validate();
if (invalid && invalid.length) {
error.value = invalid[0].text || invalid[0];
return;
}
const preview = await flowApi.applyPreview(flowGuid.value).send();
const assigns = selector.collect();
const msg = buildMessage({
proto: ASSIGN_EMBED_PROTO,
v: ASSIGN_EMBED_VERSION,
type: 'assign-confirmed',
contextId: contextId.value,
flowGuid: flowGuid.value,
defVersion: preview?.defVersion,
assigns,
});
if (!msg || !lockedOrigin) {
error.value = !msg ? '消息构造失败(形状不完整)' : '无法锁定父页 origin(referrer 缺失),拒绝回传';
return;
}
// 精确 origin 回传(同 taskEmbed.postToParent 纪律);source 由父页按 contentWindow 核
window.parent.postMessage(msg, lockedOrigin);
} finally {
submitting.value = false;
}
}
function onCancel(): void {
if (!lockedOrigin || !contextId.value) {
return; // 无法安全回传就静默(父页有关闭超时兜底)
}
const msg: AssignEmbedMessage = {
proto: ASSIGN_EMBED_PROTO,
v: ASSIGN_EMBED_VERSION,
type: 'assign-cancelled',
contextId: contextId.value,
};
window.parent.postMessage(msg, lockedOrigin);
}
</script>
<style scoped>
.assign-preview-page {
padding: 16px;
}
.actions {
margin-top: 16px;
display: flex;
gap: 12px;
}
</style>