import {
|
createEmptyNode,
|
formatDisplayTime,
|
mapNodesFromApi,
|
mapSignModeFromApi,
|
mapSignModeToApi,
|
normalizeFlowNodes,
|
nodeSignModeLabel,
|
} from "../approve-template/approveTemplateConstants.js";
|
import { buildFormPayloadFromFields, parseFormConfigToData } from "../approve-template/formConfigUtils.js";
|
|
/** 审批类型(与后端字段 approvalType 对齐,后期可同步) */
|
export const APPROVAL_TYPE_OPTIONS = [
|
{ value: "cost_reimburse", label: "费用报销申请", cellBg: "#e8f8ef", cellColor: "#1a7f4b" },
|
{ value: "travel_reimburse", label: "差旅报销申请", cellBg: "#f0f2f5", cellColor: "#606266" },
|
{ value: "overtime", label: "加班申请", cellBg: "#fdf3e8", cellColor: "#c45c26" },
|
{ value: "leave", label: "请假申请", cellBg: "#fce8f0", cellColor: "#b84d7a" },
|
{ value: "work_handover", label: "工作交接申请", cellBg: "#f0e8fc", cellColor: "#6b4d9e" },
|
{ value: "regular", label: "转正申请", cellBg: "#e8f4fc", cellColor: "#2b6cb0" },
|
{ value: "resign", label: "离职申请", cellBg: "#ffffff", cellColor: "#303133", border: "1px solid #e4e7ed" },
|
{ value: "transfer", label: "调岗申请", cellBg: "#ffffff", cellColor: "#303133", border: "1px solid #e4e7ed" },
|
{ value: "out_office", label: "公出申请", cellBg: "#e8f4ff", cellColor: "#409eff" },
|
{ value: "business_trip", label: "出差申请", cellBg: "#fdf6ec", cellColor: "#e6a23c" },
|
{ value: "procurement", label: "采购审批", cellBg: "#f4f4f5", cellColor: "#909399" },
|
{ value: "quotation", label: "报价审批", cellBg: "#f4ecfc", cellColor: "#9b59b6" },
|
{ value: "shipment", label: "发货审批", cellBg: "#e8faf6", cellColor: "#1abc9c" },
|
];
|
|
/** 审批状态 approvalStatus */
|
export const APPROVAL_STATUS_OPTIONS = [
|
{ value: "pending", label: "审核中" },
|
{ value: "approved", label: "已通过" },
|
{ value: "rejected", label: "已驳回" },
|
{ value: "cancelled", label: "已撤销" },
|
];
|
|
export const LEGACY_APPROVE_LIST_STORAGE_KEY = "oa_unified_approve_list_v1";
|
|
export function clearLegacyApproveListStorage() {
|
try {
|
localStorage.removeItem(LEGACY_APPROVE_LIST_STORAGE_KEY);
|
} catch {
|
/* ignore */
|
}
|
}
|
|
/** 提交弹窗:模板卡片(来自后端列表) */
|
export function mapSubmitTemplateCard(row) {
|
const cfg = parseFormConfigToData(row?.formConfig);
|
return {
|
id: row?.id,
|
key: String(row?.id ?? ""),
|
approvalType: cfg.approvalType || row?.approvalType || "",
|
label: row?.templateName || "—",
|
summaryPlaceholder: (row?.description || "").trim() || cfg.summaryPlaceholder || "点击填写并提交",
|
};
|
}
|
|
/** 审批记录 approveAction → 页面 result */
|
export function mapRecordResultFromApi(action) {
|
const s = String(action || "").toUpperCase();
|
if (s === "APPROVED" || s === "APPROVE" || s === "PASS") return "approved";
|
if (s === "REJECTED" || s === "REJECT" || s === "REFUSE") return "rejected";
|
return "pending";
|
}
|
|
/** 后端 records → 时间线展示结构 */
|
export function mapRecordsFromApi(records) {
|
const list = Array.isArray(records) ? records : [];
|
return list.map((r) => ({
|
id: r.id,
|
operatorName: r.approverName || r.operatorName || r.createUserName || "",
|
result: mapRecordResultFromApi(r.approveAction ?? r.action ?? r.status),
|
opinion: r.approveComment || r.comment || r.opinion || "",
|
time: formatDisplayTime(r.approveTime || r.createTime || r.time || ""),
|
raw: r,
|
}));
|
}
|
|
export function mapTaskStatusLabel(status) {
|
const s = String(status || "").toUpperCase();
|
if (s === "APPROVED") return "已通过";
|
if (s === "REJECTED") return "已驳回";
|
if (s === "PENDING") return "待审批";
|
if (s === "CANCELLED") return "已撤销";
|
return status || "—";
|
}
|
|
export function mapTaskStatusTagType(status) {
|
const s = String(status || "").toUpperCase();
|
if (s === "APPROVED") return "success";
|
if (s === "REJECTED") return "danger";
|
if (s === "CANCELLED") return "info";
|
return "warning";
|
}
|
|
/** 后端 tasks → 页面 flowNodes(按 levelNo 分组,供流程编辑/展示) */
|
export function mapTasksToFlowNodes(tasks) {
|
const list = Array.isArray(tasks) ? tasks : [];
|
if (!list.length) return [];
|
const byLevel = new Map();
|
list.forEach((t) => {
|
const level = Number(t.levelNo ?? t.taskLevel ?? t.nodeOrder ?? 1);
|
if (!byLevel.has(level)) {
|
byLevel.set(level, {
|
id: t.nodeId,
|
templateId: t.templateId,
|
nodeOrder: level,
|
signMode: mapSignModeFromApi(t.approveType),
|
approvers: [],
|
tasks: [],
|
});
|
}
|
const node = byLevel.get(level);
|
node.approvers.push({
|
id: t.id,
|
nodeId: t.nodeId,
|
templateId: t.templateId,
|
approverId: t.approverId,
|
approverName: t.approverName || "",
|
status: t.status,
|
approveComment: t.approveComment,
|
approveTime: t.approveTime,
|
});
|
node.tasks.push(t);
|
if (t.approveType != null) {
|
node.signMode = mapSignModeFromApi(t.approveType);
|
}
|
});
|
return [...byLevel.entries()]
|
.sort(([a], [b]) => a - b)
|
.map(([, node]) => node);
|
}
|
|
/** 页面 flowNodes → 后端 tasks */
|
export function mapFlowNodesToTasks(flowNodes, { instanceId, templateId } = {}) {
|
const nodes = normalizeFlowNodes(flowNodes);
|
const tasks = [];
|
nodes.forEach((n) => {
|
const levelNo = n.nodeOrder ?? 1;
|
const approveType = mapSignModeToApi(n.signMode);
|
n.approvers.forEach((a, idx) => {
|
const task = {
|
levelNo,
|
approveType,
|
approverId: a.approverId,
|
approverName: a.approverName || "",
|
sortNo: a.sortNo ?? idx + 1,
|
};
|
if (a.id != null) task.id = a.id;
|
if (a.nodeId != null) task.nodeId = a.nodeId;
|
if (a.templateId != null) task.templateId = a.templateId;
|
else if (templateId) task.templateId = templateId;
|
if (instanceId) task.instanceId = instanceId;
|
if (a.status != null) task.status = a.status;
|
tasks.push(task);
|
});
|
});
|
return tasks;
|
}
|
|
function guessFieldTypeFromValue(val) {
|
if (Array.isArray(val) && val.length === 2) return "datetimerange";
|
if (typeof val === "number") return "number";
|
if (typeof val === "string" && /^\d{4}-\d{2}-\d{2}$/.test(val)) return "date";
|
if (typeof val === "string" && val.length > 100) return "textarea";
|
return "text";
|
}
|
|
/** 单字段展示值(详情只读) */
|
export function formatFieldDisplayValue(field, val) {
|
if (val == null || val === "" || (Array.isArray(val) && !val.length)) return "—";
|
if (field?.type === "select" && field.options?.length) {
|
const hit = field.options.find((o) => String(o.value) === String(val));
|
return hit?.label || String(val);
|
}
|
if (Array.isArray(val)) return val.join(" 至 ");
|
return String(val);
|
}
|
|
/**
|
* 从行数据 / formConfig 解析填报字段定义与 formPayload(与新增提交结构一致)
|
*/
|
export function resolveInstanceFormFields(row) {
|
const cfg = parseInstanceFormConfig(row?.formConfig);
|
let fields = (row?.formFieldDefs?.length ? row.formFieldDefs : cfg.fields) || [];
|
const formPayload = {
|
...(fields.length ? buildFormPayloadFromFields(fields) : {}),
|
...cfg.formPayload,
|
...(row?.formPayload || {}),
|
};
|
if (!fields.length && Object.keys(formPayload).length) {
|
fields = Object.keys(formPayload)
|
.filter((k) => k && k !== "summary")
|
.map((k) => ({
|
key: k,
|
label: k,
|
type: guessFieldTypeFromValue(formPayload[k]),
|
required: false,
|
rows: 3,
|
min: 0,
|
precision: 0,
|
options: [],
|
}));
|
}
|
const templateSnapshot = {
|
label: row?.templateName || row?.title || "审批",
|
approvalType: cfg.approvalType || row?.approvalType || "",
|
summaryPlaceholder: cfg.summaryPlaceholder || "",
|
templateId: row?.templateId,
|
fields,
|
};
|
return { fields, formPayload, templateSnapshot, formConfigData: cfg };
|
}
|
|
/** 解析实例 formConfig */
|
export function parseInstanceFormConfig(formConfig) {
|
let raw = {};
|
if (formConfig) {
|
if (typeof formConfig === "object") raw = formConfig;
|
else {
|
try {
|
raw = JSON.parse(formConfig);
|
} catch {
|
raw = {};
|
}
|
}
|
}
|
const data = parseFormConfigToData(formConfig);
|
const payload = raw.formPayload;
|
return {
|
summaryPlaceholder: raw.summaryPlaceholder || data.summaryPlaceholder || "",
|
approvalType: raw.approvalType || "",
|
fields: data.fields || [],
|
formPayload: payload && typeof payload === "object" ? payload : {},
|
};
|
}
|
|
export function unwrapInstanceDetail(res) {
|
const data = res?.data ?? res;
|
if (!data || typeof data !== "object") return {};
|
if (data.id != null || data.instanceNo) return data;
|
if (data.approvalInstanceVo) return data.approvalInstanceVo;
|
return data;
|
}
|
|
/** 填报内容 + 模板字段定义 → formConfig JSON */
|
export function buildInstanceFormConfigJson(templateSnapshot, formPayload) {
|
const payload = formPayload || {};
|
return JSON.stringify({
|
summaryPlaceholder: templateSnapshot?.summaryPlaceholder || "",
|
approvalType: templateSnapshot?.approvalType || "",
|
fields: templateSnapshot?.fields || [],
|
formPayload: payload,
|
});
|
}
|
|
/** 组装保存/更新审批 DTO */
|
export function buildInstanceDto({ submitForm, activeTemplate, userStore, flowNodes, existingRow }) {
|
const payload = submitForm?.formPayload || {};
|
const tpl = activeTemplate || {};
|
const title =
|
String(payload.summary || payload.title || "").trim() ||
|
tpl.label ||
|
submitForm?.templateName ||
|
"审批申请";
|
const templateId = submitForm?.templateId || tpl.templateId;
|
const instanceId = existingRow?.id ?? submitForm?.instanceId;
|
const taskList = mapFlowNodesToTasks(flowNodes || submitForm?.flowNodes, {
|
instanceId,
|
templateId,
|
});
|
const isUpdate = Boolean(instanceId);
|
|
const dto = {
|
templateId,
|
templateName: submitForm?.templateName || tpl.label || "",
|
title,
|
formConfig: buildInstanceFormConfigJson({ ...tpl, fields: tpl.fields || submitForm?.formFieldDefs }, payload),
|
tasks: taskList,
|
};
|
|
if (isUpdate) {
|
dto.id = existingRow?.id ?? submitForm?.instanceId;
|
dto.instanceNo = existingRow?.instanceNo ?? submitForm?.instanceNo ?? "";
|
dto.status =
|
existingRow?.statusRaw || mapInstanceStatusToApi(existingRow?.approvalStatus) || "PENDING";
|
dto.currentLevel = existingRow?.currentLevel ?? submitForm?.currentLevel ?? 1;
|
dto.applicantId = existingRow?.applicantId ?? existingRow?.applicantNo;
|
dto.applicantName = existingRow?.applicantName || "";
|
} else {
|
dto.status = "PENDING";
|
dto.currentLevel = 1;
|
dto.applicantId = userStore?.id;
|
dto.applicantName = userStore?.nickName || userStore?.name || "";
|
}
|
return dto;
|
}
|
|
/** @deprecated 使用 buildInstanceDto */
|
export function buildSaveInstanceDto(params) {
|
return buildInstanceDto(params);
|
}
|
|
/** 校验提交审批流程(与模板页规则一致) */
|
export function validateSubmitFlowNodes(flowNodes) {
|
const nodes = normalizeFlowNodes(flowNodes);
|
if (!nodes.length) return { ok: false, message: "请至少配置一个审批节点" };
|
for (let i = 0; i < nodes.length; i++) {
|
if (!nodes[i].approvers.length) {
|
return { ok: false, message: `请为第 ${i + 1} 个节点选择至少一名审批人` };
|
}
|
}
|
return { ok: true, nodes };
|
}
|
|
/** 后端 status → 页面 approvalStatus */
|
export function mapInstanceStatusFromApi(status) {
|
const s = String(status || "").toUpperCase();
|
if (s === "APPROVED") return "approved";
|
if (s === "REJECTED") return "rejected";
|
if (s === "CANCELLED") return "cancelled";
|
return "pending";
|
}
|
|
/** 页面 approvalStatus → 后端 status */
|
export function mapInstanceStatusToApi(approvalStatus) {
|
const s = String(approvalStatus || "").toLowerCase();
|
if (s === "approved") return "APPROVED";
|
if (s === "rejected") return "REJECTED";
|
if (s === "cancelled") return "CANCELLED";
|
return "PENDING";
|
}
|
|
export function unwrapInstancePage(res) {
|
const data = res?.data ?? res;
|
return {
|
records: Array.isArray(data?.records) ? data.records : [],
|
total: Number(data?.total ?? 0),
|
};
|
}
|
|
/** 分页列表项 → 表格行 */
|
export function mapInstanceFromApi(row) {
|
if (!row) return {};
|
const approvalStatus = mapInstanceStatusFromApi(row.status);
|
const createTime = formatDisplayTime(row.createTime ?? row.applyTime ?? "");
|
const applyTime = formatDisplayTime(row.applyTime ?? "");
|
const finishTime = formatDisplayTime(row.finishTime ?? "");
|
const resolved = resolveInstanceFormFields(row);
|
const { fields, formPayload, templateSnapshot } = resolved;
|
const tasks = Array.isArray(row.tasks) ? row.tasks : [];
|
const flowNodes = tasks.length
|
? mapTasksToFlowNodes(tasks)
|
: mapNodesFromApi(row.nodes || row.flowNodes);
|
const approvalRecords = mapRecordsFromApi(row.records);
|
return {
|
id: row.id,
|
bizId: row.instanceNo || String(row.id ?? ""),
|
instanceNo: row.instanceNo || "",
|
templateId: row.templateId,
|
templateName: row.templateName || "",
|
businessId: row.businessId,
|
businessType: row.businessType,
|
applicantId: row.applicantId,
|
applicantNo: row.applicantId != null ? String(row.applicantId) : "",
|
applicantName: row.applicantName || "",
|
approvalType: row.templateName || "",
|
unread: Boolean(row.isApprove) && approvalStatus === "pending",
|
isApprove: Boolean(row.isApprove),
|
approvalStatus,
|
statusRaw: row.status,
|
createTime,
|
applyTime: applyTime === "—" ? "" : applyTime,
|
finishTime: finishTime === "—" ? "" : finishTime,
|
title: row.title || "",
|
summary: row.title || row.templateName || "",
|
currentLevel: row.currentLevel,
|
formConfig: row.formConfig,
|
formPayload,
|
formFieldDefs: fields,
|
templateSnapshot,
|
tasks,
|
records: Array.isArray(row.records) ? row.records : [],
|
flowNodes,
|
approvalFlowNodes: [],
|
currentNodeIndex: 0,
|
approvalRecords,
|
rejectReason:
|
approvalRecords.find((r) => r.result === "rejected")?.opinion || "",
|
};
|
}
|
|
/** 审批操作:与后端 status 枚举一致 */
|
export const APPROVE_ACTION_APPROVED = "APPROVED";
|
export const APPROVE_ACTION_REJECTED = "REJECTED";
|
|
/** 页面操作 → approveAction */
|
export function mapApproveActionToApi(uiResult) {
|
return uiResult === "rejected" ? APPROVE_ACTION_REJECTED : APPROVE_ACTION_APPROVED;
|
}
|
|
/** 组装审批提交 DTO */
|
export function buildApproveInstanceDto(row, uiResult, comment) {
|
const opinion = (comment || "").trim();
|
return {
|
id: row?.id,
|
approveAction: mapApproveActionToApi(uiResult),
|
approveComment: opinion || (uiResult === "approved" ? "同意" : ""),
|
};
|
}
|
|
export function buildApprovalInstanceListParams({ page, searchForm }) {
|
const params = {
|
current: page.current,
|
size: page.size,
|
};
|
const dto = {};
|
const kw = (searchForm?.applicantKeyword || "").trim();
|
if (kw) dto.applicantName = kw;
|
if (searchForm?.approvalType) {
|
const opt = APPROVAL_TYPE_OPTIONS.find((x) => x.value === searchForm.approvalType);
|
if (opt?.label) dto.templateName = opt.label;
|
}
|
if (Object.keys(dto).length) params.approvalInstanceDto = dto;
|
return params;
|
}
|
|
export function approvalTypeLabel(v) {
|
return APPROVAL_TYPE_OPTIONS.find((x) => x.value === v)?.label || v || "—";
|
}
|
|
export function approvalTypeStyle(v) {
|
const hit = APPROVAL_TYPE_OPTIONS.find((x) => x.value === v);
|
if (!hit) return {};
|
return {
|
backgroundColor: hit.cellBg,
|
color: hit.cellColor,
|
border: hit.border || "none",
|
};
|
}
|
|
export function approvalStatusLabel(v) {
|
return APPROVAL_STATUS_OPTIONS.find((x) => x.value === v)?.label || "—";
|
}
|
|
export function approvalStatusTagType(v) {
|
if (v === "approved") return "success";
|
if (v === "rejected") return "danger";
|
if (v === "cancelled") return "info";
|
return "primary";
|
}
|
|
export function unreadLabel(v) {
|
return v ? "是" : "否";
|
}
|
|
/** 列表行 → 编辑表单(仅用行数据回显) */
|
export function buildEditFormFromInstanceRow(row) {
|
const { fields, formPayload, templateSnapshot } = resolveInstanceFormFields(row);
|
const normalized = normalizeFlowNodes(
|
row?.flowNodes?.length ? row.flowNodes : mapTasksToFlowNodes(row?.tasks)
|
);
|
const flowNodes = normalized.length
|
? JSON.parse(JSON.stringify(normalized))
|
: [createEmptyNode(1)];
|
|
return {
|
templateKey: String(row?.templateId || ""),
|
templateId: row?.templateId,
|
templateName: row?.templateName || templateSnapshot.label,
|
instanceId: row?.id,
|
instanceNo: row?.instanceNo || "",
|
statusRaw: row?.statusRaw || row?.status || "PENDING",
|
currentLevel: row?.currentLevel ?? 1,
|
applicantId: row?.applicantId,
|
applicantName: row?.applicantName || "",
|
templateSnapshot,
|
formFieldDefs: fields,
|
formPayload,
|
flowNodes,
|
};
|
}
|
|
export function createEmptySubmitForm(templateKey, templateOverride, flowNodesOverride) {
|
const tpl = templateOverride || null;
|
const payload = tpl?.fields?.length ? buildFormPayloadFromFields(tpl.fields) : { summary: "" };
|
const normalized = normalizeFlowNodes(flowNodesOverride);
|
const flowNodes = normalized.length
|
? JSON.parse(JSON.stringify(normalized))
|
: [createEmptyNode(1)];
|
return {
|
templateKey: templateKey || "",
|
templateId: tpl?.templateId || "",
|
templateName: tpl?.label || "",
|
instanceId: "",
|
instanceNo: "",
|
statusRaw: "",
|
currentLevel: 1,
|
applicantId: null,
|
applicantName: "",
|
templateSnapshot: templateOverride || null,
|
formFieldDefs: tpl?.fields || [],
|
formPayload: payload,
|
flowNodes,
|
};
|
}
|