import { Search } from "@element-plus/icons-vue";
|
import dayjs from "dayjs";
|
import { ElMessageBox } from "element-plus";
|
import { computed, reactive, ref, watch } from "vue";
|
import {
|
createEmptyTemplateForm,
|
createInitialMockTemplates,
|
flowNodesSummary,
|
getBuiltinTemplates,
|
loadStoredTemplates,
|
nodeSignModeLabel,
|
saveStoredTemplates,
|
validateTemplateForm,
|
} from "./approveTemplateConstants.js";
|
|
export function useApproveTemplate() {
|
const stored = loadStoredTemplates();
|
const allTemplates = ref(stored?.length ? stored : createInitialMockTemplates());
|
|
const activeTab = ref("custom");
|
const builtinTemplates = getBuiltinTemplates();
|
|
const searchForm = reactive({
|
keyword: "",
|
enabledOnly: false,
|
});
|
|
const tableLoading = ref(false);
|
const page = reactive({ current: 1, size: 10, total: 0 });
|
|
const formDialog = reactive({ visible: false, title: "", mode: "add" });
|
const form = reactive(createEmptyTemplateForm());
|
const formRef = ref();
|
|
const detailDialog = reactive({ visible: false });
|
const detailRow = ref({});
|
|
const filteredList = computed(() => {
|
let list = [...allTemplates.value];
|
const kw = (searchForm.keyword || "").trim().toLowerCase();
|
if (kw) {
|
list = list.filter((r) => {
|
const name = (r.templateName || "").toLowerCase();
|
const desc = (r.description || "").toLowerCase();
|
return name.includes(kw) || desc.includes(kw);
|
});
|
}
|
if (searchForm.enabledOnly) {
|
list = list.filter((r) => r.enabled !== false);
|
}
|
return list.sort((a, b) => (String(a.updateTime) < String(b.updateTime) ? 1 : -1));
|
});
|
|
watch(
|
filteredList,
|
(list) => {
|
page.total = list.length;
|
const maxPage = Math.max(1, Math.ceil(list.length / page.size) || 1);
|
if (page.current > maxPage) page.current = maxPage;
|
},
|
{ immediate: true }
|
);
|
|
const tableData = computed(() => {
|
const start = (page.current - 1) * page.size;
|
return filteredList.value.slice(start, start + page.size);
|
});
|
|
const formRules = {
|
templateName: [{ required: true, message: "请输入模板名称", trigger: "blur" }],
|
};
|
|
const tableColumn = ref([
|
{ label: "模板名称", prop: "templateName", minWidth: 140 },
|
{ label: "说明", prop: "description", minWidth: 160, showOverflowTooltip: true },
|
{
|
label: "节点数",
|
prop: "flowNodes",
|
width: 80,
|
align: "center",
|
formatData: (v) => (Array.isArray(v) ? v.length : 0),
|
},
|
{
|
label: "流程概要",
|
prop: "flowNodes",
|
minWidth: 220,
|
showOverflowTooltip: true,
|
formatData: (v) => flowNodesSummary(v),
|
},
|
{
|
label: "状态",
|
prop: "enabled",
|
width: 90,
|
align: "center",
|
dataType: "tag",
|
formatData: (v) => (v !== false ? "启用" : "停用"),
|
formatType: (v) => (v !== false ? "success" : "info"),
|
},
|
{ label: "更新时间", prop: "updateTime", width: 170 },
|
{
|
dataType: "action",
|
label: "操作",
|
align: "center",
|
fixed: "right",
|
width: 200,
|
operation: [
|
{ name: "详情", type: "text", clickFun: (row) => openDetail(row) },
|
{ name: "编辑", type: "text", clickFun: (row) => openFormDialog("edit", row) },
|
{ name: "删除", type: "text", clickFun: (row) => removeTemplate(row) },
|
],
|
},
|
]);
|
|
function persist() {
|
saveStoredTemplates(allTemplates.value);
|
}
|
|
function handleQuery() {
|
tableLoading.value = true;
|
page.current = 1;
|
setTimeout(() => {
|
tableLoading.value = false;
|
}, 150);
|
}
|
|
function resetSearch() {
|
searchForm.keyword = "";
|
searchForm.enabledOnly = false;
|
handleQuery();
|
}
|
|
function pagination({ page: p, limit }) {
|
page.current = p;
|
page.size = limit;
|
}
|
|
function resetForm(row) {
|
const base = createEmptyTemplateForm();
|
if (!row) {
|
Object.assign(form, base);
|
return;
|
}
|
Object.assign(form, {
|
...base,
|
id: row.id,
|
templateName: row.templateName || "",
|
description: row.description || "",
|
enabled: row.enabled !== false,
|
flowNodes: JSON.parse(JSON.stringify(row.flowNodes || [base.flowNodes[0]])),
|
});
|
}
|
|
function openFormDialog(mode, row) {
|
formDialog.mode = mode;
|
formDialog.title = mode === "add" ? "新建自定义审批模板" : "编辑自定义审批模板";
|
resetForm(mode === "edit" ? row : null);
|
formDialog.visible = true;
|
}
|
|
function openDetail(row) {
|
detailRow.value = { ...row };
|
detailDialog.visible = true;
|
}
|
|
function isNameDuplicate(name, excludeId) {
|
const n = (name || "").trim();
|
return allTemplates.value.some((t) => t.templateName?.trim() === n && t.id !== excludeId);
|
}
|
|
async function submitForm() {
|
if (!formRef.value) return false;
|
try {
|
await formRef.value.validate();
|
} catch {
|
return false;
|
}
|
const validated = validateTemplateForm(form);
|
if (!validated.ok) {
|
return { message: validated.message };
|
}
|
if (isNameDuplicate(validated.name, form.id)) {
|
return { message: "模板名称已存在,请更换名称" };
|
}
|
const now = dayjs().format("YYYY-MM-DD HH:mm:ss");
|
if (formDialog.mode === "add") {
|
allTemplates.value.unshift({
|
id: `tpl_${Date.now()}`,
|
templateName: validated.name,
|
description: (form.description || "").trim(),
|
enabled: form.enabled !== false,
|
createTime: now,
|
updateTime: now,
|
flowNodes: validated.nodes,
|
});
|
} else {
|
const hit = allTemplates.value.find((t) => t.id === form.id);
|
if (!hit) return { message: "模板不存在或已删除" };
|
hit.templateName = validated.name;
|
hit.description = (form.description || "").trim();
|
hit.enabled = form.enabled !== false;
|
hit.flowNodes = validated.nodes;
|
hit.updateTime = now;
|
}
|
persist();
|
formDialog.visible = false;
|
page.current = 1;
|
return { ok: true };
|
}
|
|
async function removeTemplate(row) {
|
try {
|
await ElMessageBox.confirm(`确定删除模板「${row.templateName}」吗?`, "提示", {
|
type: "warning",
|
confirmButtonText: "删除",
|
cancelButtonText: "取消",
|
});
|
} catch {
|
return;
|
}
|
const idx = allTemplates.value.findIndex((t) => t.id === row.id);
|
if (idx >= 0) {
|
allTemplates.value.splice(idx, 1);
|
persist();
|
}
|
}
|
|
function toggleEnabled(row) {
|
const hit = allTemplates.value.find((t) => t.id === row.id);
|
if (!hit) return;
|
hit.enabled = !hit.enabled;
|
hit.updateTime = dayjs().format("YYYY-MM-DD HH:mm:ss");
|
persist();
|
}
|
|
return {
|
Search,
|
activeTab,
|
builtinTemplates,
|
nodeSignModeLabel,
|
flowNodesSummary,
|
searchForm,
|
tableLoading,
|
page,
|
tableData,
|
tableColumn,
|
formDialog,
|
form,
|
formRef,
|
formRules,
|
detailDialog,
|
detailRow,
|
handleQuery,
|
resetSearch,
|
pagination,
|
openFormDialog,
|
openDetail,
|
submitForm,
|
toggleEnabled,
|
};
|
}
|