From 43d00eec5989caa78c44c736853230a13d8e9f6c Mon Sep 17 00:00:00 2001
From: liding <756868258@qq.com>
Date: 星期四, 08 十月 2026 14:51:53 +0800
Subject: [PATCH] 财务管理系统模块优化

---
 src/views/inventoryManagement/index.vue                                           |    1 
 src/views/financialManagement/fundsManagement/FinanceWorkspace.vue                |  651 +++++++++++++++++++++++
 src/api/financialManagement/expenseManagement.js                                  |    8 
 src/views/financialManagement/financialReconciliation/index.vue                   |  311 +---------
 src/views/financialManagement/expenseManagement/Form.vue                          |    9 
 src/views/financialManagement/expenseManagement/index.vue                         |   16 
 src/views/financialManagement/financialReconciliation/ReconciliationWorkspace.vue |  484 +++++++++++++++++
 src/api/financialManagement/fundsManagement.js                                    |   17 
 src/views/financialManagement/fundsManagement/index.vue                           |    9 
 src/api/financialManagement/financialReconciliation.js                            |    8 
 reconciliation-repro.html                                                         |   18 
 src/views/financialManagement/expenseManagement/ExpenseCategoryReport.vue         |   72 ++
 src/views/financialManagement/financialStatements/index.vue                       |   42 +
 13 files changed, 1,354 insertions(+), 292 deletions(-)

diff --git a/reconciliation-repro.html b/reconciliation-repro.html
new file mode 100644
index 0000000..891248c
--- /dev/null
+++ b/reconciliation-repro.html
@@ -0,0 +1,18 @@
+<!doctype html>
+<html lang="zh-CN">
+<head>
+    <meta charset="UTF-8">
+    <title>reconciliation repro</title></head>
+<body>
+<div id="app"></div>
+<script type="module">
+    import {createApp} from '/node_modules/.vite/deps/vue.js';
+    import ElementPlus from '/node_modules/.vite/deps/element-plus.js';
+    import '/node_modules/element-plus/dist/index.css';
+    import ReconciliationWorkspace
+        from '/src/views/financialManagement/financialReconciliation/ReconciliationWorkspace.vue';
+
+    createApp(ReconciliationWorkspace).use(ElementPlus).mount('#app');
+</script>
+</body>
+</html>
diff --git a/src/api/financialManagement/expenseManagement.js b/src/api/financialManagement/expenseManagement.js
index 317dc47..f5b6257 100644
--- a/src/api/financialManagement/expenseManagement.js
+++ b/src/api/financialManagement/expenseManagement.js
@@ -76,3 +76,11 @@
     data: query,
   });
 }
+
+export function getExpenseCategoryReport(params) {
+  return request({
+    url: "/account/accountExpense/report/category",
+    method: "get",
+    params,
+  });
+}
diff --git a/src/api/financialManagement/financialReconciliation.js b/src/api/financialManagement/financialReconciliation.js
index d2c08d1..6241b12 100644
--- a/src/api/financialManagement/financialReconciliation.js
+++ b/src/api/financialManagement/financialReconciliation.js
@@ -35,3 +35,11 @@
     data: ids,
   });
 };
+
+export const getReconciliationTaskPage = (params) => request({ url: "/finance/reconciliation/task/page", method: "get", params });
+export const createReconciliationTask = (data) => request({ url: "/finance/reconciliation/task", method: "post", data });
+export const getReconciliationDiff = (id) => request({ url: `/finance/reconciliation/task/${id}/diff`, method: "get" });
+export const getReconciliationExternal = (id) => request({ url: `/finance/reconciliation/task/${id}/external`, method: "get" });
+export const handleReconciliationTask = (id, data) => request({ url: `/finance/reconciliation/task/${id}/handle`, method: "put", data });
+export const archiveReconciliationTask = (id) => request({ url: `/finance/reconciliation/task/${id}/archive`, method: "put" });
+export const exportReconciliationDiff = (taskId) => request({ url: "/finance/reconciliation/export", method: "post", data: { taskId }, responseType: "blob" });
diff --git a/src/api/financialManagement/fundsManagement.js b/src/api/financialManagement/fundsManagement.js
index a970638..0cf7ae5 100644
--- a/src/api/financialManagement/fundsManagement.js
+++ b/src/api/financialManagement/fundsManagement.js
@@ -12,12 +12,27 @@
 // 鏂板璧勯噾璁板綍
 export const addFunds = (data) => {
   return request({
-    url: "/finance/funds",
+    url: "/finance/funds/add",
     method: "post",
     data,
   });
 };
 
