| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <!doctype html> |
| | | <html lang="zh-CN"> |
| | | <head> |
| | | <meta charset="UTF-8"> |
| | | <title>reconciliation repro</title></head> |
| | | <body> |
| | | <div id="app"></div> |
| | | <script type="module"> |
| | | import {createApp} from '/node_modules/.vite/deps/vue.js'; |
| | | import ElementPlus from '/node_modules/.vite/deps/element-plus.js'; |
| | | import '/node_modules/element-plus/dist/index.css'; |
| | | import ReconciliationWorkspace |
| | | from '/src/views/financialManagement/financialReconciliation/ReconciliationWorkspace.vue'; |
| | | |
| | | createApp(ReconciliationWorkspace).use(ElementPlus).mount('#app'); |
| | | </script> |
| | | </body> |
| | | </html> |
| | |
| | | data: query, |
| | | }); |
| | | } |
| | | |
| | | export function getExpenseCategoryReport(params) { |
| | | return request({ |
| | | url: "/account/accountExpense/report/category", |
| | | method: "get", |
| | | params, |
| | | }); |
| | | } |
| | |
| | | data: ids, |
| | | }); |
| | | }; |
| | | |
| | | export const getReconciliationTaskPage = (params) => request({ url: "/finance/reconciliation/task/page", method: "get", params }); |
| | | export const createReconciliationTask = (data) => request({ url: "/finance/reconciliation/task", method: "post", data }); |
| | | export const getReconciliationDiff = (id) => request({ url: `/finance/reconciliation/task/${id}/diff`, method: "get" }); |
| | | export const getReconciliationExternal = (id) => request({ url: `/finance/reconciliation/task/${id}/external`, method: "get" }); |
| | | export const handleReconciliationTask = (id, data) => request({ url: `/finance/reconciliation/task/${id}/handle`, method: "put", data }); |
| | | export const archiveReconciliationTask = (id) => request({ url: `/finance/reconciliation/task/${id}/archive`, method: "put" }); |
| | | export const exportReconciliationDiff = (taskId) => request({ url: "/finance/reconciliation/export", method: "post", data: { taskId }, responseType: "blob" }); |
| | |
| | | // æ°å¢èµéè®°å½ |
| | | export const addFunds = (data) => { |
| | | return request({ |
| | | url: "/finance/funds", |
| | | url: "/finance/funds/add", |
| | | method: "post", |
| | | data, |
| | | }); |
| | | }; |
| | | |
| | | export const transferFunds = (data) => request({ url: "/finance/funds/transfer", method: "post", data }); |
| | | |
| | | export const getFinanceAccountPage = (params) => request({ url: "/finance/account/page", method: "get", params }); |
| | | export const getFinanceAccountList = () => request({ url: "/finance/account/list", method: "get" }); |
| | | export const addFinanceAccount = (data) => request({ url: "/finance/account", method: "post", data }); |
| | | export const updateFinanceAccount = (data) => request({ url: "/finance/account", method: "put", data }); |
| | | export const deleteFinanceAccount = (ids) => request({ url: "/finance/account", method: "delete", data: ids }); |
| | | export const getFundsTrend = (params) => request({ url: "/finance/account/trend", method: "get", params }); |
| | | |
| | | export const getCashflowPlanPage = (params) => request({ url: "/finance/cashflowPlan/page", method: "get", params }); |
| | | export const addCashflowPlan = (data) => request({ url: "/finance/cashflowPlan", method: "post", data }); |
| | | export const updateCashflowPlan = (data) => request({ url: "/finance/cashflowPlan", method: "put", data }); |
| | | export const deleteCashflowPlan = (ids) => request({ url: "/finance/cashflowPlan", method: "delete", data: ids }); |
| | | export const getCashflowForecast = (params) => request({ url: "/finance/cashflowPlan/forecast", method: "get", params }); |
| | | |
| | | // æ´æ°èµéè®°å½ |
| | | export const updateFunds = (data) => { |
| | | return request({ |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <el-collapse class="report-panel"> |
| | | <el-collapse-item title="æ¯åºå¤§ç±»å æ¯ä¸è¶å¿" name="category"> |
| | | <div class="charts"> |
| | | <div ref="pieRef"></div> |
| | | <div ref="trendRef"></div> |
| | | </div> |
| | | </el-collapse-item> |
| | | </el-collapse> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, onMounted, nextTick } from "vue"; |
| | | import * as echarts from "echarts"; |
| | | import { getExpenseCategoryReport } from "@/api/financialManagement/expenseManagement"; |
| | | const pieRef = ref(), |
| | | trendRef = ref(); |
| | | onMounted(async () => { |
| | | const r = await getExpenseCategoryReport({}); |
| | | const share = r.data?.share || [], |
| | | trend = r.data?.trend || []; |
| | | await nextTick(); |
| | | echarts.init(pieRef.value).setOption({ |
| | | title: { text: "æ¯åºå¤§ç±»å æ¯", left: "center" }, |
| | | tooltip: { trigger: "item" }, |
| | | series: [ |
| | | { |
| | | type: "pie", |
| | | radius: ["35%", "68%"], |
| | | data: share.map((x) => ({ name: x.categoryName, value: x.amount })), |
| | | }, |
| | | ], |
| | | }); |
| | | const months = [...new Set(trend.map((x) => x.month))], |
| | | names = [...new Set(trend.map((x) => x.categoryName))]; |
| | | echarts.init(trendRef.value).setOption({ |
| | | title: { text: "æ¯åºå¤§ç±»æåº¦è¶å¿", left: "center" }, |
| | | tooltip: { trigger: "axis" }, |
| | | legend: { top: 28 }, |
| | | xAxis: { type: "category", data: months }, |
| | | yAxis: { type: "value" }, |
| | | series: names.map((name) => ({ |
| | | name, |
| | | type: "line", |
| | | data: months.map( |
| | | (month) => |
| | | trend.find((x) => x.month === month && x.categoryName === name) |
| | | ?.amount || 0 |
| | | ), |
| | | })), |
| | | }); |
| | | }); |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .report-panel { |
| | | margin-bottom: 12px; |
| | | } |
| | | .charts { |
| | | display: grid; |
| | | grid-template-columns: 1fr 2fr; |
| | | gap: 12px; |
| | | } |
| | | .charts > div { |
| | | height: 300px; |
| | | } |
| | | @media (max-width: 900px) { |
| | | .charts { |
| | | grid-template-columns: 1fr; |
| | | } |
| | | } |
| | | </style> |
| | |
| | | clearable |
| | | /> |
| | | </el-form-item> |
| | | <el-form-item label="æ¯åºå¤§ç±»" prop="expenseCategory"> |
| | | <el-select v-model="form.expenseCategory" placeholder="è¯·éæ©æ¯åºå¤§ç±»" clearable> |
| | | <el-option v-for="item in finance_expense_category" :key="item.value" :label="item.label" :value="item.value" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item label="æ¯åºç±»å" prop="expenseType"> |
| | | <el-select |
| | | v-model="form.expenseType" |
| | |
| | | name: "æ°å¢æ¯åº", |
| | | }); |
| | | const { expense_types } = proxy.useDict("expense_types"); |
| | | const { finance_expense_category } = proxy.useDict("finance_expense_category"); |
| | | const { checkout_payment } = proxy.useDict("checkout_payment"); |
| | | const formRef = ref(null); |
| | | const formRules = { |
| | |
| | | expenseDescribed: [{ required: true, trigger: "blur", message: "请è¾å
¥" }], |
| | | expenseDate: [{ required: true, trigger: "change", message: "è¯·éæ©" }], |
| | | expenseType: [{ required: true, trigger: "change", message: "è¯·éæ©" }], |
| | | expenseCategory: [{ required: true, trigger: "change", message: "è¯·éæ©æ¯åºå¤§ç±»" }], |
| | | expenseMethod: [{ required: true, trigger: "change", message: "è¯·éæ©" }], |
| | | } |
| | | |
| | | const { form, resetForm } = useFormData({ |
| | | expenseDate: undefined, // æ¯åºæ¥æ |
| | | expenseType: undefined, // æ¯åºç±»å |
| | | expenseCategory: undefined, // æ¯åºå¤§ç±» |
| | | supplierName: undefined, // 客æ·åç§° |
| | | expenseMoney: undefined, // æ¯åºéé¢ |
| | | expenseDescribed: undefined, // æ¯åºæè¿° |
| | |
| | | if (code == 200) { |
| | | form.expenseDate = data.expenseDate; |
| | | form.expenseType = data.expenseType; |
| | | form.expenseCategory = data.expenseCategory; |
| | | form.supplierName = data.supplierName; |
| | | form.expenseMoney = data.expenseMoney; |
| | | form.expenseDescribed = data.expenseDescribed; |
| | |
| | | <template> |
| | | <div class="app-container"> |
| | | <ExpenseCategoryReport /> |
| | | <el-form :model="filters" :inline="true"> |
| | | <el-form-item label="æ¯åºæ¥æ:"> |
| | | <el-date-picker v-model="filters.entryDate" value-format="YYYY-MM-DD" format="YYYY-MM-DD" type="daterange" |
| | | placeholder="è¯·éæ©" clearable @change="changeDaterange" /> |
| | | </el-form-item> |
| | | <el-form-item label="æ¯åºå¤§ç±»:"> |
| | | <el-select v-model="filters.expenseCategory" placeholder="è¯·éæ©" clearable style="width: 160px"> |
| | | <el-option v-for="item in finance_expense_category" :key="item.value" :label="item.label" :value="item.value" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item label="仿¬¾æ¹å¼:"> |
| | | <el-select |
| | |
| | | import { ElMessageBox, ElMessage } from "element-plus"; |
| | | import dayjs from "dayjs"; |
| | | import FilesDia from "../revenueManagement/filesDia.vue"; |
| | | import ExpenseCategoryReport from "./ExpenseCategoryReport.vue"; |
| | | |
| | | defineOptions({ |
| | | name: "æ¯åºç®¡ç", |
| | |
| | | const modalRef = ref(); |
| | | const { checkout_payment } = proxy.useDict("checkout_payment"); |
| | | const { expense_types } = proxy.useDict("expense_types"); |
| | | const { finance_expense_category } = proxy.useDict("finance_expense_category"); |
| | | const filesDia = ref() |
| | | |
| | | const { |
| | |
| | | listPage, |
| | | { |
| | | expenseMethod: undefined, |
| | | expenseCategory: undefined, |
| | | entryDate: undefined, |
| | | }, |
| | | [ |
| | |
| | | prop: "expenseDate", |
| | | }, |
| | | { |
| | | label: "æ¯åºå¤§ç±»", |
| | | align: "center", |
| | | prop: "expenseCategory", |
| | | dataType: "tag", |
| | | formatData: (params) => finance_expense_category.value.find((m) => m.value == params)?.label || "æªåç±»", |
| | | }, |
| | | { |
| | | label: "æ¯åºç±»å", |
| | | align: "center", |
| | | prop: "expenseType", |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div class="reconciliation-workspace"> |
| | | <div class="workspace-heading"> |
| | | <h2>è´¢å¡å¯¹è´¦</h2> |
| | | <p>æ¯å¯¹æ¶æ¯ãèµéæµæ°´ä¸å¤é¨åè¯ï¼è®°å½å·®å¼å¤çæ¹æ¡å¹¶å½æ¡£ã</p> |
| | | </div> |
| | | <el-form inline :model="query"> |
| | | <el-form-item label="对账类å" |
| | | ><el-select v-model="query.reconType" clearable style="width: 150px" |
| | | ><el-option |
| | | v-for="(label, key) in reconTypeText" |
| | | :key="key" |
| | | :label="label" |
| | | :value="key" /></el-select |
| | | ></el-form-item> |
| | | <el-form-item label="ç¶æ" |
| | | ><el-select v-model="query.status" clearable style="width: 130px" |
| | | ><el-option |
| | | v-for="(label, key) in statusText" |
| | | :key="key" |
| | | :label="label" |
| | | :value="key" /></el-select |
| | | ></el-form-item> |
| | | <el-form-item label="对象" |
| | | ><el-input v-model="query.target" clearable |
| | | /></el-form-item> |
| | | <el-form-item |
| | | ><el-button type="primary" @click="loadTasks">æ¥è¯¢</el-button |
| | | ><el-button @click="openCreate">å起对账</el-button></el-form-item |
| | | > |
| | | </el-form> |
| | | |
| | | <el-alert |
| | | v-if="loadError" |
| | | :title="loadError" |
| | | type="error" |
| | | show-icon |
| | | :closable="false" |
| | | class="load-error" |
| | | /> |
| | | <el-table |
| | | :data="tasks" |
| | | border |
| | | v-loading="loading" |
| | | empty-text="ææ å¯¹è´¦ä»»å¡ï¼å¯ç¹å»âå起对账âå建" |
| | | > |
| | | <el-table-column prop="id" label="ä»»å¡å·" width="80" /> |
| | | <el-table-column label="ç±»å" |
| | | ><template #default="{ row }">{{ |
| | | reconTypeText[row.reconType] |
| | | }}</template></el-table-column |
| | | > |
| | | <el-table-column label="æé´" min-width="190" |
| | | ><template #default="{ row }" |
| | | >{{ row.periodStart }} è³ {{ row.periodEnd }}</template |
| | | ></el-table-column |
| | | > |
| | | <el-table-column prop="target" label="对账对象" /> |
| | | <el-table-column prop="systemAmount" label="ç³»ç»éé¢" /> |
| | | <el-table-column prop="externalAmount" label="对æ¹éé¢" /> |
| | | <el-table-column prop="diffAmount" label="å·®é¢" /> |
| | | <el-table-column label="ç»æ" |
| | | ><template #default="{ row }" |
| | | ><el-tag :type="row.result === '0' ? 'success' : 'danger'">{{ |
| | | row.result === "0" ? "ä¸è´" : "æå·®å¼" |
| | | }}</el-tag></template |
| | | ></el-table-column |
| | | > |
| | | <el-table-column label="ç¶æ" |
| | | ><template #default="{ row }">{{ |
| | | statusText[row.status] |
| | | }}</template></el-table-column |
| | | > |
| | | <el-table-column label="æä½" width="260" fixed="right" |
| | | ><template #default="{ row }"> |
| | | <el-button link type="primary" @click="showDiff(row)" |
| | | >差弿ç»</el-button |
| | | > |
| | | <el-button |
| | | link |
| | | type="primary" |
| | | :disabled="row.status === '2'" |
| | | @click="openHandle(row)" |
| | | >å¤ç</el-button |
| | | > |
| | | <el-button |
| | | link |
| | | type="success" |
| | | :disabled="row.status === '2'" |
| | | @click="archive(row)" |
| | | >彿¡£</el-button |
| | | > |
| | | <el-button link @click="exportDiff(row)">导åº</el-button> |
| | | </template></el-table-column |
| | | > |
| | | </el-table> |
| | | <pagination |
| | | v-show="total > 0" |
| | | :total="total" |
| | | :page="query.current" |
| | | :limit="query.size" |
| | | @pagination=" |
| | | (p) => { |
| | | query.current = p.page; |
| | | query.size = p.limit; |
| | | loadTasks(); |
| | | } |
| | | " |
| | | /> |
| | | |
| | | <el-dialog v-model="createVisible" title="å起对账" width="760px"> |
| | | <el-form :model="createForm" label-width="100px"> |
| | | <el-form-item label="对账类å" |
| | | ><el-select v-model="createForm.reconType" |
| | | ><el-option |
| | | v-for="(label, key) in reconTypeText" |
| | | :key="key" |
| | | :label="label" |
| | | :value="key" /></el-select |
| | | ></el-form-item> |
| | | <el-form-item label="对账æé´" |
| | | ><el-date-picker |
| | | v-model="period" |
| | | type="daterange" |
| | | value-format="YYYY-MM-DD" |
| | | start-placeholder="å¼å§æ¥æ" |
| | | end-placeholder="ç»ææ¥æ" |
| | | /></el-form-item> |
| | | <el-form-item label="èµéè´¦æ·" |
| | | ><el-select v-model="createForm.accountId" clearable |
| | | ><el-option |
| | | v-for="a in accounts" |
| | | :key="a.id" |
| | | :label="a.accountName" |
| | | :value="a.id" /></el-select |
| | | ></el-form-item> |
| | | <el-form-item label="对账对象" |
| | | ><el-input |
| | | v-model="createForm.target" |
| | | placeholder="é¶è¡/ä¾åºå/客æ·åç§°" |
| | | /></el-form-item> |
| | | </el-form> |
| | | <template v-if="createForm.reconType !== '0'"> |
| | | <el-divider content-position="left">å¤é¨æµæ°´ï¼æå·¥å½å
¥ï¼</el-divider> |
| | | <div class="external-actions"> |
| | | <el-button size="small" @click="addExternal">æ°å¢å¤é¨æµæ°´</el-button> |
| | | </div> |
| | | <el-table :data="createForm.externalList" border max-height="260"> |
| | | <el-table-column label="æ¥æ" width="145" |
| | | ><template #default="{ row }" |
| | | ><el-date-picker |
| | | v-model="row.extDate" |
| | | value-format="YYYY-MM-DD" |
| | | style="width: 125px" /></template |
| | | ></el-table-column> |
| | | <el-table-column label="徿¥åä½" |
| | | ><template #default="{ row }" |
| | | ><el-input v-model="row.extCounterparty" /></template |
| | | ></el-table-column> |
| | | <el-table-column label="æ¹å" width="100" |
| | | ><template #default="{ row }" |
| | | ><el-select v-model="row.direction" |
| | | ><el-option label="æ¶å
¥" :value="1" /><el-option |
| | | label="æ¯åº" |
| | | :value="-1" /></el-select></template |
| | | ></el-table-column> |
| | | <el-table-column label="éé¢" width="140" |
| | | ><template #default="{ row }" |
| | | ><el-input-number |
| | | v-model="row.amount" |
| | | :min="0.01" |
| | | :precision="2" |
| | | style="width: 120px" /></template |
| | | ></el-table-column> |
| | | <el-table-column label="åè¯å·" |
| | | ><template #default="{ row }" |
| | | ><el-input v-model="row.voucherNo" /></template |
| | | ></el-table-column> |
| | | <el-table-column width="60" |
| | | ><template #default="{ $index }" |
| | | ><el-button |
| | | link |
| | | type="danger" |
| | | @click="createForm.externalList.splice($index, 1)" |
| | | >å </el-button |
| | | ></template |
| | | ></el-table-column |
| | | > |
| | | </el-table> |
| | | </template> |
| | | <template #footer |
| | | ><el-button @click="createVisible = false">åæ¶</el-button |
| | | ><el-button type="primary" @click="submitTask" |
| | | >å¼å§æ¯å¯¹</el-button |
| | | ></template |
| | | > |
| | | </el-dialog> |
| | | |
| | | <el-drawer v-model="diffVisible" title="å¯¹è´¦å·®å¼æç»" size="70%"> |
| | | <el-descriptions v-if="currentTask" :column="4" border class="summary"> |
| | | <el-descriptions-item label="ä»»å¡å·">{{ |
| | | currentTask.id |
| | | }}</el-descriptions-item |
| | | ><el-descriptions-item label="ç³»ç»éé¢">{{ |
| | | currentTask.systemAmount |
| | | }}</el-descriptions-item |
| | | ><el-descriptions-item label="对æ¹éé¢">{{ |
| | | currentTask.externalAmount |
| | | }}</el-descriptions-item |
| | | ><el-descriptions-item label="å·®é¢">{{ |
| | | currentTask.diffAmount |
| | | }}</el-descriptions-item> |
| | | </el-descriptions> |
| | | <el-descriptions |
| | | v-if="currentTask?.handleNote" |
| | | :column="1" |
| | | border |
| | | class="summary" |
| | | > |
| | | <el-descriptions-item label="å¤çæ¹æ¡">{{ |
| | | currentTask.handleNote |
| | | }}</el-descriptions-item> |
| | | </el-descriptions> |
| | | <el-table :data="diffs" border> |
| | | <el-table-column label="å·®å¼ç±»å" |
| | | ><template #default="{ row }">{{ |
| | | diffTypeText[row.diffType] |
| | | }}</template></el-table-column |
| | | > |
| | | <el-table-column prop="bizType" label="ä¸å¡ç±»å" /><el-table-column |
| | | prop="bizId" |
| | | label="ä¸å¡ID" |
| | | /><el-table-column prop="bizDate" label="æ¥æ" /><el-table-column |
| | | prop="counterparty" |
| | | label="徿¥åä½" |
| | | /><el-table-column |
| | | prop="systemAmount" |
| | | label="ç³»ç»éé¢" |
| | | /><el-table-column |
| | | prop="externalAmount" |
| | | label="对æ¹éé¢" |
| | | /><el-table-column prop="remark" label="说æ" min-width="190" /> |
| | | </el-table> |
| | | <el-divider content-position="left">å¤é¨åè¯</el-divider> |
| | | <el-table :data="externals" border |
| | | ><el-table-column prop="extDate" label="æ¥æ" /><el-table-column |
| | | prop="extCounterparty" |
| | | label="徿¥åä½" /><el-table-column |
| | | prop="amount" |
| | | label="éé¢" /><el-table-column prop="direction" label="æ¹å" |
| | | ><template #default="{ row }">{{ |
| | | row.direction === 1 ? "æ¶å
¥" : "æ¯åº" |
| | | }}</template></el-table-column |
| | | ><el-table-column prop="voucherNo" label="åè¯å·" |
| | | /></el-table> |
| | | </el-drawer> |
| | | |
| | | <el-dialog v-model="handleVisible" title="å¡«åå¤çæ¹æ¡" width="520px"> |
| | | <el-input |
| | | v-model="handleForm.handleNote" |
| | | type="textarea" |
| | | :rows="5" |
| | | placeholder="请è¾å
¥å·®å¼å¤çæ¹æ¡" |
| | | /> |
| | | <template #footer |
| | | ><el-button @click="handleVisible = false">åæ¶</el-button |
| | | ><el-button type="primary" @click="saveHandle" |
| | | >ä¿åå¹¶æ è®°å¤çä¸</el-button |
| | | ></template |
| | | > |
| | | </el-dialog> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, reactive, onMounted, nextTick } from "vue"; |
| | | import { ElMessage, ElMessageBox } from "element-plus"; |
| | | import { saveAs } from "file-saver"; |
| | | import pagination from "@/components/PIMTable/Pagination.vue"; |
| | | import { getFinanceAccountList } from "@/api/financialManagement/fundsManagement"; |
| | | import { |
| | | getReconciliationTaskPage, |
| | | createReconciliationTask, |
| | | getReconciliationDiff, |
| | | getReconciliationExternal, |
| | | handleReconciliationTask, |
| | | archiveReconciliationTask, |
| | | exportReconciliationDiff, |
| | | } from "@/api/financialManagement/financialReconciliation"; |
| | | |
| | | const reconTypeText = { |
| | | 0: "å
é¨å¯¹è´¦", |
| | | 1: "é¶è¡å¯¹è´¦", |
| | | 2: "ä¾åºå对账", |
| | | 3: "客æ·å¯¹è´¦", |
| | | }, |
| | | statusText = { 0: "å¾
å¤ç", 1: "å¤çä¸", 2: "已彿¡£" }, |
| | | diffTypeText = { |
| | | 0: "ç³»ç»æå¤é¨æ ", |
| | | 1: "å¤é¨æç³»ç»æ ", |
| | | 2: "éé¢ä¸ç¬¦", |
| | | 3: "æ¥æä¸ç¬¦", |
| | | }; |
| | | const tasks = ref([]), |
| | | total = ref(0), |
| | | loading = ref(false), |
| | | loadError = ref(""), |
| | | accounts = ref([]), |
| | | createVisible = ref(false), |
| | | diffVisible = ref(false), |
| | | handleVisible = ref(false), |
| | | period = ref([]), |
| | | diffs = ref([]), |
| | | externals = ref([]), |
| | | currentTask = ref(null); |
| | | const query = reactive({ |
| | | current: 1, |
| | | size: 10, |
| | | reconType: "", |
| | | status: "", |
| | | target: "", |
| | | }), |
| | | createForm = reactive({ |
| | | reconType: "0", |
| | | accountId: null, |
| | | target: "", |
| | | externalList: [], |
| | | }), |
| | | handleForm = reactive({ id: null, handleNote: "" }); |
| | | const loadTasks = async () => { |
| | | loading.value = true; |
| | | loadError.value = ""; |
| | | try { |
| | | const r = await getReconciliationTaskPage(query); |
| | | tasks.value = r.data?.records || []; |
| | | total.value = r.data?.total || 0; |
| | | } catch (error) { |
| | | tasks.value = []; |
| | | total.value = 0; |
| | | loadError.value = |
| | | error?.message || |
| | | error?.msg || |
| | | "对账任å¡å 载失败ï¼è¯·æ£æ¥å端æå¡åæ°æ®åºèæ¬"; |
| | | } finally { |
| | | loading.value = false; |
| | | } |
| | | }; |
| | | const openCreate = () => { |
| | | Object.assign(createForm, { |
| | | reconType: "0", |
| | | accountId: null, |
| | | target: "", |
| | | externalList: [], |
| | | }); |
| | | period.value = []; |
| | | createVisible.value = true; |
| | | }; |
| | | const addExternal = () => |
| | | createForm.externalList.push({ |
| | | extDate: "", |
| | | extCounterparty: "", |
| | | amount: null, |
| | | direction: 1, |
| | | voucherNo: "", |
| | | remark: "", |
| | | }); |
| | | const submitTask = async () => { |
| | | if (period.value.length !== 2) { |
| | | ElMessage.warning("è¯·éæ©å¯¹è´¦æé´"); |
| | | return; |
| | | } |
| | | if (createForm.reconType !== "0" && !createForm.target.trim()) { |
| | | ElMessage.warning("请è¾å
¥é¶è¡ãä¾åºåæå®¢æ·åç§°"); |
| | | return; |
| | | } |
| | | if ( |
| | | createForm.reconType !== "0" && |
| | | createForm.externalList.some( |
| | | (row) => !row.extDate || !row.amount || row.amount <= 0 |
| | | ) |
| | | ) { |
| | | ElMessage.warning("请填åå¤é¨åè¯çæ¥æåæ£æ°éé¢"); |
| | | return; |
| | | } |
| | | try { |
| | | await createReconciliationTask({ |
| | | ...createForm, |
| | | periodStart: period.value[0], |
| | | periodEnd: period.value[1], |
| | | }); |
| | | ElMessage.success("对账任å¡å·²çæ"); |
| | | createVisible.value = false; |
| | | await loadTasks(); |
| | | } catch (error) { |
| | | ElMessage.error(error?.message || "对账任å¡çæå¤±è´¥"); |
| | | } |
| | | }; |
| | | const showDiff = async (row) => { |
| | | currentTask.value = row; |
| | | const [d, e] = await Promise.all([ |
| | | getReconciliationDiff(row.id), |
| | | getReconciliationExternal(row.id), |
| | | ]); |
| | | diffs.value = d.data || []; |
| | | externals.value = e.data || []; |
| | | diffVisible.value = true; |
| | | }; |
| | | const openHandle = (row) => { |
| | | handleForm.id = row.id; |
| | | handleForm.handleNote = row.handleNote || ""; |
| | | handleVisible.value = true; |
| | | }; |
| | | const saveHandle = async () => { |
| | | await handleReconciliationTask(handleForm.id, { |
| | | handleNote: handleForm.handleNote, |
| | | status: "1", |
| | | }); |
| | | ElMessage.success("å¤çæ¹æ¡å·²ä¿å"); |
| | | handleVisible.value = false; |
| | | loadTasks(); |
| | | }; |
| | | const archive = async (row) => { |
| | | if (row.result === "1" && !row.handleNote?.trim()) { |
| | | ElMessage.warning("请å
å¡«åå·®å¼å¤çæ¹æ¡"); |
| | | return; |
| | | } |
| | | try { |
| | | await ElMessageBox.confirm( |
| | | "彿¡£å该任å¡å°è¿å
¥å·²å½æ¡£ç¶æï¼æ¯å¦ç»§ç»ï¼", |
| | | "彿¡£" |
| | | ); |
| | | await archiveReconciliationTask(row.id); |
| | | ElMessage.success("已彿¡£"); |
| | | await loadTasks(); |
| | | } catch (error) { |
| | | if (error !== "cancel" && error !== "close") { |
| | | ElMessage.error(error?.message || "彿¡£å¤±è´¥"); |
| | | } |
| | | } |
| | | }; |
| | | const exportDiff = async (row) => { |
| | | const blob = await exportReconciliationDiff(row.id); |
| | | saveAs(blob, `对账差å¼-${row.id}.xlsx`); |
| | | }; |
| | | onMounted(() => { |
| | | nextTick(() => { |
| | | loadTasks(); |
| | | getFinanceAccountList() |
| | | .then((r) => { |
| | | accounts.value = r.data || []; |
| | | }) |
| | | .catch((error) => { |
| | | accounts.value = []; |
| | | console.error("èµéè´¦æ·å 载失败ï¼", error); |
| | | }); |
| | | }); |
| | | }); |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .reconciliation-workspace { |
| | | padding: 4px; |
| | | } |
| | | .workspace-heading { |
| | | margin-bottom: 16px; |
| | | } |
| | | .workspace-heading h2 { |
| | | margin: 0 0 6px; |
| | | font-size: 20px; |
| | | } |
| | | .workspace-heading p { |
| | | margin: 0; |
| | | color: #606266; |
| | | } |
| | | .load-error { |
| | | margin-bottom: 12px; |
| | | } |
| | | .external-actions { |
| | | margin-bottom: 8px; |
| | | } |
| | | .summary { |
| | | margin-bottom: 16px; |
| | | } |
| | | </style> |
| | |
| | | <template> |
| | | <div class="app-container"> |
| | | <!-- æç´¢åº --> |
| | | <div class="search_form"> |
| | | <el-form :inline="true" :model="searchForm"> |
| | | <el-form-item label="徿¥åä½ï¼"> |
| | | <el-input v-model="searchForm.counterparty" placeholder="请è¾å
¥å¾æ¥åä½" clearable style="width: 180px" /> |
| | | </el-form-item> |
| | | <el-form-item label="å¯¹è´¦ç¶æï¼"> |
| | | <el-select v-model="searchForm.status" placeholder="è¯·éæ©" clearable style="width: 150px"> |
| | | <el-option label="æªå¯¹è´¦" value="pending" /> |
| | | <el-option label="对账ä¸" value="processing" /> |
| | | <el-option label="已宿" value="done" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | <!-- <el-form-item label="æ¥æï¼"> |
| | | <el-date-picker |
| | | v-model="searchForm.dateRange" |
| | | type="daterange" |
| | | range-separator="è³" |
| | | start-placeholder="å¼å§æ¥æ" |
| | | end-placeholder="ç»ææ¥æ" |
| | | value-format="YYYY-MM-DD" |
| | | format="YYYY-MM-DD" |
| | | clearable |
| | | /> |
| | | </el-form-item> --> |
| | | <el-form-item> |
| | | <el-button type="primary" @click="handleQuery">æç´¢</el-button> |
| | | <el-button @click="resetQuery">éç½®</el-button> |
| | | </el-form-item> |
| | | </el-form> |
| | | </div> |
| | | |
| | | <!-- æä½åº --> |
| | | <div class="actions"> |
| | | <el-button type="primary" @click="openDialog('add')">æ°å¢</el-button> |
| | | <el-button type="danger" plain @click="handleDelete">å é¤</el-button> |
| | | </div> |
| | | |
| | | <!-- è¡¨æ ¼ --> |
| | | <el-table |
| | | :data="tableData" |
| | | border |
| | | v-loading="tableLoading" |
| | | @selection-change="handleSelectionChange" |
| | | style="width: 100%" |
| | | height="calc(100vh - 280px)" |
| | | :row-key="row => row.id" |
| | | > |
| | | <el-table-column type="selection" width="55" align="center" /> |
| | | <el-table-column type="index" label="åºå·" width="60" align="center" /> |
| | | <el-table-column prop="accountDate" label="å¯¹è´¦æ¥æ" width="130" show-overflow-tooltip /> |
| | | <el-table-column prop="counterparty" label="徿¥åä½" width="180" show-overflow-tooltip /> |
| | | <el-table-column prop="amount" label="对账éé¢(å
)" width="140" show-overflow-tooltip /> |
| | | <el-table-column prop="statusText" label="ç¶æ" width="120" show-overflow-tooltip /> |
| | | <el-table-column prop="remark" label="夿³¨" show-overflow-tooltip /> |
| | | <el-table-column fixed="right" label="æä½" width="150" align="center"> |
| | | <template #default="scope"> |
| | | <el-button link type="primary" size="small" @click="openDialog('edit', scope.row)">ç¼è¾</el-button> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | |
| | | <pagination |
| | | v-show="total > 0" |
| | | :total="total" |
| | | layout="total, sizes, prev, pager, next, jumper" |
| | | :page="page.current" |
| | | :limit="page.size" |
| | | @pagination="paginationChange" |
| | | <div class="app-container financial-reconciliation-page"> |
| | | <el-alert |
| | | v-if="runtimeError" |
| | | title="è´¢å¡å¯¹è´¦é¡µé¢å 载失败" |
| | | type="error" |
| | | :description="runtimeError" |
| | | show-icon |
| | | :closable="false" |
| | | /> |
| | | |
| | | <!-- å¼¹çª --> |
| | | <el-dialog v-model="dialogVisible" :title="dialogTitle" width="520px" @close="resetForm"> |
| | | <el-form :model="form" :rules="rules" ref="formRef" label-width="120px"> |
| | | <el-form-item label="å¯¹è´¦æ¥æï¼" prop="accountDate"> |
| | | <el-date-picker v-model="form.accountDate" type="date" value-format="YYYY-MM-DD" format="YYYY-MM-DD" style="width: 100%" /> |
| | | </el-form-item> |
| | | <el-form-item label="徿¥åä½ï¼" prop="counterparty"> |
| | | <el-input v-model="form.counterparty" placeholder="请è¾å
¥å¾æ¥åä½" /> |
| | | </el-form-item> |
| | | <el-form-item label="对账éé¢(å
)ï¼" prop="amount"> |
| | | <el-input-number v-model="form.amount" :min="0" :precision="2" :step="100" style="width: 100%" /> |
| | | </el-form-item> |
| | | <el-form-item label="ç¶æï¼" prop="status"> |
| | | <el-select v-model="form.status" placeholder="è¯·éæ©" style="width: 100%"> |
| | | <el-option label="æªå¯¹è´¦" value="pending" /> |
| | | <el-option label="对账ä¸" value="processing" /> |
| | | <el-option label="已宿" value="done" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item label="夿³¨ï¼"> |
| | | <el-input v-model="form.remark" type="textarea" :rows="3" placeholder="请è¾å
¥å¤æ³¨" /> |
| | | </el-form-item> |
| | | </el-form> |
| | | <template #footer> |
| | | <div class="dialog-footer"> |
| | | <el-button @click="dialogVisible = false">åæ¶</el-button> |
| | | <el-button type="primary" @click="submitForm">确认</el-button> |
| | | </div> |
| | | </template> |
| | | </el-dialog> |
| | | <ReconciliationWorkspace v-else :key="workspaceKey" /> |
| | | <el-button v-if="runtimeError" type="primary" class="retry-button" @click="retry"> |
| | | éæ°å è½½ |
| | | </el-button> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, reactive, onMounted } from 'vue' |
| | | import { ElMessage, ElMessageBox } from 'element-plus' |
| | | import pagination from '@/components/PIMTable/Pagination.vue' |
| | | import { |
| | | getReconciliationPage, |
| | | addReconciliation, |
| | | updateReconciliation, |
| | | deleteReconciliation |
| | | } from '@/api/financialManagement/financialReconciliation.js' |
| | | import { nextTick, onErrorCaptured, ref } from "vue"; |
| | | import ReconciliationWorkspace from "./ReconciliationWorkspace.vue"; |
| | | |
| | | const tableData = ref([]) |
| | | const tableLoading = ref(false) |
| | | const selectedRows = ref([]) |
| | | const dialogVisible = ref(false) |
| | | const dialogTitle = ref('æ°å¢') |
| | | const formRef = ref(null) |
| | | const runtimeError = ref(""); |
| | | const workspaceKey = ref(0); |
| | | |
| | | const page = reactive({ |
| | | current: 1, |
| | | size: 100 |
| | | }) |
| | | const total = ref(0) |
| | | onErrorCaptured((error) => { |
| | | console.error("è´¢å¡å¯¹è´¦é¡µé¢è¿è¡å¼å¸¸ï¼", error); |
| | | runtimeError.value = error?.stack || error?.message || String(error); |
| | | return false; |
| | | }); |
| | | |
| | | const searchForm = reactive({ |
| | | counterparty: '', |
| | | status: '', |
| | | dateRange: [] |
| | | }) |
| | | |
| | | const form = reactive({ |
| | | id: null, |
| | | accountDate: '', |
| | | counterparty: '', |
| | | amount: null, |
| | | status: '', |
| | | remark: '' |
| | | }) |
| | | |
| | | const rules = { |
| | | accountDate: [{ required: true, message: 'è¯·éæ©å¯¹è´¦æ¥æ', trigger: 'change' }], |
| | | counterparty: [{ required: true, message: '请è¾å
¥å¾æ¥åä½', trigger: 'blur' }], |
| | | amount: [{ required: true, message: '请è¾å
¥å¯¹è´¦éé¢', trigger: 'blur' }], |
| | | status: [{ required: true, message: 'è¯·éæ©ç¶æ', trigger: 'change' }] |
| | | } |
| | | |
| | | const statusTextMap = { |
| | | pending: 'æªå¯¹è´¦', |
| | | processing: '对账ä¸', |
| | | done: '已宿' |
| | | } |
| | | |
| | | // è·åå表 |
| | | const getList = () => { |
| | | tableLoading.value = true |
| | | const params = { |
| | | ...searchForm, |
| | | current: page.current, |
| | | size: page.size |
| | | } |
| | | if (params.dateRange && params.dateRange.length === 2) { |
| | | params.beginDate = params.dateRange[0] |
| | | params.endDate = params.dateRange[1] |
| | | } |
| | | delete params.dateRange |
| | | |
| | | getReconciliationPage(params) |
| | | .then(res => { |
| | | if (res.code === 200) { |
| | | tableData.value = (res.data?.records || []).map(item => ({ |
| | | ...item, |
| | | statusText: statusTextMap[item.status] || item.status |
| | | })) |
| | | total.value = res.data?.total || 0 |
| | | } |
| | | }) |
| | | .finally(() => { |
| | | tableLoading.value = false |
| | | }) |
| | | } |
| | | |
| | | const handleQuery = () => { |
| | | page.current = 1 |
| | | getList() |
| | | } |
| | | const resetQuery = () => { |
| | | searchForm.counterparty = '' |
| | | searchForm.status = '' |
| | | searchForm.dateRange = [] |
| | | handleQuery() |
| | | } |
| | | const paginationChange = (obj) => { |
| | | page.current = obj.page |
| | | page.size = obj.limit |
| | | getList() |
| | | } |
| | | |
| | | const handleSelectionChange = (rows) => { |
| | | selectedRows.value = rows |
| | | } |
| | | |
| | | const openDialog = (type, row) => { |
| | | dialogTitle.value = type === 'add' ? 'æ°å¢å¯¹è´¦è®°å½' : 'ç¼è¾å¯¹è´¦è®°å½' |
| | | if (type === 'edit' && row) { |
| | | Object.assign(form, row) |
| | | } else { |
| | | resetForm() |
| | | } |
| | | dialogVisible.value = true |
| | | } |
| | | |
| | | const resetForm = () => { |
| | | Object.assign(form, { |
| | | id: null, |
| | | date: '', |
| | | counterparty: '', |
| | | amount: null, |
| | | status: '', |
| | | remark: '' |
| | | }) |
| | | } |
| | | |
| | | const submitForm = () => { |
| | | formRef.value?.validate(valid => { |
| | | if (!valid) return |
| | | const payload = { ...form } |
| | | const request = payload.id ? updateReconciliation(payload) : addReconciliation(payload) |
| | | request |
| | | .then(res => { |
| | | if (res.code === 200) { |
| | | ElMessage.success('æäº¤æå') |
| | | dialogVisible.value = false |
| | | getList() |
| | | } |
| | | }) |
| | | .catch(err => { |
| | | ElMessage.error(err.msg || 'æäº¤å¤±è´¥') |
| | | }) |
| | | }) |
| | | } |
| | | |
| | | const handleDelete = () => { |
| | | if (!selectedRows.value.length) { |
| | | ElMessage.warning('è¯·éæ©è¦å é¤çè®°å½') |
| | | return |
| | | } |
| | | ElMessageBox.confirm('确认å é¤éä¸è®°å½åï¼', 'æç¤º', { |
| | | confirmButtonText: '确认', |
| | | cancelButtonText: 'åæ¶', |
| | | type: 'warning' |
| | | }).then(() => { |
| | | const ids = selectedRows.value.map(r => r.id) |
| | | deleteReconciliation(ids).then(res => { |
| | | if (res.code === 200) { |
| | | ElMessage.success('å 餿å') |
| | | getList() |
| | | selectedRows.value = [] |
| | | } |
| | | }).catch(err => { |
| | | ElMessage.error(err.msg || 'å é¤å¤±è´¥') |
| | | }) |
| | | }).catch(() => {}) |
| | | } |
| | | |
| | | onMounted(() => { |
| | | getList() |
| | | }) |
| | | const retry = async () => { |
| | | runtimeError.value = ""; |
| | | await nextTick(); |
| | | workspaceKey.value += 1; |
| | | }; |
| | | </script> |
| | | |
| | | <style scoped lang="scss"> |
| | | .app-container { |
| | | padding: 20px; |
| | | |
| | | .search_form { |
| | | background: #fff; |
| | | padding: 16px; |
| | | border-radius: 6px; |
| | | margin-bottom: 16px; |
| | | } |
| | | |
| | | .actions { |
| | | margin-bottom: 12px; |
| | | } |
| | | <style scoped> |
| | | .financial-reconciliation-page { |
| | | min-height: calc(100vh - 84px); |
| | | background: #fff; |
| | | } |
| | | .retry-button { |
| | | margin-top: 16px; |
| | | } |
| | | </style> |
| | |
| | | </template> |
| | | |
| | | <script setup> |
| | | defineOptions({ name: 'FinancialStatements' }) |
| | | import { ref, computed, onMounted, reactive } from 'vue'; |
| | | import { ElMessage } from 'element-plus'; |
| | | import 'element-plus/dist/index.css'; |
| | | import Echarts from "@/components/Echarts/echarts.vue"; |
| | | import { reportForms,reportIncome,reportExpense } from "@/api/financialManagement/financialStatements"; |
| | | import dayjs from "dayjs"; |
| | | |
| | | // æ¥æèå´ |
| | | const dateRange = ref(null); |
| | | const currentYear = dayjs().year(); |
| | | const dateRange = ref([ |
| | | dayjs().startOf('year').format('YYYY-MM-DD'), |
| | | dayjs().endOf('year').format('YYYY-MM-DD') |
| | | ]); |
| | | const chartStyle = { |
| | | width: '100%', |
| | | height: '100%', // 设置å¾è¡¨å®¹å¨çé«åº¦ |
| | |
| | | }) |
| | | |
| | | |
| | | const pageInfo = ref({ |
| | | }) |
| | | const emptyPageInfo = () => ({ |
| | | totalIncome: 0, |
| | | incomeNumber: 0, |
| | | totalExpense: 0, |
| | | expenseNumber: 0, |
| | | netRevenue: 0, |
| | | incomeType: [], |
| | | expenseType: [] |
| | | }); |
| | | const pageInfo = ref(emptyPageInfo()); |
| | | |
| | | const getData = async () => { |
| | | if (!dateRange.value || !dateRange.value.length) { |
| | |
| | | try { |
| | | const {code,data} = await reportForms({entryDateStart:dateRange.value[0], entryDateEnd:dateRange.value[1]}); |
| | | if(code === 200) { |
| | | pageInfo.value = data |
| | | pieData0.value = data.incomeType.map(item=>({ |
| | | pageInfo.value = { ...emptyPageInfo(), ...(data || {}) } |
| | | pieData0.value = (data?.incomeType || []).map(item=>({ |
| | | name:item.typeName, |
| | | value:item.account, |
| | | percent:`${item.proportion*100}%`, |
| | | amount:`Â¥${item.account}` |
| | | })) |
| | | pieData1.value = data.expenseType.map(item=>({ |
| | | pieData1.value = (data?.expenseType || []).map(item=>({ |
| | | name:item.typeName, |
| | | value:item.account, |
| | | percent:`${item.proportion*100}%`, |
| | |
| | | |
| | | } |
| | | } catch (error) { |
| | | pageInfo.value = emptyPageInfo(); |
| | | pieData0.value = []; |
| | | pieData1.value = []; |
| | | console.error('è·åè´¢å¡ææ æ°æ®å¤±è´¥ï¼', error); |
| | | ElMessage.error(error?.msg || 'è´¢å¡æ±æ»æ°æ®å 载失败'); |
| | | } |
| | | try{ |
| | | const {code,data} = await reportIncome(); |
| | |
| | | lineSeries0.value = data.map(item=>({ |
| | | name:item.typeName, |
| | | type: 'line', |
| | | data:item.account.map(item=>Number(item)) |
| | | data:(item.account || []).map(value => Number(value || 0)) |
| | | })) |
| | | |
| | | } |
| | |
| | | lineSeries1.value = data.map(item=>({ |
| | | name:item.typeName, |
| | | type: 'line', |
| | | data:item.account.map(item=>Number(item)) |
| | | data:(item.account || []).map(value => Number(value || 0)) |
| | | })) |
| | | |
| | | } |
| | |
| | | |
| | | // åå§å |
| | | onMounted(() => { |
| | | // ä¸è®¾ç½®é»è®¤æ¥æï¼ç±ç¨æ·æå¨éæ© |
| | | getData(); |
| | | }); |
| | | |
| | | // å¤çæ¥æèå´åå |
| | |
| | | |
| | | // éç½®æ¥æèå´ |
| | | const resetDateRange = () => { |
| | | dateRange.value = null; |
| | | dateRange.value = [ |
| | | `${currentYear}-01-01`, |
| | | `${currentYear}-12-31` |
| | | ]; |
| | | getData(); |
| | | }; |
| | | |
| | | </script> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div class="finance-workspace"> |
| | | <el-tabs v-model="activeTab" @tab-change="loadTab"> |
| | | <el-tab-pane label="èµéè´¦æ·" name="accounts"> |
| | | <div class="toolbar"> |
| | | <el-button type="primary" @click="openAccount()">æ°å¢è´¦æ·</el-button> |
| | | </div> |
| | | <div class="balance-grid"> |
| | | <el-card v-for="item in accounts" :key="item.id" shadow="hover"> |
| | | <div class="balance-title"> |
| | | {{ item.accountName }} |
| | | <el-tag size="small">{{ |
| | | accountTypeText[item.accountType] |
| | | }}</el-tag> |
| | | </div> |
| | | <div class="balance-amount">Â¥ {{ money(item.currentBalance) }}</div> |
| | | <div class="muted">å®å
¨çº¿ Â¥ {{ money(item.safetyLine) }}</div> |
| | | </el-card> |
| | | </div> |
| | | <el-table :data="accounts" border v-loading="loading"> |
| | | <el-table-column prop="accountName" label="è´¦æ·åç§°" /> |
| | | <el-table-column label="ç±»å" |
| | | ><template #default="{ row }">{{ |
| | | accountTypeText[row.accountType] |
| | | }}</template></el-table-column |
| | | > |
| | | <el-table-column prop="bankName" label="弿·è¡" /> |
| | | <el-table-column prop="bankNo" label="è´¦å·" /> |
| | | <el-table-column prop="openingBalance" label="æåä½é¢" /> |
| | | <el-table-column prop="currentBalance" label="å½åä½é¢" /> |
| | | <el-table-column label="ç¶æ" |
| | | ><template #default="{ row }">{{ |
| | | row.status === "0" ? "å¯ç¨" : "åç¨" |
| | | }}</template></el-table-column |
| | | > |
| | | <el-table-column label="æä½" width="140" |
| | | ><template #default="{ row }" |
| | | ><el-button link type="primary" @click="openAccount(row)" |
| | | >ç¼è¾</el-button |
| | | ><el-button link type="danger" @click="removeAccount(row)" |
| | | >å é¤</el-button |
| | | ></template |
| | | ></el-table-column |
| | | > |
| | | </el-table> |
| | | <el-divider content-position="left">è¿30æ¥èµéæµè¶å¿</el-divider> |
| | | <div ref="trendRef" class="chart"></div> |
| | | </el-tab-pane> |
| | | |
| | | <el-tab-pane label="èµéæµæ°´" name="flows"> |
| | | <el-form inline :model="flowQuery"> |
| | | <el-form-item label="è´¦æ·" |
| | | ><el-select |
| | | v-model="flowQuery.accountId" |
| | | clearable |
| | | style="width: 180px" |
| | | ><el-option |
| | | v-for="a in accounts" |
| | | :key="a.id" |
| | | :label="a.accountName" |
| | | :value="a.id" /></el-select |
| | | ></el-form-item> |
| | | <el-form-item label="ç±»å" |
| | | ><el-select |
| | | v-model="flowQuery.flowType" |
| | | clearable |
| | | style="width: 130px" |
| | | ><el-option |
| | | v-for="(label, key) in flowTypeText" |
| | | :key="key" |
| | | :label="label" |
| | | :value="key" /></el-select |
| | | ></el-form-item> |
| | | <el-form-item |
| | | ><el-button type="primary" @click="loadFlows" |
| | | >æ¥è¯¢</el-button |
| | | ></el-form-item |
| | | > |
| | | </el-form> |
| | | <div class="toolbar"> |
| | | <el-button type="primary" @click="openFlow()">ç»è®°æµæ°´</el-button |
| | | ><el-button @click="openTransfer">è´¦æ·è½¬è´¦</el-button> |
| | | </div> |
| | | <el-table :data="flows" border v-loading="loading"> |
| | | <el-table-column prop="accountDate" label="æ¥æ" width="120" /> |
| | | <el-table-column prop="accountName" label="è´¦æ·" /> |
| | | <el-table-column label="ç±»å" |
| | | ><template #default="{ row }">{{ |
| | | flowTypeText[row.flowType] |
| | | }}</template></el-table-column |
| | | > |
| | | <el-table-column prop="amount" label="éé¢" /> |
| | | <el-table-column prop="counterparty" label="徿¥åä½" /> |
| | | <el-table-column label="å
³è" |
| | | ><template #default="{ row }">{{ |
| | | row.linkType ? `${row.linkType} #${row.linkId}` : "-" |
| | | }}</template></el-table-column |
| | | > |
| | | <el-table-column prop="remark" label="夿³¨" /> |
| | | <el-table-column label="æä½" width="140" |
| | | ><template #default="{ row }" |
| | | ><template v-if="row.flowType !== '2'" |
| | | ><el-button link type="primary" @click="openFlow(row)" |
| | | >ç¼è¾</el-button |
| | | ><el-button link type="danger" @click="removeFlow(row)" |
| | | >å é¤</el-button |
| | | ></template |
| | | ><span v-else class="muted">æå¯¹æµæ°´</span></template |
| | | ></el-table-column |
| | | > |
| | | </el-table> |
| | | <pagination |
| | | v-show="flowTotal > 0" |
| | | :total="flowTotal" |
| | | :page="flowQuery.current" |
| | | :limit="flowQuery.size" |
| | | @pagination=" |
| | | (p) => { |
| | | flowQuery.current = p.page; |
| | | flowQuery.size = p.limit; |
| | | loadFlows(); |
| | | } |
| | | " |
| | | /> |
| | | </el-tab-pane> |
| | | |
| | | <el-tab-pane label="æ¶æ¯è®¡å" name="plans"> |
| | | <el-alert |
| | | v-if="forecast" |
| | | :type=" |
| | | forecast.signal === 'gap' |
| | | ? 'error' |
| | | : forecast.signal === 'surplus' |
| | | ? 'success' |
| | | : 'info' |
| | | " |
| | | :closable="false" |
| | | show-icon |
| | | class="forecast" |
| | | > |
| | | <template #title |
| | | >30天颿µä½é¢ï¼Â¥ {{ money(forecast.finalBalance) }}ï¼{{ |
| | | forecast.signal === "gap" |
| | | ? "åå¨èµé缺å£" |
| | | : forecast.signal === "surplus" |
| | | ? "é«äºå®å
¨çº¿ï¼åå¨çä½" |
| | | : "å¤äºå®å
¨çº¿" |
| | | }}</template |
| | | > |
| | | </el-alert> |
| | | <div class="toolbar"> |
| | | <el-button type="primary" @click="openPlan()">æ°å¢è®¡å</el-button |
| | | ><el-button @click="loadForecast">å·æ°é¢æµ</el-button> |
| | | </div> |
| | | <el-table :data="plans" border v-loading="loading"> |
| | | <el-table-column prop="planDate" label="è®¡åæ¥æ" /> |
| | | <el-table-column label="ç±»å" |
| | | ><template #default="{ row }">{{ |
| | | row.planType === "0" ? "è®¡åæ¶å
¥" : "è®¡åæ¯åº" |
| | | }}</template></el-table-column |
| | | > |
| | | <el-table-column prop="expectedAmount" label="é¢è®¡éé¢" /> |
| | | <el-table-column prop="category" label="åç±»" /> |
| | | <el-table-column prop="counterparty" label="徿¥åä½" /> |
| | | <el-table-column label="ç¶æ" |
| | | ><template #default="{ row }">{{ |
| | | planStatusText[row.status] |
| | | }}</template></el-table-column |
| | | > |
| | | <el-table-column label="æä½" width="140" |
| | | ><template #default="{ row }" |
| | | ><el-button link type="primary" @click="openPlan(row)" |
| | | >ç¼è¾</el-button |
| | | ><el-button link type="danger" @click="removePlan(row)" |
| | | >å é¤</el-button |
| | | ></template |
| | | ></el-table-column |
| | | > |
| | | </el-table> |
| | | <pagination |
| | | v-show="planTotal > 0" |
| | | :total="planTotal" |
| | | :page="planQuery.current" |
| | | :limit="planQuery.size" |
| | | @pagination=" |
| | | (p) => { |
| | | planQuery.current = p.page; |
| | | planQuery.size = p.limit; |
| | | loadPlans(); |
| | | } |
| | | " |
| | | /> |
| | | </el-tab-pane> |
| | | </el-tabs> |
| | | |
| | | <el-dialog |
| | | v-model="accountVisible" |
| | | :title="accountForm.id ? 'ç¼è¾è´¦æ·' : 'æ°å¢è´¦æ·'" |
| | | width="560px" |
| | | > |
| | | <el-form :model="accountForm" label-width="100px"> |
| | | <el-form-item label="è´¦æ·åç§°" |
| | | ><el-input v-model="accountForm.accountName" |
| | | /></el-form-item> |
| | | <el-form-item label="è´¦æ·ç±»å" |
| | | ><el-radio-group v-model="accountForm.accountType" |
| | | ><el-radio value="0">é¶è¡è´¦æ·</el-radio |
| | | ><el-radio value="1">ç°éè´¦æ·</el-radio></el-radio-group |
| | | ></el-form-item |
| | | > |
| | | <el-form-item label="弿·è¡" |
| | | ><el-input v-model="accountForm.bankName" |
| | | /></el-form-item> |
| | | <el-form-item label="é¶è¡è´¦å·" |
| | | ><el-input v-model="accountForm.bankNo" |
| | | /></el-form-item> |
| | | <el-form-item label="æåä½é¢" |
| | | ><el-input-number |
| | | v-model="accountForm.openingBalance" |
| | | :precision="2" |
| | | style="width: 100%" |
| | | /></el-form-item> |
| | | <el-form-item label="å®å
¨çº¿" |
| | | ><el-input-number |
| | | v-model="accountForm.safetyLine" |
| | | :precision="2" |
| | | style="width: 100%" |
| | | /></el-form-item> |
| | | <el-form-item label="ç¶æ" |
| | | ><el-switch |
| | | v-model="accountForm.status" |
| | | active-value="0" |
| | | inactive-value="1" |
| | | active-text="å¯ç¨" |
| | | /></el-form-item> |
| | | </el-form> |
| | | <template #footer |
| | | ><el-button @click="accountVisible = false">åæ¶</el-button |
| | | ><el-button type="primary" @click="saveAccount" |
| | | >ä¿å</el-button |
| | | ></template |
| | | > |
| | | </el-dialog> |
| | | |
| | | <el-dialog v-model="flowVisible" title="ç»è®°èµéæµæ°´" width="560px"> |
| | | <el-form :model="flowForm" label-width="100px"> |
| | | <el-form-item label="æ¥æ" |
| | | ><el-date-picker |
| | | v-model="flowForm.accountDate" |
| | | value-format="YYYY-MM-DD" |
| | | /></el-form-item> |
| | | <el-form-item label="è´¦æ·" |
| | | ><el-select v-model="flowForm.accountId" |
| | | ><el-option |
| | | v-for="a in accounts" |
| | | :key="a.id" |
| | | :label="a.accountName" |
| | | :value="a.id" /></el-select |
| | | ></el-form-item> |
| | | <el-form-item label="æµæ°´ç±»å" |
| | | ><el-select v-model="flowForm.flowType" |
| | | ><el-option |
| | | v-for="(label, key) in nonTransferTypes" |
| | | :key="key" |
| | | :label="label" |
| | | :value="key" /></el-select |
| | | ></el-form-item> |
| | | <el-form-item label="éé¢" |
| | | ><el-input-number |
| | | v-model="flowForm.amount" |
| | | :min="0.01" |
| | | :precision="2" |
| | | /></el-form-item> |
| | | <el-form-item label="徿¥åä½" |
| | | ><el-input v-model="flowForm.counterparty" |
| | | /></el-form-item> |
| | | <el-form-item label="å
³èç±»å" |
| | | ><el-select v-model="flowForm.linkType" clearable |
| | | ><el-option label="æ¶å
¥" value="Income" /><el-option |
| | | label="æ¯åº" |
| | | value="Expense" /></el-select |
| | | ></el-form-item> |
| | | <el-form-item label="å
³èID" |
| | | ><el-input-number v-model="flowForm.linkId" :min="1" |
| | | /></el-form-item> |
| | | <el-form-item label="夿³¨" |
| | | ><el-input v-model="flowForm.remark" |
| | | /></el-form-item> |
| | | </el-form> |
| | | <template #footer |
| | | ><el-button @click="flowVisible = false">åæ¶</el-button |
| | | ><el-button type="primary" @click="saveFlow">ä¿å</el-button></template |
| | | > |
| | | </el-dialog> |
| | | |
| | | <el-dialog v-model="transferVisible" title="è´¦æ·è½¬è´¦" width="520px"> |
| | | <el-form :model="transferForm" label-width="100px"> |
| | | <el-form-item label="转åºè´¦æ·" |
| | | ><el-select v-model="transferForm.fromAccountId" |
| | | ><el-option |
| | | v-for="a in accounts" |
| | | :key="a.id" |
| | | :label="a.accountName" |
| | | :value="a.id" /></el-select |
| | | ></el-form-item> |
| | | <el-form-item label="转å
¥è´¦æ·" |
| | | ><el-select v-model="transferForm.toAccountId" |
| | | ><el-option |
| | | v-for="a in accounts" |
| | | :key="a.id" |
| | | :label="a.accountName" |
| | | :value="a.id" /></el-select |
| | | ></el-form-item> |
| | | <el-form-item label="éé¢" |
| | | ><el-input-number |
| | | v-model="transferForm.amount" |
| | | :min="0.01" |
| | | :precision="2" |
| | | /></el-form-item> |
| | | <el-form-item label="æ¥æ" |
| | | ><el-date-picker |
| | | v-model="transferForm.transferDate" |
| | | value-format="YYYY-MM-DD" |
| | | /></el-form-item> |
| | | <el-form-item label="夿³¨" |
| | | ><el-input v-model="transferForm.remark" |
| | | /></el-form-item> |
| | | </el-form> |
| | | <template #footer |
| | | ><el-button @click="transferVisible = false">åæ¶</el-button |
| | | ><el-button type="primary" @click="saveTransfer" |
| | | >转账</el-button |
| | | ></template |
| | | > |
| | | </el-dialog> |
| | | |
| | | <el-dialog |
| | | v-model="planVisible" |
| | | :title="planForm.id ? 'ç¼è¾è®¡å' : 'æ°å¢è®¡å'" |
| | | width="560px" |
| | | > |
| | | <el-form :model="planForm" label-width="100px"> |
| | | <el-form-item label="计åç±»å" |
| | | ><el-radio-group v-model="planForm.planType" |
| | | ><el-radio value="0">è®¡åæ¶å
¥</el-radio |
| | | ><el-radio value="1">è®¡åæ¯åº</el-radio></el-radio-group |
| | | ></el-form-item |
| | | > |
| | | <el-form-item label="è®¡åæ¥æ" |
| | | ><el-date-picker |
| | | v-model="planForm.planDate" |
| | | value-format="YYYY-MM-DD" |
| | | /></el-form-item> |
| | | <el-form-item label="é¢è®¡éé¢" |
| | | ><el-input-number |
| | | v-model="planForm.expectedAmount" |
| | | :min="0.01" |
| | | :precision="2" |
| | | /></el-form-item> |
| | | <el-form-item label="åç±»" |
| | | ><el-input v-model="planForm.category" |
| | | /></el-form-item> |
| | | <el-form-item label="徿¥åä½" |
| | | ><el-input v-model="planForm.counterparty" |
| | | /></el-form-item> |
| | | <el-form-item label="ç¶æ" |
| | | ><el-select v-model="planForm.status" |
| | | ><el-option |
| | | v-for="(label, key) in planStatusText" |
| | | :key="key" |
| | | :label="label" |
| | | :value="key" /></el-select |
| | | ></el-form-item> |
| | | <el-form-item label="夿³¨" |
| | | ><el-input v-model="planForm.remark" |
| | | /></el-form-item> |
| | | </el-form> |
| | | <template #footer |
| | | ><el-button @click="planVisible = false">åæ¶</el-button |
| | | ><el-button type="primary" @click="savePlan">ä¿å</el-button></template |
| | | > |
| | | </el-dialog> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, reactive, onMounted, nextTick } from "vue"; |
| | | import * as echarts from "echarts"; |
| | | import { ElMessage, ElMessageBox } from "element-plus"; |
| | | import pagination from "@/components/PIMTable/Pagination.vue"; |
| | | import { |
| | | getFundsPage, |
| | | addFunds, |
| | | updateFunds, |
| | | deleteFunds, |
| | | transferFunds, |
| | | getFinanceAccountPage, |
| | | addFinanceAccount, |
| | | updateFinanceAccount, |
| | | deleteFinanceAccount, |
| | | getFundsTrend, |
| | | getCashflowPlanPage, |
| | | addCashflowPlan, |
| | | updateCashflowPlan, |
| | | deleteCashflowPlan, |
| | | getCashflowForecast, |
| | | } from "@/api/financialManagement/fundsManagement"; |
| | | |
| | | const activeTab = ref("accounts"), |
| | | loading = ref(false), |
| | | accounts = ref([]), |
| | | flows = ref([]), |
| | | plans = ref([]), |
| | | forecast = ref(null); |
| | | const flowTotal = ref(0), |
| | | planTotal = ref(0), |
| | | trendRef = ref(), |
| | | accountVisible = ref(false), |
| | | flowVisible = ref(false), |
| | | transferVisible = ref(false), |
| | | planVisible = ref(false); |
| | | const accountTypeText = { 0: "é¶è¡è´¦æ·", 1: "ç°éè´¦æ·" }, |
| | | flowTypeText = { 0: "æ¶å
¥", 1: "æ¯åº", 2: "转账", 3: "åå
¥", 4: "ååº" }, |
| | | nonTransferTypes = { 0: "æ¶å
¥", 1: "æ¯åº", 3: "åå
¥", 4: "ååº" }, |
| | | planStatusText = { 0: "å¾
åç", 1: "å·²åç", 2: "已忶" }; |
| | | const flowQuery = reactive({ |
| | | current: 1, |
| | | size: 10, |
| | | accountId: null, |
| | | flowType: "", |
| | | }), |
| | | planQuery = reactive({ current: 1, size: 10 }); |
| | | const accountForm = reactive({}), |
| | | flowForm = reactive({}), |
| | | transferForm = reactive({}), |
| | | planForm = reactive({}); |
| | | const money = (v) => |
| | | Number(v || 0).toLocaleString("zh-CN", { |
| | | minimumFractionDigits: 2, |
| | | maximumFractionDigits: 2, |
| | | }); |
| | | const loadAccounts = async () => { |
| | | loading.value = true; |
| | | try { |
| | | const r = await getFinanceAccountPage({ current: 1, size: 100 }); |
| | | accounts.value = r.data?.records || []; |
| | | await nextTick(); |
| | | loadTrend(); |
| | | } finally { |
| | | loading.value = false; |
| | | } |
| | | }; |
| | | const loadTrend = async () => { |
| | | if (!trendRef.value) return; |
| | | const end = new Date(), |
| | | start = new Date(Date.now() - 29 * 86400000); |
| | | const fmt = (d) => d.toISOString().slice(0, 10); |
| | | const r = await getFundsTrend({ start: fmt(start), end: fmt(end) }); |
| | | const rows = r.data || []; |
| | | echarts.init(trendRef.value).setOption({ |
| | | tooltip: { trigger: "axis" }, |
| | | legend: { data: ["æ¶å
¥", "æ¯åº", "åé¢"] }, |
| | | xAxis: { type: "category", data: rows.map((x) => x.date) }, |
| | | yAxis: { type: "value" }, |
| | | series: [ |
| | | { name: "æ¶å
¥", type: "line", data: rows.map((x) => x.income) }, |
| | | { name: "æ¯åº", type: "line", data: rows.map((x) => x.expense) }, |
| | | { name: "åé¢", type: "line", data: rows.map((x) => x.net) }, |
| | | ], |
| | | }); |
| | | }; |
| | | const loadFlows = async () => { |
| | | loading.value = true; |
| | | try { |
| | | const r = await getFundsPage(flowQuery); |
| | | flows.value = r.data?.records || []; |
| | | flowTotal.value = r.data?.total || 0; |
| | | } finally { |
| | | loading.value = false; |
| | | } |
| | | }; |
| | | const loadPlans = async () => { |
| | | loading.value = true; |
| | | try { |
| | | const r = await getCashflowPlanPage(planQuery); |
| | | plans.value = r.data?.records || []; |
| | | planTotal.value = r.data?.total || 0; |
| | | await loadForecast(); |
| | | } finally { |
| | | loading.value = false; |
| | | } |
| | | }; |
| | | const loadForecast = async () => { |
| | | const r = await getCashflowForecast({ days: 30 }); |
| | | forecast.value = r.data; |
| | | }; |
| | | const loadTab = (name) => |
| | | name === "accounts" |
| | | ? loadAccounts() |
| | | : name === "flows" |
| | | ? (accounts.value.length || loadAccounts(), loadFlows()) |
| | | : loadPlans(); |
| | | const openAccount = (row) => { |
| | | Object.assign( |
| | | accountForm, |
| | | { |
| | | id: null, |
| | | accountName: "", |
| | | accountType: "0", |
| | | bankName: "", |
| | | bankNo: "", |
| | | openingBalance: 0, |
| | | safetyLine: 0, |
| | | status: "0", |
| | | }, |
| | | row || {} |
| | | ); |
| | | accountVisible.value = true; |
| | | }; |
| | | const saveAccount = async () => { |
| | | await (accountForm.id |
| | | ? updateFinanceAccount({ ...accountForm }) |
| | | : addFinanceAccount({ ...accountForm })); |
| | | ElMessage.success("ä¿åæå"); |
| | | accountVisible.value = false; |
| | | loadAccounts(); |
| | | }; |
| | | const removeAccount = (row) => |
| | | ElMessageBox.confirm(`确认å é¤è´¦æ·â${row.accountName}âåï¼`, "æç¤º").then( |
| | | async () => { |
| | | await deleteFinanceAccount([row.id]); |
| | | ElMessage.success("å 餿å"); |
| | | loadAccounts(); |
| | | } |
| | | ); |
| | | const openFlow = (row) => { |
| | | Object.assign( |
| | | flowForm, |
| | | { |
| | | id: null, |
| | | accountDate: new Date().toISOString().slice(0, 10), |
| | | accountId: null, |
| | | flowType: "0", |
| | | amount: null, |
| | | counterparty: "", |
| | | linkType: "", |
| | | linkId: null, |
| | | remark: "", |
| | | }, |
| | | row || {} |
| | | ); |
| | | flowVisible.value = true; |
| | | }; |
| | | const saveFlow = async () => { |
| | | await (flowForm.id |
| | | ? updateFunds({ ...flowForm }) |
| | | : addFunds({ ...flowForm })); |
| | | ElMessage.success("ä¿åæå"); |
| | | flowVisible.value = false; |
| | | loadFlows(); |
| | | }; |
| | | const removeFlow = (row) => |
| | | ElMessageBox.confirm("确认å é¤è¯¥èµéæµæ°´åï¼", "æç¤º").then(async () => { |
| | | await deleteFunds([row.id]); |
| | | ElMessage.success("å 餿å"); |
| | | loadFlows(); |
| | | }); |
| | | const openTransfer = () => { |
| | | Object.assign(transferForm, { |
| | | fromAccountId: null, |
| | | toAccountId: null, |
| | | amount: null, |
| | | transferDate: new Date().toISOString().slice(0, 10), |
| | | remark: "", |
| | | }); |
| | | transferVisible.value = true; |
| | | }; |
| | | const saveTransfer = async () => { |
| | | await transferFunds({ ...transferForm }); |
| | | ElMessage.success("转账æå"); |
| | | transferVisible.value = false; |
| | | loadFlows(); |
| | | }; |
| | | const openPlan = (row) => { |
| | | Object.assign( |
| | | planForm, |
| | | { |
| | | id: null, |
| | | planType: "0", |
| | | planDate: "", |
| | | expectedAmount: null, |
| | | category: "", |
| | | counterparty: "", |
| | | status: "0", |
| | | remark: "", |
| | | }, |
| | | row || {} |
| | | ); |
| | | planVisible.value = true; |
| | | }; |
| | | const savePlan = async () => { |
| | | await (planForm.id |
| | | ? updateCashflowPlan({ ...planForm }) |
| | | : addCashflowPlan({ ...planForm })); |
| | | ElMessage.success("ä¿åæå"); |
| | | planVisible.value = false; |
| | | loadPlans(); |
| | | }; |
| | | const removePlan = (row) => |
| | | ElMessageBox.confirm("确认å é¤è¯¥æ¶æ¯è®¡ååï¼", "æç¤º").then(async () => { |
| | | await deleteCashflowPlan([row.id]); |
| | | ElMessage.success("å 餿å"); |
| | | loadPlans(); |
| | | }); |
| | | onMounted(loadAccounts); |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .finance-workspace { |
| | | padding: 4px; |
| | | } |
| | | .toolbar { |
| | | margin: 12px 0; |
| | | } |
| | | .balance-grid { |
| | | display: grid; |
| | | grid-template-columns: repeat(auto-fill, minmax(230px, 1fr)); |
| | | gap: 12px; |
| | | margin-bottom: 16px; |
| | | } |
| | | .balance-title { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | } |
| | | .balance-amount { |
| | | font-size: 25px; |
| | | font-weight: 600; |
| | | color: #303133; |
| | | margin: 14px 0; |
| | | } |
| | | .muted { |
| | | color: #909399; |
| | | font-size: 13px; |
| | | } |
| | | .chart { |
| | | height: 300px; |
| | | } |
| | | .forecast { |
| | | margin-bottom: 12px; |
| | | } |
| | | </style> |
| | |
| | | <template> |
| | | <div class="app-container"> |
| | | <div class="funds-management-page"> |
| | | <FinanceWorkspace /> |
| | | <div v-if="false" class="app-container"> |
| | | <!-- æç´¢åº --> |
| | | <div class="search_form"> |
| | | <el-form :inline="true" :model="searchForm"> |
| | |
| | | </template> |
| | | </el-dialog> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, reactive, onMounted } from 'vue' |
| | | import { ElMessage, ElMessageBox } from 'element-plus' |
| | | import pagination from '@/components/PIMTable/Pagination.vue' |
| | | import FinanceWorkspace from './FinanceWorkspace.vue' |
| | | defineOptions({ name: 'FundsManagement' }) |
| | | import { |
| | | getFundsPage, |
| | | addFunds, |
| | |
| | | } |
| | | |
| | | onMounted(() => { |
| | | getList() |
| | | // æ°çèµéå·¥ä½åºèªè¡å è½½ä¸ä¸ªä¸å¡ååºçæ°æ®ã |
| | | }) |
| | | </script> |
| | | |
| | |
| | | import {listPage, deleteLedger, update} from "@/api/lavorissce/ledger.js"; |
| | | import {ElMessageBox, ElMessage} from "element-plus"; |
| | | const { proxy } = getCurrentInstance(); |
| | | import { getCurrentMonth } from "@/utils/util" |
| | | |
| | | const page = ref({ |
| | | current: 1, |