From be85a121031530d69865fd30d0dfb2fe0998a6a3 Mon Sep 17 00:00:00 2001
From: yyb <995253665@qq.com>
Date: 星期四, 21 五月 2026 15:21:40 +0800
Subject: [PATCH] 优化查询条件分类
---
src/views/officeProcessAutomation/ApproveManage/approve-shared/approvalModuleRegistry.js | 65 +-
src/views/officeProcessAutomation/ReimburseManage/travel-reimburse/index.vue | 665 ++++------------------------
src/views/officeProcessAutomation/ReimburseManage/cost-reimburse/index.vue | 600 ++++---------------------
src/views/officeProcessAutomation/ApproveManage/approve-shared/useApprovalInstanceModule.js | 6
4 files changed, 245 insertions(+), 1,091 deletions(-)
diff --git a/src/views/officeProcessAutomation/ApproveManage/approve-shared/approvalModuleRegistry.js b/src/views/officeProcessAutomation/ApproveManage/approve-shared/approvalModuleRegistry.js
index 2bdd35a..a85cf9c 100644
--- a/src/views/officeProcessAutomation/ApproveManage/approve-shared/approvalModuleRegistry.js
+++ b/src/views/officeProcessAutomation/ApproveManage/approve-shared/approvalModuleRegistry.js
@@ -1,12 +1,8 @@
import { matchBusinessTypeValue } from "../approve-list/approveListConstants.js";
/**
- * 鍚勪笟鍔℃ā鍧椾笌瀹℃壒妯℃澘绫诲瀷鐨勬槧灏勶紙閰嶇疆鍖栧叆鍙o級 *
- * 浣跨敤鏂瑰紡锛�
- * 1. 鍦ㄤ笟鍔¢〉寮曞叆 ApprovalTemplateBindDialog锛屼紶鍏� moduleKey
- * 2. 鎴栧湪琛ㄥ崟鍐呭祵 ApprovalTemplateFormSection + useApprovalTemplateBinding({ moduleKey })
- *
- * businessType锛氳嫢鍚庣 TypeEnums 宸插浐瀹� code锛屽彲鐩存帴鍐欐 value锛涘惁鍒欑敤 typeLabels 鎸夊悕绉板尮閰�
+ * 鍚勪笟鍔℃ā鍧椾笌瀹℃壒妯℃澘绫诲瀷鐨勬槧灏勶紙閰嶇疆鍖栧叆鍙o級
+ * businessType 涓庡悗绔� TypeEnums / listPage 绾﹀畾涓�鑷达紙鍐欐鏋氫妇鍊硷級
*/
export const APPROVAL_MODULE_KEYS = {
REGULAR: "regular",
@@ -20,16 +16,30 @@
ENTERPRISE_NEWS: "enterprise_news",
};
+/** 瀹℃壒瀹炰緥 listPage / 淇濆瓨 浣跨敤鐨� businessType 鏋氫妇 */
+export const APPROVAL_BUSINESS_TYPE = {
+ [APPROVAL_MODULE_KEYS.REGULAR]: 10,
+ [APPROVAL_MODULE_KEYS.TRANSFER]: 11,
+ [APPROVAL_MODULE_KEYS.WORK_HANDOVER]: 13,
+ [APPROVAL_MODULE_KEYS.LEAVE]: 14,
+ [APPROVAL_MODULE_KEYS.OVERTIME]: 15,
+ [APPROVAL_MODULE_KEYS.TRAVEL_REIMBURSE]: 16,
+ [APPROVAL_MODULE_KEYS.COST_REIMBURSE]: 17,
+ [APPROVAL_MODULE_KEYS.ENTERPRISE_NEWS]: 18,
+};
+
/** @type {Record<string, import('./approvalModuleRegistry.js').ApprovalModuleConfig>} */
export const APPROVAL_MODULE_REGISTRY = {
[APPROVAL_MODULE_KEYS.REGULAR]: {
label: "杞鐢宠",
approvalType: "regular",
+ businessType: APPROVAL_BUSINESS_TYPE[APPROVAL_MODULE_KEYS.REGULAR],
typeLabels: ["杞", "杞鐢宠"],
},
[APPROVAL_MODULE_KEYS.TRANSFER]: {
label: "璋冨矖鐢宠",
approvalType: "transfer",
+ businessType: APPROVAL_BUSINESS_TYPE[APPROVAL_MODULE_KEYS.TRANSFER],
typeLabels: ["璋冨矖", "璋冨姩", "璋冨矖鐢宠", "璋冨姩鐢宠"],
},
[APPROVAL_MODULE_KEYS.RESIGN]: {
@@ -40,31 +50,37 @@
[APPROVAL_MODULE_KEYS.WORK_HANDOVER]: {
label: "宸ヤ綔浜ゆ帴",
approvalType: "work_handover",
+ businessType: APPROVAL_BUSINESS_TYPE[APPROVAL_MODULE_KEYS.WORK_HANDOVER],
typeLabels: ["宸ヤ綔浜ゆ帴", "浜ゆ帴", "宸ヤ綔浜ゆ帴瀹℃壒"],
},
[APPROVAL_MODULE_KEYS.LEAVE]: {
label: "璇峰亣鐢宠",
approvalType: "leave",
+ businessType: APPROVAL_BUSINESS_TYPE[APPROVAL_MODULE_KEYS.LEAVE],
typeLabels: ["璇峰亣", "璇峰亣鐢宠", "璇峰亣瀹℃壒"],
},
[APPROVAL_MODULE_KEYS.OVERTIME]: {
label: "鍔犵彮鐢宠",
approvalType: "overtime",
+ businessType: APPROVAL_BUSINESS_TYPE[APPROVAL_MODULE_KEYS.OVERTIME],
typeLabels: ["鍔犵彮", "鍔犵彮鐢宠", "鍔犵彮瀹℃壒"],
},
[APPROVAL_MODULE_KEYS.TRAVEL_REIMBURSE]: {
label: "宸梾鎶ラ攢",
approvalType: "travel_reimburse",
- typeLabels: ["宸梾", "宸梾鎶ラ攢"],
+ businessType: APPROVAL_BUSINESS_TYPE[APPROVAL_MODULE_KEYS.TRAVEL_REIMBURSE],
+ typeLabels: ["宸梾", "宸梾鎶ラ攢", "鍑哄樊鎶ラ攢"],
},
[APPROVAL_MODULE_KEYS.COST_REIMBURSE]: {
label: "璐圭敤鎶ラ攢",
approvalType: "cost_reimburse",
+ businessType: APPROVAL_BUSINESS_TYPE[APPROVAL_MODULE_KEYS.COST_REIMBURSE],
typeLabels: ["璐圭敤", "璐圭敤鎶ラ攢"],
},
[APPROVAL_MODULE_KEYS.ENTERPRISE_NEWS]: {
label: "浼佷笟鏂伴椈",
approvalType: "enterprise_news",
+ businessType: APPROVAL_BUSINESS_TYPE[APPROVAL_MODULE_KEYS.ENTERPRISE_NEWS],
typeLabels: ["浼佷笟鏂伴椈", "鏂伴椈", "鏂伴椈鍙戝竷"],
},
};
@@ -72,9 +88,9 @@
/**
* @typedef {object} ApprovalModuleConfig
* @property {string} label
- * @property {string} [approvalType] 鍒楄〃鏍峰紡鐢�
- * @property {string|number} [businessType] 涓� TypeEnums value 涓�鑷存椂鍙啓姝�
- * @property {string[]} [typeLabels] 涓� TypeEnums label 妯$硦鍖归厤
+ * @property {string} [approvalType]
+ * @property {string|number} [businessType]
+ * @property {string[]} [typeLabels]
*/
export function getApprovalModuleConfig(moduleKey) {
@@ -82,23 +98,25 @@
return APPROVAL_MODULE_REGISTRY[moduleKey] || null;
}
-/** 鍒楄〃鏌ヨ榛樿 businessType锛堜笌瀹℃壒鍒楄〃 listPage 绾﹀畾涓�鑷达級 */
+/** 鍒楄〃鏌ヨ businessType锛堜紭鍏堥厤缃灇涓撅紝涓嶅啀鍥為�� approvalType 瀛楃涓诧級 */
export function getModuleListBusinessType(moduleKey) {
const cfg = getApprovalModuleConfig(moduleKey);
if (!cfg) return "";
if (cfg.businessType != null && cfg.businessType !== "") return cfg.businessType;
- return cfg.approvalType || "";
+ return APPROVAL_BUSINESS_TYPE[moduleKey] ?? "";
}
-/** 浠� TypeEnums 閫夐」涓В鏋愭湰妯″潡鐨� businessType锛堜笌瀹℃壒鍒楄〃涓嬫媺涓�鑷达級 */
+/** 浠� TypeEnums 瑙f瀽鏈ā鍧� businessType锛涘凡閰嶇疆鏋氫妇鏃剁洿鎺ヨ繑鍥� */
export function resolveModuleBusinessType(moduleKey, typeOptions = []) {
const cfg = getApprovalModuleConfig(moduleKey);
if (!cfg) return null;
- if (cfg.businessType != null && cfg.businessType !== "") return cfg.businessType;
+
+ const fixed = getModuleListBusinessType(moduleKey);
+ if (fixed != null && fixed !== "") return fixed;
const labels = [cfg.label, ...(cfg.typeLabels || [])].filter(Boolean);
const hitByLabel = (typeOptions || []).find((opt) => {
- const optLabel = String(opt?.label || "").trim();
+ const optLabel = String(opt?.label || opt?.name || "").trim();
if (!optLabel) return false;
return labels.some(
(l) => optLabel === l || optLabel.includes(l) || l.includes(optLabel)
@@ -106,31 +124,24 @@
});
if (hitByLabel?.value != null && hitByLabel.value !== "") return hitByLabel.value;
- if (cfg.approvalType) {
- const hitByValue = (typeOptions || []).find(
- (opt) =>
- matchBusinessTypeValue(opt?.value, cfg.approvalType) ||
- matchBusinessTypeValue(opt?.code, cfg.approvalType)
- );
- if (hitByValue?.value != null && hitByValue.value !== "") return hitByValue.value;
- }
-
return cfg.approvalType || null;
}
-/** 鏀堕泦涓庢ā鍧楃浉鍏崇殑鍏ㄩ儴 businessType 鍙栧�硷紙鏋氫妇鍊� + approvalType锛夛紝鐢ㄤ簬妯℃澘鍒楄〃杩囨护 */
+/** 鍒楄〃/妯℃澘杩囨护鐢ㄧ殑 businessType 闆嗗悎 */
export function getModuleMatchingBusinessTypes(moduleKey, typeOptions = []) {
const cfg = getApprovalModuleConfig(moduleKey);
if (!cfg) return [];
+ const fixed = getModuleListBusinessType(moduleKey);
+ if (fixed != null && fixed !== "") return [fixed];
+
const values = new Set();
const primary = resolveModuleBusinessType(moduleKey, typeOptions);
if (primary != null && primary !== "") values.add(primary);
- if (cfg.approvalType) values.add(cfg.approvalType);
const labels = [cfg.label, ...(cfg.typeLabels || [])].filter(Boolean);
for (const opt of typeOptions || []) {
- const optLabel = String(opt?.label || "").trim();
+ const optLabel = String(opt?.label || opt?.name || "").trim();
if (!optLabel) continue;
const matched = labels.some(
(l) => optLabel === l || optLabel.includes(l) || l.includes(optLabel)
diff --git a/src/views/officeProcessAutomation/ApproveManage/approve-shared/useApprovalInstanceModule.js b/src/views/officeProcessAutomation/ApproveManage/approve-shared/useApprovalInstanceModule.js
index 01d90cb..8dd8bba 100644
--- a/src/views/officeProcessAutomation/ApproveManage/approve-shared/useApprovalInstanceModule.js
+++ b/src/views/officeProcessAutomation/ApproveManage/approve-shared/useApprovalInstanceModule.js
@@ -57,11 +57,13 @@
const moduleConfig = computed(() => getApprovalModuleConfig(moduleKey));
const businessTypeOptions = ref([]);
- /** 涓庡鎵瑰垪琛ㄤ竴鑷达細浼樺厛鐢� TypeEnums 鐨� value锛屽尮閰嶄笉鍒板啀鍥為�� approvalType */
+ /** 鍒楄〃鏌ヨ businessType锛氫紭鍏� registry 鍐欐鏋氫妇锛屽啀鍥為�� TypeEnums */
const defaultListBusinessType = computed(() => {
+ const fixed = getModuleListBusinessType(moduleKey);
+ if (fixed != null && fixed !== "") return fixed;
const resolved = resolveModuleBusinessType(moduleKey, businessTypeOptions.value);
if (resolved != null && resolved !== "") return resolved;
- return getModuleListBusinessType(moduleKey);
+ return "";
});
async function loadBusinessTypeOptions() {
diff --git a/src/views/officeProcessAutomation/ReimburseManage/cost-reimburse/index.vue b/src/views/officeProcessAutomation/ReimburseManage/cost-reimburse/index.vue
index b384569..c836168 100644
--- a/src/views/officeProcessAutomation/ReimburseManage/cost-reimburse/index.vue
+++ b/src/views/officeProcessAutomation/ReimburseManage/cost-reimburse/index.vue
@@ -1,8 +1,8 @@
-<!--OA妯″潡锛氳垂鐢ㄦ姤閿�-->
+<!--OA妯″潡锛氳垂鐢ㄦ姤閿�锛堝鎵瑰疄渚� listPage锛宐usinessType=17锛�-->
<template>
<div class="app-container">
<div class="search_form mb20">
- <div class="search_fields">
+ <div>
<span class="search_title">鐢宠浜猴細</span>
<el-input
v-model="searchForm.applicantKeyword"
@@ -10,40 +10,15 @@
placeholder="濮撳悕鎴栫紪鍙�"
clearable
:prefix-icon="Search"
- @keyup.enter="handleQuery"
+ @keyup.enter="onSearch"
/>
- <span class="search_title" style="margin-left: 12px">鐢宠鏃堕棿锛�</span>
- <el-date-picker
- v-model="searchForm.applyTimeFrom"
- type="date"
- placeholder="寮�濮嬫棩鏈�"
- format="YYYY-MM-DD"
- value-format="YYYY-MM-DD"
- style="width: 150px"
- clearable
- />
- <span class="search_title" style="margin-left: 8px">鑷�</span>
- <el-date-picker
- v-model="searchForm.applyTimeTo"
- type="date"
- placeholder="缁撴潫鏃ユ湡"
- format="YYYY-MM-DD"
- value-format="YYYY-MM-DD"
- style="width: 150px; margin-left: 8px"
- clearable
- />
- <el-button type="primary" style="margin-left: 10px" @click="handleQuery">鎼滅储</el-button>
+ <el-button type="primary" style="margin-left: 10px" @click="onSearch">鎼滅储</el-button>
<el-button @click="resetSearch">閲嶇疆</el-button>
</div>
- <div class="search_actions">
- <el-button type="success" plain @click="handleImportClick">瀵煎叆</el-button>
- <el-button type="warning" plain @click="handleExport">瀵煎嚭</el-button>
- <el-button type="primary" @click="openFormDialog('add')">鏂板璐圭敤鎶ラ攢</el-button>
+ <div>
+ <el-button type="primary" @click="openAddWithTemplate">鏂板璐圭敤鎶ラ攢</el-button>
</div>
</div>
-
- <input ref="importInputRef" type="file" accept="application/json,.json" class="sr-only-input" @change="onImportFile" />
-
<div class="table_list">
<PIMTable
rowKey="id"
@@ -53,404 +28,123 @@
:isSelection="false"
:tableLoading="tableLoading"
:total="page.total"
- @pagination="pagination"
+ @pagination="onPagination"
/>
</div>
- <!-- 鏂板 / 缂栬緫 -->
- <el-dialog
- v-model="formDialog.visible"
- :title="formDialog.title"
- width="1120px"
- append-to-body
- destroy-on-close
- class="cost-reimburse-form-dialog"
- @closed="onFormClosed"
- >
- <el-alert type="info" show-icon :closable="false" class="mb16">
- <template #title>鍏ㄥ搧绫昏垂鐢ㄦ姤閿� 路 鍒嗙被妯℃澘涓�閿~鎶�</template>
- <template #default>
- 鏀寔宸梾銆佸姙鍏噰璐�佷笟鍔℃嫑寰呫�佷氦閫氳垂銆侀�氳璐圭瓑锛涙寜閲戦鑷姩鍖归厤瀹℃壒閾撅紙500鍏冨唴鐩村睘涓婄骇锛岃秴5000鍏冭储鍔℃�荤洃澶嶆牳锛夈��
- </template>
- </el-alert>
+ <ApprovalInstanceSubmitDialog
+ v-model="submitDialog.visible"
+ :title="submitDialogTitle"
+ :form="submitForm"
+ :rules="submitFormRules"
+ :fields="submitFormFields"
+ :active-template="activeTemplate"
+ :user-options="flowUserOptions"
+ :is-edit="isSubmitEdit"
+ :saving="submitSaving"
+ :form-ref="submitFormRef"
+ flow-attachments-only
+ @submit="onSubmit"
+ />
- <div v-if="!formDialog.readonly" class="template-bar mb16">
- <span class="template-label">鍒嗙被妯℃澘锛�</span>
- <el-button
- v-for="(tpl, key) in CATEGORY_TEMPLATES"
- :key="key"
- size="small"
- :type="form.expenseCategory === key ? 'primary' : 'default'"
- plain
- @click="applyTemplate(key)"
- >
- {{ tpl.label }}
- </el-button>
- </div>
+ <ApprovalTemplateBindDialog
+ v-model:visible="templateBindVisible"
+ :module-key="APPROVAL_MODULE_KEYS.COST_REIMBURSE"
+ skip-form-confirm
+ @confirm="onTemplateBound"
+ @closed="onTemplateBindClosed"
+ />
- <el-form
- ref="formRef"
- :model="form"
- :rules="formRules"
- label-width="120px"
- class="cost-reimburse-form"
- :disabled="formDialog.readonly"
- >
- <el-card class="form-section" shadow="never">
- <template #header><span class="card-header-title">鍩烘湰淇℃伅</span></template>
- <el-row :gutter="20">
- <el-col :span="12">
- <el-form-item label="鍛樺伐缂栧彿">
- <el-input v-model="form.employeeNo" readonly placeholder="閫夋嫨鍛樺伐鍚庤嚜鍔ㄥ甫鍑�" />
- </el-form-item>
- </el-col>
- <el-col :span="12">
- <el-form-item label="鍛樺伐濮撳悕" prop="applicantId">
- <el-select
- v-model="form.applicantId"
- filterable
- remote
- clearable
- reserve-keyword
- placeholder="璇烽�夋嫨鎴栨悳绱㈠憳宸�"
- style="width: 100%"
- :remote-method="remoteSearchApplicantForm"
- :loading="applicantFormSearchLoading"
- @change="onApplicantChange"
- >
- <el-option
- v-for="u in applicantFormOptions"
- :key="u.userId"
- :label="userSelectLabel(u)"
- :value="u.userId"
- />
- </el-select>
- </el-form-item>
- </el-col>
- </el-row>
- <el-row :gutter="20">
- <el-col :span="12">
- <el-form-item label="璐圭敤绫诲瀷" prop="expenseCategory">
- <el-select
- v-model="form.expenseCategory"
- placeholder="璇烽�夋嫨璐圭敤绫诲瀷"
- style="width: 100%"
- @change="onExpenseCategoryChange"
- >
- <el-option
- v-for="opt in EXPENSE_CATEGORY_OPTIONS"
- :key="opt.value"
- :label="opt.label"
- :value="opt.value"
- />
- </el-select>
- </el-form-item>
- </el-col>
- <el-col :span="12">
- <el-form-item label="鎶ラ攢鐘舵��">
- <el-tag
- :type="form.approvalResult === 'approved' ? 'success' : form.approvalResult === 'rejected' ? 'danger' : 'warning'"
- effect="plain"
- >
- {{
- form.approvalResult === "approved"
- ? "宸查�氳繃"
- : form.approvalResult === "rejected"
- ? "宸查┏鍥�"
- : "瀹℃牳涓�"
- }}
- </el-tag>
- </el-form-item>
- </el-col>
- </el-row>
- <el-row :gutter="20">
- <el-col :span="24">
- <el-form-item label="鎶ラ攢鍘熷洜" prop="reimburseReason">
- <el-input
- v-model="form.reimburseReason"
- type="textarea"
- :rows="3"
- placeholder="璇峰~鍐欐姤閿�鍘熷洜"
- maxlength="2000"
- show-word-limit
- />
- </el-form-item>
- </el-col>
- </el-row>
- <el-row :gutter="20">
- <el-col :span="12">
- <el-form-item label="鎶ラ攢閲戦" prop="applyAmount">
- <div class="amount-row">
- <el-input-number
- v-model="form.applyAmount"
- :min="0"
- :precision="2"
- controls-position="right"
- class="amount-input"
- @change="autoAssignApprovalFlow"
- />
- <el-button v-if="!formDialog.readonly" type="primary" link @click="syncApplyAmountFromDetails">
- 鎸夋槑缁嗘眹鎬� {{ detailTotalAmount }} 鍏�
- </el-button>
- </div>
- </el-form-item>
- </el-col>
- </el-row>
- </el-card>
-
- <el-card class="form-section" shadow="never">
- <template #header>
- <div class="card-header-row">
- <span class="card-header-title">鎶ラ攢鏄庣粏</span>
- <el-button v-if="!formDialog.readonly" type="primary" plain size="small" @click="addExpenseDetail">
- 鏂板鏄庣粏
- </el-button>
- </div>
- </template>
-
- <el-table :data="form.expenseDetails" border size="small" class="detail-table">
- <el-table-column type="index" label="搴忓彿" width="55" align="center" />
- <el-table-column label="鍙戠エ鏃ユ湡" width="150">
- <template #default="{ row }">
- <el-date-picker
- v-if="!formDialog.readonly"
- v-model="row.invoiceDate"
- type="date"
- value-format="YYYY-MM-DD"
- size="small"
- style="width: 100%"
- />
- <span v-else>{{ row.invoiceDate || "鈥�" }}</span>
- </template>
- </el-table-column>
- <el-table-column label="璐圭敤绉戠洰" width="130">
- <template #default="{ row }">
- <el-select v-if="!formDialog.readonly" v-model="row.expenseSubject" size="small" style="width: 100%">
- <el-option
- v-for="opt in EXPENSE_SUBJECT_OPTIONS"
- :key="opt.value"
- :label="opt.label"
- :value="opt.value"
- />
- </el-select>
- <span v-else>{{ expenseSubjectLabel(row.expenseSubject) }}</span>
- </template>
- </el-table-column>
- <el-table-column label="閲戦" width="120">
- <template #default="{ row }">
- <el-input-number
- v-if="!formDialog.readonly"
- v-model="row.amount"
- :min="0"
- :precision="2"
- size="small"
- controls-position="right"
- style="width: 100%"
- @change="onDetailAmountChange"
- />
- <span v-else>{{ row.amount ?? "鈥�" }}</span>
- </template>
- </el-table-column>
- <el-table-column label="鎻忚堪" min-width="140">
- <template #default="{ row }">
- <el-input v-if="!formDialog.readonly" v-model="row.description" size="small" placeholder="璇存槑" />
- <span v-else>{{ row.description || "鈥�" }}</span>
- </template>
- </el-table-column>
- <el-table-column v-if="!formDialog.readonly" label="鎿嶄綔" width="70" align="center">
- <template #default="{ $index }">
- <el-button type="danger" link size="small" @click="removeExpenseDetail($index)">鍒犻櫎</el-button>
- </template>
- </el-table-column>
- </el-table>
- </el-card>
-
- <el-card class="form-section" shadow="never">
- <template #header><span class="card-header-title">鏀舵淇℃伅</span></template>
- <el-row :gutter="20">
- <el-col :span="8">
- <el-form-item label="鏀舵浜�" prop="payee">
- <el-input v-model="form.payee" placeholder="璇疯緭鍏ユ敹娆句汉" maxlength="50" />
- </el-form-item>
- </el-col>
- <el-col :span="8">
- <el-form-item label="鏀舵璐﹀彿" prop="payeeAccount">
- <el-input v-model="form.payeeAccount" placeholder="閾惰鍗″彿" maxlength="30" />
- </el-form-item>
- </el-col>
- <el-col :span="8">
- <el-form-item label="寮�鎴锋敮琛�" prop="bankBranch">
- <el-input v-model="form.bankBranch" placeholder="寮�鎴锋敮琛屽叏绉�" maxlength="100" />
- </el-form-item>
- </el-col>
- </el-row>
- </el-card>
-
- <el-card class="form-section" shadow="never">
- <template #header><span class="card-header-title">闄勪欢锛堝彂绁級</span></template>
- <el-form-item label-width="0" class="attachment-form-item">
- <div class="upload-block">
- <FileUpload v-model:file-list="form.attachmentList" :limit="20" button-text="鐐瑰嚮閫夋嫨鏂囦欢" />
- </div>
- </el-form-item>
- </el-card>
-
- <el-card class="form-section" shadow="never">
- <template #header>
- <div class="card-header-row">
- <span class="card-header-title">瀹℃壒娴佺▼</span>
- <el-button v-if="!formDialog.readonly" type="primary" link size="small" @click="autoAssignApprovalFlow">
- 鎸夎鍒欓噸鏂板垎閰�
- </el-button>
- </div>
- </template>
- <el-alert type="success" :title="approvalRuleHint" show-icon :closable="false" class="mb12" />
- <el-form-item prop="approvalFlowNodes" label-width="0">
- <ApprovalFlowEditor
- v-if="!formDialog.readonly"
- v-model="form.approvalFlowNodes"
- :user-options="flowUserOptions"
- @update:model-value="onApprovalFlowChange"
- />
- <ApprovalFlowProgress v-else :nodes="form.approvalFlowNodes" :current-index="form.currentNodeIndex" />
- <p v-if="!formDialog.readonly" class="flow-tip">绯荤粺宸叉寜閲戦涓庤垂鐢ㄧ被鍨嬭嚜鍔ㄥ垎閰嶅鎵逛汉锛屽彲鎵嬪姩璋冩暣銆�</p>
- </el-form-item>
- </el-card>
- </el-form>
- <template #footer>
- <el-button v-if="!formDialog.readonly" type="primary" @click="submitForm">鎻� 浜�</el-button>
- <el-button @click="formDialog.visible = false">{{ formDialog.readonly ? "鍏� 闂�" : "鍙� 娑�" }}</el-button>
- </template>
- </el-dialog>
-
- <!-- 璇︽儏 -->
- <el-dialog v-model="detailDialog.visible" title="璐圭敤鎶ラ攢璇︽儏" width="900px" append-to-body destroy-on-close>
- <DetailPanel :row="detailRow" />
- <el-divider content-position="left">瀹℃壒娴佺▼</el-divider>
- <ApprovalFlowProgress :nodes="detailRow.approvalFlowNodes" :current-index="detailRow.currentNodeIndex ?? 0" />
- <el-divider content-position="left">瀹℃壒璁板綍</el-divider>
- <el-timeline v-if="detailRow.approvalRecords?.length">
- <el-timeline-item
- v-for="(rec, i) in detailRow.approvalRecords"
- :key="i"
- :type="rec.result === 'approved' ? 'success' : rec.result === 'rejected' ? 'danger' : 'primary'"
- :timestamp="rec.time"
- >
- {{ rec.operatorName }} 鈥� {{ approvalActionLabel(rec.result) }}锛歿{ rec.opinion || "鏃犳剰瑙�" }}
- </el-timeline-item>
- </el-timeline>
- <el-empty v-else description="鏆傛棤瀹℃壒璁板綍" :image-size="60" />
- <template #footer>
- <el-button type="primary" @click="detailDialog.visible = false">鍏� 闂�</el-button>
- </template>
- </el-dialog>
-
- <!-- 瀹℃壒 -->
- <el-dialog
- v-model="approveDialog.visible"
- title="璐圭敤鎶ラ攢瀹℃壒"
- width="1000px"
- append-to-body
- destroy-on-close
- @closed="approveOpinion = ''"
- >
- <DetailPanel :row="approveDialog.row" />
- <el-divider content-position="left">娴佺▼杩涘害</el-divider>
- <ApprovalFlowProgress
- :nodes="approveDialog.row?.approvalFlowNodes"
- :current-index="approveDialog.row?.currentNodeIndex ?? 0"
- />
- <el-form label-width="100px" class="mt16">
- <el-form-item label="瀹℃壒鎰忚" required>
- <el-input
- v-model="approveOpinion"
- type="textarea"
- :rows="3"
- maxlength="500"
- show-word-limit
- placeholder="閫氳繃鍙暀绌猴紱椹冲洖璇峰~鍐欏叿浣撳師鍥狅紙濡傦細鍙戠エ妯$硦闇�閲嶄紶锛�"
- />
- </el-form-item>
- </el-form>
- <template #footer>
- <el-button type="success" @click="submitApprove('approved')">閫� 杩�</el-button>
- <el-button type="danger" @click="submitApprove('rejected')">椹� 鍥�</el-button>
- <el-button @click="approveDialog.visible = false">鍙� 娑�</el-button>
- </template>
- </el-dialog>
+ <ApprovalInstanceDetailDialog
+ v-model="detailDialog.visible"
+ title="璐圭敤鎶ラ攢璇︽儏"
+ :row="detailRow"
+ @edit="openEditFromDetail"
+ />
</div>
</template>
<script setup>
-import FileUpload from "@/components/AttachmentUpload/file/index.vue";
-import ApprovalFlowEditor from "@/views/officeProcessAutomation/AttendManage/overtime-apply/components/ApprovalFlowEditor.vue";
-import ApprovalFlowProgress from "../travel-reimburse/components/ApprovalFlowProgress.vue";
-import DetailPanel from "./components/DetailPanel.vue";
-import { useCostReimburse } from "./useCostReimburse.js";
+import { Search } from "@element-plus/icons-vue";
+import { ElMessage } from "element-plus";
+import { onMounted, reactive } from "vue";
+import ApprovalInstanceDetailDialog from "../../ApproveManage/approve-shared/components/ApprovalInstanceDetailDialog.vue";
+import ApprovalInstanceSubmitDialog from "../../ApproveManage/approve-shared/components/ApprovalInstanceSubmitDialog.vue";
+import ApprovalTemplateBindDialog from "../../ApproveManage/approve-shared/components/ApprovalTemplateBindDialog.vue";
+import { buildInstanceTableColumns } from "../../ApproveManage/approve-shared/approvalInstanceFormConfigTable.js";
+import { APPROVAL_MODULE_KEYS } from "../../ApproveManage/approve-shared/approvalModuleRegistry.js";
+import { useApprovalInstanceModule } from "../../ApproveManage/approve-shared/useApprovalInstanceModule.js";
+import { useFlowUserOptions } from "../../ApproveManage/approve-shared/useFlowUserOptions.js";
-const cr = useCostReimburse();
+const searchForm = reactive({ applicantKeyword: "" });
+
+const mod = useApprovalInstanceModule({
+ moduleKey: APPROVAL_MODULE_KEYS.COST_REIMBURSE,
+ buildExtraListParams(sf) {
+ const extra = {};
+ const kw = (sf?.applicantKeyword || "").trim();
+ if (kw && /[\u4e00-\u9fa5]/.test(kw)) extra.applicantName = kw;
+ return extra;
+ },
+});
+
const {
- Search,
- EXPENSE_CATEGORY_OPTIONS,
- CATEGORY_TEMPLATES,
- EXPENSE_SUBJECT_OPTIONS,
- expenseSubjectLabel,
- searchForm,
+ tableData,
tableLoading,
page,
- tableData,
- tableColumn,
- importInputRef,
- formRef,
- form,
- formDialog,
- formRules,
detailDialog,
detailRow,
- approveDialog,
- approveOpinion,
- applicantFormSearchLoading,
- applicantFormOptions,
- flowUserOptions,
- detailTotalAmount,
- approvalRuleHint,
- handleQuery,
- resetSearch,
- pagination,
- remoteSearchApplicantForm,
- userSelectLabel,
- onApplicantChange,
- onExpenseCategoryChange,
- applyTemplate,
- onDetailAmountChange,
- onApprovalFlowChange,
- addExpenseDetail,
- removeExpenseDetail,
- syncApplyAmountFromDetails,
- autoAssignApprovalFlow,
- openFormDialog,
- onFormClosed,
+ submitDialog,
submitForm,
- approvalActionLabel,
- submitApprove,
- handleExport,
- handleImportClick,
- onImportFile,
-} = cr;
+ submitFormRef,
+ submitSaving,
+ isSubmitEdit,
+ activeTemplate,
+ submitFormFields,
+ submitFormRules,
+ submitDialogTitle,
+ templateBindVisible,
+ handleQuery,
+ initModuleList,
+ pagination,
+ openAddWithTemplate,
+ onTemplateBound,
+ onTemplateBindClosed,
+ openEditFromDetail,
+ submitInstanceForm,
+ buildTableActions,
+} = mod;
+
+const { flowUserOptions, loadFlowUsers } = useFlowUserOptions();
+const tableColumn = buildInstanceTableColumns(tableData, buildTableActions);
+
+function onSearch() {
+ handleQuery(searchForm);
+}
+
+function resetSearch() {
+ searchForm.applicantKeyword = "";
+ onSearch();
+}
+
+function onPagination(obj) {
+ pagination(obj, searchForm);
+}
+
+async function onSubmit() {
+ const ok = await submitInstanceForm({ skipValidate: true });
+ if (ok) ElMessage.success(isSubmitEdit.value ? "淇敼鎴愬姛" : "鎻愪氦鎴愬姛");
+}
+
+onMounted(async () => {
+ loadFlowUsers();
+ await initModuleList(searchForm);
+});
</script>
<style scoped>
.mb20 {
margin-bottom: 20px;
-}
-.mb16 {
- margin-bottom: 16px;
-}
-.mb12 {
- margin-bottom: 12px;
-}
-.mt16 {
- margin-top: 16px;
}
.search_form {
display: flex;
@@ -459,98 +153,8 @@
justify-content: space-between;
gap: 12px;
}
-.search_fields {
- display: flex;
- flex-wrap: wrap;
- align-items: center;
- gap: 4px;
-}
-.search_actions {
- display: flex;
- flex-wrap: wrap;
- gap: 8px;
-}
.search_title {
font-size: 14px;
color: var(--el-text-color-regular);
-}
-.sr-only-input {
- position: absolute;
- width: 1px;
- height: 1px;
- padding: 0;
- margin: -1px;
- overflow: hidden;
- clip: rect(0, 0, 0, 0);
- white-space: nowrap;
- border: 0;
-}
-.template-bar {
- display: flex;
- flex-wrap: wrap;
- align-items: center;
- gap: 8px;
-}
-.template-label {
- font-size: 14px;
- color: var(--el-text-color-secondary);
- flex-shrink: 0;
-}
-.form-section {
- margin-bottom: 16px;
- border: 1px solid var(--el-border-color-lighter);
-}
-.form-section :deep(.el-card__header) {
- padding: 12px 16px;
- background: var(--el-fill-color-lighter);
-}
-.form-section :deep(.el-card__body) {
- padding: 16px 16px 4px;
-}
-.card-header-title {
- font-size: 15px;
- font-weight: 600;
-}
-.card-header-row {
- display: flex;
- align-items: center;
- justify-content: space-between;
- gap: 12px;
-}
-.amount-row {
- display: flex;
- align-items: center;
- gap: 12px;
- width: 100%;
-}
-.amount-input {
- flex: 1;
- min-width: 160px;
-}
-.attachment-form-item {
- margin-bottom: 0;
-}
-.detail-table {
- margin-bottom: 0;
-}
-.upload-block {
- width: 100%;
-}
-.flow-tip {
- font-size: 12px;
- color: var(--el-text-color-secondary);
- margin-top: 8px;
-}
-.cost-reimburse-form-dialog :deep(.el-dialog__body) {
- padding-top: 12px;
-}
-.cost-reimburse-form :deep(.el-form-item) {
- margin-bottom: 18px;
-}
-.cost-reimburse-form :deep(.el-input-number) {
- width: 100%;
-}
-.cost-reimburse-form :deep(.el-row) {
- margin-bottom: 0;
}
</style>
diff --git a/src/views/officeProcessAutomation/ReimburseManage/travel-reimburse/index.vue b/src/views/officeProcessAutomation/ReimburseManage/travel-reimburse/index.vue
index 2e81e18..89b6d25 100644
--- a/src/views/officeProcessAutomation/ReimburseManage/travel-reimburse/index.vue
+++ b/src/views/officeProcessAutomation/ReimburseManage/travel-reimburse/index.vue
@@ -1,4 +1,4 @@
-<!--OA妯″潡锛氬樊鏃呮姤閿�-->
+<!--OA妯″潡锛氬樊鏃呮姤閿�锛堝鎵瑰疄渚� listPage锛宐usinessType=16锛�-->
<template>
<div class="app-container">
<div class="search_form mb20">
@@ -10,40 +10,15 @@
placeholder="濮撳悕鎴栫紪鍙�"
clearable
:prefix-icon="Search"
- @keyup.enter="handleQuery"
+ @keyup.enter="onSearch"
/>
- <span class="search_title" style="margin-left: 12px">鍑哄樊寮�濮嬶細</span>
- <el-date-picker
- v-model="searchForm.travelStartFrom"
- type="date"
- placeholder="寮�濮嬫棩鏈�"
- format="YYYY-MM-DD"
- value-format="YYYY-MM-DD"
- style="width: 150px"
- clearable
- />
- <span class="search_title" style="margin-left: 8px">缁撴潫锛�</span>
- <el-date-picker
- v-model="searchForm.travelEndTo"
- type="date"
- placeholder="缁撴潫鏃ユ湡"
- format="YYYY-MM-DD"
- value-format="YYYY-MM-DD"
- style="width: 150px"
- clearable
- />
- <el-button type="primary" style="margin-left: 10px" @click="handleQuery">鎼滅储</el-button>
+ <el-button type="primary" style="margin-left: 10px" @click="onSearch">鎼滅储</el-button>
<el-button @click="resetSearch">閲嶇疆</el-button>
</div>
- <div class="search_actions">
- <el-button type="success" plain @click="handleImportClick">瀵煎叆</el-button>
- <el-button type="warning" plain @click="handleExport">瀵煎嚭</el-button>
- <el-button type="primary" @click="openFormDialog('add')">鏂板宸梾鎶ラ攢</el-button>
+ <div>
+ <el-button type="primary" @click="openAddWithTemplate">鏂板宸梾鎶ラ攢</el-button>
</div>
</div>
-
- <input ref="importInputRef" type="file" accept="application/json,.json" class="sr-only-input" @change="onImportFile" />
-
<div class="table_list">
<PIMTable
rowKey="id"
@@ -53,462 +28,123 @@
:isSelection="false"
:tableLoading="tableLoading"
:total="page.total"
- @pagination="pagination"
+ @pagination="onPagination"
/>
</div>
- <!-- 鏂板 / 缂栬緫 -->
- <el-dialog
- v-model="formDialog.visible"
- :title="formDialog.title"
- width="1120px"
- append-to-body
- destroy-on-close
- class="travel-reimburse-form-dialog"
- @closed="onFormClosed"
- >
- <el-alert
- v-if="budgetHint.visible"
- :title="budgetHint.title"
- :type="budgetHint.type"
- :description="budgetHint.description"
- show-icon
- :closable="false"
- class="mb16"
- />
- <el-alert v-if="overBudgetWarnings.length" type="warning" show-icon :closable="false" class="mb16">
- <template #title>宸梾鏍囧噯瓒呮敮鎻愰啋锛堥渶鐗规壒锛�</template>
- <ul class="warn-list">
- <li v-for="(w, i) in overBudgetWarnings" :key="i">{{ w }}</li>
- </ul>
- </el-alert>
+ <ApprovalInstanceSubmitDialog
+ v-model="submitDialog.visible"
+ :title="submitDialogTitle"
+ :form="submitForm"
+ :rules="submitFormRules"
+ :fields="submitFormFields"
+ :active-template="activeTemplate"
+ :user-options="flowUserOptions"
+ :is-edit="isSubmitEdit"
+ :saving="submitSaving"
+ :form-ref="submitFormRef"
+ flow-attachments-only
+ @submit="onSubmit"
+ />
- <el-form
- ref="formRef"
- :model="form"
- :rules="formRules"
- label-width="120px"
- class="travel-reimburse-form"
- :disabled="formDialog.readonly"
- >
- <el-card class="form-section" shadow="never">
- <template #header><span class="card-header-title">鍩烘湰淇℃伅</span></template>
- <el-row :gutter="20">
- <el-col :span="12">
- <el-form-item label="鍛樺伐缂栧彿">
- <el-input v-model="form.employeeNo" readonly placeholder="閫夋嫨鍛樺伐鍚庤嚜鍔ㄥ甫鍑�" />
- </el-form-item>
- </el-col>
- <el-col :span="12">
- <el-form-item label="鍛樺伐濮撳悕" prop="applicantId">
- <el-select
- v-model="form.applicantId"
- filterable
- remote
- clearable
- reserve-keyword
- placeholder="璇烽�夋嫨鎴栨悳绱㈠憳宸�"
- style="width: 100%"
- :remote-method="remoteSearchApplicantForm"
- :loading="applicantFormSearchLoading"
- @change="onApplicantChange"
- >
- <el-option
- v-for="u in applicantFormOptions"
- :key="u.userId"
- :label="userSelectLabel(u)"
- :value="u.userId"
- />
- </el-select>
- </el-form-item>
- </el-col>
- </el-row>
- <el-row :gutter="20">
- <el-col :span="24">
- <el-form-item label="鎶ラ攢鍘熷洜" prop="reimburseReason">
- <el-input
- v-model="form.reimburseReason"
- type="textarea"
- :rows="3"
- placeholder="璇峰~鍐欏嚭宸強鎶ラ攢鍘熷洜"
- maxlength="2000"
- show-word-limit
- />
- </el-form-item>
- </el-col>
- </el-row>
- <el-row :gutter="20">
- <el-col :span="12">
- <el-form-item label="鍑哄樊寮�濮�" prop="travelStartTime">
- <el-date-picker
- v-model="form.travelStartTime"
- type="datetime"
- placeholder="寮�濮嬫椂闂�"
- format="YYYY-MM-DD HH:mm:ss"
- value-format="YYYY-MM-DD HH:mm:ss"
- style="width: 100%"
- @change="onTravelRangeChange"
- />
- </el-form-item>
- </el-col>
- <el-col :span="12">
- <el-form-item label="鍑哄樊缁撴潫" prop="travelEndTime">
- <el-date-picker
- v-model="form.travelEndTime"
- type="datetime"
- placeholder="缁撴潫鏃堕棿"
- format="YYYY-MM-DD HH:mm:ss"
- value-format="YYYY-MM-DD HH:mm:ss"
- style="width: 100%"
- @change="onTravelRangeChange"
- />
- </el-form-item>
- </el-col>
- </el-row>
- <el-row :gutter="20">
- <el-col :span="8">
- <el-form-item label="鍑哄樊澶╂暟">
- <el-input :model-value="travelDaysDisplay" readonly>
- <template #append>澶�</template>
- </el-input>
- </el-form-item>
- </el-col>
- <el-col :span="8">
- <el-form-item label="鍑哄樊鍦�" prop="departurePlace">
- <el-input v-model="form.departurePlace" placeholder="鍑哄彂鍩庡競" @blur="recalcTravelStandards" />
- </el-form-item>
- </el-col>
- <el-col :span="8">
- <el-form-item label="鐩殑鍦�" prop="destination">
- <el-input v-model="form.destination" placeholder="鐩殑鍩庡競" @blur="recalcTravelStandards" />
- </el-form-item>
- </el-col>
- </el-row>
- </el-card>
+ <ApprovalTemplateBindDialog
+ v-model:visible="templateBindVisible"
+ :module-key="APPROVAL_MODULE_KEYS.TRAVEL_REIMBURSE"
+ skip-form-confirm
+ @confirm="onTemplateBound"
+ @closed="onTemplateBindClosed"
+ />
- <el-card class="form-section" shadow="never">
- <template #header>
- <div class="card-header-row">
- <span class="card-header-title">宸梾鏍囧噯</span>
- <el-text type="info" size="small">{{ travelTierLabel }} 路 鐢熸椿琛ヨ创寤鸿 {{ suggestedLivingSubsidy }} 鍏�</el-text>
- </div>
- </template>
- <el-row :gutter="20">
- <el-col :span="8">
- <el-form-item label="閰掑簵鏍囧噯">
- <el-input-number
- v-model="form.hotelStandard"
- :min="0"
- :precision="2"
- controls-position="right"
- style="width: 100%"
- @change="recalcTravelStandards"
- />
- </el-form-item>
- </el-col>
- <el-col :span="8">
- <el-form-item label="浣忓澶╂暟">
- <el-input-number
- v-model="form.hotelDays"
- :min="0"
- :max="365"
- :precision="0"
- controls-position="right"
- style="width: 100%"
- @change="recalcTravelStandards"
- />
- </el-form-item>
- </el-col>
- <el-col :span="8">
- <el-form-item label="鐢熸椿琛ヨ创">
- <el-input-number
- v-model="form.livingSubsidy"
- :min="0"
- :precision="2"
- controls-position="right"
- style="width: 100%"
- @change="recalcTravelStandards"
- />
- </el-form-item>
- </el-col>
- </el-row>
- <el-row :gutter="20">
- <el-col :span="8">
- <el-form-item label="浜ら�氳ˉ璐�">
- <el-input :model-value="String(suggestedTransportSubsidy)" readonly><template #append>鍏�</template></el-input>
- </el-form-item>
- </el-col>
- <el-col :span="8">
- <el-form-item label="浣忓闄愰">
- <el-input :model-value="String(suggestedHotelLimit)" readonly><template #append>鍏�</template></el-input>
- </el-form-item>
- </el-col>
- <el-col :span="8">
- <el-form-item label="鐗规壒鏍囪">
- <el-tag :type="form.needSpecialApproval ? 'danger' : 'success'" effect="plain">
- {{ form.needSpecialApproval ? "瓒呮敮闇�鐗规壒" : "鍦ㄦ爣鍑嗚寖鍥村唴" }}
- </el-tag>
- </el-form-item>
- </el-col>
- </el-row>
- </el-card>
-
- <el-card class="form-section" shadow="never">
- <template #header><span class="card-header-title">閲戦涓庢敹娆�</span></template>
- <el-row :gutter="20">
- <el-col :span="12">
- <el-form-item label="鐢宠閲戦" prop="applyAmount">
- <div class="amount-row">
- <el-input-number v-model="form.applyAmount" :min="0" :precision="2" controls-position="right" class="amount-input" />
- <el-button v-if="!formDialog.readonly" type="primary" link @click="syncApplyAmountFromDetails">
- 鎸夋槑缁嗘眹鎬� {{ detailTotalAmount }} 鍏�
- </el-button>
- </div>
- </el-form-item>
- </el-col>
- <el-col :span="12">
- <el-form-item label="鏀舵浜�" prop="payee">
- <el-input v-model="form.payee" placeholder="璇疯緭鍏ユ敹娆句汉" maxlength="50" />
- </el-form-item>
- </el-col>
- </el-row>
- </el-card>
-
- <el-card class="form-section" shadow="never">
- <template #header>
- <div class="card-header-row">
- <span class="card-header-title">鎶ラ攢鏄庣粏</span>
- <el-button v-if="!formDialog.readonly" type="primary" plain size="small" @click="addExpenseDetail">鏂板鏄庣粏</el-button>
- </div>
- </template>
-
- <el-table :data="form.expenseDetails" border size="small" class="detail-table">
- <el-table-column type="index" label="搴忓彿" width="55" align="center" />
- <el-table-column label="鍙戠エ鏃ユ湡" width="150">
- <template #default="{ row }">
- <el-date-picker
- v-if="!formDialog.readonly"
- v-model="row.invoiceDate"
- type="date"
- value-format="YYYY-MM-DD"
- size="small"
- style="width: 100%"
- />
- <span v-else>{{ row.invoiceDate || "鈥�" }}</span>
- </template>
- </el-table-column>
- <el-table-column label="璐圭敤绉戠洰" width="130">
- <template #default="{ row }">
- <el-select
- v-if="!formDialog.readonly"
- v-model="row.expenseSubject"
- size="small"
- style="width: 100%"
- @change="recalcTravelStandards"
- >
- <el-option
- v-for="opt in EXPENSE_SUBJECT_OPTIONS"
- :key="opt.value"
- :label="opt.label"
- :value="opt.value"
- />
- </el-select>
- <span v-else>{{ expenseSubjectLabel(row.expenseSubject) }}</span>
- </template>
- </el-table-column>
- <el-table-column label="閲戦" width="120">
- <template #default="{ row }">
- <el-input-number
- v-if="!formDialog.readonly"
- v-model="row.amount"
- :min="0"
- :precision="2"
- size="small"
- controls-position="right"
- style="width: 100%"
- @change="onDetailAmountChange"
- />
- <span v-else>{{ row.amount ?? "鈥�" }}</span>
- </template>
- </el-table-column>
- <el-table-column label="鎻忚堪" min-width="140">
- <template #default="{ row }">
- <el-input v-if="!formDialog.readonly" v-model="row.description" size="small" placeholder="璇存槑" />
- <span v-else>{{ row.description || "鈥�" }}</span>
- </template>
- </el-table-column>
- <el-table-column v-if="!formDialog.readonly" label="鎿嶄綔" width="70" align="center">
- <template #default="{ $index }">
- <el-button type="danger" link size="small" @click="removeExpenseDetail($index)">鍒犻櫎</el-button>
- </template>
- </el-table-column>
- </el-table>
- </el-card>
-
- <el-card class="form-section" shadow="never">
- <template #header><span class="card-header-title">闄勪欢锛堝彂绁級</span></template>
- <el-form-item label-width="0" class="attachment-form-item">
- <div class="upload-block">
- <FileUpload v-model:file-list="form.attachmentList" :limit="20" button-text="鐐瑰嚮閫夋嫨鏂囦欢" />
- </div>
- </el-form-item>
- </el-card>
-
- <el-card class="form-section" shadow="never">
- <template #header><span class="card-header-title">瀹℃壒娴佺▼</span></template>
- <el-form-item prop="approvalFlowNodes" label-width="0">
- <ApprovalFlowEditor
- v-if="!formDialog.readonly"
- v-model="form.approvalFlowNodes"
- :user-options="flowUserOptions"
- @update:model-value="onApprovalFlowChange"
- />
- <ApprovalFlowProgress v-else :nodes="form.approvalFlowNodes" :current-index="form.currentNodeIndex" />
- <p v-if="!formDialog.readonly" class="flow-tip">鑷冲皯淇濈暀涓�涓妭鐐癸紱瀹℃牳涓�佸凡閫氳繃鐨勫崟鎹笉鍙紪杈戙��</p>
- </el-form-item>
- </el-card>
- </el-form>
- <template #footer>
- <el-button v-if="!formDialog.readonly" type="primary" @click="submitForm">鎻� 浜�</el-button>
- <el-button @click="formDialog.visible = false">{{ formDialog.readonly ? "鍏� 闂�" : "鍙� 娑�" }}</el-button>
- </template>
- </el-dialog>
-
- <!-- 璇︽儏 -->
- <el-dialog v-model="detailDialog.visible" title="宸梾鎶ラ攢璇︽儏" width="900px" append-to-body destroy-on-close>
- <DetailPanel :row="detailRow" />
- <ApprovalFlowProgress
- class="mt16"
- :nodes="detailRow.approvalFlowNodes"
- :current-index="detailRow.currentNodeIndex ?? 0"
- />
- <el-divider content-position="left">瀹℃壒璁板綍锛堝叏娴佺▼鐣欑棔锛�</el-divider>
- <el-timeline v-if="detailRow.approvalRecords?.length">
- <el-timeline-item
- v-for="(rec, i) in detailRow.approvalRecords"
- :key="i"
- :type="rec.result === 'approved' ? 'success' : rec.result === 'rejected' ? 'danger' : 'primary'"
- :timestamp="rec.time"
- >
- {{ rec.operatorName }} 鈥� {{ approvalActionLabel(rec.result) }}锛歿{ rec.opinion || "鏃犳剰瑙�" }}
- </el-timeline-item>
- </el-timeline>
- <el-empty v-else description="鏆傛棤瀹℃壒璁板綍" :image-size="60" />
- <template #footer>
- <el-button type="primary" @click="detailDialog.visible = false">鍏� 闂�</el-button>
- </template>
- </el-dialog>
-
- <!-- 瀹℃壒 -->
- <el-dialog
- v-model="approveDialog.visible"
- title="宸梾鎶ラ攢瀹℃壒"
- width="1000px"
- append-to-body
- destroy-on-close
- @closed="approveOpinion = ''"
- >
- <DetailPanel :row="approveDialog.row" />
- <el-divider content-position="left">娴佺▼杩涘害</el-divider>
- <ApprovalFlowProgress
- :nodes="approveDialog.row?.approvalFlowNodes"
- :current-index="approveDialog.row?.currentNodeIndex ?? 0"
- />
- <el-form label-width="100px" class="mt16">
- <el-form-item label="瀹℃壒鎰忚">
- <el-input
- v-model="approveOpinion"
- type="textarea"
- :rows="3"
- maxlength="500"
- show-word-limit
- placeholder="閫氳繃鍙暀绌猴紱椹冲洖璇峰~鍐欏師鍥�"
- />
- </el-form-item>
- </el-form>
- <template #footer>
- <el-button type="success" @click="submitApprove('approved')">閫� 杩�</el-button>
- <el-button type="danger" @click="submitApprove('rejected')">椹� 鍥�</el-button>
- <el-button @click="approveDialog.visible = false">鍙� 娑�</el-button>
- </template>
- </el-dialog>
+ <ApprovalInstanceDetailDialog
+ v-model="detailDialog.visible"
+ title="宸梾鎶ラ攢璇︽儏"
+ :row="detailRow"
+ @edit="openEditFromDetail"
+ />
</div>
</template>
<script setup>
-import FileUpload from "@/components/AttachmentUpload/file/index.vue";
-import ApprovalFlowEditor from "@/views/officeProcessAutomation/AttendManage/overtime-apply/components/ApprovalFlowEditor.vue";
-import ApprovalFlowProgress from "./components/ApprovalFlowProgress.vue";
-import DetailPanel from "./components/DetailPanel.vue";
-import { useTravelReimburse } from "./useTravelReimburse.js";
+import { Search } from "@element-plus/icons-vue";
+import { ElMessage } from "element-plus";
+import { onMounted, reactive } from "vue";
+import ApprovalInstanceDetailDialog from "../../ApproveManage/approve-shared/components/ApprovalInstanceDetailDialog.vue";
+import ApprovalInstanceSubmitDialog from "../../ApproveManage/approve-shared/components/ApprovalInstanceSubmitDialog.vue";
+import ApprovalTemplateBindDialog from "../../ApproveManage/approve-shared/components/ApprovalTemplateBindDialog.vue";
+import { buildInstanceTableColumns } from "../../ApproveManage/approve-shared/approvalInstanceFormConfigTable.js";
+import { APPROVAL_MODULE_KEYS } from "../../ApproveManage/approve-shared/approvalModuleRegistry.js";
+import { useApprovalInstanceModule } from "../../ApproveManage/approve-shared/useApprovalInstanceModule.js";
+import { useFlowUserOptions } from "../../ApproveManage/approve-shared/useFlowUserOptions.js";
-const tr = useTravelReimburse();
+const searchForm = reactive({ applicantKeyword: "" });
+
+const mod = useApprovalInstanceModule({
+ moduleKey: APPROVAL_MODULE_KEYS.TRAVEL_REIMBURSE,
+ buildExtraListParams(sf) {
+ const extra = {};
+ const kw = (sf?.applicantKeyword || "").trim();
+ if (kw && /[\u4e00-\u9fa5]/.test(kw)) extra.applicantName = kw;
+ return extra;
+ },
+});
+
const {
- Search,
- EXPENSE_SUBJECT_OPTIONS,
- expenseSubjectLabel,
- searchForm,
+ tableData,
tableLoading,
page,
- tableData,
- tableColumn,
- importInputRef,
- formRef,
- form,
- formDialog,
- formRules,
detailDialog,
detailRow,
- approveDialog,
- approveOpinion,
- applicantFormSearchLoading,
- applicantFormOptions,
- flowUserOptions,
- travelDaysDisplay,
- travelTierLabel,
- suggestedLivingSubsidy,
- suggestedTransportSubsidy,
- suggestedHotelLimit,
- detailTotalAmount,
- overBudgetWarnings,
- budgetHint,
- handleQuery,
- resetSearch,
- pagination,
- remoteSearchApplicantForm,
- userSelectLabel,
- onApplicantChange,
- recalcTravelStandards,
- onTravelRangeChange,
- onDetailAmountChange,
- onApprovalFlowChange,
- addExpenseDetail,
- removeExpenseDetail,
- syncApplyAmountFromDetails,
- openFormDialog,
- onFormClosed,
+ submitDialog,
submitForm,
- openDetail,
- approvalActionLabel,
- submitApprove,
- handleExport,
- handleImportClick,
- onImportFile,
-} = tr;
+ submitFormRef,
+ submitSaving,
+ isSubmitEdit,
+ activeTemplate,
+ submitFormFields,
+ submitFormRules,
+ submitDialogTitle,
+ templateBindVisible,
+ handleQuery,
+ initModuleList,
+ pagination,
+ openAddWithTemplate,
+ onTemplateBound,
+ onTemplateBindClosed,
+ openEditFromDetail,
+ submitInstanceForm,
+ buildTableActions,
+} = mod;
+
+const { flowUserOptions, loadFlowUsers } = useFlowUserOptions();
+const tableColumn = buildInstanceTableColumns(tableData, buildTableActions);
+
+function onSearch() {
+ handleQuery(searchForm);
+}
+
+function resetSearch() {
+ searchForm.applicantKeyword = "";
+ onSearch();
+}
+
+function onPagination(obj) {
+ pagination(obj, searchForm);
+}
+
+async function onSubmit() {
+ const ok = await submitInstanceForm({ skipValidate: true });
+ if (ok) ElMessage.success(isSubmitEdit.value ? "淇敼鎴愬姛" : "鎻愪氦鎴愬姛");
+}
+
+onMounted(async () => {
+ loadFlowUsers();
+ await initModuleList(searchForm);
+});
</script>
<style scoped>
.mb20 {
margin-bottom: 20px;
-}
-.mb16 {
- margin-bottom: 16px;
-}
-.mb8 {
- margin-bottom: 8px;
-}
-.mt16 {
- margin-top: 16px;
}
.search_form {
display: flex;
@@ -517,107 +153,8 @@
justify-content: space-between;
gap: 12px;
}
-.search_actions {
- display: flex;
- flex-wrap: wrap;
- gap: 8px;
-}
.search_title {
font-size: 14px;
color: var(--el-text-color-regular);
-}
-.sr-only-input {
- position: absolute;
- width: 1px;
- height: 1px;
- padding: 0;
- margin: -1px;
- overflow: hidden;
- clip: rect(0, 0, 0, 0);
- white-space: nowrap;
- border: 0;
-}
-.form-section {
- margin-bottom: 16px;
- border: 1px solid var(--el-border-color-lighter);
-}
-.form-section :deep(.el-card__header) {
- padding: 12px 16px;
- background: var(--el-fill-color-lighter);
-}
-.form-section :deep(.el-card__body) {
- padding: 16px 16px 4px;
-}
-.card-header-title {
- font-size: 15px;
- font-weight: 600;
-}
-.card-header-row {
- display: flex;
- align-items: center;
- justify-content: space-between;
- gap: 12px;
-}
-.amount-row {
- display: flex;
- align-items: center;
- gap: 12px;
- width: 100%;
-}
-.amount-input {
- flex: 1;
- min-width: 160px;
-}
-.w-full {
- width: 100%;
-}
-.attachment-form-item {
- margin-bottom: 0;
-}
-.detail-table {
- margin-bottom: 0;
-}
-.section-title {
- font-size: 15px;
- font-weight: 600;
- margin: 8px 0 12px;
- color: var(--el-text-color-primary);
- border-left: 3px solid var(--el-color-primary);
- padding-left: 8px;
-}
-.field-tip {
- font-size: 12px;
- color: var(--el-text-color-secondary);
- margin-top: 4px;
-}
-.warn-list {
- margin: 0;
- padding-left: 18px;
-}
-.detail-toolbar {
- margin-bottom: 8px;
-}
-.upload-block {
- width: 100%;
-}
-.flow-tip {
- font-size: 12px;
- color: var(--el-text-color-secondary);
- margin-top: 8px;
-}
-.sync-btn {
- margin-top: 4px;
-}
-.travel-reimburse-form-dialog :deep(.el-dialog__body) {
- padding-top: 12px;
-}
-.travel-reimburse-form :deep(.el-form-item) {
- margin-bottom: 18px;
-}
-.travel-reimburse-form :deep(.el-input-number) {
- width: 100%;
-}
-.travel-reimburse-form :deep(.el-row) {
- margin-bottom: 0;
}
</style>
--
Gitblit v1.9.3