+export const transferFunds = (data) => request({ url: "/finance/funds/transfer", method: "post", data });
+
+export const getFinanceAccountPage = (params) => request({ url: "/finance/account/page", method: "get", params });
+export const getFinanceAccountList = () => request({ url: "/finance/account/list", method: "get" });
+export const addFinanceAccount = (data) => request({ url: "/finance/account", method: "post", data });
+export const updateFinanceAccount = (data) => request({ url: "/finance/account", method: "put", data });
+export const deleteFinanceAccount = (ids) => request({ url: "/finance/account", method: "delete", data: ids });
+export const getFundsTrend = (params) => request({ url: "/finance/account/trend", method: "get", params });
+
+export const getCashflowPlanPage = (params) => request({ url: "/finance/cashflowPlan/page", method: "get", params });
+export const addCashflowPlan = (data) => request({ url: "/finance/cashflowPlan", method: "post", data });
+export const updateCashflowPlan = (data) => request({ url: "/finance/cashflowPlan", method: "put", data });
+export const deleteCashflowPlan = (ids) => request({ url: "/finance/cashflowPlan", method: "delete", data: ids });
+export const getCashflowForecast = (params) => request({ url: "/finance/cashflowPlan/forecast", method: "get", params });
+
 // 鏇存柊璧勯噾璁板綍
 export const updateFunds = (data) => {
   return request({
diff --git a/src/views/financialManagement/expenseManagement/ExpenseCategoryReport.vue b/src/views/financialManagement/expenseManagement/ExpenseCategoryReport.vue
new file mode 100644
index 0000000..ac7a8e9
--- /dev/null
+++ b/src/views/financialManagement/expenseManagement/ExpenseCategoryReport.vue
@@ -0,0 +1,72 @@
+<template>
+  <el-collapse class="report-panel">
+    <el-collapse-item title="鏀嚭澶х被鍗犳瘮涓庤秼鍔�" name="category">
+      <div class="charts">
+        <div ref="pieRef"></div>
+        <div ref="trendRef"></div>
+      </div>
+    </el-collapse-item>
+  </el-collapse>
+</template>
+
+<script setup>
+import { ref, onMounted, nextTick } from "vue";
+import * as echarts from "echarts";
+import { getExpenseCategoryReport } from "@/api/financialManagement/expenseManagement";
+const pieRef = ref(),
+  trendRef = ref();
+onMounted(async () => {
+  const r = await getExpenseCategoryReport({});
+  const share = r.data?.share || [],
+    trend = r.data?.trend || [];
+  await nextTick();
+  echarts.init(pieRef.value).setOption({
+    title: { text: "鏀嚭澶х被鍗犳瘮", left: "center" },
+    tooltip: { trigger: "item" },
+    series: [
+      {
+        type: "pie",
+        radius: ["35%", "68%"],
+        data: share.map((x) => ({ name: x.categoryName, value: x.amount })),
+      },
+    ],
+  });
+  const months = [...new Set(trend.map((x) => x.month))],
+    names = [...new Set(trend.map((x) => x.categoryName))];
+  echarts.init(trendRef.value).setOption({
+    title: { text: "鏀嚭澶х被鏈堝害瓒嬪娍", left: "center" },
+    tooltip: { trigger: "axis" },
+    legend: { top: 28 },
+    xAxis: { type: "category", data: months },
+    yAxis: { type: "value" },
+    series: names.map((name) => ({
+      name,
+      type: "line",
+      data: months.map(
+        (month) =>
+          trend.find((x) => x.month === month && x.categoryName === name)
+            ?.amount || 0
+      ),
+    })),
+  });
+});
+</script>
+
+<style scoped>
+.report-panel {
+  margin-bottom: 12px;
+}
+.charts {
+  display: grid;
+  grid-template-columns: 1fr 2fr;
+  gap: 12px;
+}
+.charts > div {
+  height: 300px;
+}
+@media (max-width: 900px) {
+  .charts {
+    grid-template-columns: 1fr;
+  }
+}
+</style>
diff --git a/src/views/financialManagement/expenseManagement/Form.vue b/src/views/financialManagement/expenseManagement/Form.vue
index 9cfe5da..933d53f 100644
--- a/src/views/financialManagement/expenseManagement/Form.vue
+++ b/src/views/financialManagement/expenseManagement/Form.vue
@@ -11,6 +11,11 @@
             clearable
           />
         </el-form-item>
+        <el-form-item label="鏀嚭澶х被" prop="expenseCategory">
+          <el-select v-model="form.expenseCategory" placeholder="璇烽�夋嫨鏀嚭澶х被" clearable>
+            <el-option v-for="item in finance_expense_category" :key="item.value" :label="item.label" :value="item.value" />
+          </el-select>
+        </el-form-item>
         <el-form-item label="鏀嚭绫诲瀷" prop="expenseType">
           <el-select
             v-model="form.expenseType"
@@ -65,6 +70,7 @@
   name: "鏂板鏀嚭",
 });
 const { expense_types } = proxy.useDict("expense_types");
+const { finance_expense_category } = proxy.useDict("finance_expense_category");
 const { checkout_payment } = proxy.useDict("checkout_payment");
 const formRef = ref(null);
 const formRules = {
@@ -73,12 +79,14 @@
 	expenseDescribed: [{ required: true, trigger: "blur", message: "璇疯緭鍏�" }],
 	expenseDate: [{ required: true, trigger: "change", message: "璇烽�夋嫨" }],
   expenseType: [{ required: true, trigger: "change", message: "璇烽�夋嫨" }],
+  expenseCategory: [{ required: true, trigger: "change", message: "璇烽�夋嫨鏀嚭澶х被" }],
   expenseMethod: [{ required: true, trigger: "change", message: "璇烽�夋嫨" }],
 }
 
 const { form, resetForm } = useFormData({
   expenseDate: undefined, // 鏀嚭鏃ユ湡
   expenseType: undefined, // 鏀嚭绫诲瀷
+  expenseCategory: undefined, // 鏀嚭澶х被
   supplierName: undefined, // 瀹㈡埛鍚嶇О
   expenseMoney: undefined, // 鏀嚭閲戦
   expenseDescribed: undefined, // 鏀嚭鎻忚堪
@@ -92,6 +100,7 @@
   if (code == 200) {
     form.expenseDate = data.expenseDate;
     form.expenseType = data.expenseType;
+    form.expenseCategory = data.expenseCategory;
     form.supplierName = data.supplierName;
     form.expenseMoney = data.expenseMoney;
     form.expenseDescribed = data.expenseDescribed;
diff --git a/src/views/financialManagement/expenseManagement/index.vue b/src/views/financialManagement/expenseManagement/index.vue
index a45c32d..a79e321 100644
--- a/src/views/financialManagement/expenseManagement/index.vue
+++ b/src/views/financialManagement/expenseManagement/index.vue
@@ -1,9 +1,15 @@
 <template>
   <div class="app-container">
+    <ExpenseCategoryReport />
     <el-form :model="filters" :inline="true">
       <el-form-item label="鏀嚭鏃ユ湡:">
         <el-date-picker v-model="filters.entryDate" value-format="YYYY-MM-DD" format="YYYY-MM-DD" type="daterange"
                         placeholder="璇烽�夋嫨" clearable @change="changeDaterange" />
+      </el-form-item>
+      <el-form-item label="鏀嚭澶х被:">
+        <el-select v-model="filters.expenseCategory" placeholder="璇烽�夋嫨" clearable style="width: 160px">
+          <el-option v-for="item in finance_expense_category" :key="item.value" :label="item.label" :value="item.value" />
+        </el-select>
       </el-form-item>
       <el-form-item label="浠樻鏂瑰紡:">
         <el-select
@@ -81,6 +87,7 @@
 import { ElMessageBox, ElMessage } from "element-plus";
 import dayjs from "dayjs";
 import FilesDia from "../revenueManagement/filesDia.vue";
+import ExpenseCategoryReport from "./ExpenseCategoryReport.vue";
 
 defineOptions({
   name: "鏀嚭绠$悊",
@@ -92,6 +99,7 @@
 const modalRef = ref();
 const { checkout_payment } = proxy.useDict("checkout_payment");
 const { expense_types } = proxy.useDict("expense_types");
+const { finance_expense_category } = proxy.useDict("finance_expense_category");
 const filesDia = ref()
 
 const {
@@ -106,6 +114,7 @@
   listPage,
   {
     expenseMethod: undefined,
+    expenseCategory: undefined,
     entryDate: undefined,
   },
   [
@@ -115,6 +124,13 @@
       prop: "expenseDate",
     },
     {
+      label: "鏀嚭澶х被",
+      align: "center",
+      prop: "expenseCategory",
+      dataType: "tag",
+      formatData: (params) => finance_expense_category.value.find((m) => m.value == params)?.label || "鏈垎绫�",
+    },
+    {
       label: "鏀嚭绫诲瀷",
       align: "center",
       prop: "expenseType",
diff --git a/src/views/financialManagement/financialReconciliation/ReconciliationWorkspace.vue b/src/views/financialManagement/financialReconciliation/ReconciliationWorkspace.vue
new file mode 100644
index 0000000..fd23f2c
--- /dev/null
+++ b/src/views/financialManagement/financialReconciliation/ReconciliationWorkspace.vue
@@ -0,0 +1,484 @@
+<template>
+  <div class="reconciliation-workspace">
+    <div class="workspace-heading">
+      <h2>璐㈠姟瀵硅处</h2>
+      <p>姣斿鏀舵敮銆佽祫閲戞祦姘翠笌澶栭儴鍑瘉锛岃褰曞樊寮傚鐞嗘柟妗堝苟褰掓。銆�</p>
+    </div>
+    <el-form inline :model="query">
+      <el-form-item label="瀵硅处绫诲瀷"
+        ><el-select v-model="query.reconType" clearable style="width: 150px"
+          ><el-option
+            v-for="(label, key) in reconTypeText"
+            :key="key"
+            :label="label"
+            :value="key" /></el-select
+      ></el-form-item>
+      <el-form-item label="鐘舵��"
+        ><el-select v-model="query.status" clearable style="width: 130px"
+          ><el-option
+            v-for="(label, key) in statusText"
+            :key="key"
+            :label="label"
+            :value="key" /></el-select
+      ></el-form-item>
+      <el-form-item label="瀵硅薄"
+        ><el-input v-model="query.target" clearable
+      /></el-form-item>
+      <el-form-item
+        ><el-button type="primary" @click="loadTasks">鏌ヨ</el-button
+        ><el-button @click="openCreate">鍙戣捣瀵硅处</el-button></el-form-item
+      >
+    </el-form>
+
+    <el-alert
+      v-if="loadError"
+      :title="loadError"
+      type="error"
+      show-icon
+      :closable="false"
+      class="load-error"
+    />
+    <el-table
+      :data="tasks"
+      border
+      v-loading="loading"
+      empty-text="鏆傛棤瀵硅处浠诲姟锛屽彲鐐瑰嚮鈥滃彂璧峰璐︹�濆垱寤�"
+    >
+      <el-table-column prop="id" label="浠诲姟鍙�" width="80" />
+      <el-table-column label="绫诲瀷"
+        ><template #default="{ row }">{{
+          reconTypeText[row.reconType]
+        }}</template></el-table-column
+      >
+      <el-table-column label="鏈熼棿" min-width="190"
+        ><template #default="{ row }"
+          >{{ row.periodStart }} 鑷� {{ row.periodEnd }}</template
+        ></el-table-column
+      >
+      <el-table-column prop="target" label="瀵硅处瀵硅薄" />
+      <el-table-column prop="systemAmount" label="绯荤粺閲戦" />
+      <el-table-column prop="externalAmount" label="瀵规柟閲戦" />
+      <el-table-column prop="diffAmount" label="宸" />
+      <el-table-column label="缁撴灉"
+        ><template #default="{ row }"
+          ><el-tag :type="row.result === '0' ? 'success' : 'danger'">{{
+            row.result === "0" ? "涓�鑷�" : "鏈夊樊寮�"
+          }}</el-tag></template
+        ></el-table-column
+      >
+      <el-table-column label="鐘舵��"
+        ><template #default="{ row }">{{
+          statusText[row.status]
+        }}</template></el-table-column
+      >
+      <el-table-column label="鎿嶄綔" width="260" fixed="right"
+        ><template #default="{ row }">
+          <el-button link type="primary" @click="showDiff(row)"
+            >宸紓鏄庣粏</el-button
+          >
+          <el-button
+            link
+            type="primary"
+            :disabled="row.status === '2'"
+            @click="openHandle(row)"
+            >澶勭悊</el-button
+          >
+          <el-button
+            link
+            type="success"
+            :disabled="row.status === '2'"
+            @click="archive(row)"
+            >褰掓。</el-button
+          >
+          <el-button link @click="exportDiff(row)">瀵煎嚭</el-button>
+        </template></el-table-column
+      >
+    </el-table>
+    <pagination
+      v-show="total > 0"
+      :total="total"
+      :page="query.current"
+      :limit="query.size"
+      @pagination="
+        (p) => {
+          query.current = p.page;
+          query.size = p.limit;
+          loadTasks();
+        }
+      "
+    />
+
+    <el-dialog v-model="createVisible" title="鍙戣捣瀵硅处" width="760px">
+      <el-form :model="createForm" label-width="100px">
+        <el-form-item label="瀵硅处绫诲瀷"
+          ><el-select v-model="createForm.reconType"
+            ><el-option
+              v-for="(label, key) in reconTypeText"
+              :key="key"
+              :label="label"
+              :value="key" /></el-select
+        ></el-form-item>
+        <el-form-item label="瀵硅处鏈熼棿"
+          ><el-date-picker
+            v-model="period"
+            type="daterange"
+            value-format="YYYY-MM-DD"
+            start-placeholder="寮�濮嬫棩鏈�"
+            end-placeholder="缁撴潫鏃ユ湡"
+        /></el-form-item>
+        <el-form-item label="璧勯噾璐︽埛"
+          ><el-select v-model="createForm.accountId" clearable
+            ><el-option
+              v-for="a in accounts"
+              :key="a.id"
+              :label="a.accountName"
+              :value="a.id" /></el-select
+        ></el-form-item>
+        <el-form-item label="瀵硅处瀵硅薄"
+          ><el-input
+            v-model="createForm.target"
+            placeholder="閾惰/渚涘簲鍟�/瀹㈡埛鍚嶇О"
+        /></el-form-item>
+      </el-form>
+      <template v-if="createForm.reconType !== '0'">
+        <el-divider content-position="left">澶栭儴娴佹按锛堟墜宸ュ綍鍏ワ級</el-divider>
+        <div class="external-actions">
+          <el-button size="small" @click="addExternal">鏂板澶栭儴娴佹按</el-button>
+        </div>
+        <el-table :data="createForm.externalList" border max-height="260">
+          <el-table-column label="鏃ユ湡" width="145"
+            ><template #default="{ row }"
+              ><el-date-picker
+                v-model="row.extDate"
+                value-format="YYYY-MM-DD"
+                style="width: 125px" /></template
+          ></el-table-column>
+          <el-table-column label="寰�鏉ュ崟浣�"
+            ><template #default="{ row }"
+              ><el-input v-model="row.extCounterparty" /></template
+          ></el-table-column>
+          <el-table-column label="鏂瑰悜" width="100"
+            ><template #default="{ row }"
+              ><el-select v-model="row.direction"
+                ><el-option label="鏀跺叆" :value="1" /><el-option
+                  label="鏀嚭"
+                  :value="-1" /></el-select></template
+          ></el-table-column>
+          <el-table-column label="閲戦" width="140"
+            ><template #default="{ row }"
+              ><el-input-number
+                v-model="row.amount"
+                :min="0.01"
+                :precision="2"
+                style="width: 120px" /></template
+          ></el-table-column>
+          <el-table-column label="鍑瘉鍙�"
+            ><template #default="{ row }"
+              ><el-input v-model="row.voucherNo" /></template
+          ></el-table-column>
+          <el-table-column width="60"
+            ><template #default="{ $index }"
+              ><el-button
+                link
+                type="danger"
+                @click="createForm.externalList.splice($index, 1)"
+                >鍒�</el-button
+              ></template
+            ></el-table-column
+          >
+        </el-table>
+      </template>
+      <template #footer
+        ><el-button @click="createVisible = false">鍙栨秷</el-button
+        ><el-button type="primary" @click="submitTask"
+          >寮�濮嬫瘮瀵�</el-button
+        ></template
+      >
+    </el-dialog>
+
+    <el-drawer v-model="diffVisible" title="瀵硅处宸紓鏄庣粏" size="70%">
+      <el-descriptions v-if="currentTask" :column="4" border class="summary">
+        <el-descriptions-item label="浠诲姟鍙�">{{
+          currentTask.id
+        }}</el-descriptions-item
+        ><el-descriptions-item label="绯荤粺閲戦">{{
+          currentTask.systemAmount
+        }}</el-descriptions-item
+        ><el-descriptions-item label="瀵规柟閲戦">{{
+          currentTask.externalAmount
+        }}</el-descriptions-item
+        ><el-descriptions-item label="宸">{{
+          currentTask.diffAmount
+        }}</el-descriptions-item>
+      </el-descriptions>
+      <el-descriptions
+        v-if="currentTask?.handleNote"
+        :column="1"
+        border
+        class="summary"
+      >
+        <el-descriptions-item label="澶勭悊鏂规">{{
+          currentTask.handleNote
+        }}</el-descriptions-item>
+      </el-descriptions>
+      <el-table :data="diffs" border>
+        <el-table-column label="宸紓绫诲瀷"
+          ><template #default="{ row }">{{
+            diffTypeText[row.diffType]
+          }}</template></el-table-column
+        >
+        <el-table-column prop="bizType" label="涓氬姟绫诲瀷" /><el-table-column
+          prop="bizId"
+          label="涓氬姟ID"
+        /><el-table-column prop="bizDate" label="鏃ユ湡" /><el-table-column
+          prop="counterparty"
+          label="寰�鏉ュ崟浣�"
+        /><el-table-column
+          prop="systemAmount"
+          label="绯荤粺閲戦"
+        /><el-table-column
+          prop="externalAmount"
+          label="瀵规柟閲戦"
+        /><el-table-column prop="remark" label="璇存槑" min-width="190" />
+      </el-table>
+      <el-divider content-position="left">澶栭儴鍑瘉</el-divider>
+      <el-table :data="externals" border
+        ><el-table-column prop="extDate" label="鏃ユ湡" /><el-table-column
+          prop="extCounterparty"
+          label="寰�鏉ュ崟浣�" /><el-table-column
+          prop="amount"
+          label="閲戦" /><el-table-column prop="direction" label="鏂瑰悜"
+          ><template #default="{ row }">{{
+            row.direction === 1 ? "鏀跺叆" : "鏀嚭"
+          }}</template></el-table-column
+        ><el-table-column prop="voucherNo" label="鍑瘉鍙�"
+      /></el-table>
+    </el-drawer>
+
+    <el-dialog v-model="handleVisible" title="濉啓澶勭悊鏂规" width="520px">
+      <el-input
+        v-model="handleForm.handleNote"
+        type="textarea"
+        :rows="5"
+        placeholder="璇疯緭鍏ュ樊寮傚鐞嗘柟妗�"
+      />
+      <template #footer
+        ><el-button @click="handleVisible = false">鍙栨秷</el-button
+        ><el-button type="primary" @click="saveHandle"
+          >淇濆瓨骞舵爣璁板鐞嗕腑</el-button
+        ></template
+      >
+    </el-dialog>
+  </div>
+</template>
+
+<script setup>
+import { ref, reactive, onMounted, nextTick } from "vue";
+import { ElMessage, ElMessageBox } from "element-plus";
+import { saveAs } from "file-saver";
+import pagination from "@/components/PIMTable/Pagination.vue";
+import { getFinanceAccountList } from "@/api/financialManagement/fundsManagement";
+import {
+  getReconciliationTaskPage,
+  createReconciliationTask,
+  getReconciliationDiff,
+  getReconciliationExternal,
+  handleReconciliationTask,
+  archiveReconciliationTask,
+  exportReconciliationDiff,
+} from "@/api/financialManagement/financialReconciliation";
+
+const reconTypeText = {
+    0: "鍐呴儴瀵硅处",
+    1: "閾惰瀵硅处",
+    2: "渚涘簲鍟嗗璐�",
+    3: "瀹㈡埛瀵硅处",
+  },
+  statusText = { 0: "寰呭鐞�", 1: "澶勭悊涓�", 2: "宸插綊妗�" },
+  diffTypeText = {
+    0: "绯荤粺鏈夊閮ㄦ棤",
+    1: "澶栭儴鏈夌郴缁熸棤",
+    2: "閲戦涓嶇",
+    3: "鏃ユ湡涓嶇",
+  };
+const tasks = ref([]),
+  total = ref(0),
+  loading = ref(false),
+  loadError = ref(""),
+  accounts = ref([]),
+  createVisible = ref(false),
+  diffVisible = ref(false),
+  handleVisible = ref(false),
+  period = ref([]),
+  diffs = ref([]),
+  externals = ref([]),
+  currentTask = ref(null);
+const query = reactive({
+    current: 1,
+    size: 10,
+    reconType: "",
+    status: "",
+    target: "",
+  }),
+  createForm = reactive({
+    reconType: "0",
+    accountId: null,
+    target: "",
+    externalList: [],
+  }),
+  handleForm = reactive({ id: null, handleNote: "" });
+const loadTasks = async () => {
+  loading.value = true;
+  loadError.value = "";
+  try {
+    const r = await getReconciliationTaskPage(query);
+    tasks.value = r.data?.records || [];
+    total.value = r.data?.total || 0;
+  } catch (error) {
+    tasks.value = [];
+    total.value = 0;
+    loadError.value =
+      error?.message ||
+      error?.msg ||
+      "瀵硅处浠诲姟鍔犺浇澶辫触锛岃妫�鏌ュ悗绔湇鍔″強鏁版嵁搴撹剼鏈�";
+  } finally {
+    loading.value = false;
+  }
+};
+const openCreate = () => {
+  Object.assign(createForm, {
+    reconType: "0",
+    accountId: null,
+    target: "",
+    externalList: [],
+  });
+  period.value = [];
+  createVisible.value = true;
+};
+const addExternal = () =>
+  createForm.externalList.push({
+    extDate: "",
+    extCounterparty: "",
+    amount: null,
+    direction: 1,
+    voucherNo: "",
+    remark: "",
+  });
+const submitTask = async () => {
+  if (period.value.length !== 2) {
+    ElMessage.warning("璇烽�夋嫨瀵硅处鏈熼棿");
+    return;
+  }
+  if (createForm.reconType !== "0" && !createForm.target.trim()) {
+    ElMessage.warning("璇疯緭鍏ラ摱琛屻�佷緵搴斿晢鎴栧鎴峰悕绉�");
+    return;
+  }
+  if (
+    createForm.reconType !== "0" &&
+    createForm.externalList.some(
+      (row) => !row.extDate || !row.amount || row.amount <= 0
+    )
+  ) {
+    ElMessage.warning("璇峰~鍐欏閮ㄥ嚟璇佺殑鏃ユ湡鍜屾鏁伴噾棰�");
+    return;
+  }
+  try {
+    await createReconciliationTask({
+      ...createForm,
+      periodStart: period.value[0],
+      periodEnd: period.value[1],
+    });
+    ElMessage.success("瀵硅处浠诲姟宸茬敓鎴�");
+    createVisible.value = false;
+    await loadTasks();
+  } catch (error) {
+    ElMessage.error(error?.message || "瀵硅处浠诲姟鐢熸垚澶辫触");
+  }
+};
+const showDiff = async (row) => {
+  currentTask.value = row;
+  const [d, e] = await Promise.all([
+    getReconciliationDiff(row.id),
+    getReconciliationExternal(row.id),
+  ]);
+  diffs.value = d.data || [];
+  externals.value = e.data || [];
+  diffVisible.value = true;
+};
+const openHandle = (row) => {
+  handleForm.id = row.id;
+  handleForm.handleNote = row.handleNote || "";
+  handleVisible.value = true;
+};
+const saveHandle = async () => {
+  await handleReconciliationTask(handleForm.id, {
+    handleNote: handleForm.handleNote,
+    status: "1",
+  });
+  ElMessage.success("澶勭悊鏂规宸蹭繚瀛�");
+  handleVisible.value = false;
+  loadTasks();
+};
+const archive = async (row) => {
+  if (row.result === "1" && !row.handleNote?.trim()) {
+    ElMessage.warning("璇峰厛濉啓宸紓澶勭悊鏂规");
+    return;
+  }
+  try {
+    await ElMessageBox.confirm(
+      "褰掓。鍚庤浠诲姟灏嗚繘鍏ュ凡褰掓。鐘舵�侊紝鏄惁缁х画锛�",
+      "褰掓。"
+    );
+    await archiveReconciliationTask(row.id);
+    ElMessage.success("宸插綊妗�");
+    await loadTasks();
+  } catch (error) {
+    if (error !== "cancel" && error !== "close") {
+      ElMessage.error(error?.message || "褰掓。澶辫触");
+    }
+  }
+};
+const exportDiff = async (row) => {
+  const blob = await exportReconciliationDiff(row.id);
+  saveAs(blob, `瀵硅处宸紓-${row.id}.xlsx`);
+};
+onMounted(() => {
+  nextTick(() => {
+    loadTasks();
+    getFinanceAccountList()
+      .then((r) => {
+        accounts.value = r.data || [];
+      })
+      .catch((error) => {
+        accounts.value = [];
+        console.error("璧勯噾璐︽埛鍔犺浇澶辫触锛�", error);
+      });
+  });
+});
+</script>
+
+<style scoped>
+.reconciliation-workspace {
+  padding: 4px;
+}
+.workspace-heading {
+  margin-bottom: 16px;
+}
+.workspace-heading h2 {
+  margin: 0 0 6px;
+  font-size: 20px;
+}
+.workspace-heading p {
+  margin: 0;
+  color: #606266;
+}
+.load-error {
+  margin-bottom: 12px;
+}
+.external-actions {
+  margin-bottom: 8px;
+}
+.summary {
+  margin-bottom: 16px;
+}
+</style>
diff --git a/src/views/financialManagement/financialReconciliation/index.vue b/src/views/financialManagement/financialReconciliation/index.vue
index fa4db0a..87355a1 100644
--- a/src/views/financialManagement/financialReconciliation/index.vue
+++ b/src/views/financialManagement/financialReconciliation/index.vue
@@ -1,291 +1,46 @@
 <template>
