| src/views/financialManagement/assets/fixedAssets.vue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/views/financialManagement/assets/intangibleAssets.vue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/views/financialManagement/voucher/detailLedger.vue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/views/financialManagement/voucher/generalLedger.vue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 |
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(); }); 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(); }); 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, ","); }; // 联调约定:明细账接口可按辅助核算过滤(auxiliaryType/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> 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>