From 1fba2685678695cca45127adaada26c7b96eec12 Mon Sep 17 00:00:00 2001
From: yyb <995253665@qq.com>
Date: 星期六, 16 五月 2026 14:46:00 +0800
Subject: [PATCH] : 重构费用报销模块界面和功能

---
 src/views/officeProcessAutomation/ReimburseManage/cost-reimburse/index.vue |  558 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 551 insertions(+), 7 deletions(-)

diff --git a/src/views/officeProcessAutomation/ReimburseManage/cost-reimburse/index.vue b/src/views/officeProcessAutomation/ReimburseManage/cost-reimburse/index.vue
index 2ee0a60..b384569 100644
--- a/src/views/officeProcessAutomation/ReimburseManage/cost-reimburse/index.vue
+++ b/src/views/officeProcessAutomation/ReimburseManage/cost-reimburse/index.vue
@@ -1,12 +1,556 @@
-<!--
-  妯″潡涓枃鍚嶏細璐圭敤鎶ラ攢
-  鐩綍鏍囪瘑锛歊eimburseManage/cost-reimburse锛坈ost-reimburse 鈫� 涓枃锛氳垂鐢ㄦ姤閿�锛�
-  澶嶇敤椤甸潰锛欯/views/procurementManagement/procurementLedger/index.vue锛堥噰璐彴璐︼紱鏂囦欢鍚� index.vue 鈫� 鍏ュ彛椤碉級
--->
+<!--OA妯″潡锛氳垂鐢ㄦ姤閿�-->
 <template>
-  <ProcurementLedger />
+  <div class="app-container">
+    <div class="search_form mb20">
+      <div class="search_fields">
+        <span class="search_title">鐢宠浜猴細</span>
+        <el-input
+          v-model="searchForm.applicantKeyword"
+          style="width: 220px"
+          placeholder="濮撳悕鎴栫紪鍙�"
+          clearable
+          :prefix-icon="Search"
+          @keyup.enter="handleQuery"
+        />
+        <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 @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>
+    </div>
+
+    <input ref="importInputRef" type="file" accept="application/json,.json" class="sr-only-input" @change="onImportFile" />
+
+    <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="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>
+
+      <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>
+
+      <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>
+  </div>
 </template>
 
 <script setup>
-import ProcurementLedger from '@/views/procurementManagement/procurementLedger/index.vue'
+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";
+
+const cr = useCostReimburse();
+const {
+  Search,
+  EXPENSE_CATEGORY_OPTIONS,
+  CATEGORY_TEMPLATES,
+  EXPENSE_SUBJECT_OPTIONS,
+  expenseSubjectLabel,
+  searchForm,
+  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,
+  submitForm,
+  approvalActionLabel,
+  submitApprove,
+  handleExport,
+  handleImportClick,
+  onImportFile,
+} = cr;
 </script>
+
+<style scoped>
+.mb20 {
+  margin-bottom: 20px;
+}
+.mb16 {
+  margin-bottom: 16px;
+}
+.mb12 {
+  margin-bottom: 12px;
+}
+.mt16 {
+  margin-top: 16px;
+}
+.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;
+  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>

--
Gitblit v1.9.3