-  <div class="app-container">
-    <!-- 鎼滅储鍖� -->
-    <div class="search_form">
-      <el-form :inline="true" :model="searchForm">
-        <el-form-item label="寰�鏉ュ崟浣嶏細">
-          <el-input v-model="searchForm.counterparty" placeholder="璇疯緭鍏ュ線鏉ュ崟浣�" clearable style="width: 180px" />
-        </el-form-item>
-        <el-form-item label="瀵硅处鐘舵�侊細">
-          <el-select v-model="searchForm.status" placeholder="璇烽�夋嫨" clearable style="width: 150px">
-            <el-option label="鏈璐�" value="pending" />
-            <el-option label="瀵硅处涓�" value="processing" />
-            <el-option label="宸插畬鎴�" value="done" />
-          </el-select>
-        </el-form-item>
-        <!-- <el-form-item label="鏃ユ湡锛�">
-          <el-date-picker
-            v-model="searchForm.dateRange"
-            type="daterange"
-            range-separator="鑷�"
-            start-placeholder="寮�濮嬫棩鏈�"
-            end-placeholder="缁撴潫鏃ユ湡"
-            value-format="YYYY-MM-DD"
-            format="YYYY-MM-DD"
-            clearable
-          />
-        </el-form-item> -->
-        <el-form-item>
-          <el-button type="primary" @click="handleQuery">鎼滅储</el-button>
-          <el-button @click="resetQuery">閲嶇疆</el-button>
-        </el-form-item>
-      </el-form>
-    </div>
-
-    <!-- 鎿嶄綔鍖� -->
-    <div class="actions">
-      <el-button type="primary" @click="openDialog('add')">鏂板</el-button>
-      <el-button type="danger" plain @click="handleDelete">鍒犻櫎</el-button>
-    </div>
-
-    <!-- 琛ㄦ牸 -->
-    <el-table
-      :data="tableData"
-      border
-      v-loading="tableLoading"
-      @selection-change="handleSelectionChange"
-      style="width: 100%"
-      height="calc(100vh - 280px)"
-      :row-key="row => row.id"
-    >
-      <el-table-column type="selection" width="55" align="center" />
-      <el-table-column type="index" label="搴忓彿" width="60" align="center" />
-      <el-table-column prop="accountDate" label="瀵硅处鏃ユ湡" width="130" show-overflow-tooltip />
-      <el-table-column prop="counterparty" label="寰�鏉ュ崟浣�" width="180" show-overflow-tooltip />
-      <el-table-column prop="amount" label="瀵硅处閲戦(鍏�)" width="140" show-overflow-tooltip />
-      <el-table-column prop="statusText" label="鐘舵��" width="120" show-overflow-tooltip />
-      <el-table-column prop="remark" label="澶囨敞" show-overflow-tooltip />
-      <el-table-column fixed="right" label="鎿嶄綔" width="150" align="center">
-        <template #default="scope">
-          <el-button link type="primary" size="small" @click="openDialog('edit', scope.row)">缂栬緫</el-button>
-        </template>
-      </el-table-column>
-    </el-table>
-
-    <pagination
-      v-show="total > 0"
-      :total="total"
-      layout="total, sizes, prev, pager, next, jumper"
-      :page="page.current"
-      :limit="page.size"
-      @pagination="paginationChange"
+  <div class="app-container financial-reconciliation-page">
+    <el-alert
+      v-if="runtimeError"
+      title="璐㈠姟瀵硅处椤甸潰鍔犺浇澶辫触"
+      type="error"
+      :description="runtimeError"
+      show-icon
+      :closable="false"
     />
