From ee7f1874a91a46ae219308b26ae71e7a14fb7ffe Mon Sep 17 00:00:00 2001
From: 云 <2163098428@qq.com>
Date: 星期三, 13 五月 2026 14:04:52 +0800
Subject: [PATCH] refactor(financial): 重构财务模块界面布局和功能
---
src/views/financialManagement/assets/intangibleAssets.vue | 21 +
src/views/financialManagement/voucher/detailLedger.vue | 383 +++++++++++++++--------------
src/views/financialManagement/assets/fixedAssets.vue | 21 +
src/views/financialManagement/voucher/generalLedger.vue | 326 +++++++++++++++---------
4 files changed, 438 insertions(+), 313 deletions(-)
diff --git a/src/views/financialManagement/assets/fixedAssets.vue b/src/views/financialManagement/assets/fixedAssets.vue
index c713b52..c6241c8 100644
--- a/src/views/financialManagement/assets/fixedAssets.vue
+++ b/src/views/financialManagement/assets/fixedAssets.vue
@@ -43,6 +43,7 @@
</div>
<PIMTable
rowKey="id"
+ isSelection
:column="columns"
:tableData="dataList"
:page="{
@@ -50,6 +51,7 @@
size: pagination.pageSize,
total: pagination.total,
}"
+ @selection-change="handleSelectionChange"
@pagination="changePage"
>
<template #originalValue="{ row }">
@@ -227,12 +229,18 @@
];
const dataList = ref([]);
+const multipleList = ref([]);
const dialogVisible = ref(false);
const dialogTitle = ref("");
const formRef = ref(null);
const isEdit = ref(false);
const isView = ref(false);
const currentId = ref(null);
+const selectedIds = computed(() =>
+ multipleList.value
+ .map(item => item?.id)
+ .filter(id => id !== undefined && id !== null && id !== "")
+);
const createDefaultForm = () => ({
assetCode: "",
@@ -322,10 +330,15 @@
status: filters.status,
});
dataList.value = data?.records || [];
+ multipleList.value = [];
pagination.total = Number(data?.total || 0);
} catch (error) {
// 鎻愮ず鐢卞叏灞�璇锋眰鎷︽埅鍣ㄥ鐞嗭紝杩欓噷浠呴槻姝㈡湭鎹曡幏寮傚父
}
+};
+
+const handleSelectionChange = (selectionList) => {
+ multipleList.value = selectionList;
};
const resetFilters = () => {
@@ -388,12 +401,16 @@
};
const handleDepreciation = () => {
- ElMessageBox.confirm("纭杩涜鏈湀鎶樻棫璁℃彁鍚楋紵", "鎻愮ず", {
+ const ids = selectedIds.value;
+ const confirmText = ids.length
+ ? `纭瀵归�変腑鐨� ${ids.length} 鏉¤祫浜ц繘琛屾湰鏈堟姌鏃ц鎻愬悧锛焋
+ : "纭杩涜鏈湀鎶樻棫璁℃彁鍚楋紵";
+ ElMessageBox.confirm(confirmText, "鎻愮ず", {
confirmButtonText: "纭",
cancelButtonText: "鍙栨秷",
type: "info",
}).then(async () => {
- await depreciateFixedAsset({});
+ await depreciateFixedAsset({ ids });
ElMessage.success("鎶樻棫璁℃彁瀹屾垚");
await getTableData();
});
diff --git a/src/views/financialManagement/assets/intangibleAssets.vue b/src/views/financialManagement/assets/intangibleAssets.vue
index 06b5583..47820c2 100644
--- a/src/views/financialManagement/assets/intangibleAssets.vue
+++ b/src/views/financialManagement/assets/intangibleAssets.vue
@@ -44,6 +44,7 @@
</div>
<PIMTable
rowKey="id"
+ isSelection
:column="columns"
:tableData="dataList"
:page="{
@@ -51,6 +52,7 @@
size: pagination.pageSize,
total: pagination.total,
}"
+ @selection-change="handleSelectionChange"
@pagination="changePage"
>
<template #originalValue="{ row }">
@@ -220,12 +222,18 @@
];
const dataList = ref([]);
+const multipleList = ref([]);
const dialogVisible = ref(false);
const dialogTitle = ref("");
const formRef = ref(null);
const isEdit = ref(false);
const isView = ref(false);
const currentId = ref(null);
+const selectedIds = computed(() =>
+ multipleList.value
+ .map(item => item?.id)
+ .filter(id => id !== undefined && id !== null && id !== "")
+);
const createDefaultForm = () => ({
assetCode: "",
@@ -320,10 +328,15 @@
status: filters.status,
});
dataList.value = data?.records || [];
+ multipleList.value = [];
pagination.total = Number(data?.total || 0);
} catch (error) {
// 鎻愮ず鐢卞叏灞�璇锋眰鎷︽埅鍣ㄥ鐞嗭紝杩欓噷浠呴槻姝㈡湭鎹曡幏寮傚父
}
+};
+
+const handleSelectionChange = (selectionList) => {
+ multipleList.value = selectionList;
};
const resetFilters = () => {
@@ -386,12 +399,16 @@
};
const handleAmortization = () => {
- ElMessageBox.confirm("纭杩涜鏈湀鎽婇攢璁℃彁鍚楋紵", "鎻愮ず", {
+ const ids = selectedIds.value;
+ const confirmText = ids.length
+ ? `纭瀵归�変腑鐨� ${ids.length} 鏉¤祫浜ц繘琛屾湰鏈堟憡閿�璁℃彁鍚楋紵`
+ : "纭杩涜鏈湀鎽婇攢璁℃彁鍚楋紵";
+ ElMessageBox.confirm(confirmText, "鎻愮ず", {
confirmButtonText: "纭",
cancelButtonText: "鍙栨秷",
type: "info",
}).then(async () => {
- await amortizeIntangibleAsset({});
+ await amortizeIntangibleAsset({ ids });
ElMessage.success("鎽婇攢璁℃彁瀹屾垚");
await getTableData();
});
diff --git a/src/views/financialManagement/voucher/detailLedger.vue b/src/views/financialManagement/voucher/detailLedger.vue
index 8ed7dcb..1909d0e 100644
--- a/src/views/financialManagement/voucher/detailLedger.vue
+++ b/src/views/financialManagement/voucher/detailLedger.vue
@@ -1,79 +1,80 @@
<template>
- <div class="app-container">
- <el-form :model="filters" :inline="true">
- <el-form-item label="浼氳绉戠洰:">
- <el-cascader v-model="filters.subject" :options="subjectOptions" :props="{ label: 'name', value: 'code', checkStrictly: true }" placeholder="璇烽�夋嫨浼氳绉戠洰" clearable style="width: 250px;" filterable />
- </el-form-item>
- <el-form-item label="杈呭姪鏍哥畻:">
- <el-select v-model="filters.auxiliary" placeholder="璇烽�夋嫨杈呭姪鏍哥畻" clearable style="width: 180px;">
- <el-option label="瀹㈡埛" value="customer" />
- <el-option label="渚涘簲鍟�" value="supplier" />
- <el-option label="閮ㄩ棬" value="department" />
- <el-option label="鍛樺伐" value="employee" />
- <el-option label="椤圭洰" value="project" />
- </el-select>
- </el-form-item>
- <el-form-item label="鏍哥畻瀵硅薄:">
- <el-select v-model="filters.auxiliaryItem" placeholder="璇烽�夋嫨鏍哥畻瀵硅薄" clearable style="width: 200px;" :disabled="!filters.auxiliary">
- <el-option v-for="item in auxiliaryItems" :key="item.value" :label="item.label" :value="item.value" />
- </el-select>
- </el-form-item>
- <el-form-item label="鏈熼棿:">
- <el-date-picker v-model="filters.startMonth" type="month" placeholder="寮�濮嬫湀浠�" value-format="YYYY-MM" style="width: 140px;" />
- <span style="margin: 0 10px;">鑷�</span>
- <el-date-picker v-model="filters.endMonth" type="month" placeholder="缁撴潫鏈堜唤" value-format="YYYY-MM" style="width: 140px;" />
- </el-form-item>
- <el-form-item>
- <el-button type="primary" @click="getTableData">鏌ヨ</el-button>
- <el-button @click="resetFilters">閲嶇疆</el-button>
- <el-button @click="handlePrint" icon="Printer">鎵撳嵃</el-button>
- <el-button @click="handleOut" icon="Download">瀵煎嚭</el-button>
- </el-form-item>
- </el-form>
+ <div class="app-container ledger-page">
+ <div class="ledger-layout">
+ <aside class="subject-panel">
+ <el-input v-model="subjectKeyword" placeholder="璇疯緭鍏ョ鐩悕绉�/缂栧彿" clearable prefix-icon="Search" />
+ <el-scrollbar class="subject-tree-scroll">
+ <el-tree
+ ref="subjectTreeRef"
+ :data="subjectOptions"
+ node-key="code"
+ :props="{ label: 'name', children: 'children' }"
+ highlight-current
+ default-expand-all
+ :expand-on-click-node="false"
+ :filter-node-method="filterSubjectNode"
+ @node-click="handleSubjectClick"
+ >
+ <template #default="{ data }">
+ <span class="subject-node">{{ data.code }} {{ data.name }}</span>
+ </template>
+ </el-tree>
+ </el-scrollbar>
+ </aside>
- <div class="ledger-header" v-if="currentSubject">
- <h2>绉戠洰鏄庣粏璐�</h2>
- <p>绉戠洰: {{ currentSubject.code }} {{ currentSubject.name }}</p>
- <p v-if="filters.auxiliary && filters.auxiliaryItem">杈呭姪鏍哥畻: {{ getAuxiliaryLabel() }}</p>
- <p>鏈熼棿: {{ filters.startMonth }} 鑷� {{ filters.endMonth }}</p>
+ <section class="ledger-content">
+ <el-form :model="filters" :inline="true" class="filter-form">
+ <el-form-item label="鏈熼棿:">
+ <el-date-picker v-model="filters.startMonth" type="month" placeholder="寮�濮嬫湀浠�" value-format="YYYY-MM" style="width: 140px;" />
+ <span style="margin: 0 10px;">鑷�</span>
+ <el-date-picker v-model="filters.endMonth" type="month" placeholder="缁撴潫鏈堜唤" value-format="YYYY-MM" style="width: 140px;" />
+ </el-form-item>
+ <el-form-item>
+ <el-button type="primary" @click="getTableData">鏌ヨ</el-button>
+ <el-button @click="resetFilters">閲嶇疆</el-button>
+ <el-button @click="handlePrint" icon="Printer">鎵撳嵃</el-button>
+ <el-button @click="handleOut" icon="Download">瀵煎嚭</el-button>
+ </el-form-item>
+ </el-form>
+
+ <div class="table_list">
+ <el-table :data="dataList" border style="width: 100%">
+ <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 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 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>
+ </template>
+ </el-table-column>
+ <el-table-column label="鏂瑰悜" width="80">
+ <template #default="{ row }">
+ <el-tag :type="row.direction === '鍊�' ? 'success' : 'danger'" size="small">{{ row.direction }}</el-tag>
+ </template>
+ </el-table-column>
+ <el-table-column label="浣欓" width="150">
+ <template #default="{ row }">
+ <span :class="row.balance >= 0 ? 'text-primary' : 'text-warning'">楼{{ formatMoney(Math.abs(row.balance)) }}</span>
+ </template>
+ </el-table-column>
+ </el-table>
+ </div>
+
+ <el-empty v-if="!currentSubject" description="璇烽�夋嫨浼氳绉戠洰鏌ヨ" style="margin-top: 50px;" />
+ </section>
</div>
-
- <div class="table_list">
- <el-table :data="dataList" border style="width: 100%" show-summary :summary-method="getSummaries">
- <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 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 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>
- </template>
- </el-table-column>
- <el-table-column label="鏂瑰悜" width="80">
- <template #default="{ row }">
- <el-tag :type="row.direction === '鍊�' ? 'success' : 'danger'" size="small">{{ row.direction }}</el-tag>
- </template>
- </el-table-column>
- <el-table-column label="浣欓" width="150">
- <template #default="{ row }">
- <span :class="row.balance >= 0 ? 'text-primary' : 'text-warning'">楼{{ formatMoney(Math.abs(row.balance)) }}</span>
- </template>
- </el-table-column>
- </el-table>
- </div>
-
- <el-empty v-if="!currentSubject" description="璇烽�夋嫨浼氳绉戠洰鏌ヨ" style="margin-top: 50px;" />
</div>
</template>
<script setup>
-import { ref, reactive, onMounted, computed, watch } from "vue";
+import { ref, reactive, onMounted, computed, watch, nextTick } from "vue";
import { ElMessage } from "element-plus";
import { listAccountSubject } from "@/api/financialManagement/accountSubject";
import { getDetailLedger } from "@/api/financialManagement/ledger";
@@ -83,15 +84,28 @@
});
const filters = reactive({
- subject: [],
- auxiliary: "",
- auxiliaryItem: "",
+ subject: "",
startMonth: "",
endMonth: "",
});
const dataList = ref([]);
const subjectOptions = ref([]);
+const subjectKeyword = ref("");
+const subjectTreeRef = ref();
+
+const getPreviousMonth = () => {
+ const date = new Date();
+ date.setDate(1);
+ date.setMonth(date.getMonth() - 1);
+ const year = date.getFullYear();
+ const month = String(date.getMonth() + 1).padStart(2, "0");
+ return `${year}-${month}`;
+};
+
+const defaultMonth = getPreviousMonth();
+filters.startMonth = defaultMonth;
+filters.endMonth = defaultMonth;
const fallbackSubjects = [
{ code: "1122", name: "搴旀敹璐︽" },
@@ -99,79 +113,14 @@
{ code: "6602", name: "绠$悊璐圭敤" },
];
-const toCascaderTree = (nodes = []) =>
+const toTree = (nodes = []) =>
nodes
.filter(item => item.subjectCode && item.subjectName)
.map(item => ({
code: item.subjectCode,
name: item.subjectName,
- children: toCascaderTree(item.children || []),
+ children: toTree(item.children || []),
}));
-
-const loadSubjectOptions = async () => {
- try {
- const { data } = await listAccountSubject({
- current: 1,
- size: 1000,
- });
- const options = toCascaderTree(data?.records || []);
- if (options.length > 0) {
- subjectOptions.value = options;
- return;
- }
- } catch (error) {
- // 鍏ㄥ眬鎷︽埅鍣ㄥ凡鎻愮ず锛屼笅闈㈣蛋鍏滃簳绉戠洰
- }
- subjectOptions.value = fallbackSubjects.map(item => ({ ...item, children: [] }));
-};
-
-const auxiliaryItems = computed(() => {
- const map = {
- customer: [
- { value: "1", label: "鍖椾含绉戞妧鏈夐檺鍏徃" },
- { value: "2", label: "涓婃捣璐告槗鍏徃" },
- { value: "3", label: "骞垮窞瀹炰笟鏈夐檺鍏徃" },
- ],
- supplier: [
- { value: "1", label: "鍖椾含鍘熸潗鏂欎緵搴斿晢" },
- { value: "2", label: "涓婃捣鐢靛瓙鍏冨櫒浠跺叕鍙�" },
- { value: "3", label: "骞垮窞鍖呰鏉愭枡鍘�" },
- ],
- department: [
- { value: "1", label: "璐㈠姟閮�" },
- { value: "2", label: "閿�鍞儴" },
- { value: "3", label: "閲囪喘閮�" },
- ],
- employee: [
- { value: "1", label: "寮犱笁" },
- { value: "2", label: "鏉庡洓" },
- { value: "3", label: "鐜嬩簲" },
- ],
- project: [
- { value: "1", label: "椤圭洰A" },
- { value: "2", label: "椤圭洰B" },
- { value: "3", label: "椤圭洰C" },
- ],
- };
- return map[filters.auxiliary] || [];
-});
-
-watch(() => filters.auxiliary, () => {
- filters.auxiliaryItem = "";
-});
-
-const currentSubject = computed(() => {
- const code = getSelectedSubjectCode(filters.subject);
- if (!code) return null;
- return findSubject(subjectOptions.value, code);
-});
-
-const getSelectedSubjectCode = (subjectValue) => {
- if (Array.isArray(subjectValue)) {
- return subjectValue.length ? subjectValue[subjectValue.length - 1] : "";
- }
- return subjectValue || "";
-};
const findSubject = (options, code) => {
for (const item of options) {
@@ -184,9 +133,68 @@
return null;
};
-const getAuxiliaryLabel = () => {
- const item = auxiliaryItems.value.find(i => i.value === filters.auxiliaryItem);
- return item ? item.label : "";
+const currentSubject = computed(() => {
+ if (!filters.subject) return null;
+ return findSubject(subjectOptions.value, filters.subject);
+});
+
+const getFirstSubjectCode = (nodes = []) => {
+ for (const item of nodes) {
+ if (item.code) return item.code;
+ if (item.children && item.children.length > 0) {
+ const childCode = getFirstSubjectCode(item.children);
+ if (childCode) return childCode;
+ }
+ }
+ return "";
+};
+
+const setDefaultSubjectSelection = async () => {
+ const firstCode = getFirstSubjectCode(subjectOptions.value);
+ if (!firstCode) {
+ filters.subject = "";
+ subjectTreeRef.value?.setCurrentKey(null);
+ return;
+ }
+ filters.subject = firstCode;
+ await nextTick();
+ subjectTreeRef.value?.setCurrentKey(firstCode);
+};
+
+const filterSubjectNode = (value, data) => {
+ const keyword = value?.trim();
+ if (!keyword) return true;
+ return `${data.code}${data.name}`.includes(keyword);
+};
+
+watch(subjectKeyword, (value) => {
+ subjectTreeRef.value?.filter(value || "");
+});
+
+const handleSubjectClick = async (data) => {
+ filters.subject = data.code;
+ await getTableData();
+};
+
+const loadSubjectOptions = async () => {
+ let options = [];
+ try {
+ const { data } = await listAccountSubject({
+ current: 1,
+ size: 1000,
+ });
+ options = toTree(data?.records || []);
+ } catch (error) {
+ // 鍏ㄥ眬鎷︽埅鍣ㄥ凡鎻愮ず锛屼笅闈㈣蛋鍏滃簳绉戠洰
+ }
+ if (options.length === 0) {
+ options = fallbackSubjects.map(item => ({ ...item, children: [] }));
+ }
+ subjectOptions.value = options;
+ await setDefaultSubjectSelection();
+ if (filters.subject) {
+ await getTableData();
+ }
};
const formatMoney = (value) => {
@@ -194,7 +202,7 @@
return Number(value).toFixed(2).replace(/\B(?=(\d{3})+(?!\d))/g, ",");
};
-// 鑱旇皟绾﹀畾锛氭槑缁嗚处鎺ュ彛鍙寜杈呭姪鏍哥畻杩囨护锛坅uxiliaryType/auxiliaryId锛�
+// 鑱旇皟绾﹀畾锛氭槑缁嗚处鎸夌鐩笌鏈熼棿杩囨护
const getTableData = async () => {
if (!currentSubject.value) {
dataList.value = [];
@@ -203,8 +211,6 @@
try {
const { data } = await getDetailLedger({
subjectCode: currentSubject.value.code,
- auxiliaryType: filters.auxiliary,
- auxiliaryId: filters.auxiliaryItem,
startMonth: filters.startMonth,
endMonth: filters.endMonth,
});
@@ -214,36 +220,16 @@
}
};
-const resetFilters = () => {
- filters.subject = [];
- filters.auxiliary = "";
- filters.auxiliaryItem = "";
- filters.startMonth = "2024-01";
- filters.endMonth = "2024-03";
+const resetFilters = async () => {
+ filters.startMonth = defaultMonth;
+ filters.endMonth = defaultMonth;
dataList.value = [];
-};
-
-const getSummaries = (param) => {
- const { columns, data } = param;
- const sums = [];
- columns.forEach((column, index) => {
- if (index === 0) {
- sums[index] = "鍚堣";
- return;
- }
- if (column.property === "debit") {
- const values = data.map(item => Number(item.debit));
- const sum = values.reduce((prev, curr) => prev + curr, 0);
- sums[index] = "楼" + formatMoney(sum);
- } else if (column.property === "credit") {
- const values = data.map(item => Number(item.credit));
- const sum = values.reduce((prev, curr) => prev + curr, 0);
- sums[index] = "楼" + formatMoney(sum);
- } else {
- sums[index] = "";
- }
- });
- return sums;
+ subjectKeyword.value = "";
+ subjectTreeRef.value?.filter("");
+ await setDefaultSubjectSelection();
+ if (filters.subject) {
+ await getTableData();
+ }
};
const handlePrint = () => {
@@ -260,16 +246,37 @@
</script>
<style lang="scss" scoped>
-.ledger-header {
- text-align: center;
- margin-bottom: 20px;
- h2 {
- margin: 0 0 10px 0;
- }
- p {
- color: #606266;
- margin: 5px 0;
- }
+.ledger-layout {
+ display: flex;
+ gap: 16px;
+}
+
+.subject-panel {
+ width: 260px;
+ flex-shrink: 0;
+ padding: 12px;
+ border: 1px solid #e4e7ed;
+ border-radius: 8px;
+ background-color: #fff;
+}
+
+.subject-tree-scroll {
+ height: 600px;
+ margin-top: 12px;
+}
+
+.subject-node {
+ display: inline-flex;
+ align-items: center;
+}
+
+.ledger-content {
+ flex: 1;
+ min-width: 0;
+}
+
+.filter-form {
+ margin-bottom: 12px;
}
.text-primary {
@@ -291,4 +298,12 @@
color: #e6a23c;
font-weight: bold;
}
+
+.subject-panel :deep(.el-tree-node__content) {
+ height: 34px;
+}
+
+.subject-panel :deep(.el-tree-node.is-current > .el-tree-node__content) {
+ background-color: #f0f7ff;
+}
</style>
diff --git a/src/views/financialManagement/voucher/generalLedger.vue b/src/views/financialManagement/voucher/generalLedger.vue
index 9ec3ea1..9683487 100644
--- a/src/views/financialManagement/voucher/generalLedger.vue
+++ b/src/views/financialManagement/voucher/generalLedger.vue
@@ -1,64 +1,80 @@
<template>
- <div class="app-container">
- <el-form :model="filters" :inline="true">
- <el-form-item label="浼氳绉戠洰:">
- <el-cascader v-model="filters.subject" :options="subjectOptions" :props="{ label: 'name', value: 'code', checkStrictly: true }" placeholder="璇烽�夋嫨浼氳绉戠洰" clearable style="width: 250px;" filterable />
- </el-form-item>
- <el-form-item label="鏈熼棿:">
- <el-date-picker v-model="filters.startMonth" type="month" placeholder="寮�濮嬫湀浠�" value-format="YYYY-MM" style="width: 140px;" />
- <span style="margin: 0 10px;">鑷�</span>
- <el-date-picker v-model="filters.endMonth" type="month" placeholder="缁撴潫鏈堜唤" value-format="YYYY-MM" style="width: 140px;" />
- </el-form-item>
- <el-form-item>
- <el-button type="primary" @click="getTableData">鏌ヨ</el-button>
- <el-button @click="resetFilters">閲嶇疆</el-button>
- <el-button @click="handlePrint" icon="Printer">鎵撳嵃</el-button>
- <el-button @click="handleOut" icon="Download">瀵煎嚭</el-button>
- </el-form-item>
- </el-form>
+ <div class="app-container ledger-page">
+ <div class="ledger-layout">
+ <aside class="subject-panel">
+ <el-input v-model="subjectKeyword" placeholder="璇疯緭鍏ョ鐩悕绉�/缂栧彿" clearable prefix-icon="Search" />
+ <el-scrollbar class="subject-tree-scroll">
+ <el-tree
+ ref="subjectTreeRef"
+ :data="subjectOptions"
+ node-key="code"
+ :props="{ label: 'name', children: 'children' }"
+ highlight-current
+ default-expand-all
+ :expand-on-click-node="false"
+ :filter-node-method="filterSubjectNode"
+ @node-click="handleSubjectClick"
+ >
+ <template #default="{ data }">
+ <span class="subject-node">{{ data.code }} {{ data.name }}</span>
+ </template>
+ </el-tree>
+ </el-scrollbar>
+ </aside>
- <div class="ledger-header" v-if="currentSubject">
- <h2>绉戠洰鎬昏处</h2>
- <p>绉戠洰: {{ currentSubject.code }} {{ currentSubject.name }}</p>
- <p>鏈熼棿: {{ filters.startMonth }} 鑷� {{ filters.endMonth }}</p>
+ <section class="ledger-content">
+ <el-form :model="filters" :inline="true" class="filter-form">
+ <el-form-item label="鏈熼棿:">
+ <el-date-picker v-model="filters.startMonth" type="month" placeholder="寮�濮嬫湀浠�" value-format="YYYY-MM" style="width: 140px;" />
+ <span style="margin: 0 10px;">鑷�</span>
+ <el-date-picker v-model="filters.endMonth" type="month" placeholder="缁撴潫鏈堜唤" value-format="YYYY-MM" style="width: 140px;" />
+ </el-form-item>
+ <el-form-item>
+ <el-button type="primary" @click="getTableData">鏌ヨ</el-button>
+ <el-button @click="resetFilters">閲嶇疆</el-button>
+ <el-button @click="handlePrint" icon="Printer">鎵撳嵃</el-button>
+ <el-button @click="handleOut" icon="Download">瀵煎嚭</el-button>
+ </el-form-item>
+ </el-form>
+
+ <div class="table_list">
+ <el-table :data="dataList" border style="width: 100%">
+ <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 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 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>
+ </template>
+ </el-table-column>
+ <el-table-column label="鏂瑰悜" width="80">
+ <template #default="{ row }">
+ <el-tag :type="row.direction === '鍊�' ? 'success' : 'danger'" size="small">{{ row.direction }}</el-tag>
+ </template>
+ </el-table-column>
+ <el-table-column label="浣欓" width="150">
+ <template #default="{ row }">
+ <span :class="row.balance >= 0 ? 'text-primary' : 'text-warning'">楼{{ formatMoney(Math.abs(row.balance)) }}</span>
+ </template>
+ </el-table-column>
+ </el-table>
+ </div>
+
+ <el-empty v-if="!currentSubject" description="璇烽�夋嫨浼氳绉戠洰鏌ヨ" style="margin-top: 50px;" />
+ </section>
</div>
-
- <div class="table_list">
- <el-table :data="dataList" border style="width: 100%" show-summary :summary-method="getSummaries">
- <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 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 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>
- </template>
- </el-table-column>
- <el-table-column label="鏂瑰悜" width="80">
- <template #default="{ row }">
- <el-tag :type="row.direction === '鍊�' ? 'success' : 'danger'" size="small">{{ row.direction }}</el-tag>
- </template>
- </el-table-column>
- <el-table-column label="浣欓" width="150">
- <template #default="{ row }">
- <span :class="row.balance >= 0 ? 'text-primary' : 'text-warning'">楼{{ formatMoney(Math.abs(row.balance)) }}</span>
- </template>
- </el-table-column>
- </el-table>
- </div>
-
- <el-empty v-if="!currentSubject" description="璇烽�夋嫨浼氳绉戠洰鏌ヨ" style="margin-top: 50px;" />
</div>
</template>
<script setup>
-import { ref, reactive, onMounted, computed } from "vue";
+import { ref, reactive, onMounted, computed, watch, nextTick } from "vue";
import { ElMessage } from "element-plus";
import { listAccountSubject } from "@/api/financialManagement/accountSubject";
import { getGeneralLedger } from "@/api/financialManagement/ledger";
@@ -68,13 +84,28 @@
});
const filters = reactive({
- subject: [],
+ subject: "",
startMonth: "",
endMonth: "",
});
const dataList = ref([]);
const subjectOptions = ref([]);
+const subjectKeyword = ref("");
+const subjectTreeRef = ref();
+
+const getPreviousMonth = () => {
+ const date = new Date();
+ date.setDate(1);
+ date.setMonth(date.getMonth() - 1);
+ const year = date.getFullYear();
+ const month = String(date.getMonth() + 1).padStart(2, "0");
+ return `${year}-${month}`;
+};
+
+const defaultMonth = getPreviousMonth();
+filters.startMonth = defaultMonth;
+filters.endMonth = defaultMonth;
const fallbackSubjects = [
{ code: "1001", name: "搴撳瓨鐜伴噾" },
@@ -84,45 +115,14 @@
{ code: "6001", name: "涓昏惀涓氬姟鏀跺叆" },
];
-const toCascaderTree = (nodes = []) =>
+const toTree = (nodes = []) =>
nodes
.filter(item => item.subjectCode && item.subjectName)
.map(item => ({
code: item.subjectCode,
name: item.subjectName,
- children: toCascaderTree(item.children || []),
+ children: toTree(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(() => {
- const code = getSelectedSubjectCode(filters.subject);
- if (!code) return null;
- return findSubject(subjectOptions.value, code);
-});
-
-const getSelectedSubjectCode = (subjectValue) => {
- if (Array.isArray(subjectValue)) {
- return subjectValue.length ? subjectValue[subjectValue.length - 1] : "";
- }
- return subjectValue || "";
-};
const findSubject = (options, code) => {
for (const item of options) {
@@ -133,6 +133,71 @@
}
}
return null;
+};
+
+const currentSubject = computed(() => {
+ if (!filters.subject) return null;
+ return findSubject(subjectOptions.value, filters.subject);
+});
+
+const getFirstSubjectCode = (nodes = []) => {
+ for (const item of nodes) {
+ if (item.code) return item.code;
+ if (item.children && item.children.length > 0) {
+ const childCode = getFirstSubjectCode(item.children);
+ if (childCode) return childCode;
+ }
+ }
+ return "";
+};
+
+const setDefaultSubjectSelection = async () => {
+ const firstCode = getFirstSubjectCode(subjectOptions.value);
+ if (!firstCode) {
+ filters.subject = "";
+ subjectTreeRef.value?.setCurrentKey(null);
+ return;
+ }
+ filters.subject = firstCode;
+ await nextTick();
+ subjectTreeRef.value?.setCurrentKey(firstCode);
+};
+
+const filterSubjectNode = (value, data) => {
+ const keyword = value?.trim();
+ if (!keyword) return true;
+ return `${data.code}${data.name}`.includes(keyword);
+};
+
+watch(subjectKeyword, (value) => {
+ subjectTreeRef.value?.filter(value || "");
+});
+
+const handleSubjectClick = async (data) => {
+ filters.subject = data.code;
+ await getTableData();
+};
+
+const loadSubjectOptions = async () => {
+ let options = [];
+ try {
+ const { data } = await listAccountSubject({
+ current: 1,
+ size: 1000,
+ status: 0,
+ });
+ options = toTree(data?.records || []);
+ } catch (error) {
+ // 鍏ㄥ眬鎷︽埅鍣ㄥ凡鎻愮ず锛屼笅闈㈣蛋鍏滃簳绉戠洰
+ }
+ if (options.length === 0) {
+ options = fallbackSubjects.map(item => ({ ...item, children: [] }));
+ }
+ subjectOptions.value = options;
+ await setDefaultSubjectSelection();
+ if (filters.subject) {
+ await getTableData();
+ }
};
const formatMoney = (value) => {
@@ -158,34 +223,16 @@
}
};
-const resetFilters = () => {
- filters.subject = [];
- filters.startMonth = "2024-01";
- filters.endMonth = "2024-03";
+const resetFilters = async () => {
+ filters.startMonth = defaultMonth;
+ filters.endMonth = defaultMonth;
dataList.value = [];
-};
-
-const getSummaries = (param) => {
- const { columns, data } = param;
- const sums = [];
- columns.forEach((column, index) => {
- if (index === 0) {
- sums[index] = "鍚堣";
- return;
- }
- if (column.property === "debit") {
- const values = data.map(item => Number(item.debit));
- const sum = values.reduce((prev, curr) => prev + curr, 0);
- sums[index] = "楼" + formatMoney(sum);
- } else if (column.property === "credit") {
- const values = data.map(item => Number(item.credit));
- const sum = values.reduce((prev, curr) => prev + curr, 0);
- sums[index] = "楼" + formatMoney(sum);
- } else {
- sums[index] = "";
- }
- });
- return sums;
+ subjectKeyword.value = "";
+ subjectTreeRef.value?.filter("");
+ await setDefaultSubjectSelection();
+ if (filters.subject) {
+ await getTableData();
+ }
};
const handlePrint = () => {
@@ -202,16 +249,37 @@
</script>
<style lang="scss" scoped>
-.ledger-header {
- text-align: center;
- margin-bottom: 20px;
- h2 {
- margin: 0 0 10px 0;
- }
- p {
- color: #606266;
- margin: 5px 0;
- }
+.ledger-layout {
+ display: flex;
+ gap: 16px;
+}
+
+.subject-panel {
+ width: 260px;
+ flex-shrink: 0;
+ padding: 12px;
+ border: 1px solid #e4e7ed;
+ border-radius: 8px;
+ background-color: #fff;
+}
+
+.subject-tree-scroll {
+ height: 600px;
+ margin-top: 12px;
+}
+
+.subject-node {
+ display: inline-flex;
+ align-items: center;
+}
+
+.ledger-content {
+ flex: 1;
+ min-width: 0;
+}
+
+.filter-form {
+ margin-bottom: 12px;
}
.text-primary {
@@ -233,4 +301,12 @@
color: #e6a23c;
font-weight: bold;
}
+
+.subject-panel :deep(.el-tree-node__content) {
+ height: 34px;
+}
+
+.subject-panel :deep(.el-tree-node.is-current > .el-tree-node__content) {
+ background-color: #f0f7ff;
+}
</style>
--
Gitblit v1.9.3