From 5b248a9716688d8132cfb02b4ba0abecd4060b06 Mon Sep 17 00:00:00 2001
From: yyb <995253665@qq.com>
Date: 星期三, 20 五月 2026 11:49:08 +0800
Subject: [PATCH] 审批模板流程化
---
src/views/officeProcessAutomation/ApproveManage/approve-template/index.vue | 803 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 files changed, 796 insertions(+), 7 deletions(-)
diff --git a/src/views/officeProcessAutomation/ApproveManage/approve-template/index.vue b/src/views/officeProcessAutomation/ApproveManage/approve-template/index.vue
index f88c88f..59850e2 100644
--- a/src/views/officeProcessAutomation/ApproveManage/approve-template/index.vue
+++ b/src/views/officeProcessAutomation/ApproveManage/approve-template/index.vue
@@ -1,12 +1,801 @@
-<!--
- 妯″潡涓枃鍚嶏細瀹℃壒妯℃澘
- 鐩綍鏍囪瘑锛欰pproveManage/approve-template锛坅pprove-template 鈫� 涓枃锛氬鎵规ā鏉匡級
- 澶嶇敤椤甸潰锛欯/views/procurementManagement/procurementLedger/index.vue锛堥噰璐彴璐︼紱鏂囦欢鍚� index.vue 鈫� 鍏ュ彛椤碉級
--->
+<!--OA妯″潡锛氬鎵规ā鏉�-->
+
<template>
- <ProcurementLedger />
+
+ <div class="app-container approve-template-page">
+
+ <div class="search_form mb20">
+
+ <div class="search_fields">
+
+ <span class="search_title">妯℃澘鍚嶇О锛�</span>
+
+ <el-input
+
+ v-model="searchForm.keyword"
+
+ style="width: 220px"
+
+ placeholder="鎼滅储鍚嶇О鎴栬鏄�"
+
+ clearable
+
+ :prefix-icon="Search"
+
+ @keyup.enter="handleQuery"
+
+ />
+
+ <el-checkbox v-model="searchForm.enabledOnly" class="ml12" @change="handleQuery">
+
+ 浠呮樉绀哄惎鐢�
+
+ </el-checkbox>
+
+ <el-button type="primary" :icon="Search" class="ml10" @click="handleQuery">鎼滅储</el-button>
+
+ <el-button :icon="RefreshRight" @click="resetSearch">閲嶇疆</el-button>
+
+ </div>
+
+ <div class="search_actions">
+
+ <el-button type="primary" :icon="Plus" @click="openFormDialog('add')">鏂板缓妯℃澘</el-button>
+
+ </div>
+
+ </div>
+
+
+
+ <div class="table_list">
+
+ <PIMTable
+
+ rowKey="id"
+
+ :column="tableColumn"
+
+ :tableData="tableData"
+
+ :page="page"
+
+ :isSelection="false"
+
+ :tableLoading="tableLoading"
+
+ :total="page.total"
+
+ @pagination="pagination"
+
+ />
+
+ </div>
+
+
+
+ <!-- 鏂板缓 / 缂栬緫 -->
+
+ <el-dialog
+
+ v-model="formDialog.visible"
+
+ :title="formDialog.title"
+
+ width="1020px"
+
+ append-to-body
+
+ destroy-on-close
+
+ class="template-form-dialog"
+
+ @closed="onFormDialogClosed"
+
+ >
+
+ <el-form
+
+ v-if="formDialog.visible"
+
+ ref="formRef"
+
+ :model="form"
+
+ :rules="formRules"
+
+ label-width="100px"
+
+ >
+
+ <el-row :gutter="20">
+
+ <el-col :span="8">
+
+ <el-form-item label="妯℃澘鍚嶇О" prop="templateName">
+
+ <el-input v-model="form.templateName" placeholder="濡傦細椤圭洰绔嬮」瀹℃壒" maxlength="50" show-word-limit />
+
+ </el-form-item>
+
+ </el-col>
+
+ <el-col :span="8">
+
+ <el-form-item label="妯℃澘绫诲瀷" prop="businessType">
+
+ <el-select v-model="form.businessType" placeholder="璇烽�夋嫨" style="width: 100%">
+
+ <el-option
+
+ v-for="opt in templateTypeOptions"
+
+ :key="`tpl-type-${opt.value}`"
+
+ :label="opt.label"
+
+ :value="opt.value"
+
+ />
+
+ </el-select>
+
+ </el-form-item>
+
+ </el-col>
+
+ <el-col :span="8">
+
+ <el-form-item label="鍚敤鐘舵��">
+
+ <el-switch v-model="form.enabled" active-text="鍚敤" inactive-text="鍋滅敤" />
+
+ </el-form-item>
+
+ </el-col>
+
+ </el-row>
+
+ <el-form-item label="妯℃澘璇存槑">
+
+ <el-input
+
+ v-model="form.description"
+
+ type="textarea"
+
+ :rows="2"
+
+ placeholder="绠�瑕佽鏄庤妯℃澘鐨勯�傜敤鍦烘櫙"
+
+ maxlength="200"
+
+ show-word-limit
+
+ />
+
+ </el-form-item>
+
+ <el-form-item label="濉姤閰嶇疆">
+
+ <FormConfigEditor v-model="form.formConfigData" />
+
+ <p class="flow-tip">閰嶇疆鎻愪氦瀹℃壒鏃堕渶濉啓鐨勮〃鍗曢」锛屼繚瀛樺悗鍐欏叆 formConfig锛圝SON锛夈��</p>
+
+ </el-form-item>
+
+ <el-form-item label="瀹℃壒娴佺▼" required>
+
+ <TemplateFlowEditor v-model="form.flowNodes" :user-options="flowUserOptions" />
+
+ <p class="flow-tip">
+
+ 鎸夐『搴忔祦杞細鍙负姣忎釜鑺傜偣娣诲姞澶氬悕瀹℃壒浜猴紱浼氱闇�鍏ㄩ儴閫氳繃锛屾垨绛句换涓�浜洪�氳繃鍗冲彲杩涘叆涓嬩竴鑺傜偣銆�
+
+ </p>
+
+ </el-form-item>
+
+ <el-form-item label="闄勪欢">
+
+ <div class="upload-block">
+
+ <FileUpload v-model:file-list="form.storageBlobDTOs" :limit="10" button-text="鐐瑰嚮閫夋嫨鏂囦欢" />
+
+ </div>
+
+ <p class="flow-tip">鍙笂浼犳ā鏉胯鏄庢枃妗c�佸埗搴︽枃浠剁瓑锛堥�夊~锛夈��</p>
+
+ </el-form-item>
+
+ </el-form>
+
+ <template #footer>
+
+ <el-button type="primary" @click="onSubmitForm">淇� 瀛�</el-button>
+
+ <el-button @click="formDialog.visible = false">鍙� 娑�</el-button>
+
+ </template>
+
+ </el-dialog>
+
+
+
+ <!-- 璇︽儏 -->
+
+ <el-dialog v-model="detailDialog.visible" title="妯℃澘璇︽儏" width="880px" append-to-body destroy-on-close>
+
+ <div v-loading="detailLoading" class="detail-dialog-body">
+
+ <el-descriptions :column="2" border>
+
+ <el-descriptions-item label="妯℃澘鍚嶇О">{{ detailRow.templateName }}</el-descriptions-item>
+
+ <el-descriptions-item label="妯℃澘绫诲瀷">{{ templateTypeLabel(detailRow.businessType) }}</el-descriptions-item>
+
+ <el-descriptions-item label="鐘舵��">
+
+ <el-tag :type="detailRow.enabled !== false ? 'success' : 'info'" size="small">
+
+ {{ detailRow.enabled !== false ? "鍚敤" : "鍋滅敤" }}
+
+ </el-tag>
+
+ </el-descriptions-item>
+
+ <el-descriptions-item label="璇存槑" :span="2">{{ detailRow.description || "鈥�" }}</el-descriptions-item>
+
+ <el-descriptions-item label="濉姤鎻愮ず" :span="2">
+
+ {{ detailFormConfig.summaryPlaceholder || "鈥�" }}
+
+ </el-descriptions-item>
+
+ <el-descriptions-item label="鍒涘缓浜�">{{ detailRow.createdUserName || "鈥�" }}</el-descriptions-item>
+
+ <el-descriptions-item label="鍒涘缓鏃堕棿">{{ formatDisplayTime(detailRow.createdTime) }}</el-descriptions-item>
+
+ <el-descriptions-item label="鏇存柊鏃堕棿">{{ formatDisplayTime(detailRow.updatedTime) }}</el-descriptions-item>
+
+ </el-descriptions>
+
+ <el-divider content-position="left">濉姤椤癸紙{{ detailFormConfig.fields?.length || 0 }} 椤癸級</el-divider>
+
+ <el-table
+
+ v-if="detailFormConfig.fields?.length"
+
+ :data="detailFormConfig.fields"
+
+ border
+
+ size="small"
+
+ class="mb16"
+
+ >
+
+ <el-table-column prop="label" label="鏄剧ず鍚嶇О" min-width="120" />
+
+ <el-table-column prop="key" label="瀛楁鏍囪瘑" min-width="100" />
+
+ <el-table-column label="绫诲瀷" width="100">
+
+ <template #default="{ row }">{{ formFieldTypeLabel(row.type) }}</template>
+
+ </el-table-column>
+
+ <el-table-column label="閫夐」鏉ユ簮" width="100">
+
+ <template #default="{ row }">
+
+ {{ row.type === 'select' ? selectOptionSourceLabel(row.optionSource) : '鈥�' }}
+
+ </template>
+
+ </el-table-column>
+
+ <el-table-column label="蹇呭~" width="70" align="center">
+
+ <template #default="{ row }">{{ row.required !== false ? "鏄�" : "鍚�" }}</template>
+
+ </el-table-column>
+
+ <el-table-column label="榛樿鍊�" min-width="120" show-overflow-tooltip>
+
+ <template #default="{ row }">{{ formatDefaultValueDisplay(row) }}</template>
+
+ </el-table-column>
+
+ </el-table>
+
+ <el-empty v-else description="鏈厤缃~鎶ラ」" :image-size="48" class="mb16" />
+
+ <el-divider content-position="left">瀹℃壒娴佺▼锛坽{ detailRow.flowNodes?.length || 0 }} 涓妭鐐癸級</el-divider>
+
+ <div v-if="detailRow.flowNodes?.length" class="detail-flow">
+
+ <div v-for="(node, index) in detailRow.flowNodes" :key="index" class="detail-node">
+
+ <div class="detail-node-head">
+
+ <span class="detail-node-order">鑺傜偣 {{ index + 1 }}</span>
+
+ <el-tag size="small" :type="node.signMode === 'or_sign' ? 'warning' : 'primary'">
+
+ {{ nodeSignModeLabel(node.signMode) }}
+
+ </el-tag>
+
+ </div>
+
+ <div class="detail-approvers">
+
+ <el-tag
+
+ v-for="a in node.approvers"
+
+ :key="String(a.approverId)"
+
+ class="detail-approver-tag"
+
+ effect="plain"
+
+ >
+
+ {{ a.approverName || "鈥�" }}
+
+ </el-tag>
+
+ <span v-if="!node.approvers?.length" class="text-muted">鏈厤缃鎵逛汉</span>
+
+ </div>
+
+ <el-icon v-if="index < detailRow.flowNodes.length - 1" class="detail-arrow"><ArrowRight /></el-icon>
+
+ </div>
+
+ </div>
+
+ <el-empty v-else description="鏆傛棤娴佺▼鑺傜偣" :image-size="60" />
+
+ <el-divider content-position="left">闄勪欢锛坽{ detailAttachments.length }} 涓級</el-divider>
+
+ <template v-if="detailAttachments.length">
+
+ <el-tag
+
+ v-for="(f, i) in detailAttachments"
+
+ :key="i"
+
+ class="detail-attachment-tag"
+
+ type="info"
+
+ effect="plain"
+
+ >
+
+ {{ attachmentDisplayName(f) }}
+
+ </el-tag>
+
+ </template>
+
+ <el-empty v-else description="鏆傛棤闄勪欢" :image-size="48" />
+
+ </div>
+
+ <template #footer>
+
+ <el-button @click="detailDialog.visible = false">鍏� 闂�</el-button>
+
+ <el-button type="primary" @click="editFromDetail">缂� 杈�</el-button>
+
+ </template>
+
+ </el-dialog>
+
+ </div>
+
</template>
+
+
<script setup>
-import ProcurementLedger from '@/views/procurementManagement/procurementLedger/index.vue'
+
+import { ArrowRight, Plus, RefreshRight } from "@element-plus/icons-vue";
+
+import { ElMessage } from "element-plus";
+
+import { computed, nextTick, onMounted, ref } from "vue";
+
+import { userListNoPageByTenantId } from "@/api/system/user.js";
+
+import FileUpload from "@/components/AttachmentUpload/file/index.vue";
+
+import FormConfigEditor from "./components/FormConfigEditor.vue";
+
+import TemplateFlowEditor from "./components/TemplateFlowEditor.vue";
+
+import { formatDisplayTime, mapAttachmentsFromApi } from "./approveTemplateConstants.js";
+
+import { formatDefaultValueDisplay, formFieldTypeLabel, parseFormConfigToData } from "./formConfigUtils.js";
+import { selectOptionSourceLabel } from "./selectOptionSource.js";
+
+import { useApproveTemplate } from "./useApproveTemplate.js";
+
+
+
+const {
+
+ Search,
+
+ templateTypeOptions,
+
+ loadTemplateTypeOptions,
+
+ templateTypeLabel,
+
+ nodeSignModeLabel,
+
+ searchForm,
+
+ tableLoading,
+
+ page,
+
+ tableData,
+
+ tableColumn,
+
+ formDialog,
+
+ form,
+
+ formRef,
+
+ formRules,
+
+ detailDialog,
+
+ detailRow,
+
+ detailLoading,
+
+ fetchTemplateList,
+
+ handleQuery,
+
+ resetSearch,
+
+ pagination,
+
+ openFormDialog,
+
+ openDetail,
+
+ submitForm,
+
+} = useApproveTemplate();
+
+
+
+const flowUserOptions = ref([]);
+
+
+
+const detailFormConfig = computed(() =>
+
+ parseFormConfigToData(detailRow.value?.formConfigData ?? detailRow.value?.formConfig)
+
+);
+
+
+
+const detailAttachments = computed(() => mapAttachmentsFromApi(detailRow.value));
+
+
+
+function attachmentDisplayName(file) {
+
+ if (!file) return "鏈懡鍚�";
+
+ return file.name || file.originalFilename || file.fileName || "鏈懡鍚�";
+
+}
+
+
+
+function unwrapArray(payload) {
+
+ if (Array.isArray(payload)) return payload;
+
+ if (payload?.data && Array.isArray(payload.data)) return payload.data;
+
+ if (payload?.rows && Array.isArray(payload.rows)) return payload.rows;
+
+ return [];
+
+}
+
+
+
+function isActiveUser(u) {
+
+ if (u.delFlag === "2" || u.delFlag === 2) return false;
+
+ if (u.status == null) return true;
+
+ return String(u.status) === "0";
+
+}
+
+
+
+async function loadUsers() {
+
+ try {
+
+ const res = await userListNoPageByTenantId();
+
+ flowUserOptions.value = unwrapArray(res).filter(isActiveUser);
+
+ } catch {
+
+ flowUserOptions.value = [];
+
+ }
+
+}
+
+
+
+async function onSubmitForm() {
+
+ const ret = await submitForm();
+
+ if (ret?.message) {
+
+ ElMessage.warning(ret.message);
+
+ return;
+
+ }
+
+ if (ret?.ok) ElMessage.success("淇濆瓨鎴愬姛");
+
+}
+
+
+
+function onFormDialogClosed() {
+
+ formRef.value?.resetFields?.();
+
+}
+
+
+
+async function editFromDetail() {
+
+ const row = detailRow.value;
+
+ detailDialog.visible = false;
+
+ await nextTick();
+
+ openFormDialog("edit", row);
+
+}
+
+
+
+onMounted(() => {
+
+ loadUsers();
+
+ loadTemplateTypeOptions();
+
+ fetchTemplateList();
+
+});
+
</script>
+
+
+
+<style scoped>
+
+.mb20 {
+
+ margin-bottom: 20px;
+
+}
+
+.mb16 {
+
+ margin-bottom: 16px;
+
+}
+
+.mb16.el-empty {
+
+ padding: 8px 0;
+
+}
+
+.ml10 {
+
+ margin-left: 10px;
+
+}
+
+.ml12 {
+
+ margin-left: 12px;
+
+}
+
+.search_form {
+
+ display: flex;
+
+ flex-wrap: wrap;
+
+ align-items: center;
+
+ justify-content: space-between;
+
+ gap: 12px;
+
+}
+
+.search_fields {
+
+ display: flex;
+
+ flex-wrap: wrap;
+
+ align-items: center;
+
+ gap: 4px;
+
+}
+
+.search_actions {
+
+ display: flex;
+
+ gap: 8px;
+
+}
+
+.flow-tip {
+
+ font-size: 12px;
+
+ color: var(--el-text-color-secondary);
+
+ margin: 8px 0 0;
+
+ line-height: 1.5;
+
+}
+
+.detail-flow {
+
+ display: flex;
+
+ flex-wrap: wrap;
+
+ align-items: flex-start;
+
+ gap: 8px;
+
+}
+
+.detail-node {
+
+ position: relative;
+
+ min-width: 180px;
+
+ max-width: 240px;
+
+ padding: 12px;
+
+ border: 1px solid var(--el-border-color-lighter);
+
+ border-radius: 8px;
+
+ background: var(--el-fill-color-lighter);
+
+}
+
+.detail-node-head {
+
+ display: flex;
+
+ align-items: center;
+
+ justify-content: space-between;
+
+ margin-bottom: 8px;
+
+}
+
+.detail-node-order {
+
+ font-weight: 600;
+
+ font-size: 13px;
+
+}
+
+.detail-approvers {
+
+ display: flex;
+
+ flex-wrap: wrap;
+
+ gap: 4px;
+
+}
+
+.detail-approver-tag {
+
+ margin: 0;
+
+}
+
+.detail-arrow {
+
+ position: absolute;
+
+ right: -20px;
+
+ top: 50%;
+
+ transform: translateY(-50%);
+
+ color: var(--el-text-color-placeholder);
+
+}
+
+.detail-dialog-body {
+
+ min-height: 120px;
+
+}
+
+.upload-block {
+
+ width: 100%;
+
+}
+
+.detail-attachment-tag {
+
+ margin: 0 8px 8px 0;
+
+}
+
+.text-muted {
+
+ font-size: 12px;
+
+ color: var(--el-text-color-placeholder);
+
+}
+
+.template-form-dialog :deep(.el-dialog__body) {
+
+ padding-top: 8px;
+
+}
+
+</style>
+
--
Gitblit v1.9.3