-
-    <!-- 寮圭獥 -->
-    <el-dialog v-model="dialogVisible" :title="dialogTitle" width="520px" @close="resetForm">
-      <el-form :model="form" :rules="rules" ref="formRef" label-width="120px">
-        <el-form-item label="瀵硅处鏃ユ湡锛�" prop="accountDate">
-          <el-date-picker v-model="form.accountDate" type="date" value-format="YYYY-MM-DD" format="YYYY-MM-DD" style="width: 100%" />
-        </el-form-item>
-        <el-form-item label="寰�鏉ュ崟浣嶏細" prop="counterparty">
-          <el-input v-model="form.counterparty" placeholder="璇疯緭鍏ュ線鏉ュ崟浣�" />
-        </el-form-item>
-        <el-form-item label="瀵硅处閲戦(鍏�)锛�" prop="amount">
-          <el-input-number v-model="form.amount" :min="0" :precision="2" :step="100" style="width: 100%" />
-        </el-form-item>
-        <el-form-item label="鐘舵�侊細" prop="status">
-          <el-select v-model="form.status" placeholder="璇烽�夋嫨" style="width: 100%">
-            <el-option label="鏈璐�" value="pending" />
-            <el-option label="瀵硅处涓�" value="processing" />
-            <el-option label="宸插畬鎴�" value="done" />
-          </el-select>
-        </el-form-item>
-        <el-form-item label="澶囨敞锛�">
-          <el-input v-model="form.remark" type="textarea" :rows="3" placeholder="璇疯緭鍏ュ娉�" />
-        </el-form-item>
-      </el-form>
-      <template #footer>
-        <div class="dialog-footer">
-          <el-button @click="dialogVisible = false">鍙栨秷</el-button>
-          <el-button type="primary" @click="submitForm">纭</el-button>
-        </div>
-      </template>
-    </el-dialog>
+    <ReconciliationWorkspace v-else :key="workspaceKey" />
+    <el-button v-if="runtimeError" type="primary" class="retry-button" @click="retry">
+      閲嶆柊鍔犺浇
+    </el-button>
   </div>
 </template>
 
 <script setup>
