From df5efb2ca2b0cf74d9160ffe2b6c215c4ddc9c99 Mon Sep 17 00:00:00 2001
From: yyb <995253665@qq.com>
Date: 星期四, 21 五月 2026 17:48:17 +0800
Subject: [PATCH] 差旅报销费用报销
---
src/views/officeProcessAutomation/ApproveManage/approve-list/index.vue | 253 ++++++++++++++++++++++++++++++++++++--------------
1 files changed, 182 insertions(+), 71 deletions(-)
diff --git a/src/views/officeProcessAutomation/ApproveManage/approve-list/index.vue b/src/views/officeProcessAutomation/ApproveManage/approve-list/index.vue
index cdae763..bbfa56a 100644
--- a/src/views/officeProcessAutomation/ApproveManage/approve-list/index.vue
+++ b/src/views/officeProcessAutomation/ApproveManage/approve-list/index.vue
@@ -3,30 +3,35 @@
<div class="app-container">
<div class="search_form mb20">
<div class="search_fields">
- <span class="search_title">瀹℃壒绫诲瀷锛�</span>
+ <span class="search_title">妯℃澘绫诲瀷锛�</span>
<el-select
- v-model="searchForm.approvalType"
- placeholder="璇烽�夋嫨瀹℃壒绫诲瀷"
+ v-model="searchForm.businessType"
+ placeholder="璇烽�夋嫨妯℃澘绫诲瀷"
clearable
filterable
style="width: 200px"
>
<el-option
- v-for="opt in APPROVAL_TYPE_OPTIONS"
+ v-for="opt in searchBusinessTypeOptions"
+ :key="`search-biz-type-${opt.value}`"
+ :label="opt.label"
+ :value="opt.value"
+ />
+ </el-select>
+ <span class="search_title" style="margin-left: 12px">瀹℃壒鐘舵�侊細</span>
+ <el-select
+ v-model="searchForm.status"
+ placeholder="璇烽�夋嫨瀹℃壒鐘舵��"
+ clearable
+ style="width: 140px"
+ >
+ <el-option
+ v-for="opt in APPROVAL_STATUS_SEARCH_OPTIONS"
:key="opt.value"
:label="opt.label"
:value="opt.value"
/>
</el-select>
- <span class="search_title" style="margin-left: 12px">鐢宠浜哄悕绉帮細</span>
- <el-input
- v-model="searchForm.applicantKeyword"
- style="width: 200px"
- placeholder="璇疯緭鍏ョ敵璇蜂汉鍚嶇О"
- clearable
- :prefix-icon="Search"
- @keyup.enter="handleQuery"
- />
<span class="search_title" style="margin-left: 12px">鍒涘缓鏃堕棿锛�</span>
<el-date-picker
v-model="searchForm.createTimeRange"
@@ -77,67 +82,79 @@
@closed="resetSubmitDialogState"
>
<template v-if="submitDialog.step === 1 && !isSubmitEdit">
- <p class="template-hint">璇烽�夋嫨宸插惎鐢ㄧ殑瀹℃壒妯℃澘锛岀郴缁熷皢鎸夋ā鏉块厤缃紩瀵煎~鎶ャ��</p>
+ <p class="template-hint">璇峰厛閫夋嫨妯℃澘绫诲瀷锛屽啀閫夋嫨璇ョ被鍨嬩笅宸插惎鐢ㄧ殑瀹℃壒妯℃澘銆�</p>
<div v-loading="submitTemplatesLoading" class="template-grid">
<div
- v-for="card in submitTemplateCards"
- :key="card.key"
+ v-for="opt in submitBusinessTypeOptions"
+ :key="`biz-type-${opt.value}`"
class="template-card"
- @click="onTemplatePick(card)"
+ :class="{ 'is-disabled': !countTemplatesByBusinessType(opt.value) }"
+ @click="onBusinessTypePick(opt.value)"
>
- <span class="template-card-type" :style="approvalTypeStyle(card.approvalType)">
- {{ card.label }}
+ <span class="template-card-type">{{ opt.label }}</span>
+ <span class="template-card-desc">
+ {{ countTemplatesByBusinessType(opt.value) }} 涓彲鐢ㄦā鏉�
</span>
- <span class="template-card-desc">{{ card.summaryPlaceholder }}</span>
</div>
<el-empty
- v-if="!submitTemplatesLoading && !submitTemplateCards.length"
- description="鏆傛棤鍙敤瀹℃壒妯℃澘"
+ v-if="!submitTemplatesLoading && !submitBusinessTypeOptions.length"
+ description="鏆傛棤妯℃澘绫诲瀷"
:image-size="80"
class="template-empty"
/>
</div>
</template>
+ <template v-else-if="submitDialog.step === 2 && !isSubmitEdit">
+ <p class="template-hint">
+ 褰撳墠绫诲瀷锛歿{ selectedBusinessTypeLabel || "鈥�" }}锛岃閫夋嫨鍏蜂綋瀹℃壒妯℃澘銆�
+ <el-button type="primary" link class="ml8" @click="backToBusinessTypePick">鏇存崲绫诲瀷</el-button>
+ </p>
+ <ApprovalTemplatePicker
+ :cards="submitTemplateCards"
+ :loading="submitTemplatesLoading"
+ @pick="onTemplatePick"
+ />
+ </template>
+
<template v-else>
<div v-loading="submitTemplatesLoading && !isSubmitEdit">
<el-form ref="submitFormRef" :model="submitForm" :rules="submitFormRules" label-width="120px">
- <el-form-item label="瀹℃壒绫诲瀷">
+ <el-form-item v-if="isSubmitEdit" label="瀹℃壒绫诲瀷">
<span class="approve-type-cell" :style="approvalTypeStyle(activeTemplate.approvalType)">
{{ activeTemplate.label }}
</span>
- <el-button
- v-if="!isSubmitEdit"
- type="primary"
- link
- class="ml12"
- @click="backToTemplatePick"
- >
- 鏇存崲妯℃澘
- </el-button>
</el-form-item>
- <FormPayloadFields
+ <ApprovalTemplateFormSection
+ :active-template="activeTemplate"
:fields="submitFormFields"
:form-payload="submitForm.formPayload"
+ v-model:flow-nodes="submitForm.flowNodes"
+ v-model:attachments="submitForm.storageBlobDTOs"
+ :template-attachments="submitForm.templateAttachments"
+ :user-options="flowUserOptions"
+ :show-template-name="!isSubmitEdit"
+ :allow-change-template="!isSubmitEdit"
+ @change-template="backToTemplatePick"
/>
- <el-form-item label="瀹℃壒娴佺▼" required>
- <TemplateFlowEditor v-model="submitForm.flowNodes" :user-options="flowUserOptions" />
- <p class="flow-tip">
- 鎸夐『搴忔祦杞細鍙负姣忎釜鑺傜偣娣诲姞澶氬悕瀹℃壒浜猴紱浼氱闇�鍏ㄩ儴閫氳繃锛屾垨绛句换涓�浜洪�氳繃鍗冲彲杩涘叆涓嬩竴鑺傜偣銆�
- </p>
- </el-form-item>
</el-form>
</div>
</template>
<template #footer>
<el-button
- v-if="submitDialog.step === 2 || isSubmitEdit"
+ v-if="submitDialog.step === 3 || isSubmitEdit"
type="primary"
:loading="submitSaving"
@click="onSubmitInstance"
>
{{ isSubmitEdit ? "淇� 瀛�" : "鎻� 浜�" }}
+ </el-button>
+ <el-button
+ v-if="submitDialog.step === 2 && !isSubmitEdit"
+ @click="backToBusinessTypePick"
+ >
+ 涓婁竴姝�
</el-button>
<el-button @click="submitDialog.visible = false">
{{ submitDialog.step === 1 && !isSubmitEdit ? "鍙� 娑�" : "鍏� 闂�" }}
@@ -206,6 +223,64 @@
</template>
</el-dialog>
+ <!-- 宸梾/璐圭敤鎶ラ攢璇︽儏锛堝鎵瑰垪琛級 -->
+ <el-dialog
+ v-model="reimburseDialog.visible"
+ :title="reimburseDialog.mode === 'approve' ? reimburseApproveTitle : reimburseDetailTitle"
+ width="1000px"
+ append-to-body
+ destroy-on-close
+ @closed="approveOpinion = ''"
+ >
+ <FinReimburseApprovePanel
+ :mode="reimburseDialog.mode"
+ :module-key="reimburseDialog.moduleKey"
+ :reimburse-row="reimburseDialog.reimburseRow"
+ :loading="reimburseDialog.loading"
+ v-model:approve-opinion="approveOpinion"
+ />
+ <template #footer>
+ <template v-if="reimburseDialog.mode === 'approve'">
+ <el-button
+ type="success"
+ :loading="approveSubmitting"
+ @click="onReimburseApprove('approved')"
+ >
+ 閫� 杩�
+ </el-button>
+ <el-button
+ type="danger"
+ :loading="approveSubmitting"
+ @click="onReimburseApprove('rejected')"
+ >
+ 椹� 鍥�
+ </el-button>
+ <el-button :disabled="approveSubmitting" @click="reimburseDialog.visible = false">
+ 鍙� 娑�
+ </el-button>
+ </template>
+ <template v-else>
+ <el-button
+ v-if="reimburseDialog.instanceRow?.approvalStatus === 'pending'"
+ @click="openEditFromReimburseDetail"
+ >
+ 淇� 鏀�
+ </el-button>
+ <el-button
+ v-if="
+ reimburseDialog.instanceRow?.approvalStatus === 'pending' &&
+ reimburseDialog.instanceRow?.isApprove
+ "
+ type="primary"
+ @click="openReimburseApproveFromDetail"
+ >
+ 鍘诲鎵�
+ </el-button>
+ <el-button type="primary" @click="reimburseDialog.visible = false">鍏� 闂�</el-button>
+ </template>
+ </template>
+ </el-dialog>
+
<!-- 瀹℃壒鎿嶄綔 -->
<el-dialog
v-model="approveDialog.visible"
@@ -260,10 +335,12 @@
<script setup>
import { Plus, RefreshRight } from "@element-plus/icons-vue";
import { ElMessage } from "element-plus";
-import { onMounted, ref } from "vue";
-import { userListNoPageByTenantId } from "@/api/system/user.js";
-import TemplateFlowEditor from "../approve-template/components/TemplateFlowEditor.vue";
-import FormPayloadFields from "./components/FormPayloadFields.vue";
+import { computed, onMounted, ref } from "vue";
+import { APPROVAL_MODULE_KEYS } from "../approve-shared/approvalModuleRegistry.js";
+import FinReimburseApprovePanel from "../../ReimburseManage/shared/components/FinReimburseApprovePanel.vue";
+import ApprovalTemplateFormSection from "../approve-shared/components/ApprovalTemplateFormSection.vue";
+import ApprovalTemplatePicker from "../approve-shared/components/ApprovalTemplatePicker.vue";
+import { useFlowUserOptions } from "../approve-shared/useFlowUserOptions.js";
import { formatDisplayTime } from "../approve-template/approveTemplateConstants.js";
import { approvalTypeStyle } from "./approveListConstants.js";
import ApproveDetailPanel from "./components/ApproveDetailPanel.vue";
@@ -273,9 +350,16 @@
const al = useApproveList();
const {
Search,
- APPROVAL_TYPE_OPTIONS,
+ APPROVAL_STATUS_SEARCH_OPTIONS,
+ searchBusinessTypeOptions,
+ loadSearchBusinessTypeOptions,
+ submitBusinessTypeOptions,
submitTemplateCards,
+ selectedBusinessTypeLabel,
+ countTemplatesByBusinessType,
submitTemplatesLoading,
+ onBusinessTypePick,
+ backToBusinessTypePick,
approvalTypeLabel,
approvalActionLabel,
searchForm,
@@ -285,9 +369,11 @@
tableColumn,
detailDialog,
detailRow,
+ reimburseDialog,
approveDialog,
approveOpinion,
approveSubmitting,
+ submitReimburseApprove,
submitDialog,
isSubmitEdit,
submitDialogTitle,
@@ -311,37 +397,37 @@
openApprove,
} = al;
-const flowUserOptions = ref([]);
-
-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 = [];
- }
-}
+const { flowUserOptions, loadFlowUsers } = useFlowUserOptions();
async function onSubmitInstance() {
const ok = await submitInstanceForm();
if (ok) ElMessage.success(isSubmitEdit.value ? "淇敼鎴愬姛" : "瀹℃壒宸叉彁浜�");
}
+const reimburseDetailTitle = computed(() =>
+ reimburseDialog.moduleKey === APPROVAL_MODULE_KEYS.COST_REIMBURSE
+ ? "璐圭敤鎶ラ攢璇︽儏"
+ : "宸梾鎶ラ攢璇︽儏"
+);
+const reimburseApproveTitle = computed(() =>
+ reimburseDialog.moduleKey === APPROVAL_MODULE_KEYS.COST_REIMBURSE
+ ? "璐圭敤鎶ラ攢瀹℃壒"
+ : "宸梾鎶ラ攢瀹℃壒"
+);
+
async function onApprove(result) {
const ret = await submitApprove(result);
+ if (ret?.needOpinion) {
+ ElMessage.warning("椹冲洖鏃惰濉啓瀹℃壒鎰忚");
+ return;
+ }
+ if (ret?.ok) {
+ ElMessage.success(result === "approved" ? "宸查�氳繃" : "宸查┏鍥�");
+ }
+}
+
+async function onReimburseApprove(result) {
+ const ret = await submitReimburseApprove(result);
if (ret?.needOpinion) {
ElMessage.warning("椹冲洖鏃惰濉啓瀹℃壒鎰忚");
return;
@@ -355,10 +441,10 @@
return formatDisplayTime(time) || "鈥�";
}
-function openApproveFromDetail() {
+async function openApproveFromDetail() {
const row = detailRow.value;
detailDialog.visible = false;
- openApprove(row);
+ await openApprove(row);
}
function openEditFromDetail() {
@@ -367,8 +453,22 @@
openEditDialog(row);
}
+function openEditFromReimburseDetail() {
+ const row = reimburseDialog.instanceRow;
+ reimburseDialog.visible = false;
+ if (row) openEditDialog(row);
+}
+
+async function openReimburseApproveFromDetail() {
+ const row = reimburseDialog.instanceRow;
+ if (!row) return;
+ reimburseDialog.mode = "approve";
+ approveOpinion.value = "";
+}
+
onMounted(() => {
- loadUsers();
+ loadFlowUsers();
+ loadSearchBusinessTypeOptions();
handleQuery();
});
</script>
@@ -434,6 +534,17 @@
border-color: var(--el-color-primary);
box-shadow: var(--shadow-sm, 0 2px 8px rgba(0, 0, 0, 0.06));
}
+.template-card.is-disabled {
+ opacity: 0.5;
+ cursor: not-allowed;
+}
+.template-card.is-disabled:hover {
+ border-color: var(--el-border-color-lighter);
+ box-shadow: none;
+}
+.ml8 {
+ margin-left: 8px;
+}
.template-card-type {
display: inline-block;
padding: 2px 8px;
--
Gitblit v1.9.3