From 792cae2ccf15b3c9bd56a319bc0685b3e9b0fd4c Mon Sep 17 00:00:00 2001
From: yyb <995253665@qq.com>
Date: 星期二, 12 五月 2026 15:23:23 +0800
Subject: [PATCH] Merge branch 'dev_NEW_pro' of http://114.132.189.42:9002/r/product-inventory-management into dev_NEW_pro

---
 src/api/financialManagement/intangibleAsset.js            |   50 ++
 src/api/financialManagement/voucher.js                    |   54 ++
 src/api/financialManagement/fixedAsset.js                 |   50 ++
 src/views/financialManagement/generalLedger/index.vue     |   74 ++
 src/views/financialManagement/assets/intangibleAssets.vue |  157 +++---
 src/views/financialManagement/voucher/index.vue           |  333 +++++++++----
 FINANCIAL_MANAGEMENT_BACKEND_SPEC.md                      |  233 ++++++++++
 src/api/financialManagement/ledger.js                     |   19 
 src/views/financialManagement/voucher/detailLedger.vue    |  103 ++--
 src/views/financialManagement/assets/fixedAssets.vue      |  149 +++---
 src/views/financialManagement/voucher/generalLedger.vue   |  107 ++--
 11 files changed, 952 insertions(+), 377 deletions(-)

