Files
flow-center-front/src/views/flow/admin/Defs.vue
T
chenxing 670988816c
build / build (push) Successful in 3m17s
fix(api): 修正 POST 请求体丢失致 def/save 报 body missing
alova 的 Method 请求体是构造函数第 5 个参数(instance.data),不是 config 上的键。
原 post() 把 body 塞进 config,被适配器静默忽略,POST 恒发空 body,
后端 @RequestBody 直接抛 HttpMessageNotReadableException: Required request body is missing。

受影响的接口(同一 helper):
- def/save、map/save、callback/repush:一律 400 Required request body is missing
- task/reject:opinion 送不到,而 FlowTaskService 强制要求非空,拒绝必然失败
- task/approve:请求成功但审批意见被静默丢弃

同时:
- Defs.vue 保存时回传 baseGuid,否则「升版本」会另起流程线而非同线升版本
- 补上调用方已在使用但 FlowApi 未定义的 callbackHung / callbackMarkDead

验证:本地后端实测 def/save 带 body 返回 code=0(新建草稿、编辑草稿、
baseGuid 升版本 version+1 且 flowGuid 不变);alova 运行时对照确认
config.body 与 config.data 均发不出 body,仅第 5 参有效。
2026-09-15 19:04:46 +08:00

177 lines
6.6 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<script setup lang="ts">
// 流程定义管理(M1:JSON 文本编辑模式;发布跑静态校验,把"任务无主"拦在发布期)
import { ref, onMounted } from "vue";
import { useRouter } from "vue-router";
import { Message } from "view-ui-plus";
import { flowApi } from "@/api/module/flow";
const router = useRouter();
const DEF_STATUS: Record<number, string> = { 0: "草稿", 1: "已发布", 2: "停用" };
const loading = ref(false);
const rows = ref<any[]>([]);
const editVisible = ref(false);
const saving = ref(false);
const form = ref<any>({ id: null, code: "", name: "", contentJson: "" });
const versionVisible = ref(false);
const versions = ref<any[]>([]);
const SAMPLE_JSON = `{
"code": "sample_flow",
"name": "示例流程",
"start": "start",
"nodes": [
{"id": "start", "type": "START", "name": "开始"},
{"id": "n1", "type": "APPROVAL", "name": "科室审核",
"assignee": {"type": "DEPT_KEY", "deptKey": "ybb"}, "countersign": "ALL"},
{"id": "end", "type": "END"}
],
"edges": [
{"from": "start", "to": "n1"},
{"from": "n1", "to": "end"}
],
"settings": {"rejectPolicy": "END_REJECTED", "urge": {"enabled": true, "intervalHours": 24, "maxTimes": 3}}
}`;
const columns = [
{ title: "流程", key: "name", minWidth: 180 },
{ title: "编码", key: "code", width: 160 },
{ title: "版本", key: "version", width: 70 },
{ title: "状态", width: 90, render: (h: any, p: any) => {
const c = p.row.status === 1 ? "green" : p.row.status === 0 ? "orange" : "gray";
return h("Tag", { color: c }, () => DEF_STATUS[p.row.status] || p.row.status);
} },
{ title: "GUID", key: "flowGuid", width: 200 },
{ title: "发布时间", width: 160, render: (h: any, p: any) => h("span", (p.row.publishTime || "").replace("T", " ") || "-") },
{ title: "操作", width: 330, render: (h: any, p: any) => {
const btns: any[] = [];
if (p.row.status === 0) {
btns.push(h("Button", { size: "small", type: "primary", onClick: () => onEdit(p.row) }, () => "编辑"));
btns.push(h("Button", { size: "small", type: "success", onClick: () => onPublish(p.row) }, () => "发布"));
} else if (p.row.status === 1) {
btns.push(h("Button", { size: "small", onClick: () => onNewVersion(p.row) }, () => "升版本"));
btns.push(h("Button", { size: "small", type: "warning", onClick: () => onOffline(p.row) }, () => "停用"));
}
btns.push(h("Button", { size: "small", onClick: () => onDiagram(p.row) }, () => "图示"));
btns.push(h("Button", { size: "small", onClick: () => onVersions(p.row) }, () => "版本历史"));
return h("div", { style: "display:flex;gap:4px" }, btns);
} },
];
async function load() {
loading.value = true;
try {
rows.value = (await flowApi.defList().send()) || [];
} finally {
loading.value = false;
}
}
function onEdit(row: any) {
flowApi.defGet(row.id).send().then((d: any) => {
form.value = { id: d.id, code: d.code, name: d.name, contentJson: d.contentJson };
editVisible.value = true;
});
}
function onNew() {
form.value = { id: null, code: "", name: "", contentJson: SAMPLE_JSON };
editVisible.value = true;
}
async function onNewVersion(row: any) {
// 升版本:以已发布内容为底稿建草稿(flowGuid 不变,version 自动 +1)
const d: any = await flowApi.defGet(row.id).send();
form.value = { id: null, code: row.code, name: row.name, contentJson: d.contentJson, baseGuid: row.flowGuid };
Message.info("已载入当前版本内容,保存即创建新草稿版本");
editVisible.value = true;
}
async function onSave() {
saving.value = true;
try {
await flowApi.defSave({
id: form.value.id || undefined,
code: form.value.code,
name: form.value.name,
contentJson: form.value.contentJson,
baseGuid: form.value.baseGuid,
}).send();
Message.success("已保存");
editVisible.value = false;
load();
} finally {
saving.value = false;
}
}
async function onPublish(row: any) {
try {
await flowApi.defPublish(row.id).send();
Message.success("发布成功");
load();
} catch (e: any) {
Message.error(e?.message || "发布校验失败");
}
}
async function onOffline(row: any) {
await flowApi.defOffline(row.id).send();
Message.success("已停用");
load();
}
async function onVersions(row: any) {
versions.value = (await flowApi.defVersions(row.flowGuid).send()) || [];
versionVisible.value = true;
}
function onDiagram(row: any) {
router.push({ path: "/diagram", query: { defId: row.id, mode: "auto" } });
}
onMounted(load);
</script>
<template>
<div class="page">
<div class="filter">
<Button type="primary" @click="onNew">新建流程</Button>
</div>
<Table :columns="columns" :data="rows" :loading="loading" stripe />
<Modal v-model="editVisible" :title="form.id ? '编辑流程' : '新建流程'" width="760" :mask-closable="false">
<Form :label-width="80">
<FormItem label="编码">
<Input v-model="form.code" :disabled="!!form.id" placeholder="流程编码(内部管理键,发布后不可改)" />
</FormItem>
<FormItem label="名称">
<Input v-model="form.name" placeholder="流程名" />
</FormItem>
<FormItem label="定义 JSON">
<Input v-model="form.contentJson" type="textarea" :rows="16"
placeholder="§5.1 流程定义 JSON(START/END 唯一、无孤儿无环、DEPT_KEY 需先配好映射)" />
</FormItem>
</Form>
<template #footer>
<Button @click="editVisible = false">取消</Button>
<Button type="primary" :loading="saving" @click="onSave">保存(草稿)</Button>
</template>
</Modal>
<Modal v-model="versionVisible" title="版本历史" width="640">
<Table :columns="[
{ title: '版本', key: 'version', width: 80 },
{ title: '名称', key: 'name' },
{ title: '状态', width: 90, render: (h, p) => h('Tag', () => DEF_STATUS[p.row.status]) },
{ title: '发布时间', width: 160, render: (h, p) => (p.row.publishTime || '').replace('T', ' ') || '-' },
]" :data="versions" size="small" />
</Modal>
</div>
</template>
<style scoped>
.page { padding: 12px 16px; }
.filter { margin-bottom: 10px; }
</style>