-import { ref, reactive, onMounted } from 'vue'
-import { ElMessage, ElMessageBox } from 'element-plus'
-import pagination from '@/components/PIMTable/Pagination.vue'
-import {
-  getReconciliationPage,
-  addReconciliation,
-  updateReconciliation,
-  deleteReconciliation
-} from '@/api/financialManagement/financialReconciliation.js'
+import { nextTick, onErrorCaptured, ref } from "vue";
+import ReconciliationWorkspace from "./ReconciliationWorkspace.vue";
 
-const tableData = ref([])
-const tableLoading = ref(false)
-const selectedRows = ref([])
-const dialogVisible = ref(false)
-const dialogTitle = ref('鏂板')
-const formRef = ref(null)
+const runtimeError = ref("");
+const workspaceKey = ref(0);
 
-const page = reactive({
-  current: 1,
-  size: 100
-})
-const total = ref(0)
+onErrorCaptured((error) => {
+  console.error("璐㈠姟瀵硅处椤甸潰杩愯寮傚父锛�", error);
+  runtimeError.value = error?.stack || error?.message || String(error);
+  return false;
+});
 
-const searchForm = reactive({
-  counterparty: '',
-  status: '',
-  dateRange: []
-})
-
-const form = reactive({
-  id: null,
-  accountDate: '',
-  counterparty: '',
-  amount: null,
-  status: '',
-  remark: ''
-})
-
-const rules = {
-  accountDate: [{ required: true, message: '璇烽�夋嫨瀵硅处鏃ユ湡', trigger: 'change' }],
-  counterparty: [{ required: true, message: '璇疯緭鍏ュ線鏉ュ崟浣�', trigger: 'blur' }],
-  amount: [{ required: true, message: '璇疯緭鍏ュ璐﹂噾棰�', trigger: 'blur' }],
-  status: [{ required: true, message: '璇烽�夋嫨鐘舵��', trigger: 'change' }]
-}
-
-const statusTextMap = {
-  pending: '鏈璐�',
-  processing: '瀵硅处涓�',
-  done: '宸插畬鎴�'
-}
-
-// 鑾峰彇鍒楄〃
-const getList = () => {
-  tableLoading.value = true
-  const params = {
-    ...searchForm,
-    current: page.current,
-    size: page.size
-  }
-  if (params.dateRange && params.dateRange.length === 2) {
-    params.beginDate = params.dateRange[0]
-    params.endDate = params.dateRange[1]
-  }
-  delete params.dateRange
-
-  getReconciliationPage(params)
-    .then(res => {
-      if (res.code === 200) {
-        tableData.value = (res.data?.records || []).map(item => ({
-          ...item,
-          statusText: statusTextMap[item.status] || item.status
-        }))
-        total.value = res.data?.total || 0
-      }
-    })
-    .finally(() => {
-      tableLoading.value = false
-    })
-}
-
-const handleQuery = () => {
-  page.current = 1
-  getList()
-}
-const resetQuery = () => {
-  searchForm.counterparty = ''
-  searchForm.status = ''
-  searchForm.dateRange = []
-  handleQuery()
-}
-const paginationChange = (obj) => {
-  page.current = obj.page
-  page.size = obj.limit
-  getList()
-}
-
-const handleSelectionChange = (rows) => {
-  selectedRows.value = rows
-}
-
-const openDialog = (type, row) => {
-  dialogTitle.value = type === 'add' ? '鏂板瀵硅处璁板綍' : '缂栬緫瀵硅处璁板綍'
-  if (type === 'edit' && row) {
-    Object.assign(form, row)
-  } else {
-    resetForm()
-  }
-  dialogVisible.value = true
-}
-
-const resetForm = () => {
-  Object.assign(form, {
-    id: null,
-    date: '',
-    counterparty: '',
-    amount: null,
-    status: '',
-    remark: ''
-  })
-}
-
-const submitForm = () => {
-  formRef.value?.validate(valid => {
-    if (!valid) return
-    const payload = { ...form }
-    const request = payload.id ? updateReconciliation(payload) : addReconciliation(payload)
-    request
-      .then(res => {
-        if (res.code === 200) {
-          ElMessage.success('鎻愪氦鎴愬姛')
-          dialogVisible.value = false
-          getList()
-        }
-      })
-      .catch(err => {
-        ElMessage.error(err.msg || '鎻愪氦澶辫触')
-      })
-  })
-}
-
-const handleDelete = () => {
-  if (!selectedRows.value.length) {
-    ElMessage.warning('璇烽�夋嫨瑕佸垹闄ょ殑璁板綍')
-    return
-  }
-  ElMessageBox.confirm('纭鍒犻櫎閫変腑璁板綍鍚楋紵', '鎻愮ず', {
-    confirmButtonText: '纭',
-    cancelButtonText: '鍙栨秷',
-    type: 'warning'
-  }).then(() => {
-    const ids = selectedRows.value.map(r => r.id)
-    deleteReconciliation(ids).then(res => {
-      if (res.code === 200) {
-        ElMessage.success('鍒犻櫎鎴愬姛')
-        getList()
-        selectedRows.value = []
-      }
-    }).catch(err => {
-      ElMessage.error(err.msg || '鍒犻櫎澶辫触')
-    })
-  }).catch(() => {})
-}
-
-onMounted(() => {
-  getList()
-})
+const retry = async () => {
+  runtimeError.value = "";
+  await nextTick();
+  workspaceKey.value += 1;
+};
 </script>
 
-<style scoped lang="scss">
-.app-container {
-  padding: 20px;
-
-  .search_form {
-    background: #fff;
-    padding: 16px;
-    border-radius: 6px;
-    margin-bottom: 16px;
-  }
-
-  .actions {
-    margin-bottom: 12px;
-  }
+<style scoped>
+.financial-reconciliation-page {
+  min-height: calc(100vh - 84px);
+  background: #fff;
+}
+.retry-button {
+  margin-top: 16px;
 }
 </style>
diff --git a/src/views/financialManagement/financialStatements/index.vue b/src/views/financialManagement/financialStatements/index.vue
index e5f9b23..4a0b674 100644
--- a/src/views/financialManagement/financialStatements/index.vue
+++ b/src/views/financialManagement/financialStatements/index.vue
@@ -130,14 +130,20 @@
 </template>
 
 <script setup>
+defineOptions({ name: 'FinancialStatements' })
 import { ref, computed, onMounted, reactive } from 'vue';
+import { ElMessage } from 'element-plus';
 import 'element-plus/dist/index.css';
 import Echarts from "@/components/Echarts/echarts.vue";
 import { reportForms,reportIncome,reportExpense } from "@/api/financialManagement/financialStatements";
 import dayjs from "dayjs";
 
 // 鏃ユ湡鑼冨洿
-const dateRange = ref(null);
+const currentYear = dayjs().year();
+const dateRange = ref([
+  dayjs().startOf('year').format('YYYY-MM-DD'),
+  dayjs().endOf('year').format('YYYY-MM-DD')
+]);
 const chartStyle = {
 	width: '100%',
 	height: '100%', // 璁剧疆鍥捐〃瀹瑰櫒鐨勯珮搴�
@@ -315,8 +321,16 @@
 })
 
 