diff --git a/FINANCIAL_MANAGEMENT_BACKEND_SPEC.md b/FINANCIAL_MANAGEMENT_BACKEND_SPEC.md
new file mode 100644
index 0000000..ee7e5b7
--- /dev/null
+++ b/FINANCIAL_MANAGEMENT_BACKEND_SPEC.md
@@ -0,0 +1,233 @@
+# 璐㈠姟绠$悊鍚庣鏂囨。锛堜粎璐熻矗妯″潡锛�
+
+鏇存柊鏃堕棿锛�2026-05-12  
+閫傜敤鑼冨洿锛堜粎浠ヤ笅 6 涓ā鍧楋級锛�
+1. 鍥哄畾璧勪骇锛坄/financial/fixed-assets`锛�
+2. 鏃犲舰璧勪骇锛坄/financial/intangible-assets`锛�
+3. 鎬昏处绉戠洰锛坄/financial/general-ledger`锛�
+4. 鍑瘉锛坄/financial/voucher`锛�
+5. 绉戠洰鎬昏处锛坄/financial/voucher-general-ledger`锛�
+6. 绉戠洰鏄庣粏璐︼紙`/financial/voucher-detail-ledger`锛�
+
+---
+
+## 1. 缁熶竴绾﹀畾
+
+### 1.1 鍝嶅簲缁撴瀯
+```json
+{
+  "code": 200,
+  "msg": "success",
+  "data": {}
+}
+```
+
+### 1.2 鍒嗛〉缁撴瀯锛堝鏋滄槸鍒嗛〉鎺ュ彛锛�
+璇锋眰鍙傛暟寤鸿锛�
+- `current`锛堥〉鐮侊級
+- `size`锛堟瘡椤垫潯鏁帮級
+
+鍝嶅簲寤鸿锛�
+```json
+{
+  "code": 200,
+  "data": {
+    "records": [],
+    "total": 0
+  }
+}
+```
+
+### 1.3 閲戦涓庣簿搴�
+- 閲戦瀛楁寤鸿 `decimal(18,2)`銆�
+- 鍓嶅悗绔粺涓�淇濈暀涓や綅灏忔暟銆�
+
+---
+
+## 2. 妯″潡涓�锛氭�昏处绉戠洰锛堝凡鎺ョ湡瀹� API锛�
+
+鍓嶇鏂囦欢锛歚src/views/financialManagement/generalLedger/index.vue`  
+API 鏂囦欢锛歚src/api/financialManagement/accountSubject.js`
+
+### 2.1 鎺ュ彛鐜扮姸
+- `GET /accountSubject/list`
+- `POST /accountSubject/add`
+- `PUT /accountSubject/edit`
+- `DELETE /accountSubject/remove/{ids}`
+- `POST /accountSubject/export`
+
+### 2.2 瀛楁妯″瀷
+- `id`
+- `subjectCode`锛堢鐩紪鐮侊級
+- `subjectName`锛堢鐩悕绉帮級
+- `subjectType`锛堢鐩被鍨嬶級
+- `balanceDirection`锛堜綑棰濇柟鍚戯細鍊熸柟/璐锋柟锛�
+- `status`锛�0 鍚敤锛�1 绂佺敤锛�
+- `remark`
+
+### 2.3 涓氬姟瑙勫垯
+- `subjectCode`銆乣subjectName`銆乣subjectType` 蹇呭~銆�
+- 鍒犻櫎闇�瑕佸仛寮曠敤鏍¢獙锛堣嫢宸茶鍑瘉鍒嗗綍寮曠敤锛屼笉鍏佽鍒犻櫎锛夈��
+
+---
+
+## 3. 妯″潡浜岋細鍥哄畾璧勪骇锛堝綋鍓嶅墠绔负 mock锛屽緟鍚庣瀹炵幇锛�
+
+鍓嶇鏂囦欢锛歚src/views/financialManagement/assets/fixedAssets.vue`
+
+### 3.1 寤鸿鎺ュ彛
+- `GET /financial/fixedAsset/page`
+- `POST /financial/fixedAsset/add`
+- `PUT /financial/fixedAsset/update`
+- `DELETE /financial/fixedAsset/delete`
+- `POST /financial/fixedAsset/depreciate`锛堟寜鏈堣鎻愶級
+
+### 3.2 瀛楁妯″瀷
+- `id, assetCode, assetName, category, specification`
+- `purchaseDate, originalValue, usefulLife, residualRate`
+- `accumulatedDepreciation, netValue`
+- `location, department, keeper, status, remark`
+
+### 3.3 鏍稿績鍏紡锛堝繀椤讳竴鑷达級
+- `monthlyDepreciation = originalValue * (1 - residualRate/100) / (usefulLife*12)`
+- `accumulatedDepreciation += monthlyDepreciation`
+- `netValue = originalValue - accumulatedDepreciation`
+
+### 3.4 鐘舵�佸缓璁�
+- `in_use`锛堝湪鐢級
+- `idle`锛堥棽缃級
+- `repair`锛堢淮淇腑锛�
+- `scrapped`锛堟姤搴燂級
+
+---
+
+## 4. 妯″潡涓夛細鏃犲舰璧勪骇锛堝綋鍓嶅墠绔负 mock锛屽緟鍚庣瀹炵幇锛�
+
+鍓嶇鏂囦欢锛歚src/views/financialManagement/assets/intangibleAssets.vue`
+
+### 4.1 寤鸿鎺ュ彛
+- `GET /financial/intangibleAsset/page`
+- `POST /financial/intangibleAsset/add`
+- `PUT /financial/intangibleAsset/update`
+- `DELETE /financial/intangibleAsset/delete`
+- `POST /financial/intangibleAsset/amortize`锛堟寜鏈堟憡閿�锛�
+
+### 4.2 瀛楁妯″瀷
+- `id, assetCode, assetName, category, certificateNo`
+- `acquisitionDate, originalValue, amortizationPeriod, residualRate`
+- `accumulatedAmortization, netValue`
+- `validityDate, status, description, remark`
+
+### 4.3 鏍稿績鍏紡锛堝繀椤讳竴鑷达級
+- `monthlyAmortization = originalValue * (1 - residualRate/100) / (amortizationPeriod*12)`
+- `accumulatedAmortization += monthlyAmortization`
+- `netValue = originalValue - accumulatedAmortization`
+- 褰� `netValue <= 0`锛�
+  - `netValue = 0`
+  - `status = amortized`
+
+### 4.4 鐘舵�佸缓璁�
+- `in_use`锛堝湪鐢級
+- `expired`锛堝埌鏈燂級
+- `amortized`锛堝凡鎽婇攢瀹岋級
+
+---
+
+## 5. 妯″潡鍥涳細鍑瘉锛堝綋鍓嶅墠绔负 mock锛屽緟鍚庣瀹炵幇锛�
+
+鍓嶇鏂囦欢锛歚src/views/financialManagement/voucher/index.vue`
+
+### 5.1 寤鸿鎺ュ彛
+- `GET /financial/voucher/page`
+- `POST /financial/voucher/add`
+- `PUT /financial/voucher/update`
+- `POST /financial/voucher/post`锛堣繃璐︼級
+- `POST /financial/voucher/cancel`锛堜綔搴燂級
+- `GET /financial/voucher/detail/{id}`
+
+### 5.2 涓昏〃瀛楁
+- `id, voucherNo, voucherDate, summary`
+- `debit, credit, creator, status, attachmentCount, remark`
+
+### 5.3 鍒嗗綍瀛楁
+- `subjectCode, subjectName, summary, debit, credit`
+
+### 5.4 鍏抽敭鏍¢獙
+- 鍒嗗綍鑷冲皯涓�鏉℃湁鏁堣锛堢鐩笉绌猴紝涓斿�熸柟鎴栬捶鏂� > 0锛夈��
+- 鍊熻捶骞宠 锛歚sum(debit) == sum(credit)` 涓� > 0锛屼笉婊¤冻绂佹淇濆瓨銆�
+
+### 5.5 鐘舵�佹祦杞�
+- `unposted -> posted`
+- `unposted -> cancelled`
+
+---
+
+## 6. 妯″潡浜旓細绉戠洰鎬昏处锛堝綋鍓嶅墠绔负 mock锛屽緟鍚庣瀹炵幇锛�
+
+鍓嶇鏂囦欢锛歚src/views/financialManagement/voucher/generalLedger.vue`
+
+### 6.1 寤鸿鎺ュ彛
+- `GET /financial/ledger/general`
+
+### 6.2 璇锋眰鍙傛暟
+- `subjectCode`锛堟湯绾ф垨鎸囧畾绉戠洰锛�
+- `startMonth`锛圷YYY-MM锛�
+- `endMonth`锛圷YYY-MM锛�
+
+### 6.3 鍝嶅簲瀛楁
+- `date, voucherNo, summary`
+- `debit, credit, direction, balance`
+
+### 6.4 瑙勫垯
+- 浠呭湪閫夋嫨绉戠洰鍚庤繑鍥炴暟鎹��
+- 鏀寔鈥滄湡鍒濅綑棰� / 鏈湀鍚堣 / 鏈勾绱鈥濊锛堝彲閫氳繃 `rowType` 瀛楁鍖哄垎锛夈��
+
+---
+
+## 7. 妯″潡鍏細绉戠洰鏄庣粏璐︼紙褰撳墠鍓嶇涓� mock锛屽緟鍚庣瀹炵幇锛�
+
+鍓嶇鏂囦欢锛歚src/views/financialManagement/voucher/detailLedger.vue`
+
+### 7.1 寤鸿鎺ュ彛
+- `GET /financial/ledger/detail`
+
+### 7.2 璇锋眰鍙傛暟
+- `subjectCode`
+- `auxiliaryType`锛坈ustomer/supplier/department/employee/project锛�
+- `auxiliaryId`
+- `startMonth`锛圷YYY-MM锛�
+- `endMonth`锛圷YYY-MM锛�
+
+### 7.3 鍝嶅簲瀛楁
+- `date, voucherNo, summary`
+- `debit, credit, direction, balance`
+
+### 7.4 瑙勫垯
+- 鍏堥�夌鐩紝鍐嶆煡鏄庣粏銆�
+- 杈呭姪鏍哥畻鏉′欢涓哄彲閫夛紝浣嗗缓璁悗绔敮鎸佺淮搴﹁繃婊ゃ��
+
+---
+
+## 8. 鎺ㄨ崘鏈�灏忚〃璁捐锛堜粎鏈寖鍥达級
+
+- `fin_account_subject`
+- `fin_fixed_asset`
+- `fin_intangible_asset`
+- `fin_voucher`
+- `fin_voucher_entry`
+- `fin_ledger_snapshot_general`锛堝彲閫夛紝鍋氭�ц兘浼樺寲锛�
+- `fin_ledger_snapshot_detail`锛堝彲閫夛紝鍋氭�ц兘浼樺寲锛�
+
+---
+
+## 9. AI 鐢熸垚鍚庣浠诲姟椤哄簭锛堝缓璁級
+
+1. 鍏堝畬鎴� **鎬昏处绉戠洰**锛堝凡鏈� API锛屾渶绋冲畾锛夈��  
+2. 瀹屾垚 **鍑瘉 + 鍒嗗綍 + 鍊熻捶骞宠 鏍¢獙 + 鐘舵�佹祦杞�**銆�  
+3. 瀹炵幇 **绉戠洰鎬昏处 / 绉戠洰鏄庣粏璐�** 鏌ヨ銆�  
+4. 瀹炵幇 **鍥哄畾璧勪骇鎶樻棫** 涓� **鏃犲舰璧勪骇鎽婇攢**銆�  
+5. 琛ユ祴璇曪細  
+   - 鍊熻捶骞宠 鏍¢獙  
+   - 鎶樻棫/鎽婇攢鍏紡  
+   - 绉戠洰琚紩鐢ㄧ姝㈠垹闄�  
+
diff --git a/src/api/financialManagement/fixedAsset.js b/src/api/financialManagement/fixedAsset.js
new file mode 100644
index 0000000..5c28db4
--- /dev/null
+++ b/src/api/financialManagement/fixedAsset.js
@@ -0,0 +1,50 @@
+import request from "@/utils/request";
+
+// 鍥哄畾璧勪骇鍒嗛〉鏌ヨ锛坈urrent/size锛�
+export function listFixedAssetPage(params) {
+  return request({
+    url: "/financial/fixedAsset/page",
+    method: "get",
+    params,
+  });
+}
+
+// 鏂板鍥哄畾璧勪骇
+export function addFixedAsset(data) {
+  return request({
+    url: "/financial/fixedAsset/add",
+    method: "post",
+    data,
+  });
+}
+
+// 淇敼鍥哄畾璧勪骇
+export function updateFixedAsset(data) {
+  return request({
+    url: "/financial/fixedAsset/update",
+    method: "put",
+    data,
+  });
+}
+
+// 鍒犻櫎鍥哄畾璧勪骇锛堝悗绔姹� ids=1&ids=2 褰㈠紡锛�
+export function deleteFixedAsset(ids) {
+  const idList = Array.isArray(ids) ? ids : [ids];
+  const query = idList
+    .filter(id => id !== undefined && id !== null && id !== "")
+    .map(id => `ids=${encodeURIComponent(id)}`)
+    .join("&");
+  return request({
+    url: `/financial/fixedAsset/delete?${query}`,
+    method: "delete",
+  });
+}
+
+// 鎶樻棫璁℃彁锛坽} 琛ㄧず鍏ㄩ儴鍦ㄧ敤璧勪骇锛�
+export function depreciateFixedAsset(data = {}) {
+  return request({
+    url: "/financial/fixedAsset/depreciate",
+    method: "post",
+    data,
+  });
+}
diff --git a/src/api/financialManagement/intangibleAsset.js b/src/api/financialManagement/intangibleAsset.js
new file mode 100644
index 0000000..802e649
--- /dev/null
+++ b/src/api/financialManagement/intangibleAsset.js
@@ -0,0 +1,50 @@
+import request from "@/utils/request";
+
+// 鏃犲舰璧勪骇鍒嗛〉鏌ヨ锛坈urrent/size锛�
+export function listIntangibleAssetPage(params) {
+  return request({
+    url: "/financial/intangibleAsset/page",
+    method: "get",
+    params,
+  });
+}
+
+// 鏂板鏃犲舰璧勪骇
+export function addIntangibleAsset(data) {
+  return request({
+    url: "/financial/intangibleAsset/add",
+    method: "post",
+    data,
+  });
+}
+
+// 淇敼鏃犲舰璧勪骇
+export function updateIntangibleAsset(data) {
+  return request({
+    url: "/financial/intangibleAsset/update",
+    method: "put",
+    data,
+  });
+}
+
+// 鍒犻櫎鏃犲舰璧勪骇锛堝悗绔姹� ids=1&ids=2 褰㈠紡锛�
+export function deleteIntangibleAsset(ids) {
+  const idList = Array.isArray(ids) ? ids : [ids];
+  const query = idList
+    .filter(id => id !== undefined && id !== null && id !== "")
+    .map(id => `ids=${encodeURIComponent(id)}`)
+    .join("&");
+  return request({
+    url: `/financial/intangibleAsset/delete?${query}`,
+    method: "delete",
+  });
+}
+
+// 鎽婇攢璁℃彁锛坽} 琛ㄧず鍏ㄩ儴鍦ㄧ敤璧勪骇锛�
+export function amortizeIntangibleAsset(data = {}) {
+  return request({
+    url: "/financial/intangibleAsset/amortize",
+    method: "post",
+    data,
+  });
+}
diff --git a/src/api/financialManagement/ledger.js b/src/api/financialManagement/ledger.js
new file mode 100644
index 0000000..17e62fc
--- /dev/null
+++ b/src/api/financialManagement/ledger.js
@@ -0,0 +1,19 @@
+import request from "@/utils/request";
+
+// 绉戠洰鎬昏处
+export function getGeneralLedger(params) {
+  return request({
+    url: "/financial/ledger/general",
+    method: "get",
+    params,
+  });
+}
+
+// 绉戠洰鏄庣粏璐�
+export function getDetailLedger(params) {
+  return request({
+    url: "/financial/ledger/detail",
+    method: "get",
+    params,
+  });
+}
diff --git a/src/api/financialManagement/voucher.js b/src/api/financialManagement/voucher.js
new file mode 100644
index 0000000..ccb0908
--- /dev/null
+++ b/src/api/financialManagement/voucher.js
@@ -0,0 +1,54 @@
+import request from "@/utils/request";
+
+// 鍑瘉鍒嗛〉鏌ヨ锛坈urrent/size + 杩囨护鏉′欢锛�
+export function listVoucherPage(params) {
+  return request({
+    url: "/financial/voucher/page",
+    method: "get",
+    params,
+  });
+}
+
+// 鏂板鍑瘉
+export function addVoucher(data) {
+  return request({
+    url: "/financial/voucher/add",
+    method: "post",
+    data,
+  });
+}
+
+// 淇敼鍑瘉锛堜粎鏈繃璐︼級
+export function updateVoucher(data) {
+  return request({
+    url: "/financial/voucher/update",
+    method: "put",
+    data,
+  });
+}
+
+// 杩囪处
+export function postVoucher(data) {
+  return request({
+    url: "/financial/voucher/post",
+    method: "post",
+    data,
+  });
+}
+
+// 浣滃簾
+export function cancelVoucher(data) {
+  return request({
+    url: "/financial/voucher/cancel",
+    method: "post",
+    data,
+  });
+}
+
+// 璇︽儏
+export function getVoucherDetail(id) {
+  return request({
+    url: `/financial/voucher/detail/${id}`,
+    method: "get",
+  });
+}
diff --git a/src/views/financialManagement/assets/fixedAssets.vue b/src/views/financialManagement/assets/fixedAssets.vue
index 61eb245..58b2210 100644
--- a/src/views/financialManagement/assets/fixedAssets.vue
+++ b/src/views/financialManagement/assets/fixedAssets.vue
@@ -189,6 +189,13 @@
 import { ref, reactive, onMounted, computed } from "vue";
 import { ElMessage, ElMessageBox } from "element-plus";
 import FormDialog from "@/components/Dialog/FormDialog.vue";
+import {
+  listFixedAssetPage,
+  addFixedAsset,
+  updateFixedAsset,
+  deleteFixedAsset,
+  depreciateFixedAsset,
+} from "@/api/financialManagement/fixedAsset";
 
 defineOptions({
   name: "鍥哄畾璧勪骇",
@@ -210,13 +217,13 @@
 const columns = [
   { label: "璧勪骇缂栧彿", prop: "assetCode", width: "130" },
   { label: "璧勪骇鍚嶇О", prop: "assetName", width: "150" },
-  { label: "璧勪骇绫诲埆", prop: "category", slot: "category" },
+  { label: "璧勪骇绫诲埆", prop: "category", dataType: "slot", slot: "category" },
   { label: "瑙勬牸鍨嬪彿", prop: "specification", width: "120" },
-  { label: "璧勪骇鍘熷��", prop: "originalValue", slot: "originalValue" },
-  { label: "绱鎶樻棫", prop: "accumulatedDepreciation", slot: "accumulatedDepreciation" },
-  { label: "璧勪骇鍑�鍊�", prop: "netValue", slot: "netValue" },
-  { label: "鐘舵��", prop: "status", slot: "status" },
-  { label: "鎿嶄綔", prop: "operation", slot: "operation", width: "180", fixed: "right" },
+  { label: "璧勪骇鍘熷��", prop: "originalValue", dataType: "slot", slot: "originalValue" },
+  { label: "绱鎶樻棫", prop: "accumulatedDepreciation", dataType: "slot", slot: "accumulatedDepreciation" },
+  { label: "璧勪骇鍑�鍊�", prop: "netValue", dataType: "slot", slot: "netValue" },
+  { label: "鐘舵��", prop: "status", dataType: "slot", slot: "status" },
+  { label: "鎿嶄綔", prop: "operation", dataType: "slot", slot: "operation", width: "180", fixed: "right" },
 ];
 
 const dataList = ref([]);
@@ -226,7 +233,7 @@
 const isEdit = ref(false);
 const currentId = ref(null);
 
-const form = reactive({
+const createDefaultForm = () => ({
   assetCode: "",
   assetName: "",
   category: "",
@@ -244,6 +251,10 @@
   remark: "",
 });
 
+const form = reactive({
+  ...createDefaultForm(),
+});
+
 const rules = {
   assetName: [{ required: true, message: "璇疯緭鍏ヨ祫浜у悕绉�", trigger: "blur" }],
   category: [{ required: true, message: "璇烽�夋嫨璧勪骇绫诲埆", trigger: "change" }],
@@ -251,13 +262,6 @@
   originalValue: [{ required: true, message: "璇疯緭鍏ヨ祫浜у師鍊�", trigger: "blur" }],
   usefulLife: [{ required: true, message: "璇疯緭鍏ヤ娇鐢ㄥ勾闄�", trigger: "blur" }],
 };
-
-const mockData = [
-  { id: 1, assetCode: "GD2024001", assetName: "鍔炲叕鐢佃剳", category: "electronic", specification: "鑱旀兂ThinkPad X1", purchaseDate: "2023-01-15", originalValue: 8000, usefulLife: 5, residualRate: 5, accumulatedDepreciation: 1520, netValue: 6480, location: "鍔炲叕瀹�", department: "璐㈠姟閮�", keeper: "寮犱笁", status: "in_use", remark: "" },
-  { id: 2, assetCode: "GD2024002", assetName: "鎵撳嵃鏈�", category: "electronic", specification: "鎯犳櫘M479fdw", purchaseDate: "2023-03-20", originalValue: 3500, usefulLife: 5, residualRate: 5, accumulatedDepreciation: 532, netValue: 2968, location: "鏂囧嵃瀹�", department: "琛屾斂閮�", keeper: "鏉庡洓", status: "in_use", remark: "" },
-  { id: 3, assetCode: "GD2024003", assetName: "鍔炲叕妗屾", category: "furniture", specification: "瀹炴湪鍔炲叕妗�", purchaseDate: "2023-06-10", originalValue: 2500, usefulLife: 10, residualRate: 5, accumulatedDepreciation: 118.75, netValue: 2381.25, location: "鍔炲叕瀹�", department: "閿�鍞儴", keeper: "鐜嬩簲", status: "in_use", remark: "" },
-  { id: 4, assetCode: "GD2024004", assetName: "鍟嗗姟杞�", category: "vehicle", specification: "鍒厠GL8", purchaseDate: "2022-08-01", originalValue: 280000, usefulLife: 10, residualRate: 5, accumulatedDepreciation: 53200, netValue: 226800, location: "鍋滆溅鍦�", department: "琛屾斂閮�", keeper: "璧靛叚", status: "in_use", remark: "" },
-];
 
 const totalOriginalValue = computed(() => {
   return dataList.value.reduce((sum, item) => sum + Number(item.originalValue), 0);
@@ -288,35 +292,39 @@
 };
 
 const getStatusLabel = (status) => {
-  const map = { in_use: "鍦ㄧ敤", idle: "闂茬疆", scrapped: "鎶ュ簾" };
-  return map[status] || status;
+  const key = String(status || "").toLowerCase();
+  const map = { in_use: "鍦ㄧ敤", idle: "闂茬疆", repair: "缁翠慨涓�", scrapped: "鎶ュ簾" };
+  return map[key] || status;
 };
 
 const getStatusType = (status) => {
-  const map = { in_use: "success", idle: "warning", scrapped: "info" };
-  return map[status] || "";
+  const key = String(status || "").toLowerCase();
+  const map = { in_use: "success", idle: "warning", repair: "warning", scrapped: "info" };
+  return map[key] || "";
 };
 
 const calculateNetValue = () => {
-  form.netValue = Number((form.originalValue - form.accumulatedDepreciation).toFixed(2));
+  const originalValue = Number(form.originalValue || 0);
+  const accumulatedDepreciation = Number(form.accumulatedDepreciation || 0);
+  form.netValue = Number((originalValue - accumulatedDepreciation).toFixed(2));
 };
 
-const getTableData = () => {
-  let result = [...mockData];
-  if (filters.assetCode) {
-    result = result.filter(item => item.assetCode.includes(filters.assetCode));
+// 鑱旇皟绾﹀畾锛氬垎椤靛弬鏁板浐瀹氫负 current/size锛岃繑鍥� data.records/data.total
+const getTableData = async () => {
+  try {
+    const { data } = await listFixedAssetPage({
+      current: pagination.currentPage,
+      size: pagination.pageSize,
+      assetCode: filters.assetCode,
+      assetName: filters.assetName,
+      category: filters.category,
+      status: filters.status,
+    });
+    dataList.value = data?.records || [];
+    pagination.total = Number(data?.total || 0);
+  } catch (error) {
+    // 鎻愮ず鐢卞叏灞�璇锋眰鎷︽埅鍣ㄥ鐞嗭紝杩欓噷浠呴槻姝㈡湭鎹曡幏寮傚父
   }
-  if (filters.assetName) {
-    result = result.filter(item => item.assetName.includes(filters.assetName));
-  }
-  if (filters.category) {
-    result = result.filter(item => item.category === filters.category);
-  }
-  if (filters.status) {
-    result = result.filter(item => item.status === filters.status);
-  }
-  pagination.total = result.length;
-  dataList.value = result.slice((pagination.currentPage - 1) * pagination.pageSize, pagination.currentPage * pagination.pageSize);
 };
 
 const resetFilters = () => {
@@ -334,25 +342,15 @@
   getTableData();
 };
 
+const buildAssetCode = () => `GD${Date.now().toString().slice(-10)}`;
+
 const add = () => {
   isEdit.value = false;
+  currentId.value = null;
   dialogTitle.value = "鏂板鍥哄畾璧勪骇";
-  Object.assign(form, {
-    assetCode: "GD" + Date.now().toString().slice(-8),
-    assetName: "",
-    category: "",
-    specification: "",
+  Object.assign(form, createDefaultForm(), {
+    assetCode: buildAssetCode(),
     purchaseDate: new Date().toISOString().split('T')[0],
-    originalValue: 0,
-    usefulLife: 5,
-    residualRate: 5,
-    accumulatedDepreciation: 0,
-    netValue: 0,
-    location: "",
-    department: "",
-    keeper: "",
-    status: "in_use",
-    remark: "",
   });
   dialogVisible.value = true;
 };
@@ -361,7 +359,7 @@
   isEdit.value = true;
   currentId.value = row.id;
   dialogTitle.value = "缂栬緫鍥哄畾璧勪骇";
-  Object.assign(form, row);
+  Object.assign(form, createDefaultForm(), row);
   dialogVisible.value = true;
 };
 
@@ -374,13 +372,14 @@
     confirmButtonText: "纭畾",
     cancelButtonText: "鍙栨秷",
     type: "warning",
-  }).then(() => {
-    const index = mockData.findIndex(item => item.id === row.id);
-    if (index !== -1) {
-      mockData.splice(index, 1);
+  }).then(async () => {
+    // 鑱旇皟绾﹀畾锛氬垹闄ゆ帴鍙d娇鐢� ids=1&ids=2
+    await deleteFixedAsset([row.id]);
+    if (dataList.value.length === 1 && pagination.currentPage > 1) {
+      pagination.currentPage -= 1;
     }
     ElMessage.success("鍒犻櫎鎴愬姛");
-    getTableData();
+    await getTableData();
   });
 };
 
@@ -389,16 +388,10 @@
     confirmButtonText: "纭",
     cancelButtonText: "鍙栨秷",
     type: "info",
-  }).then(() => {
-    mockData.forEach(item => {
-      if (item.status === "in_use") {
-        const monthlyDepreciation = (item.originalValue * (1 - item.residualRate / 100)) / (item.usefulLife * 12);
-        item.accumulatedDepreciation = Number((item.accumulatedDepreciation + monthlyDepreciation).toFixed(2));
-        item.netValue = Number((item.originalValue - item.accumulatedDepreciation).toFixed(2));
-      }
-    });
+  }).then(async () => {
+    await depreciateFixedAsset({});
     ElMessage.success("鎶樻棫璁℃彁瀹屾垚");
-    getTableData();
+    await getTableData();
   });
 };
 
@@ -407,22 +400,24 @@
 };
 
 const submitForm = () => {
-  formRef.value.validate((valid) => {
+  formRef.value.validate(async valid => {
     if (valid) {
-      calculateNetValue();
-      if (isEdit.value) {
-        const index = mockData.findIndex(item => item.id === currentId.value);
-        if (index !== -1) {
-          mockData[index] = { ...mockData[index], ...form };
+      try {
+        calculateNetValue();
+        const payload = { ...form };
+        if (isEdit.value) {
+          payload.id = currentId.value;
+          await updateFixedAsset(payload);
+          ElMessage.success("缂栬緫鎴愬姛");
+        } else {
+          await addFixedAsset(payload);
+          ElMessage.success("鏂板鎴愬姛");
         }
-        ElMessage.success("缂栬緫鎴愬姛");
-      } else {
-        const newId = mockData.length > 0 ? Math.max(...mockData.map(item => item.id)) + 1 : 1;
-        mockData.push({ id: newId, ...form });
-        ElMessage.success("鏂板鎴愬姛");
+        dialogVisible.value = false;
+        await getTableData();
+      } catch (error) {
+        // 鎻愮ず鐢卞叏灞�璇锋眰鎷︽埅鍣ㄥ鐞嗭紝杩欓噷浠呴槻姝㈡湭鎹曡幏寮傚父
       }
-      dialogVisible.value = false;
-      getTableData();
     }
   });
 };
diff --git a/src/views/financialManagement/assets/intangibleAssets.vue b/src/views/financialManagement/assets/intangibleAssets.vue
index 14dae55..649ec5b 100644
--- a/src/views/financialManagement/assets/intangibleAssets.vue
+++ b/src/views/financialManagement/assets/intangibleAssets.vue
@@ -182,6 +182,13 @@
 import { ref, reactive, onMounted, computed } from "vue";
 import { ElMessage, ElMessageBox } from "element-plus";
 import FormDialog from "@/components/Dialog/FormDialog.vue";
+import {
+  listIntangibleAssetPage,
+  addIntangibleAsset,
+  updateIntangibleAsset,
+  deleteIntangibleAsset,
+  amortizeIntangibleAsset,
+} from "@/api/financialManagement/intangibleAsset";
 
 defineOptions({
   name: "鏃犲舰璧勪骇",
@@ -203,13 +210,13 @@
 const columns = [
   { label: "璧勪骇缂栧彿", prop: "assetCode", width: "130" },
   { label: "璧勪骇鍚嶇О", prop: "assetName", width: "150" },
-  { label: "璧勪骇绫诲埆", prop: "category", slot: "category" },
+  { label: "璧勪骇绫诲埆", prop: "category", dataType: "slot", slot: "category" },
   { label: "璇佷功缂栧彿", prop: "certificateNo", width: "150" },
-  { label: "璧勪骇鍘熷��", prop: "originalValue", slot: "originalValue" },
-  { label: "绱鎽婇攢", prop: "accumulatedAmortization", slot: "accumulatedAmortization" },
-  { label: "璧勪骇鍑�鍊�", prop: "netValue", slot: "netValue" },
-  { label: "鐘舵��", prop: "status", slot: "status" },
-  { label: "鎿嶄綔", prop: "operation", slot: "operation", width: "180", fixed: "right" },
+  { label: "璧勪骇鍘熷��", prop: "originalValue", dataType: "slot", slot: "originalValue" },
+  { label: "绱鎽婇攢", prop: "accumulatedAmortization", dataType: "slot", slot: "accumulatedAmortization" },
+  { label: "璧勪骇鍑�鍊�", prop: "netValue", dataType: "slot", slot: "netValue" },
+  { label: "鐘舵��", prop: "status", dataType: "slot", slot: "status" },
+  { label: "鎿嶄綔", prop: "operation", dataType: "slot", slot: "operation", width: "180", fixed: "right" },
 ];
 
 const dataList = ref([]);
@@ -219,7 +226,7 @@
 const isEdit = ref(false);
 const currentId = ref(null);
 
-const form = reactive({
+const createDefaultForm = () => ({
   assetCode: "",
   assetName: "",
   category: "",
@@ -236,6 +243,10 @@
   remark: "",
 });
 
+const form = reactive({
+  ...createDefaultForm(),
+});
+
 const rules = {
   assetName: [{ required: true, message: "璇疯緭鍏ヨ祫浜у悕绉�", trigger: "blur" }],
   category: [{ required: true, message: "璇烽�夋嫨璧勪骇绫诲埆", trigger: "change" }],
@@ -243,13 +254,6 @@
   originalValue: [{ required: true, message: "璇疯緭鍏ヨ祫浜у師鍊�", trigger: "blur" }],
   amortizationPeriod: [{ required: true, message: "璇疯緭鍏ユ憡閿�骞撮檺", trigger: "blur" }],
 };
-
-const mockData = [
-  { id: 1, assetCode: "WX2024001", assetName: "ERP杞欢璁稿彲", category: "software", certificateNo: "SW-2023-001", acquisitionDate: "2023-01-01", originalValue: 50000, amortizationPeriod: 10, residualRate: 0, accumulatedAmortization: 5000, netValue: 45000, validityDate: "2033-01-01", status: "in_use", description: "浼佷笟璧勬簮璁″垝绠$悊绯荤粺", remark: "" },
-  { id: 2, assetCode: "WX2024002", assetName: "鍙戞槑涓撳埄", category: "patent", certificateNo: "ZL202210123456.7", acquisitionDate: "2022-06-15", originalValue: 100000, amortizationPeriod: 20, residualRate: 0, accumulatedAmortization: 3750, netValue: 96250, validityDate: "2042-06-15", status: "in_use", description: "涓�绉嶆柊鍨嬬敓浜у伐鑹�", remark: "" },
-  { id: 3, assetCode: "WX2024003", assetName: "鍟嗘爣鏉�", category: "trademark", certificateNo: "TM-2023-008", acquisitionDate: "2023-03-10", originalValue: 20000, amortizationPeriod: 10, residualRate: 0, accumulatedAmortization: 1500, netValue: 18500, validityDate: "2033-03-10", status: "in_use", description: "鍏徃鍝佺墝鍟嗘爣", remark: "" },
-  { id: 4, assetCode: "WX2024004", assetName: "鍦熷湴浣跨敤鏉�", category: "land", certificateNo: "鍦熷浗鐢�(2023)绗�001鍙�", acquisitionDate: "2023-07-01", originalValue: 500000, amortizationPeriod: 50, residualRate: 0, accumulatedAmortization: 5000, netValue: 495000, validityDate: "2073-07-01", status: "in_use", description: "宸ヤ笟鐢ㄥ湴浣跨敤鏉�", remark: "" },
-];
 
 const totalOriginalValue = computed(() => {
   return dataList.value.reduce((sum, item) => sum + Number(item.originalValue), 0);
@@ -281,35 +285,44 @@
 };
 
 const getStatusLabel = (status) => {
-  const map = { in_use: "鍦ㄧ敤", idle: "闂茬疆", amortized: "宸叉憡閿�瀹屾瘯" };
-  return map[status] || status;
+  const key = String(status || "").toLowerCase();
+  const map = {
+    in_use: "鍦ㄧ敤",
+    idle: "闂茬疆",
+    expired: "宸插埌鏈�",
+    amortized: "宸叉憡閿�瀹屾瘯",
+  };
+  return map[key] || status;
 };
 
 const getStatusType = (status) => {
-  const map = { in_use: "success", idle: "warning", amortized: "info" };
-  return map[status] || "";
+  const key = String(status || "").toLowerCase();
+  const map = { in_use: "success", idle: "warning", expired: "warning", amortized: "info" };
+  return map[key] || "";
 };
 
 const calculateNetValue = () => {
-  form.netValue = Number((form.originalValue - form.accumulatedAmortization).toFixed(2));
+  const originalValue = Number(form.originalValue || 0);
+  const accumulatedAmortization = Number(form.accumulatedAmortization || 0);
+  form.netValue = Number((originalValue - accumulatedAmortization).toFixed(2));
 };
 
-const getTableData = () => {
-  let result = [...mockData];
-  if (filters.assetCode) {
-    result = result.filter(item => item.assetCode.includes(filters.assetCode));
+// 鑱旇皟绾﹀畾锛氬垎椤靛弬鏁板浐瀹氫负 current/size锛岃繑鍥� data.records/data.total
+const getTableData = async () => {
+  try {
+    const { data } = await listIntangibleAssetPage({
+      current: pagination.currentPage,
+      size: pagination.pageSize,
+      assetCode: filters.assetCode,
+      assetName: filters.assetName,
+      category: filters.category,
+      status: filters.status,
+    });
+    dataList.value = data?.records || [];
+    pagination.total = Number(data?.total || 0);
+  } catch (error) {
+    // 鎻愮ず鐢卞叏灞�璇锋眰鎷︽埅鍣ㄥ鐞嗭紝杩欓噷浠呴槻姝㈡湭鎹曡幏寮傚父
   }
-  if (filters.assetName) {
-    result = result.filter(item => item.assetName.includes(filters.assetName));
-  }
-  if (filters.category) {
-    result = result.filter(item => item.category === filters.category);
-  }
-  if (filters.status) {
-    result = result.filter(item => item.status === filters.status);
-  }
-  pagination.total = result.length;
-  dataList.value = result.slice((pagination.currentPage - 1) * pagination.pageSize, pagination.currentPage * pagination.pageSize);
 };
 
 const resetFilters = () => {
@@ -327,24 +340,15 @@
   getTableData();
 };
 
+const buildAssetCode = () => `WX${Date.now().toString().slice(-10)}`;
+
 const add = () => {
   isEdit.value = false;
+  currentId.value = null;
   dialogTitle.value = "鏂板鏃犲舰璧勪骇";
-  Object.assign(form, {
-    assetCode: "WX" + Date.now().toString().slice(-8),
-    assetName: "",
-    category: "",
-    certificateNo: "",
+  Object.assign(form, createDefaultForm(), {
+    assetCode: buildAssetCode(),
     acquisitionDate: new Date().toISOString().split('T')[0],
-    originalValue: 0,
-    amortizationPeriod: 10,
-    residualRate: 0,
-    accumulatedAmortization: 0,
-    netValue: 0,
-    validityDate: "",
-    status: "in_use",
-    description: "",
-    remark: "",
   });
   dialogVisible.value = true;
 };
@@ -353,7 +357,7 @@
   isEdit.value = true;
   currentId.value = row.id;
   dialogTitle.value = "缂栬緫鏃犲舰璧勪骇";
-  Object.assign(form, row);
+  Object.assign(form, createDefaultForm(), row);
   dialogVisible.value = true;
 };
 
@@ -366,13 +370,14 @@
     confirmButtonText: "纭畾",
     cancelButtonText: "鍙栨秷",
     type: "warning",
-  }).then(() => {
-    const index = mockData.findIndex(item => item.id === row.id);
-    if (index !== -1) {
-      mockData.splice(index, 1);
+  }).then(async () => {
+    // 鑱旇皟绾﹀畾锛氬垹闄ゆ帴鍙d娇鐢� ids=1&ids=2
+    await deleteIntangibleAsset([row.id]);
+    if (dataList.value.length === 1 && pagination.currentPage > 1) {
+      pagination.currentPage -= 1;
     }
     ElMessage.success("鍒犻櫎鎴愬姛");
-    getTableData();
+    await getTableData();
   });
 };
 
@@ -381,20 +386,10 @@
     confirmButtonText: "纭",
     cancelButtonText: "鍙栨秷",
     type: "info",
-  }).then(() => {
-    mockData.forEach(item => {
-      if (item.status === "in_use") {
-        const monthlyAmortization = (item.originalValue * (1 - item.residualRate / 100)) / (item.amortizationPeriod * 12);
-        item.accumulatedAmortization = Number((item.accumulatedAmortization + monthlyAmortization).toFixed(2));
-        item.netValue = Number((item.originalValue - item.accumulatedAmortization).toFixed(2));
-        if (item.netValue <= 0) {
-          item.status = "amortized";
-          item.netValue = 0;
-        }
-      }
-    });
+  }).then(async () => {
+    await amortizeIntangibleAsset({});
     ElMessage.success("鎽婇攢璁℃彁瀹屾垚");
-    getTableData();
+    await getTableData();
   });
 };
 
@@ -403,22 +398,24 @@
 };
 
 const submitForm = () => {
-  formRef.value.validate((valid) => {
+  formRef.value.validate(async valid => {
     if (valid) {
-      calculateNetValue();
-      if (isEdit.value) {
-        const index = mockData.findIndex(item => item.id === currentId.value);
-        if (index !== -1) {
-          mockData[index] = { ...mockData[index], ...form };
+      try {
+        calculateNetValue();
+        const payload = { ...form };
+        if (isEdit.value) {
+          payload.id = currentId.value;
+          await updateIntangibleAsset(payload);
+          ElMessage.success("缂栬緫鎴愬姛");
+        } else {
+          await addIntangibleAsset(payload);
+          ElMessage.success("鏂板鎴愬姛");
         }
-        ElMessage.success("缂栬緫鎴愬姛");
-      } else {
-        const newId = mockData.length > 0 ? Math.max(...mockData.map(item => item.id)) + 1 : 1;
-        mockData.push({ id: newId, ...form });
-        ElMessage.success("鏂板鎴愬姛");
+        dialogVisible.value = false;
+        await getTableData();
+      } catch (error) {
+        // 鎻愮ず鐢卞叏灞�璇锋眰鎷︽埅鍣ㄥ鐞嗭紝杩欓噷浠呴槻姝㈡湭鎹曡幏寮傚父
       }
-      dialogVisible.value = false;
-      getTableData();
     }
   });
 };
diff --git a/src/views/financialManagement/generalLedger/index.vue b/src/views/financialManagement/generalLedger/index.vue
index fbe2210..2d370f4 100644
--- a/src/views/financialManagement/generalLedger/index.vue
+++ b/src/views/financialManagement/generalLedger/index.vue
@@ -68,6 +68,10 @@
                :rules="rules"
                ref="formRef"
                label-width="100px">
+        <el-form-item label="鐖剁骇绉戠洰">
+          <el-input :model-value="parentSubjectLabel"
+                    disabled />
+        </el-form-item>
         <el-form-item label="绉戠洰缂栫爜"
                       prop="subjectCode">
           <el-input v-model="form.subjectCode"
@@ -201,8 +205,15 @@
       label: "鎿嶄綔",
       align: "center",
       fixed: "right",
-      width: "150",
+      width: "220",
       operation: [
+        {
+          name: "鏂板",
+          type: "primary",
+          clickFun: row => {
+            addChild(row);
+          },
+        },
         {
           name: "缂栬緫",
           type: "primary",
@@ -224,11 +235,13 @@
   const dataList = ref([]);
   const dialogVisible = ref(false);
   const dialogTitle = ref("");
+  const parentSubjectLabel = ref("椤剁骇绉戠洰");
   const formRef = ref(null);
   const isEdit = ref(false);
 
   const form = reactive({
     id: undefined,
+    parentId: null,
     subjectCode: "",
     subjectName: "",
     subjectType: "",
@@ -258,8 +271,8 @@
 
   const getTableData = () => {
     const query = {
-      pageNum: pagination.currentPage,
-      pageSize: pagination.pageSize,
+      current: pagination.currentPage,
+      size: pagination.pageSize,
       ...filters,
     };
     listAccountSubject(query).then(response => {
@@ -282,11 +295,19 @@
     getTableData();
   };
 
-  const add = () => {
-    isEdit.value = false;
-    dialogTitle.value = "鏂板绉戠洰";
+  const buildParentSubjectLabel = parentRow => {
+    if (!parentRow) {
+      return "椤剁骇绉戠洰";
+    }
+    const code = parentRow.subjectCode || "";
+    const name = parentRow.subjectName || "";
+    return `${code} ${name}`.trim();
+  };
+
+  const resetForm = ({ parentId = null, parentRow = null } = {}) => {
     Object.assign(form, {
       id: undefined,
+      parentId,
       subjectCode: "",
       subjectName: "",
       subjectType: "",
@@ -294,13 +315,54 @@
       status: 0,
       remark: "",
     });
+    parentSubjectLabel.value = buildParentSubjectLabel(parentRow);
+  };
+
+  const add = () => {
+    isEdit.value = false;
+    dialogTitle.value = "鏂板绉戠洰";
+    resetForm({ parentId: null, parentRow: null });
     dialogVisible.value = true;
+  };
+
+  const addChild = row => {
+    isEdit.value = false;
+    dialogTitle.value = "鏂板瀛愮鐩�";
+    resetForm({ parentId: row.id, parentRow: row });
+    form.subjectType = row.subjectType || "";
+    form.balanceDirection = row.balanceDirection || "鍊熸柟";
+    dialogVisible.value = true;
+  };
+
+  const findSubjectById = (nodes, id) => {
+    for (const item of nodes || []) {
+      if (item.id === id) {
+        return item;
+      }
+      if (item.children && item.children.length > 0) {
+        const found = findSubjectById(item.children, id);
+        if (found) {
+          return found;
+        }
+      }
+    }
+    return null;
   };
 
   const edit = row => {
     isEdit.value = true;
     dialogTitle.value = "缂栬緫绉戠洰";
     Object.assign(form, row);
+    form.parentId = row.parentId ?? null;
+    const parentRow =
+      row.parentId === null || row.parentId === undefined
+        ? null
+        : findSubjectById(dataList.value, row.parentId);
+    parentSubjectLabel.value = parentRow
+      ? buildParentSubjectLabel(parentRow)
+      : row.parentId
+      ? `涓婄骇ID: ${row.parentId}`
+      : buildParentSubjectLabel(null);
     dialogVisible.value = true;
   };
 
diff --git a/src/views/financialManagement/voucher/detailLedger.vue b/src/views/financialManagement/voucher/detailLedger.vue
index 7f85790..202ece1 100644
--- a/src/views/financialManagement/voucher/detailLedger.vue
+++ b/src/views/financialManagement/voucher/detailLedger.vue
@@ -43,13 +43,13 @@
         <el-table-column prop="date" label="鏃ユ湡" width="120" />
         <el-table-column prop="voucherNo" label="鍑瘉瀛楀彿" width="120" />
         <el-table-column prop="summary" label="鎽樿" min-width="200" show-overflow-tooltip />
-        <el-table-column label="鍊熸柟" width="150">
+        <el-table-column prop="debit" label="鍊熸柟" width="150">
           <template #default="{ row }">
             <span v-if="row.debit > 0" class="text-danger">楼{{ formatMoney(row.debit) }}</span>
             <span v-else>-</span>
           </template>
         </el-table-column>
-        <el-table-column label="璐锋柟" width="150">
+        <el-table-column prop="credit" label="璐锋柟" width="150">
           <template #default="{ row }">
             <span v-if="row.credit > 0" class="text-success">楼{{ formatMoney(row.credit) }}</span>
             <span v-else>-</span>
@@ -75,6 +75,8 @@
 <script setup>
 import { ref, reactive, onMounted, computed, watch } from "vue";
 import { ElMessage } from "element-plus";
+import { listAccountSubject } from "@/api/financialManagement/accountSubject";
+import { getDetailLedger } from "@/api/financialManagement/ledger";
 
 defineOptions({
   name: "绉戠洰鏄庣粏璐�",
@@ -89,36 +91,36 @@
 });
 
 const dataList = ref([]);
+const subjectOptions = ref([]);
 
-const subjectOptions = [
-  {
-    code: "1122",
-    name: "搴旀敹璐︽",
-    children: [
-      { code: "112201", name: "鍖椾含绉戞妧鏈夐檺鍏徃" },
-      { code: "112202", name: "涓婃捣璐告槗鍏徃" },
-      { code: "112203", name: "骞垮窞瀹炰笟鏈夐檺鍏徃" },
-    ],
-  },
-  {
-    code: "2202",
-    name: "搴斾粯璐︽",
-    children: [
-      { code: "220201", name: "鍖椾含鍘熸潗鏂欎緵搴斿晢" },
-      { code: "220202", name: "涓婃捣鐢靛瓙鍏冨櫒浠跺叕鍙�" },
-      { code: "220203", name: "骞垮窞鍖呰鏉愭枡鍘�" },
-    ],
-  },
-  {
-    code: "6602",
-    name: "绠$悊璐圭敤",
-    children: [
-      { code: "660201", name: "鍔炲叕璐�" },
-      { code: "660202", name: "宸梾璐�" },
-      { code: "660203", name: "涓氬姟鎷涘緟璐�" },
-    ],
-  },
+const fallbackSubjects = [
+  { code: "1122", name: "搴旀敹璐︽" },
+  { code: "2202", name: "搴斾粯璐︽" },
+  { code: "6602", name: "绠$悊璐圭敤" },
 ];
+
+const loadSubjectOptions = async () => {
+  try {
+    const { data } = await listAccountSubject({
+      current: 1,
+      size: 1000,
+    });
+    const records = data?.records || [];
+    if (records.length > 0) {
+      subjectOptions.value = records
+        .filter(item => item.subjectCode && item.subjectName)
+        .map(item => ({
+          code: item.subjectCode,
+          name: item.subjectName,
+          children: [],
+        }));
+      return;
+    }
+  } catch (error) {
+    // 鍏ㄥ眬鎷︽埅鍣ㄥ凡鎻愮ず锛屼笅闈㈣蛋鍏滃簳绉戠洰
+  }
+  subjectOptions.value = fallbackSubjects.map(item => ({ ...item, children: [] }));
+};
 
 const auxiliaryItems = computed(() => {
   const map = {
@@ -158,7 +160,7 @@
 const currentSubject = computed(() => {
   if (!filters.subject || filters.subject.length === 0) return null;
   const code = filters.subject[filters.subject.length - 1];
-  return findSubject(subjectOptions, code);
+  return findSubject(subjectOptions.value, code);
 });
 
 const findSubject = (options, code) => {
@@ -182,31 +184,24 @@
   return Number(value).toFixed(2).replace(/\B(?=(\d{3})+(?!\d))/g, ",");
 };
 
-const mockData = [
-  { date: "2024-01-01", voucherNo: "-", summary: "鏈熷垵浣欓", debit: 0, credit: 0, direction: "鍊�", balance: 10000 },
-  { date: "2024-01-05", voucherNo: "璁�-0001", summary: "閿�鍞嚭搴�", debit: 5000, credit: 0, direction: "鍊�", balance: 15000 },
-  { date: "2024-01-10", voucherNo: "璁�-0002", summary: "鏀跺埌璐ф", debit: 0, credit: 3000, direction: "鍊�", balance: 12000 },
-  { date: "2024-01-15", voucherNo: "璁�-0003", summary: "閿�鍞嚭搴�", debit: 8000, credit: 0, direction: "鍊�", balance: 20000 },
-  { date: "2024-01-20", voucherNo: "璁�-0004", summary: "閿�鍞��璐�", debit: 0, credit: 2000, direction: "鍊�", balance: 18000 },
-  { date: "2024-01-25", voucherNo: "璁�-0005", summary: "鏀跺埌璐ф", debit: 0, credit: 5000, direction: "鍊�", balance: 13000 },
-  { date: "2024-01-31", voucherNo: "-", summary: "鏈湀鍚堣", debit: 13000, credit: 10000, direction: "鍊�", balance: 13000 },
-  { date: "2024-02-01", voucherNo: "-", summary: "鏈熷垵浣欓", debit: 0, credit: 0, direction: "鍊�", balance: 13000 },
-  { date: "2024-02-10", voucherNo: "璁�-0006", summary: "閿�鍞嚭搴�", debit: 6000, credit: 0, direction: "鍊�", balance: 19000 },
-  { date: "2024-02-15", voucherNo: "璁�-0007", summary: "鏀跺埌璐ф", debit: 0, credit: 4000, direction: "鍊�", balance: 15000 },
-  { date: "2024-02-28", voucherNo: "-", summary: "鏈湀鍚堣", debit: 6000, credit: 4000, direction: "鍊�", balance: 15000 },
-  { date: "2024-03-01", voucherNo: "-", summary: "鏈熷垵浣欓", debit: 0, credit: 0, direction: "鍊�", balance: 15000 },
-  { date: "2024-03-05", voucherNo: "璁�-0008", summary: "閿�鍞嚭搴�", debit: 7000, credit: 0, direction: "鍊�", balance: 22000 },
-  { date: "2024-03-10", voucherNo: "璁�-0009", summary: "鏀跺埌璐ф", debit: 0, credit: 6000, direction: "鍊�", balance: 16000 },
-  { date: "2024-03-31", voucherNo: "-", summary: "鏈湀鍚堣", debit: 7000, credit: 6000, direction: "鍊�", balance: 16000 },
-  { date: "2024-03-31", voucherNo: "-", summary: "鏈勾绱", debit: 26000, credit: 20000, direction: "鍊�", balance: 16000 },
-];
-
-const getTableData = () => {
+// 鑱旇皟绾﹀畾锛氭槑缁嗚处鎺ュ彛鍙寜杈呭姪鏍哥畻杩囨护锛坅uxiliaryType/auxiliaryId锛�
+const getTableData = async () => {
   if (!currentSubject.value) {
     dataList.value = [];
     return;
   }
-  dataList.value = [...mockData];
+  try {
+    const { data } = await getDetailLedger({
+      subjectCode: currentSubject.value.code,
+      auxiliaryType: filters.auxiliary,
+      auxiliaryId: filters.auxiliaryItem,
+      startMonth: filters.startMonth,
+      endMonth: filters.endMonth,
+    });
+    dataList.value = Array.isArray(data) ? data : data?.records || [];
+  } catch (error) {
+    // 鎻愮ず鐢卞叏灞�璇锋眰鎷︽埅鍣ㄥ鐞嗭紝杩欓噷浠呴槻姝㈡湭鎹曡幏寮傚父
+  }
 };
 
 const resetFilters = () => {
@@ -249,8 +244,8 @@
   ElMessage.success("瀵煎嚭鎴愬姛");
 };
 
-onMounted(() => {
-  // 榛樿涓嶅姞杞芥暟鎹紝闇�瑕侀�夋嫨绉戠洰
+onMounted(async () => {
+  await loadSubjectOptions();
 });
 </script>
 
diff --git a/src/views/financialManagement/voucher/generalLedger.vue b/src/views/financialManagement/voucher/generalLedger.vue
index 5da2d70..b21feac 100644
--- a/src/views/financialManagement/voucher/generalLedger.vue
+++ b/src/views/financialManagement/voucher/generalLedger.vue
@@ -28,13 +28,13 @@
         <el-table-column prop="date" label="鏃ユ湡" width="120" />
         <el-table-column prop="voucherNo" label="鍑瘉瀛楀彿" width="120" />
         <el-table-column prop="summary" label="鎽樿" min-width="200" show-overflow-tooltip />
-        <el-table-column label="鍊熸柟" width="150">
+        <el-table-column prop="debit" label="鍊熸柟" width="150">
           <template #default="{ row }">
             <span v-if="row.debit > 0" class="text-danger">楼{{ formatMoney(row.debit) }}</span>
             <span v-else>-</span>
           </template>
         </el-table-column>
-        <el-table-column label="璐锋柟" width="150">
+        <el-table-column prop="credit" label="璐锋柟" width="150">
           <template #default="{ row }">
             <span v-if="row.credit > 0" class="text-success">楼{{ formatMoney(row.credit) }}</span>
             <span v-else>-</span>
@@ -60,6 +60,8 @@
 <script setup>
 import { ref, reactive, onMounted, computed } from "vue";
 import { ElMessage } from "element-plus";
+import { listAccountSubject } from "@/api/financialManagement/accountSubject";
+import { getGeneralLedger } from "@/api/financialManagement/ledger";
 
 defineOptions({
   name: "绉戠洰鎬昏处",
@@ -72,42 +74,47 @@
 });
 
 const dataList = ref([]);
+const subjectOptions = ref([]);
 
-const subjectOptions = [
-  {
-    code: "1001",
-    name: "搴撳瓨鐜伴噾",
-    children: [],
-  },
-  {
-    code: "1002",
-    name: "閾惰瀛樻",
-    children: [
-      { code: "100201", name: "宸ュ晢閾惰" },
-      { code: "100202", name: "寤鸿閾惰" },
-    ],
-  },
-  {
-    code: "1122",
-    name: "搴旀敹璐︽",
-    children: [],
-  },
-  {
-    code: "2202",
-    name: "搴斾粯璐︽",
-    children: [],
-  },
-  {
-    code: "6001",
-    name: "涓昏惀涓氬姟鏀跺叆",
-    children: [],
-  },
+const fallbackSubjects = [
+  { code: "1001", name: "搴撳瓨鐜伴噾" },
+  { code: "1002", name: "閾惰瀛樻" },
+  { code: "1122", name: "搴旀敹璐︽" },
+  { code: "2202", name: "搴斾粯璐︽" },
+  { code: "6001", name: "涓昏惀涓氬姟鏀跺叆" },
 ];
+
+const toCascaderTree = (nodes = []) =>
+  nodes
+    .filter(item => item.subjectCode && item.subjectName)
+    .map(item => ({
+      code: item.subjectCode,
+      name: item.subjectName,
+      children: toCascaderTree(item.children || []),
+    }));
+
+const loadSubjectOptions = async () => {
+  try {
+    const { data } = await listAccountSubject({
+      current: 1,
+      size: 1000,
+      status: 0,
+    });
+    const options = toCascaderTree(data?.records || []);
+    if (options.length > 0) {
+      subjectOptions.value = options;
+      return;
+    }
+  } catch (error) {
+    // 鍏ㄥ眬鎷︽埅鍣ㄥ凡鎻愮ず锛屼笅闈㈣蛋鍏滃簳绉戠洰
+  }
+  subjectOptions.value = fallbackSubjects.map(item => ({ ...item, children: [] }));
+};
 
 const currentSubject = computed(() => {
   if (!filters.subject || filters.subject.length === 0) return null;
   const code = filters.subject[filters.subject.length - 1];
-  return findSubject(subjectOptions, code);
+  return findSubject(subjectOptions.value, code);
 });
 
 const findSubject = (options, code) => {
@@ -126,30 +133,22 @@
   return Number(value).toFixed(2).replace(/\B(?=(\d{3})+(?!\d))/g, ",");
 };
 
-const mockData = [
-  { date: "2024-01-01", voucherNo: "-", summary: "鏈熷垵浣欓", debit: 0, credit: 0, direction: "鍊�", balance: 100000 },
-  { date: "2024-01-05", voucherNo: "璁�-0001", summary: "閿�鍞敹鍏�", debit: 5650, credit: 0, direction: "鍊�", balance: 105650 },
-  { date: "2024-01-10", voucherNo: "璁�-0002", summary: "閲囪喘鏀嚭", debit: 0, credit: 8000, direction: "鍊�", balance: 97650 },
-  { date: "2024-01-15", voucherNo: "璁�-0003", summary: "鏀跺埌璐ф", debit: 10000, credit: 0, direction: "鍊�", balance: 107650 },
-  { date: "2024-01-20", voucherNo: "璁�-0004", summary: "鏀粯璐圭敤", debit: 0, credit: 5000, direction: "鍊�", balance: 102650 },
-  { date: "2024-01-31", voucherNo: "-", summary: "鏈湀鍚堣", debit: 15650, credit: 13000, direction: "鍊�", balance: 102650 },
-  { date: "2024-02-01", voucherNo: "-", summary: "鏈熷垵浣欓", debit: 0, credit: 0, direction: "鍊�", balance: 102650 },
-  { date: "2024-02-10", voucherNo: "璁�-0005", summary: "閿�鍞敹鍏�", debit: 8000, credit: 0, direction: "鍊�", balance: 110650 },
-  { date: "2024-02-15", voucherNo: "璁�-0006", summary: "閲囪喘鏀嚭", debit: 0, credit: 12000, direction: "鍊�", balance: 98650 },
-  { date: "2024-02-28", voucherNo: "-", summary: "鏈湀鍚堣", debit: 8000, credit: 12000, direction: "鍊�", balance: 98650 },
-  { date: "2024-03-01", voucherNo: "-", summary: "鏈熷垵浣欓", debit: 0, credit: 0, direction: "鍊�", balance: 98650 },
-  { date: "2024-03-05", voucherNo: "璁�-0007", summary: "閿�鍞敹鍏�", debit: 12000, credit: 0, direction: "鍊�", balance: 110650 },
-  { date: "2024-03-10", voucherNo: "璁�-0008", summary: "鏀粯宸ヨ祫", debit: 0, credit: 15000, direction: "鍊�", balance: 95650 },
-  { date: "2024-03-31", voucherNo: "-", summary: "鏈湀鍚堣", debit: 12000, credit: 15000, direction: "鍊�", balance: 95650 },
-  { date: "2024-03-31", voucherNo: "-", summary: "鏈勾绱", debit: 35650, credit: 40000, direction: "鍊�", balance: 95650 },
-];
-
-const getTableData = () => {
+// 鑱旇皟绾﹀畾锛氭�昏处鎺ュ彛杩斿洖琛屾暟缁勶紙rowType/date/voucherNo/summary/debit/credit/direction/balance锛�
+const getTableData = async () => {
   if (!currentSubject.value) {
     dataList.value = [];
     return;
   }
-  dataList.value = [...mockData];
+  try {
+    const { data } = await getGeneralLedger({
+      subjectCode: currentSubject.value.code,
+      startMonth: filters.startMonth,
+      endMonth: filters.endMonth,
+    });
+    dataList.value = Array.isArray(data) ? data : data?.records || [];
+  } catch (error) {
+    // 鎻愮ず鐢卞叏灞�璇锋眰鎷︽埅鍣ㄥ鐞嗭紝杩欓噷浠呴槻姝㈡湭鎹曡幏寮傚父
+  }
 };
 
 const resetFilters = () => {
@@ -190,8 +189,8 @@
   ElMessage.success("瀵煎嚭鎴愬姛");
 };
 
-onMounted(() => {
-  // 榛樿涓嶅姞杞芥暟鎹紝闇�瑕侀�夋嫨绉戠洰
+onMounted(async () => {
+  await loadSubjectOptions();
 });
 </script>
 
diff --git a/src/views/financialManagement/voucher/index.vue b/src/views/financialManagement/voucher/index.vue
index 817185c..2d34f5c 100644
--- a/src/views/financialManagement/voucher/index.vue
+++ b/src/views/financialManagement/voucher/index.vue
@@ -62,9 +62,9 @@
         </template>
         <template #operation="{ row }">
           <el-button type="primary" link @click="view(row)">鏌ョ湅</el-button>
-          <el-button type="primary" link @click="edit(row)" v-if="row.status === 'unposted'">缂栬緫</el-button>
-          <el-button type="success" link @click="handlePost(row)" v-if="row.status === 'unposted'">杩囪处</el-button>
-          <el-button type="danger" link @click="handleCancel(row)" v-if="row.status === 'unposted'">浣滃簾</el-button>
+          <el-button type="primary" link @click="edit(row)" v-if="canEditVoucher(row.status)">缂栬緫</el-button>
+          <el-button type="success" link @click="handlePost(row)" v-if="canEditVoucher(row.status)">杩囪处</el-button>
+          <el-button type="danger" link @click="handleCancel(row)" v-if="canEditVoucher(row.status)">浣滃簾</el-button>
         </template>
       </PIMTable>
     </div>
@@ -137,9 +137,18 @@
                     <el-input v-model="entry.summary" placeholder="璇疯緭鍏ユ憳瑕�" @focus="selectRow(rowIndex)" />
                   </td>
                   <td class="col-subject">
-                    <el-select v-model="entry.subjectCode" placeholder="閫夋嫨绉戠洰" filterable @change="(val) => handleSubjectChange(val, rowIndex)" @focus="selectRow(rowIndex)">
-                      <el-option v-for="item in subjectList" :key="item.code" :label="item.code + item.name" :value="item.code" />
-                    </el-select>
+                    <el-tree-select
+                      v-model="entry.subjectCode"
+                      :data="subjectTreeOptions"
+                      :props="subjectTreeSelectProps"
+                      placeholder="閫夋嫨绉戠洰"
+                      filterable
+                      check-strictly
+                      clearable
+                      :render-after-expand="false"
+                      @change="(val) => handleSubjectChange(val, rowIndex)"
+                      @focus="selectRow(rowIndex)"
+                    />
                     <div class="subject-name">{{ entry.subjectName }}</div>
                   </td>
                   <!-- 鍊熸柟11鍒� -->
@@ -205,6 +214,15 @@
 import { ref, reactive, onMounted, computed, nextTick } from "vue";
 import { ElMessage, ElMessageBox } from "element-plus";
 import FormDialog from "@/components/Dialog/FormDialog.vue";
+import { listAccountSubject } from "@/api/financialManagement/accountSubject";
+import {
+  listVoucherPage,
+  addVoucher,
+  updateVoucher,
+  postVoucher,
+  cancelVoucher,
+  getVoucherDetail,
+} from "@/api/financialManagement/voucher";
 
 defineOptions({
   name: "鍑瘉绠$悊",
@@ -227,11 +245,11 @@
   { label: "鍑瘉瀛楀彿", prop: "voucherNo", width: "120" },
   { label: "鍑瘉鏃ユ湡", prop: "voucherDate", width: "120" },
   { label: "鎽樿", prop: "summary", showOverflowTooltip: true },
-  { label: "鍊熸柟閲戦", prop: "debit", slot: "debit" },
-  { label: "璐锋柟閲戦", prop: "credit", slot: "credit" },
+  { label: "鍊熸柟閲戦", prop: "debit", dataType: "slot", slot: "debit" },
+  { label: "璐锋柟閲戦", prop: "credit", dataType: "slot", slot: "credit" },
   { label: "鍒跺崟浜�", prop: "creator", width: "100" },
-  { label: "鐘舵��", prop: "status", slot: "status" },
-  { label: "鎿嶄綔", prop: "operation", slot: "operation", width: "220", fixed: "right" },
+  { label: "鐘舵��", prop: "status", dataType: "slot", slot: "status" },
+  { label: "鎿嶄綔", prop: "operation", dataType: "slot", slot: "operation", width: "220", fixed: "right" },
 ];
 
 const dataList = ref([]);
@@ -241,25 +259,64 @@
 const isEdit = ref(false);
 const currentId = ref(null);
 
-const subjectList = [
-  { code: "1001", name: "搴撳瓨鐜伴噾" },
-  { code: "1002", name: "閾惰瀛樻" },
-  { code: "1122", name: "搴旀敹璐︽" },
-  { code: "2202", name: "搴斾粯璐︽" },
-  { code: "5001", name: "鐢熶骇鎴愭湰" },
-  { code: "6001", name: "涓昏惀涓氬姟鏀跺叆" },
-  { code: "6401", name: "涓昏惀涓氬姟鎴愭湰" },
+const fallbackSubjectTree = [
+  { subjectCode: "1001", subjectName: "搴撳瓨鐜伴噾", balanceDirection: "鍊熸柟", children: [] },
+  { subjectCode: "1002", subjectName: "閾惰瀛樻", balanceDirection: "鍊熸柟", children: [] },
+  { subjectCode: "1122", subjectName: "搴旀敹璐︽", balanceDirection: "鍊熸柟", children: [] },
+  { subjectCode: "2202", subjectName: "搴斾粯璐︽", balanceDirection: "璐锋柟", children: [] },
+  { subjectCode: "5001", subjectName: "鐢熶骇鎴愭湰", balanceDirection: "鍊熸柟", children: [] },
+  { subjectCode: "6001", subjectName: "涓昏惀涓氬姟鏀跺叆", balanceDirection: "璐锋柟", children: [] },
+  { subjectCode: "6401", subjectName: "涓昏惀涓氬姟鎴愭湰", balanceDirection: "鍊熸柟", children: [] },
 ];
 
-const form = reactive({
+const subjectTreeOptions = ref([]);
+const subjectList = ref([]);
+const subjectTreeSelectProps = {
+  children: "children",
+  label: "label",
+  value: "value",
+};
+
+const buildSubjectTreeOptions = (nodes = [], flatList = []) =>
+  (nodes || [])
+    .filter(item => item.subjectCode && item.subjectName)
+    .map(item => {
+      const balanceDirection = item.balanceDirection || "";
+      const flatItem = {
+        code: item.subjectCode,
+        name: item.subjectName,
+        balanceDirection,
+      };
+      flatList.push(flatItem);
+      return {
+        value: flatItem.code,
+        label: `${flatItem.code} ${flatItem.name}${balanceDirection ? ` [${balanceDirection}]` : ""}`,
+        children: buildSubjectTreeOptions(item.children || [], flatList),
+      };
+    });
+
+const createEmptyEntry = () => ({
+  subjectCode: "",
+  subjectName: "",
+  balanceDirection: "",
+  summary: "",
+  debit: 0,
+  credit: 0,
+});
+
+const createDefaultForm = () => ({
   voucherNo: "",
   voucherPrefix: "璁�",
   voucherNum: "",
   voucherDate: "",
   attachmentCount: 0,
-  entries: [],
+  entries: [createEmptyEntry(), createEmptyEntry()],
   creator: "寮犱笁",
   remark: "",
+});
+
+const form = reactive({
+  ...createDefaultForm(),
 });
 
 const selectedRowIndex = ref(-1);
@@ -276,12 +333,6 @@
 const rules = {
   voucherDate: [{ required: true, message: "璇烽�夋嫨鍑瘉鏃ユ湡", trigger: "change" }],
 };
-
-const mockData = [
-  { id: 1, voucherNo: "璁�-0001", voucherDate: "2024-01-15", summary: "閿�鍞敹鍏�", debit: 5650, credit: 5650, creator: "寮犱笁", status: "posted", entries: [{ subjectCode: "1002", subjectName: "閾惰瀛樻", summary: "閿�鍞敹鍏�", debit: 5650, credit: 0 }, { subjectCode: "6001", subjectName: "涓昏惀涓氬姟鏀跺叆", summary: "閿�鍞敹鍏�", debit: 0, credit: 5000 }, { subjectCode: "2221", subjectName: "搴斾氦绋庤垂", summary: "閿�椤圭◣棰�", debit: 0, credit: 650 }] },
-  { id: 2, voucherNo: "璁�-0002", voucherDate: "2024-01-16", summary: "閲囪喘鍘熸潗鏂�", debit: 9040, credit: 9040, creator: "鏉庡洓", status: "unposted", entries: [{ subjectCode: "5001", subjectName: "鐢熶骇鎴愭湰", summary: "閲囪喘鍘熸潗鏂�", debit: 8000, credit: 0 }, { subjectCode: "2221", subjectName: "搴斾氦绋庤垂", summary: "杩涢」绋庨", debit: 1040, credit: 0 }, { subjectCode: "2202", subjectName: "搴斾粯璐︽", summary: "閲囪喘鍘熸潗鏂�", debit: 0, credit: 9040 }] },
-  { id: 3, voucherNo: "璁�-0003", voucherDate: "2024-01-18", summary: "鏀粯璐ф", debit: 5000, credit: 5000, creator: "寮犱笁", status: "posted", entries: [{ subjectCode: "2202", subjectName: "搴斾粯璐︽", summary: "鏀粯璐ф", debit: 5000, credit: 0 }, { subjectCode: "1002", subjectName: "閾惰瀛樻", summary: "鏀粯璐ф", debit: 0, credit: 5000 }] },
-];
 
 const totalDebit = computed(() => {
   return dataList.value.reduce((sum, item) => sum + Number(item.debit), 0);
@@ -304,32 +355,70 @@
   return Number(value).toFixed(2).replace(/\B(?=(\d{3})+(?!\d))/g, ",");
 };
 
+const normalizeVoucherStatus = status => String(status || "").toLowerCase();
+
+const canEditVoucher = status => {
+  const key = normalizeVoucherStatus(status);
+  return key === "unposted" || status === "鏈繃璐�";
+};
+
 const getStatusLabel = (status) => {
+  const key = normalizeVoucherStatus(status);
   const map = { unposted: "鏈繃璐�", posted: "宸茶繃璐�", cancelled: "宸蹭綔搴�" };
-  return map[status] || status;
+  return map[key] || status;
 };
 
 const getStatusType = (status) => {
+  const key = normalizeVoucherStatus(status);
   const map = { unposted: "warning", posted: "success", cancelled: "info" };
-  return map[status] || "";
+  return map[key] || "";
 };
 
-const getTableData = () => {
-  let result = [...mockData];
-  if (filters.voucherNo) {
-    result = result.filter(item => item.voucherNo.includes(filters.voucherNo));
+// 鑱旇皟绾﹀畾锛氬垎椤靛弬鏁颁娇鐢� current/size锛屾棩鏈熻寖鍥存媶鍒嗕负 startDate/endDate
+const getTableData = async () => {
+  try {
+    const [startDate, endDate] =
+      filters.dateRange && filters.dateRange.length === 2 ? filters.dateRange : ["", ""];
+    const { data } = await listVoucherPage({
+      current: pagination.currentPage,
+      size: pagination.pageSize,
+      voucherNo: filters.voucherNo,
+      creator: filters.creator,
+      status: filters.status,
+      startDate,
+      endDate,
+    });
+    dataList.value = data?.records || [];
+    pagination.total = Number(data?.total || 0);
+  } catch (error) {
+    // 鎻愮ず鐢卞叏灞�璇锋眰鎷︽埅鍣ㄥ鐞嗭紝杩欓噷浠呴槻姝㈡湭鎹曡幏寮傚父
   }
-  if (filters.dateRange && filters.dateRange.length === 2) {
-    result = result.filter(item => item.voucherDate >= filters.dateRange[0] && item.voucherDate <= filters.dateRange[1]);
+};
+
+// 鍑瘉鍒嗗綍閲岀殑绉戠洰涓嬫媺涓庢�昏处绉戠洰淇濇寔涓�鑷达紝閬垮厤鎻愪氦涓嶅瓨鍦ㄧ鐩�
+const loadSubjectList = async () => {
+  try {
+    const { data } = await listAccountSubject({
+      current: 1,
+      size: 1000,
+      status: 0
+    });
+    const flatList = [];
+    const treeOptions = buildSubjectTreeOptions(data?.records || [], flatList);
+    if (treeOptions.length > 0) {
+      subjectTreeOptions.value = treeOptions;
+      subjectList.value = flatList;
+      return;
+    }
+    const fallbackFlatList = [];
+    subjectTreeOptions.value = buildSubjectTreeOptions(fallbackSubjectTree, fallbackFlatList);
+    subjectList.value = fallbackFlatList;
+  } catch (error) {
+    // 鍏ㄥ眬鎷︽埅鍣ㄥ凡鎻愮ず閿欒锛岃繖閲屼繚鐣欓粯璁ょ鐩綔涓哄厹搴�
+    const fallbackFlatList = [];
+    subjectTreeOptions.value = buildSubjectTreeOptions(fallbackSubjectTree, fallbackFlatList);
+    subjectList.value = fallbackFlatList;
   }
-  if (filters.creator) {
-    result = result.filter(item => item.creator === filters.creator);
-  }
-  if (filters.status) {
-    result = result.filter(item => item.status === filters.status);
-  }
-  pagination.total = result.length;
-  dataList.value = result.slice((pagination.currentPage - 1) * pagination.pageSize, pagination.currentPage * pagination.pageSize);
 };
 
 const resetFilters = () => {
@@ -348,7 +437,7 @@
 };
 
 const addEntry = () => {
-  form.entries.push({ subjectCode: "", subjectName: "", summary: "", debit: 0, credit: 0 });
+  form.entries.push(createEmptyEntry());
 };
 
 const selectRow = (index) => {
@@ -402,61 +491,69 @@
 };
 
 const removeEntry = (index) => {
+  if (form.entries.length <= 2) {
+    return;
+  }
   form.entries.splice(index, 1);
-  calculateTotal();
 };
 
 const handleSubjectChange = (val, index) => {
-  const subject = subjectList.find(item => item.code === val);
+  const subject = subjectList.value.find(item => item.code === val);
   if (subject) {
     form.entries[index].subjectName = subject.name;
+    form.entries[index].balanceDirection = subject.balanceDirection || "";
+  } else {
+    form.entries[index].subjectName = "";
+    form.entries[index].balanceDirection = "";
   }
-};
-
-const calculateTotal = () => {
-  // 鑷姩璁$畻锛岀敱computed灞炴�у鐞�
 };
 
 const add = () => {
   isEdit.value = false;
+  currentId.value = null;
   dialogTitle.value = "鏂板鍑瘉";
-  const nextNum = String(mockData.length + 1).padStart(2, "0");
-  Object.assign(form, {
-    voucherNo: "璁�-" + nextNum,
+  const nextNum = String((pagination.total || 0) + 1).padStart(4, "0");
+  Object.assign(form, createDefaultForm(), {
     voucherPrefix: "璁�",
     voucherNum: nextNum,
+    voucherNo: `璁�-${nextNum}`,
     voucherDate: new Date().toISOString().split('T')[0],
-    attachmentCount: 0,
-    entries: [
-      { subjectCode: "", subjectName: "", summary: "", debit: 0, credit: 0 },
-      { subjectCode: "", subjectName: "", summary: "", debit: 0, credit: 0 },
-      { subjectCode: "", subjectName: "", summary: "", debit: 0, credit: 0 },
-      { subjectCode: "", subjectName: "", summary: "", debit: 0, credit: 0 },
-    ],
-    creator: "寮犱笁",
-    remark: "",
   });
   selectedRowIndex.value = 0;
   dialogVisible.value = true;
 };
 
-const edit = (row) => {
-  isEdit.value = true;
-  currentId.value = row.id;
-  dialogTitle.value = "缂栬緫鍑瘉";
-  const parts = row.voucherNo.split('-');
-  Object.assign(form, {
-    ...row,
-    voucherPrefix: parts[0] || '璁�',
-    voucherNum: parts[1] || '',
-  });
-  if (form.entries.length < 4) {
-    while (form.entries.length < 4) {
-      form.entries.push({ subjectCode: "", subjectName: "", summary: "", debit: 0, credit: 0 });
+const edit = async row => {
+  try {
+    isEdit.value = true;
+    currentId.value = row.id;
+    dialogTitle.value = "缂栬緫鍑瘉";
+    const { data } = await getVoucherDetail(row.id);
+    const detail = data || row;
+    const parts = (detail.voucherNo || "").split("-");
+    Object.assign(form, createDefaultForm(), detail, {
+      voucherPrefix: parts[0] || "璁�",
+      voucherNum: parts[1] || "",
+      entries:
+        detail.entries?.map(item => ({
+          subjectCode: item.subjectCode || "",
+          subjectName: item.subjectName || "",
+          balanceDirection: item.balanceDirection || "",
+          summary: item.summary || "",
+          debit: Number(item.debit || 0),
+          credit: Number(item.credit || 0),
+        })) || [],
+    });
+    if (form.entries.length < 2) {
+      while (form.entries.length < 2) {
+        form.entries.push(createEmptyEntry());
+      }
     }
+    selectedRowIndex.value = 0;
+    dialogVisible.value = true;
+  } catch (error) {
+    // 鎻愮ず鐢卞叏灞�璇锋眰鎷︽埅鍣ㄥ鐞嗭紝杩欓噷浠呴槻姝㈡湭鎹曡幏寮傚父
   }
-  selectedRowIndex.value = 0;
-  dialogVisible.value = true;
 };
 
 const view = (row) => {
@@ -468,13 +565,10 @@
     confirmButtonText: "纭",
     cancelButtonText: "鍙栨秷",
     type: "info",
-  }).then(() => {
-    const index = mockData.findIndex(item => item.id === row.id);
-    if (index !== -1) {
-      mockData[index].status = "posted";
-    }
+  }).then(async () => {
+    await postVoucher({ id: row.id });
     ElMessage.success("杩囪处鎴愬姛");
-    getTableData();
+    await getTableData();
   });
 };
 
@@ -483,13 +577,10 @@
     confirmButtonText: "纭",
     cancelButtonText: "鍙栨秷",
     type: "warning",
-  }).then(() => {
-    const index = mockData.findIndex(item => item.id === row.id);
-    if (index !== -1) {
-      mockData[index].status = "cancelled";
-    }
+  }).then(async () => {
+    await cancelVoucher({ id: row.id });
     ElMessage.success("浣滃簾鎴愬姛");
-    getTableData();
+    await getTableData();
   });
 };
 
@@ -502,45 +593,74 @@
 };
 
 const submitForm = () => {
-  formRef.value.validate((valid) => {
+  formRef.value.validate(async valid => {
     if (valid) {
+      // 鍓嶇疆鏍¢獙锛氫笌鍚庣瑙勫垯瀵归綈锛屽噺灏戞棤鏁堣姹�
       if (!isBalanced.value) {
         ElMessage.error("鍊熻捶涓嶅钩琛★紝璇锋鏌ュ垎褰�");
         return;
       }
 
-      const validEntries = form.entries.filter(e => e.subjectCode && (e.debit > 0 || e.credit > 0));
+      const validEntries = form.entries.filter(
+        entry => entry.subjectCode && (Number(entry.debit) > 0 || Number(entry.credit) > 0)
+      );
+      if (validEntries.length === 0) {
+        ElMessage.error("璇疯嚦灏戝~鍐欎竴鏉℃湁鏁堝垎褰�");
+        return;
+      }
+
+      const invalidEntry = validEntries.find(
+        entry => Number(entry.debit) > 0 && Number(entry.credit) > 0
+      );
+      if (invalidEntry) {
+        ElMessage.error("鍚屼竴鍒嗗綍涓嶈兘鍚屾椂濉啓鍊熸柟鍜岃捶鏂�");
+        return;
+      }
+
       const summary = validEntries.find(e => e.debit > 0)?.summary || "";
 
       const voucherNo = `${form.voucherPrefix}-${form.voucherNum}`;
       const dataToSave = {
-        ...form,
         voucherNo,
+        voucherDate: form.voucherDate,
         summary,
+        creator: form.creator,
+        attachmentCount: Number(form.attachmentCount || 0),
+        remark: form.remark,
         debit: totalDebitEntry.value,
         credit: totalCreditEntry.value,
-        entries: validEntries,
+        entries: validEntries.map(entry => ({
+          subjectCode: entry.subjectCode,
+          subjectName: entry.subjectName,
+          summary: entry.summary,
+          debit: Number(entry.debit || 0),
+          credit: Number(entry.credit || 0),
+        })),
       };
 
-      if (isEdit.value) {
-        const index = mockData.findIndex(item => item.id === currentId.value);
-        if (index !== -1) {
-          mockData[index] = { ...mockData[index], ...dataToSave };
+      try {
+        if (isEdit.value) {
+          await updateVoucher({
+            id: currentId.value,
+            ...dataToSave,
+          });
+          ElMessage.success("缂栬緫鎴愬姛");
+        } else {
+          await addVoucher(dataToSave);
+          ElMessage.success("鏂板鎴愬姛");
         }
-        ElMessage.success("缂栬緫鎴愬姛");
-      } else {
-        const newId = mockData.length > 0 ? Math.max(...mockData.map(item => item.id)) + 1 : 1;
-        mockData.push({ id: newId, ...dataToSave, status: "unposted" });
-        ElMessage.success("鏂板鎴愬姛");
+        dialogVisible.value = false;
+        await getTableData();
+      } catch (error) {
+        // 鎻愮ず鐢卞叏灞�璇锋眰鎷︽埅鍣ㄥ鐞嗭紝杩欓噷浠呴槻姝㈡湭鎹曡幏寮傚父
       }
-      dialogVisible.value = false;
-      getTableData();
     }
   });
 };
 
-onMounted(() => {
-  getTableData();
+onMounted(async () => {
+  await loadSubjectList();
+  await getTableData();
 });
 </script>
 
@@ -780,7 +900,8 @@
     .col-subject {
       position: relative;
 
-      .el-select {
+      .el-select,
+      .el-tree-select {
         .el-input input {
           font-size: 12px;
         }

--
Gitblit v1.9.3