-const pageInfo = ref({
-})
+const emptyPageInfo = () => ({
+  totalIncome: 0,
+  incomeNumber: 0,
+  totalExpense: 0,
+  expenseNumber: 0,
+  netRevenue: 0,
+  incomeType: [],
+  expenseType: []
+});
+const pageInfo = ref(emptyPageInfo());
 
 const getData = async () => {
   if (!dateRange.value || !dateRange.value.length) {
@@ -325,14 +339,14 @@
   try {
     const {code,data} = await reportForms({entryDateStart:dateRange.value[0], entryDateEnd:dateRange.value[1]});
     if(code === 200) {
-      pageInfo.value = data
-      pieData0.value = data.incomeType.map(item=>({
+      pageInfo.value = { ...emptyPageInfo(), ...(data || {}) }
+      pieData0.value = (data?.incomeType || []).map(item=>({
         name:item.typeName,
         value:item.account,
         percent:`${item.proportion*100}%`,
         amount:`楼${item.account}`
       }))
-      pieData1.value = data.expenseType.map(item=>({
+      pieData1.value = (data?.expenseType || []).map(item=>({
         name:item.typeName,
         value:item.account,
         percent:`${item.proportion*100}%`,
@@ -341,7 +355,11 @@
 
     }
   } catch (error) {
+    pageInfo.value = emptyPageInfo();
+    pieData0.value = [];
+    pieData1.value = [];
     console.error('鑾峰彇璐㈠姟鎸囨爣鏁版嵁澶辫触锛�', error);
+    ElMessage.error(error?.msg || '璐㈠姟姹囨�绘暟鎹姞杞藉け璐�');
   }
   try{
     const {code,data} = await reportIncome();
@@ -349,7 +367,7 @@
       lineSeries0.value = data.map(item=>({
         name:item.typeName,
         type: 'line',
-        data:item.account.map(item=>Number(item))
+        data:(item.account || []).map(value => Number(value || 0))
       }))
 
     }
@@ -362,7 +380,7 @@
       lineSeries1.value = data.map(item=>({
         name:item.typeName,
         type: 'line',
-        data:item.account.map(item=>Number(item))
+        data:(item.account || []).map(value => Number(value || 0))
       }))
 
     }
@@ -374,7 +392,7 @@
 
 // 鍒濆鍖�
 onMounted(() => {
-  // 涓嶈缃粯璁ゆ棩鏈燂紝鐢辩敤鎴锋墜鍔ㄩ�夋嫨
+  getData();
 });
 
 // 澶勭悊鏃ユ湡鑼冨洿鍙樺寲
@@ -387,7 +405,11 @@
 
 // 閲嶇疆鏃ユ湡鑼冨洿
 const resetDateRange = () => {
-  dateRange.value = null;
+  dateRange.value = [
+    `${currentYear}-01-01`,
+    `${currentYear}-12-31`
+  ];
+  getData();
 };
 
 </script>
diff --git a/src/views/financialManagement/fundsManagement/FinanceWorkspace.vue b/src/views/financialManagement/fundsManagement/FinanceWorkspace.vue
new file mode 100644
index 0000000..05c5afd
--- /dev/null
+++ b/src/views/financialManagement/fundsManagement/FinanceWorkspace.vue
@@ -0,0 +1,651 @@
+<template>
+  <div class="finance-workspace">
+    <el-tabs v-model="activeTab" @tab-change="loadTab">
+      <el-tab-pane label="璧勯噾璐︽埛" name="accounts">
+        <div class="toolbar">
+          <el-button type="primary" @click="openAccount()">鏂板璐︽埛</el-button>
+        </div>
+        <div class="balance-grid">
+          <el-card v-for="item in accounts" :key="item.id" shadow="hover">
+            <div class="balance-title">
+              {{ item.accountName }}
+              <el-tag size="small">{{
+                accountTypeText[item.accountType]
+              }}</el-tag>
+            </div>
+            <div class="balance-amount">楼 {{ money(item.currentBalance) }}</div>
+            <div class="muted">瀹夊叏绾� 楼 {{ money(item.safetyLine) }}</div>
+          </el-card>
+        </div>
+        <el-table :data="accounts" border v-loading="loading">
+          <el-table-column prop="accountName" label="璐︽埛鍚嶇О" />
+          <el-table-column label="绫诲瀷"
+            ><template #default="{ row }">{{
+              accountTypeText[row.accountType]
+            }}</template></el-table-column
+          >
+          <el-table-column prop="bankName" label="寮�鎴疯" />
+          <el-table-column prop="bankNo" label="璐﹀彿" />
+          <el-table-column prop="openingBalance" label="鏈熷垵浣欓" />
+          <el-table-column prop="currentBalance" label="褰撳墠浣欓" />
+          <el-table-column label="鐘舵��"
+            ><template #default="{ row }">{{
+              row.status === "0" ? "鍚敤" : "鍋滅敤"
+            }}</template></el-table-column
+          >
+          <el-table-column label="鎿嶄綔" width="140"
+            ><template #default="{ row }"
+              ><el-button link type="primary" @click="openAccount(row)"
+                >缂栬緫</el-button
+              ><el-button link type="danger" @click="removeAccount(row)"
+                >鍒犻櫎</el-button
+              ></template
+            ></el-table-column
+          >
+        </el-table>
+        <el-divider content-position="left">杩�30鏃ヨ祫閲戞祦瓒嬪娍</el-divider>
+        <div ref="trendRef" class="chart"></div>
+      </el-tab-pane>
+
+      <el-tab-pane label="璧勯噾娴佹按" name="flows">
+        <el-form inline :model="flowQuery">
+          <el-form-item label="璐︽埛"
+            ><el-select
+              v-model="flowQuery.accountId"
+              clearable
+              style="width: 180px"
+              ><el-option
+                v-for="a in accounts"
+                :key="a.id"
+                :label="a.accountName"
+                :value="a.id" /></el-select
+          ></el-form-item>
+          <el-form-item label="绫诲瀷"
+            ><el-select
+              v-model="flowQuery.flowType"
+              clearable
+              style="width: 130px"
+              ><el-option
+                v-for="(label, key) in flowTypeText"
+                :key="key"
+                :label="label"
+                :value="key" /></el-select
+          ></el-form-item>
+          <el-form-item
+            ><el-button type="primary" @click="loadFlows"
+              >鏌ヨ</el-button
+            ></el-form-item
+          >
+        </el-form>
+        <div class="toolbar">
+          <el-button type="primary" @click="openFlow()">鐧昏娴佹按</el-button
+          ><el-button @click="openTransfer">璐︽埛杞处</el-button>
+        </div>
+        <el-table :data="flows" border v-loading="loading">
+          <el-table-column prop="accountDate" label="鏃ユ湡" width="120" />
+          <el-table-column prop="accountName" label="璐︽埛" />
+          <el-table-column label="绫诲瀷"
+            ><template #default="{ row }">{{
+              flowTypeText[row.flowType]
+            }}</template></el-table-column
+          >
+          <el-table-column prop="amount" label="閲戦" />
+          <el-table-column prop="counterparty" label="寰�鏉ュ崟浣�" />
+          <el-table-column label="鍏宠仈"
+            ><template #default="{ row }">{{
+              row.linkType ? `${row.linkType} #${row.linkId}` : "-"
+            }}</template></el-table-column
+          >
+          <el-table-column prop="remark" label="澶囨敞" />
+          <el-table-column label="鎿嶄綔" width="140"
+            ><template #default="{ row }"
+              ><template v-if="row.flowType !== '2'"
+                ><el-button link type="primary" @click="openFlow(row)"
+                  >缂栬緫</el-button
+                ><el-button link type="danger" @click="removeFlow(row)"
+                  >鍒犻櫎</el-button
+                ></template
+              ><span v-else class="muted">鎴愬娴佹按</span></template
+            ></el-table-column
+          >
+        </el-table>
+        <pagination
+          v-show="flowTotal > 0"
+          :total="flowTotal"
+          :page="flowQuery.current"
+          :limit="flowQuery.size"
+          @pagination="
+            (p) => {
+              flowQuery.current = p.page;
+              flowQuery.size = p.limit;
+              loadFlows();
+            }
+          "
+        />
+      </el-tab-pane>
+
+      <el-tab-pane label="鏀舵敮璁″垝" name="plans">
+        <el-alert
+          v-if="forecast"
+          :type="
+            forecast.signal === 'gap'
+              ? 'error'
+              : forecast.signal === 'surplus'
+              ? 'success'
+              : 'info'
+          "
+          :closable="false"
+          show-icon
+          class="forecast"
+        >
+          <template #title
+            >30澶╅娴嬩綑棰濓細楼 {{ money(forecast.finalBalance) }}锛泏{
+              forecast.signal === "gap"
+                ? "瀛樺湪璧勯噾缂哄彛"
+                : forecast.signal === "surplus"
+                ? "楂樹簬瀹夊叏绾匡紝瀛樺湪鐩堜綑"
+                : "澶勪簬瀹夊叏绾�"
+            }}</template
+          >
+        </el-alert>
+        <div class="toolbar">
+          <el-button type="primary" @click="openPlan()">鏂板璁″垝</el-button
+          ><el-button @click="loadForecast">鍒锋柊棰勬祴</el-button>
+        </div>
+        <el-table :data="plans" border v-loading="loading">
+          <el-table-column prop="planDate" label="璁″垝鏃ユ湡" />
+          <el-table-column label="绫诲瀷"
+            ><template #default="{ row }">{{
+              row.planType === "0" ? "璁″垝鏀跺叆" : "璁″垝鏀嚭"
+            }}</template></el-table-column
+          >
+          <el-table-column prop="expectedAmount" label="棰勮閲戦" />
+          <el-table-column prop="category" label="鍒嗙被" />
+          <el-table-column prop="counterparty" label="寰�鏉ュ崟浣�" />
+          <el-table-column label="鐘舵��"
+            ><template #default="{ row }">{{
+              planStatusText[row.status]
+            }}</template></el-table-column
+          >
+          <el-table-column label="鎿嶄綔" width="140"
+            ><template #default="{ row }"
+              ><el-button link type="primary" @click="openPlan(row)"
+                >缂栬緫</el-button
+              ><el-button link type="danger" @click="removePlan(row)"
+                >鍒犻櫎</el-button
+              ></template
+            ></el-table-column
+          >
+        </el-table>
+        <pagination
+          v-show="planTotal > 0"
+          :total="planTotal"
+          :page="planQuery.current"
+          :limit="planQuery.size"
+          @pagination="
+            (p) => {
+              planQuery.current = p.page;
+              planQuery.size = p.limit;
+              loadPlans();
+            }
+          "
+        />
+      </el-tab-pane>
+    </el-tabs>
+
+    <el-dialog
+      v-model="accountVisible"
+      :title="accountForm.id ? '缂栬緫璐︽埛' : '鏂板璐︽埛'"
+      width="560px"
+    >
+      <el-form :model="accountForm" label-width="100px">
+        <el-form-item label="璐︽埛鍚嶇О"
+          ><el-input v-model="accountForm.accountName"
+        /></el-form-item>
+        <el-form-item label="璐︽埛绫诲瀷"
+          ><el-radio-group v-model="accountForm.accountType"
+            ><el-radio value="0">閾惰璐︽埛</el-radio
+            ><el-radio value="1">鐜伴噾璐︽埛</el-radio></el-radio-group
+          ></el-form-item
+        >
+        <el-form-item label="寮�鎴疯"
+          ><el-input v-model="accountForm.bankName"
+        /></el-form-item>
+        <el-form-item label="閾惰璐﹀彿"
+          ><el-input v-model="accountForm.bankNo"
+        /></el-form-item>
+        <el-form-item label="鏈熷垵浣欓"
+          ><el-input-number
+            v-model="accountForm.openingBalance"
+            :precision="2"
+            style="width: 100%"
+        /></el-form-item>
+        <el-form-item label="瀹夊叏绾�"
+          ><el-input-number
+            v-model="accountForm.safetyLine"
+            :precision="2"
+            style="width: 100%"
+        /></el-form-item>
+        <el-form-item label="鐘舵��"
+          ><el-switch
+            v-model="accountForm.status"
+            active-value="0"
+            inactive-value="1"
+            active-text="鍚敤"
+        /></el-form-item>
+      </el-form>
+      <template #footer
+        ><el-button @click="accountVisible = false">鍙栨秷</el-button
+        ><el-button type="primary" @click="saveAccount"
+          >淇濆瓨</el-button
+        ></template
+      >
+    </el-dialog>
+
+    <el-dialog v-model="flowVisible" title="鐧昏璧勯噾娴佹按" width="560px">
+      <el-form :model="flowForm" label-width="100px">
+        <el-form-item label="鏃ユ湡"
+          ><el-date-picker
+            v-model="flowForm.accountDate"
+            value-format="YYYY-MM-DD"
+        /></el-form-item>
+        <el-form-item label="璐︽埛"
+          ><el-select v-model="flowForm.accountId"
+            ><el-option
+              v-for="a in accounts"
+              :key="a.id"
+              :label="a.accountName"
+              :value="a.id" /></el-select
+        ></el-form-item>
+        <el-form-item label="娴佹按绫诲瀷"
+          ><el-select v-model="flowForm.flowType"
+            ><el-option
+              v-for="(label, key) in nonTransferTypes"
+              :key="key"
+              :label="label"
+              :value="key" /></el-select
+        ></el-form-item>
+        <el-form-item label="閲戦"
+          ><el-input-number
+            v-model="flowForm.amount"
+            :min="0.01"
+            :precision="2"
+        /></el-form-item>
+        <el-form-item label="寰�鏉ュ崟浣�"
+          ><el-input v-model="flowForm.counterparty"
+        /></el-form-item>
+        <el-form-item label="鍏宠仈绫诲瀷"
+          ><el-select v-model="flowForm.linkType" clearable
+            ><el-option label="鏀跺叆" value="Income" /><el-option
+              label="鏀嚭"
+              value="Expense" /></el-select
+        ></el-form-item>
+        <el-form-item label="鍏宠仈ID"
+          ><el-input-number v-model="flowForm.linkId" :min="1"
+        /></el-form-item>
+        <el-form-item label="澶囨敞"
+          ><el-input v-model="flowForm.remark"
+        /></el-form-item>
+      </el-form>
+      <template #footer
+        ><el-button @click="flowVisible = false">鍙栨秷</el-button
+        ><el-button type="primary" @click="saveFlow">淇濆瓨</el-button></template
+      >
+    </el-dialog>
+
+    <el-dialog v-model="transferVisible" title="璐︽埛杞处" width="520px">
+      <el-form :model="transferForm" label-width="100px">
+        <el-form-item label="杞嚭璐︽埛"
+          ><el-select v-model="transferForm.fromAccountId"
+            ><el-option
+              v-for="a in accounts"
+              :key="a.id"
+              :label="a.accountName"
+              :value="a.id" /></el-select
+        ></el-form-item>
+        <el-form-item label="杞叆璐︽埛"
+          ><el-select v-model="transferForm.toAccountId"
+            ><el-option
+              v-for="a in accounts"
+              :key="a.id"
+              :label="a.accountName"
+              :value="a.id" /></el-select
+        ></el-form-item>
+        <el-form-item label="閲戦"
+          ><el-input-number
+            v-model="transferForm.amount"
+            :min="0.01"
+            :precision="2"
+        /></el-form-item>
+        <el-form-item label="鏃ユ湡"
+          ><el-date-picker
+            v-model="transferForm.transferDate"
+            value-format="YYYY-MM-DD"
+        /></el-form-item>
+        <el-form-item label="澶囨敞"
+          ><el-input v-model="transferForm.remark"
+        /></el-form-item>
+      </el-form>
+      <template #footer
+        ><el-button @click="transferVisible = false">鍙栨秷</el-button
+        ><el-button type="primary" @click="saveTransfer"
+          >杞处</el-button
+        ></template
+      >
+    </el-dialog>
+
+    <el-dialog
+      v-model="planVisible"
+      :title="planForm.id ? '缂栬緫璁″垝' : '鏂板璁″垝'"
+      width="560px"
+    >
+      <el-form :model="planForm" label-width="100px">
+        <el-form-item label="璁″垝绫诲瀷"
+          ><el-radio-group v-model="planForm.planType"
+            ><el-radio value="0">璁″垝鏀跺叆</el-radio
+            ><el-radio value="1">璁″垝鏀嚭</el-radio></el-radio-group
+          ></el-form-item
+        >
+        <el-form-item label="璁″垝鏃ユ湡"
+          ><el-date-picker
+            v-model="planForm.planDate"
+            value-format="YYYY-MM-DD"
+        /></el-form-item>
+        <el-form-item label="棰勮閲戦"
+          ><el-input-number
+            v-model="planForm.expectedAmount"
+            :min="0.01"
+            :precision="2"
+        /></el-form-item>
+        <el-form-item label="鍒嗙被"
+          ><el-input v-model="planForm.category"
+        /></el-form-item>
+        <el-form-item label="寰�鏉ュ崟浣�"
+          ><el-input v-model="planForm.counterparty"
+        /></el-form-item>
+        <el-form-item label="鐘舵��"
+          ><el-select v-model="planForm.status"
+            ><el-option
+              v-for="(label, key) in planStatusText"
+              :key="key"
+              :label="label"
+              :value="key" /></el-select
+        ></el-form-item>
+        <el-form-item label="澶囨敞"
+          ><el-input v-model="planForm.remark"
+        /></el-form-item>
+      </el-form>
+      <template #footer
+        ><el-button @click="planVisible = false">鍙栨秷</el-button
+        ><el-button type="primary" @click="savePlan">淇濆瓨</el-button></template
+      >
+    </el-dialog>
+  </div>
+</template>
+
+<script setup>
+import { ref, reactive, onMounted, nextTick } from "vue";
+import * as echarts from "echarts";
+import { ElMessage, ElMessageBox } from "element-plus";
+import pagination from "@/components/PIMTable/Pagination.vue";
+import {
+  getFundsPage,
+  addFunds,
+  updateFunds,
+  deleteFunds,
+  transferFunds,
+  getFinanceAccountPage,
+  addFinanceAccount,
+  updateFinanceAccount,
+  deleteFinanceAccount,
+  getFundsTrend,
+  getCashflowPlanPage,
+  addCashflowPlan,
+  updateCashflowPlan,
+  deleteCashflowPlan,
+  getCashflowForecast,
+} from "@/api/financialManagement/fundsManagement";
+
+const activeTab = ref("accounts"),
+  loading = ref(false),
+  accounts = ref([]),
+  flows = ref([]),
+  plans = ref([]),
+  forecast = ref(null);
+const flowTotal = ref(0),
+  planTotal = ref(0),
+  trendRef = ref(),
+  accountVisible = ref(false),
+  flowVisible = ref(false),
+  transferVisible = ref(false),
+  planVisible = ref(false);
+const accountTypeText = { 0: "閾惰璐︽埛", 1: "鐜伴噾璐︽埛" },
+  flowTypeText = { 0: "鏀跺叆", 1: "鏀嚭", 2: "杞处", 3: "瀛樺叆", 4: "鍙栧嚭" },
+  nonTransferTypes = { 0: "鏀跺叆", 1: "鏀嚭", 3: "瀛樺叆", 4: "鍙栧嚭" },
+  planStatusText = { 0: "寰呭彂鐢�", 1: "宸插彂鐢�", 2: "宸插彇娑�" };
+const flowQuery = reactive({
+    current: 1,
+    size: 10,
+    accountId: null,
+    flowType: "",
+  }),
+  planQuery = reactive({ current: 1, size: 10 });
+const accountForm = reactive({}),
+  flowForm = reactive({}),
+  transferForm = reactive({}),
+  planForm = reactive({});
+const money = (v) =>
+  Number(v || 0).toLocaleString("zh-CN", {
+    minimumFractionDigits: 2,
+    maximumFractionDigits: 2,
+  });
+const loadAccounts = async () => {
+  loading.value = true;
+  try {
+    const r = await getFinanceAccountPage({ current: 1, size: 100 });
+    accounts.value = r.data?.records || [];
+    await nextTick();
+    loadTrend();
+  } finally {
+    loading.value = false;
+  }
+};
+const loadTrend = async () => {
+  if (!trendRef.value) return;
+  const end = new Date(),
+    start = new Date(Date.now() - 29 * 86400000);
+  const fmt = (d) => d.toISOString().slice(0, 10);
+  const r = await getFundsTrend({ start: fmt(start), end: fmt(end) });
+  const rows = r.data || [];
+  echarts.init(trendRef.value).setOption({
+    tooltip: { trigger: "axis" },
+    legend: { data: ["鏀跺叆", "鏀嚭", "鍑�棰�"] },
+    xAxis: { type: "category", data: rows.map((x) => x.date) },
+    yAxis: { type: "value" },
+    series: [
+      { name: "鏀跺叆", type: "line", data: rows.map((x) => x.income) },
+      { name: "鏀嚭", type: "line", data: rows.map((x) => x.expense) },
+      { name: "鍑�棰�", type: "line", data: rows.map((x) => x.net) },
+    ],
+  });
+};
+const loadFlows = async () => {
+  loading.value = true;
+  try {
+    const r = await getFundsPage(flowQuery);
+    flows.value = r.data?.records || [];
+    flowTotal.value = r.data?.total || 0;
+  } finally {
+    loading.value = false;
+  }
+};
+const loadPlans = async () => {
+  loading.value = true;
+  try {
+    const r = await getCashflowPlanPage(planQuery);
+    plans.value = r.data?.records || [];
+    planTotal.value = r.data?.total || 0;
+    await loadForecast();
+  } finally {
+    loading.value = false;
+  }
+};
+const loadForecast = async () => {
+  const r = await getCashflowForecast({ days: 30 });
+  forecast.value = r.data;
+};
+const loadTab = (name) =>
+  name === "accounts"
+    ? loadAccounts()
+    : name === "flows"
+    ? (accounts.value.length || loadAccounts(), loadFlows())
+    : loadPlans();
+const openAccount = (row) => {
+  Object.assign(
+    accountForm,
+    {
+      id: null,
+      accountName: "",
+      accountType: "0",
+      bankName: "",
+      bankNo: "",
+      openingBalance: 0,
+      safetyLine: 0,
+      status: "0",
+    },
+    row || {}
+  );
+  accountVisible.value = true;
+};
+const saveAccount = async () => {
+  await (accountForm.id
+    ? updateFinanceAccount({ ...accountForm })
+    : addFinanceAccount({ ...accountForm }));
+  ElMessage.success("淇濆瓨鎴愬姛");
+  accountVisible.value = false;
+  loadAccounts();
+};
+const removeAccount = (row) =>
+  ElMessageBox.confirm(`纭鍒犻櫎璐︽埛鈥�${row.accountName}鈥濆悧锛焋, "鎻愮ず").then(
+    async () => {
+      await deleteFinanceAccount([row.id]);
+      ElMessage.success("鍒犻櫎鎴愬姛");
+      loadAccounts();
+    }
+  );
+const openFlow = (row) => {
+  Object.assign(
+    flowForm,
+    {
+      id: null,
+      accountDate: new Date().toISOString().slice(0, 10),
+      accountId: null,
+      flowType: "0",
+      amount: null,
+      counterparty: "",
+      linkType: "",
+      linkId: null,
+      remark: "",
+    },
+    row || {}
+  );
+  flowVisible.value = true;
+};
+const saveFlow = async () => {
+  await (flowForm.id
+    ? updateFunds({ ...flowForm })
+    : addFunds({ ...flowForm }));
+  ElMessage.success("淇濆瓨鎴愬姛");
+  flowVisible.value = false;
+  loadFlows();
+};
+const removeFlow = (row) =>
+  ElMessageBox.confirm("纭鍒犻櫎璇ヨ祫閲戞祦姘村悧锛�", "鎻愮ず").then(async () => {
+    await deleteFunds([row.id]);
+    ElMessage.success("鍒犻櫎鎴愬姛");
+    loadFlows();
+  });
+const openTransfer = () => {
+  Object.assign(transferForm, {
+    fromAccountId: null,
+    toAccountId: null,
+    amount: null,
+    transferDate: new Date().toISOString().slice(0, 10),
+    remark: "",
+  });
+  transferVisible.value = true;
+};
+const saveTransfer = async () => {
+  await transferFunds({ ...transferForm });
+  ElMessage.success("杞处鎴愬姛");
+  transferVisible.value = false;
+  loadFlows();
+};
+const openPlan = (row) => {
+  Object.assign(
+    planForm,
+    {
+      id: null,
+      planType: "0",
+      planDate: "",
+      expectedAmount: null,
+      category: "",
+      counterparty: "",
+      status: "0",
+      remark: "",
+    },
+    row || {}
+  );
+  planVisible.value = true;
+};
+const savePlan = async () => {
+  await (planForm.id
+    ? updateCashflowPlan({ ...planForm })
+    : addCashflowPlan({ ...planForm }));
+  ElMessage.success("淇濆瓨鎴愬姛");
+  planVisible.value = false;
+  loadPlans();
+};
+const removePlan = (row) =>
+  ElMessageBox.confirm("纭鍒犻櫎璇ユ敹鏀鍒掑悧锛�", "鎻愮ず").then(async () => {
+    await deleteCashflowPlan([row.id]);
+    ElMessage.success("鍒犻櫎鎴愬姛");
+    loadPlans();
+  });
+onMounted(loadAccounts);
+</script>
+
+<style scoped>
+.finance-workspace {
+  padding: 4px;
+}
+.toolbar {
+  margin: 12px 0;
+}
+.balance-grid {
+  display: grid;
+  grid-template-columns: repeat(auto-fill, minmax(230px, 1fr));
+  gap: 12px;
+  margin-bottom: 16px;
+}
+.balance-title {
+  display: flex;
+  justify-content: space-between;
+}
+.balance-amount {
+  font-size: 25px;
+  font-weight: 600;
+  color: #303133;
+  margin: 14px 0;
+}
+.muted {
+  color: #909399;
+  font-size: 13px;
+}
+.chart {
+  height: 300px;
+}
+.forecast {
+  margin-bottom: 12px;
+}
+</style>
diff --git a/src/views/financialManagement/fundsManagement/index.vue b/src/views/financialManagement/fundsManagement/index.vue
index 845dc52..c20f7cb 100644
--- a/src/views/financialManagement/fundsManagement/index.vue
+++ b/src/views/financialManagement/fundsManagement/index.vue
@@ -1,5 +1,7 @@
 <template>
-  <div class="app-container">
+  <div class="funds-management-page">
+  <FinanceWorkspace />
+  <div v-if="false" class="app-container">
     <!-- 鎼滅储鍖� -->
     <div class="search_form">
       <el-form :inline="true" :model="searchForm">
@@ -106,12 +108,15 @@
       </template>
     </el-dialog>
   </div>
+  </div>
 </template>
 
 <script setup>
 import { ref, reactive, onMounted } from 'vue'
 import { ElMessage, ElMessageBox } from 'element-plus'
 import pagination from '@/components/PIMTable/Pagination.vue'
+import FinanceWorkspace from './FinanceWorkspace.vue'
+defineOptions({ name: 'FundsManagement' })
 import {
   getFundsPage,
   addFunds,
@@ -276,7 +281,7 @@
 }
 
 onMounted(() => {
-  getList()
+  // 鏂扮増璧勯噾宸ヤ綔鍖鸿嚜琛屽姞杞戒笁涓笟鍔″垎鍖虹殑鏁版嵁銆�
 })
 </script>
 
diff --git a/src/views/inventoryManagement/index.vue b/src/views/inventoryManagement/index.vue
index f371e26..b39bc62 100644
--- a/src/views/inventoryManagement/index.vue
+++ b/src/views/inventoryManagement/index.vue
@@ -141,7 +141,6 @@
 import {listPage, deleteLedger, update} from "@/api/lavorissce/ledger.js";
 import {ElMessageBox, ElMessage} from "element-plus";
 const { proxy } = getCurrentInstance();
-import { getCurrentMonth } from "@/utils/util"
 
 const page = ref({
 	current: 1,

--
Gitblit v1.9.3