From fca0461c6014fc7dd1b9848017fd3bbae3f8d7de Mon Sep 17 00:00:00 2001
From: gaoluyang <2820782392@qq.com>
Date: 星期一, 20 四月 2026 09:21:07 +0800
Subject: [PATCH] 进销存升级 1.财务模块前端页面(routerjs文件后面要回退)
---
src/views/financialManagement/receivable/salesOut.vue | 271 ++
src/views/financialManagement/payable/payment.vue | 376 +++
src/views/financialManagement/generalLedger/index.vue | 290 ++
src/views/financialManagement/receivable/receipt.vue | 355 +++
src/views/financialManagement/voucher/index.vue | 418 ++++
src/views/financialManagement/receivable/invoiceApply.vue | 358 +++
src/router/index.js | 113 +
src/views/financialManagement/assets/fixedAssets.vue | 461 ++++
src/views/financialManagement/receivable/salesReturn.vue | 305 +++
src/views/financialManagement/payable/input-invoice.vue | 404 ++++
src/views/financialManagement/payable/paymentApply.vue | 359 +++
src/views/financialManagement/payable/reconciliation.vue | 254 ++
src/views/financialManagement/payable/purchaseIn.vue | 331 +++
src/views/financialManagement/assets/intangibleAssets.vue | 457 ++++
src/views/financialManagement/receivable/reconciliation.vue | 258 ++
src/views/financialManagement/receivable/outputInvoice.vue | 368 +++
src/views/financialManagement/voucher/detailLedger.vue | 289 ++
src/views/financialManagement/voucher/generalLedger.vue | 230 ++
18 files changed, 5,897 insertions(+), 0 deletions(-)
diff --git a/src/router/index.js b/src/router/index.js
index f342004..52c31d4 100644
--- a/src/router/index.js
+++ b/src/router/index.js
@@ -119,6 +119,119 @@
},
],
},
+ // 璐㈠姟绠$悊妯″潡璺敱
+ {
+ path: "/financial",
+ component: Layout,
+ hidden: false,
+ redirect: "/financial/general-ledger",
+ alwaysShow: true,
+ meta: { title: "璐㈠姟绠$悊", icon: "money" },
+ children: [
+ {
+ path: "general-ledger",
+ component: () => import("@/views/financialManagement/generalLedger/index.vue"),
+ name: "GeneralLedger",
+ meta: { title: "鎬诲笎绉戠洰" }
+ },
+ {
+ path: "sales-out",
+ component: () => import("@/views/financialManagement/receivable/salesOut.vue"),
+ name: "SalesOut",
+ meta: { title: "閿�鍞嚭搴�" }
+ },
+ {
+ path: "sales-return",
+ component: () => import("@/views/financialManagement/receivable/salesReturn.vue"),
+ name: "SalesReturn",
+ meta: { title: "閿�鍞��璐�" }
+ },
+ {
+ path: "receivable-reconciliation",
+ component: () => import("@/views/financialManagement/receivable/reconciliation.vue"),
+ name: "ReceivableReconciliation",
+ meta: { title: "搴旀敹瀵硅处" }
+ },
+ {
+ path: "invoice-apply",
+ component: () => import("@/views/financialManagement/receivable/invoiceApply.vue"),
+ name: "InvoiceApply",
+ meta: { title: "寮�绁ㄧ敵璇�" }
+ },
+ {
+ path: "output-invoice",
+ component: () => import("@/views/financialManagement/receivable/outputInvoice.vue"),
+ name: "OutputInvoice",
+ meta: { title: "閿�椤瑰彂绁�" }
+ },
+ {
+ path: "receipt",
+ component: () => import("@/views/financialManagement/receivable/receipt.vue"),
+ name: "Receipt",
+ meta: { title: "鏀舵鍗�" }
+ },
+ {
+ path: "purchase-in",
+ component: () => import("@/views/financialManagement/payable/purchaseIn.vue"),
+ name: "PurchaseIn",
+ meta: { title: "閲囪喘鍏ュ簱" }
+ },
+ {
+ path: "payable-reconciliation",
+ component: () => import("@/views/financialManagement/payable/reconciliation.vue"),
+ name: "PayableReconciliation",
+ meta: { title: "搴斾粯瀵硅处" }
+ },
+ {
+ path: "input-invoice",
+ component: () => import("@/views/financialManagement/payable/input-invoice.vue"),
+ name: "InputInvoice",
+ meta: { title: "杩涢」鍙戠エ" }
+ },
+ {
+ path: "payment-apply",
+ component: () => import("@/views/financialManagement/payable/paymentApply.vue"),
+ name: "PaymentApply",
+ meta: { title: "浠樻鐢宠" }
+ },
+ {
+ path: "payment",
+ component: () => import("@/views/financialManagement/payable/payment.vue"),
+ name: "Payment",
+ meta: { title: "浠樻鍗�" }
+ },
+ {
+ path: "fixed-assets",
+ component: () => import("@/views/financialManagement/assets/fixedAssets.vue"),
+ name: "FixedAssets",
+ meta: { title: "鍥哄畾璧勪骇" }
+ },
+ {
+ path: "intangible-assets",
+ component: () => import("@/views/financialManagement/assets/intangibleAssets.vue"),
+ name: "IntangibleAssets",
+ meta: { title: "鏃犲舰璧勪骇" }
+ },
+ {
+ path: "voucher",
+ component: () => import("@/views/financialManagement/voucher/index.vue"),
+ name: "Voucher",
+ meta: { title: "鍑瘉" }
+ },
+ {
+ path: "voucher-general-ledger",
+ component: () => import("@/views/financialManagement/voucher/generalLedger.vue"),
+ name: "VoucherGeneralLedger",
+ meta: { title: "绉戠洰鎬诲笎" }
+ },
+ {
+ path: "voucher-detail-ledger",
+ component: () => import("@/views/financialManagement/voucher/detailLedger.vue"),
+ name: "VoucherDetailLedger",
+ meta: { title: "绉戠洰鏄庣粏甯�" }
+ }
+ ]
+ }
];
// 鍔ㄦ�佽矾鐢憋紝鍩轰簬鐢ㄦ埛鏉冮檺鍔ㄦ�佸幓鍔犺浇
diff --git a/src/views/financialManagement/assets/fixedAssets.vue b/src/views/financialManagement/assets/fixedAssets.vue
new file mode 100644
index 0000000..1690a33
--- /dev/null
+++ b/src/views/financialManagement/assets/fixedAssets.vue
@@ -0,0 +1,461 @@
+<template>
+ <div class="app-container">
+ <el-form :model="filters" :inline="true">
+ <el-form-item label="璧勪骇缂栧彿:">
+ <el-input v-model="filters.assetCode" placeholder="璇疯緭鍏ヨ祫浜х紪鍙�" clearable style="width: 200px;" />
+ </el-form-item>
+ <el-form-item label="璧勪骇鍚嶇О:">
+ <el-input v-model="filters.assetName" placeholder="璇疯緭鍏ヨ祫浜у悕绉�" clearable style="width: 200px;" />
+ </el-form-item>
+ <el-form-item label="璧勪骇绫诲埆:">
+ <el-select v-model="filters.category" placeholder="璇烽�夋嫨绫诲埆" clearable style="width: 150px;">
+ <el-option label="鎴垮眿寤虹瓚" value="building" />
+ <el-option label="鏈哄櫒璁惧" value="machine" />
+ <el-option label="杩愯緭宸ュ叿" value="vehicle" />
+ <el-option label="鐢靛瓙璁惧" value="electronic" />
+ <el-option label="鍔炲叕瀹跺叿" value="furniture" />
+ </el-select>
+ </el-form-item>
+ <el-form-item label="鐘舵��:">
+ <el-select v-model="filters.status" placeholder="璇烽�夋嫨鐘舵��" clearable style="width: 150px;">
+ <el-option label="鍦ㄧ敤" value="in_use" />
+ <el-option label="闂茬疆" value="idle" />
+ <el-option label="鎶ュ簾" value="scrapped" />
+ </el-select>
+ </el-form-item>
+ <el-form-item>
+ <el-button type="primary" @click="getTableData">鎼滅储</el-button>
+ <el-button @click="resetFilters">閲嶇疆</el-button>
+ </el-form-item>
+ </el-form>
+ <div class="table_list">
+ <div class="actions">
+ <div>
+ <el-statistic title="璧勪骇鍘熷�煎悎璁�" :value="totalOriginalValue" precision="2" prefix="楼" />
+ <el-statistic title="绱鎶樻棫鍚堣" :value="totalDepreciation" precision="2" prefix="楼" style="margin-left: 30px;" />
+ <el-statistic title="鍑�鍊煎悎璁�" :value="totalNetValue" precision="2" prefix="楼" style="margin-left: 30px;" />
+ </div>
+ <div>
+ <el-button type="primary" @click="add" icon="Plus">鏂板璧勪骇</el-button>
+ <el-button type="warning" @click="handleDepreciation" icon="Money">鎶樻棫璁℃彁</el-button>
+ <el-button @click="handleOut" icon="Download">瀵煎嚭</el-button>
+ </div>
+ </div>
+ <PIMTable
+ rowKey="id"
+ :column="columns"
+ :tableData="dataList"
+ :page="{
+ current: pagination.currentPage,
+ size: pagination.pageSize,
+ total: pagination.total,
+ }"
+ @pagination="changePage"
+ >
+ <template #originalValue="{ row }">
+ <span class="text-primary">楼{{ formatMoney(row.originalValue) }}</span>
+ </template>
+ <template #accumulatedDepreciation="{ row }">
+ <span class="text-warning">楼{{ formatMoney(row.accumulatedDepreciation) }}</span>
+ </template>
+ <template #netValue="{ row }">
+ <span class="text-success">楼{{ formatMoney(row.netValue) }}</span>
+ </template>
+ <template #category="{ row }">
+ <el-tag>{{ getCategoryLabel(row.category) }}</el-tag>
+ </template>
+ <template #status="{ row }">
+ <el-tag :type="getStatusType(row.status)">{{ getStatusLabel(row.status) }}</el-tag>
+ </template>
+ <template #operation="{ row }">
+ <el-button type="primary" link @click="view(row)">鏌ョ湅</el-button>
+ <el-button type="primary" link @click="edit(row)">缂栬緫</el-button>
+ <el-button type="danger" link @click="handleDelete(row)">鍒犻櫎</el-button>
+ </template>
+ </PIMTable>
+ </div>
+
+ <el-dialog :title="dialogTitle" v-model="dialogVisible" width="800px" append-to-body>
+ <el-form :model="form" :rules="rules" ref="formRef" label-width="120px">
+ <el-row :gutter="20">
+ <el-col :span="12">
+ <el-form-item label="璧勪骇缂栧彿" prop="assetCode">
+ <el-input v-model="form.assetCode" placeholder="绯荤粺鑷姩鐢熸垚" disabled />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="璧勪骇鍚嶇О" prop="assetName">
+ <el-input v-model="form.assetName" placeholder="璇疯緭鍏ヨ祫浜у悕绉�" />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="20">
+ <el-col :span="12">
+ <el-form-item label="璧勪骇绫诲埆" prop="category">
+ <el-select v-model="form.category" placeholder="璇烽�夋嫨璧勪骇绫诲埆" style="width: 100%;">
+ <el-option label="鎴垮眿寤虹瓚" value="building" />
+ <el-option label="鏈哄櫒璁惧" value="machine" />
+ <el-option label="杩愯緭宸ュ叿" value="vehicle" />
+ <el-option label="鐢靛瓙璁惧" value="electronic" />
+ <el-option label="鍔炲叕瀹跺叿" value="furniture" />
+ </el-select>
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="瑙勬牸鍨嬪彿" prop="specification">
+ <el-input v-model="form.specification" placeholder="璇疯緭鍏ヨ鏍煎瀷鍙�" />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="20">
+ <el-col :span="12">
+ <el-form-item label="璐疆鏃ユ湡" prop="purchaseDate">
+ <el-date-picker v-model="form.purchaseDate" type="date" placeholder="閫夋嫨鏃ユ湡" value-format="YYYY-MM-DD" style="width: 100%;" />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="璧勪骇鍘熷��" prop="originalValue">
+ <el-input-number v-model="form.originalValue" :min="0" :precision="2" style="width: 100%;" @change="calculateNetValue" />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="20">
+ <el-col :span="12">
+ <el-form-item label="浣跨敤骞撮檺" prop="usefulLife">
+ <el-input-number v-model="form.usefulLife" :min="1" :max="50" style="width: 100%;" />
+ <span style="margin-left: 10px;">骞�</span>
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="娈嬪�肩巼" prop="residualRate">
+ <el-input-number v-model="form.residualRate" :min="0" :max="10" :precision="2" style="width: 100%;" />
+ <span style="margin-left: 10px;">%</span>
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="20">
+ <el-col :span="12">
+ <el-form-item label="绱鎶樻棫">
+ <el-input v-model="form.accumulatedDepreciation" disabled />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="璧勪骇鍑�鍊�">
+ <el-input v-model="form.netValue" disabled />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="20">
+ <el-col :span="12">
+ <el-form-item label="瀛樻斁鍦扮偣" prop="location">
+ <el-input v-model="form.location" placeholder="璇疯緭鍏ュ瓨鏀惧湴鐐�" />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="浣跨敤閮ㄩ棬" prop="department">
+ <el-input v-model="form.department" placeholder="璇疯緭鍏ヤ娇鐢ㄩ儴闂�" />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="20">
+ <el-col :span="12">
+ <el-form-item label="淇濈浜�" prop="keeper">
+ <el-input v-model="form.keeper" placeholder="璇疯緭鍏ヤ繚绠′汉" />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="鐘舵��" prop="status">
+ <el-select v-model="form.status" placeholder="璇烽�夋嫨鐘舵��" style="width: 100%;">
+ <el-option label="鍦ㄧ敤" value="in_use" />
+ <el-option label="闂茬疆" value="idle" />
+ <el-option label="鎶ュ簾" value="scrapped" />
+ </el-select>
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-form-item label="澶囨敞" prop="remark">
+ <el-input v-model="form.remark" type="textarea" :rows="3" placeholder="璇疯緭鍏ュ娉�" />
+ </el-form-item>
+ </el-form>
+ <template #footer>
+ <el-button @click="dialogVisible = false">鍙栨秷</el-button>
+ <el-button type="primary" @click="submitForm">纭畾</el-button>
+ </template>
+ </el-dialog>
+ </div>
+</template>
+
+<script setup>
+import { ref, reactive, onMounted, computed } from "vue";
+import { ElMessage, ElMessageBox } from "element-plus";
+
+defineOptions({
+ name: "鍥哄畾璧勪骇",
+});
+
+const filters = reactive({
+ assetCode: "",
+ assetName: "",
+ category: "",
+ status: "",
+});
+
+const pagination = reactive({
+ currentPage: 1,
+ pageSize: 10,
+ total: 0,
+});
+
+const columns = [
+ { label: "璧勪骇缂栧彿", prop: "assetCode", width: "130" },
+ { label: "璧勪骇鍚嶇О", prop: "assetName", width: "150" },
+ { label: "璧勪骇绫诲埆", prop: "category", slot: "category" },
+ { label: "瑙勬牸鍨嬪彿", prop: "specification", width: "120" },
+ { label: "璧勪骇鍘熷��", prop: "originalValue", slot: "originalValue" },
+ { label: "绱鎶樻棫", prop: "accumulatedDepreciation", slot: "accumulatedDepreciation" },
+ { label: "璧勪骇鍑�鍊�", prop: "netValue", slot: "netValue" },
+ { label: "鐘舵��", prop: "status", slot: "status" },
+ { label: "鎿嶄綔", prop: "operation", slot: "operation", width: "180", fixed: "right" },
+];
+
+const dataList = ref([]);
+const dialogVisible = ref(false);
+const dialogTitle = ref("");
+const formRef = ref(null);
+const isEdit = ref(false);
+const currentId = ref(null);
+
+const form = reactive({
+ assetCode: "",
+ assetName: "",
+ category: "",
+ specification: "",
+ purchaseDate: "",
+ originalValue: 0,
+ usefulLife: 5,
+ residualRate: 5,
+ accumulatedDepreciation: 0,
+ netValue: 0,
+ location: "",
+ department: "",
+ keeper: "",
+ status: "in_use",
+ remark: "",
+});
+
+const rules = {
+ assetName: [{ required: true, message: "璇疯緭鍏ヨ祫浜у悕绉�", trigger: "blur" }],
+ category: [{ required: true, message: "璇烽�夋嫨璧勪骇绫诲埆", trigger: "change" }],
+ purchaseDate: [{ required: true, message: "璇烽�夋嫨璐疆鏃ユ湡", trigger: "change" }],
+ originalValue: [{ required: true, message: "璇疯緭鍏ヨ祫浜у師鍊�", trigger: "blur" }],
+ usefulLife: [{ required: true, message: "璇疯緭鍏ヤ娇鐢ㄥ勾闄�", trigger: "blur" }],
+};
+
+const mockData = [
+ { id: 1, assetCode: "GD2024001", assetName: "鍔炲叕鐢佃剳", category: "electronic", specification: "鑱旀兂ThinkPad X1", purchaseDate: "2023-01-15", originalValue: 8000, usefulLife: 5, residualRate: 5, accumulatedDepreciation: 1520, netValue: 6480, location: "鍔炲叕瀹�", department: "璐㈠姟閮�", keeper: "寮犱笁", status: "in_use", remark: "" },
+ { id: 2, assetCode: "GD2024002", assetName: "鎵撳嵃鏈�", category: "electronic", specification: "鎯犳櫘M479fdw", purchaseDate: "2023-03-20", originalValue: 3500, usefulLife: 5, residualRate: 5, accumulatedDepreciation: 532, netValue: 2968, location: "鏂囧嵃瀹�", department: "琛屾斂閮�", keeper: "鏉庡洓", status: "in_use", remark: "" },
+ { id: 3, assetCode: "GD2024003", assetName: "鍔炲叕妗屾", category: "furniture", specification: "瀹炴湪鍔炲叕妗�", purchaseDate: "2023-06-10", originalValue: 2500, usefulLife: 10, residualRate: 5, accumulatedDepreciation: 118.75, netValue: 2381.25, location: "鍔炲叕瀹�", department: "閿�鍞儴", keeper: "鐜嬩簲", status: "in_use", remark: "" },
+ { id: 4, assetCode: "GD2024004", assetName: "鍟嗗姟杞�", category: "vehicle", specification: "鍒厠GL8", purchaseDate: "2022-08-01", originalValue: 280000, usefulLife: 10, residualRate: 5, accumulatedDepreciation: 53200, netValue: 226800, location: "鍋滆溅鍦�", department: "琛屾斂閮�", keeper: "璧靛叚", status: "in_use", remark: "" },
+];
+
+const totalOriginalValue = computed(() => {
+ return dataList.value.reduce((sum, item) => sum + Number(item.originalValue), 0);
+});
+
+const totalDepreciation = computed(() => {
+ return dataList.value.reduce((sum, item) => sum + Number(item.accumulatedDepreciation), 0);
+});
+
+const totalNetValue = computed(() => {
+ return dataList.value.reduce((sum, item) => sum + Number(item.netValue), 0);
+});
+
+const formatMoney = (value) => {
+ if (value === undefined || value === null) return "0.00";
+ return Number(value).toFixed(2).replace(/\B(?=(\d{3})+(?!\d))/g, ",");
+};
+
+const getCategoryLabel = (category) => {
+ const map = {
+ building: "鎴垮眿寤虹瓚",
+ machine: "鏈哄櫒璁惧",
+ vehicle: "杩愯緭宸ュ叿",
+ electronic: "鐢靛瓙璁惧",
+ furniture: "鍔炲叕瀹跺叿",
+ };
+ return map[category] || category;
+};
+
+const getStatusLabel = (status) => {
+ const map = { in_use: "鍦ㄧ敤", idle: "闂茬疆", scrapped: "鎶ュ簾" };
+ return map[status] || status;
+};
+
+const getStatusType = (status) => {
+ const map = { in_use: "success", idle: "warning", scrapped: "info" };
+ return map[status] || "";
+};
+
+const calculateNetValue = () => {
+ form.netValue = Number((form.originalValue - form.accumulatedDepreciation).toFixed(2));
+};
+
+const getTableData = () => {
+ let result = [...mockData];
+ if (filters.assetCode) {
+ result = result.filter(item => item.assetCode.includes(filters.assetCode));
+ }
+ if (filters.assetName) {
+ result = result.filter(item => item.assetName.includes(filters.assetName));
+ }
+ if (filters.category) {
+ result = result.filter(item => item.category === filters.category);
+ }
+ if (filters.status) {
+ result = result.filter(item => item.status === filters.status);
+ }
+ pagination.total = result.length;
+ dataList.value = result.slice((pagination.currentPage - 1) * pagination.pageSize, pagination.currentPage * pagination.pageSize);
+};
+
+const resetFilters = () => {
+ filters.assetCode = "";
+ filters.assetName = "";
+ filters.category = "";
+ filters.status = "";
+ pagination.currentPage = 1;
+ getTableData();
+};
+
+const changePage = ({ current, size }) => {
+ pagination.currentPage = current;
+ pagination.pageSize = size;
+ getTableData();
+};
+
+const add = () => {
+ isEdit.value = false;
+ dialogTitle.value = "鏂板鍥哄畾璧勪骇";
+ Object.assign(form, {
+ assetCode: "GD" + Date.now().toString().slice(-8),
+ assetName: "",
+ category: "",
+ specification: "",
+ purchaseDate: new Date().toISOString().split('T')[0],
+ originalValue: 0,
+ usefulLife: 5,
+ residualRate: 5,
+ accumulatedDepreciation: 0,
+ netValue: 0,
+ location: "",
+ department: "",
+ keeper: "",
+ status: "in_use",
+ remark: "",
+ });
+ dialogVisible.value = true;
+};
+
+const edit = (row) => {
+ isEdit.value = true;
+ currentId.value = row.id;
+ dialogTitle.value = "缂栬緫鍥哄畾璧勪骇";
+ Object.assign(form, row);
+ dialogVisible.value = true;
+};
+
+const view = (row) => {
+ ElMessage.info(`鏌ョ湅璧勪骇: ${row.assetName}`);
+};
+
+const handleDelete = (row) => {
+ ElMessageBox.confirm("纭鍒犻櫎璇ュ浐瀹氳祫浜у悧锛�", "鎻愮ず", {
+ confirmButtonText: "纭畾",
+ cancelButtonText: "鍙栨秷",
+ type: "warning",
+ }).then(() => {
+ const index = mockData.findIndex(item => item.id === row.id);
+ if (index !== -1) {
+ mockData.splice(index, 1);
+ }
+ ElMessage.success("鍒犻櫎鎴愬姛");
+ getTableData();
+ });
+};
+
+const handleDepreciation = () => {
+ ElMessageBox.confirm("纭杩涜鏈湀鎶樻棫璁℃彁鍚楋紵", "鎻愮ず", {
+ confirmButtonText: "纭",
+ cancelButtonText: "鍙栨秷",
+ type: "info",
+ }).then(() => {
+ mockData.forEach(item => {
+ if (item.status === "in_use") {
+ const monthlyDepreciation = (item.originalValue * (1 - item.residualRate / 100)) / (item.usefulLife * 12);
+ item.accumulatedDepreciation = Number((item.accumulatedDepreciation + monthlyDepreciation).toFixed(2));
+ item.netValue = Number((item.originalValue - item.accumulatedDepreciation).toFixed(2));
+ }
+ });
+ ElMessage.success("鎶樻棫璁℃彁瀹屾垚");
+ getTableData();
+ });
+};
+
+const handleOut = () => {
+ ElMessage.success("瀵煎嚭鎴愬姛");
+};
+
+const submitForm = () => {
+ formRef.value.validate((valid) => {
+ if (valid) {
+ calculateNetValue();
+ if (isEdit.value) {
+ const index = mockData.findIndex(item => item.id === currentId.value);
+ if (index !== -1) {
+ mockData[index] = { ...mockData[index], ...form };
+ }
+ ElMessage.success("缂栬緫鎴愬姛");
+ } else {
+ const newId = mockData.length > 0 ? Math.max(...mockData.map(item => item.id)) + 1 : 1;
+ mockData.push({ id: newId, ...form });
+ ElMessage.success("鏂板鎴愬姛");
+ }
+ dialogVisible.value = false;
+ getTableData();
+ }
+ });
+};
+
+onMounted(() => {
+ getTableData();
+});
+</script>
+
+<style lang="scss" scoped>
+.actions {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 15px;
+
+ > div:first-child {
+ display: flex;
+ align-items: center;
+ }
+}
+
+.text-primary {
+ color: #409eff;
+ font-weight: bold;
+}
+
+.text-warning {
+ color: #e6a23c;
+ font-weight: bold;
+}
+
+.text-success {
+ color: #67c23a;
+ font-weight: bold;
+}
+</style>
diff --git a/src/views/financialManagement/assets/intangibleAssets.vue b/src/views/financialManagement/assets/intangibleAssets.vue
new file mode 100644
index 0000000..f0b9af9
--- /dev/null
+++ b/src/views/financialManagement/assets/intangibleAssets.vue
@@ -0,0 +1,457 @@
+<template>
+ <div class="app-container">
+ <el-form :model="filters" :inline="true">
+ <el-form-item label="璧勪骇缂栧彿:">
+ <el-input v-model="filters.assetCode" placeholder="璇疯緭鍏ヨ祫浜х紪鍙�" clearable style="width: 200px;" />
+ </el-form-item>
+ <el-form-item label="璧勪骇鍚嶇О:">
+ <el-input v-model="filters.assetName" placeholder="璇疯緭鍏ヨ祫浜у悕绉�" clearable style="width: 200px;" />
+ </el-form-item>
+ <el-form-item label="璧勪骇绫诲埆:">
+ <el-select v-model="filters.category" placeholder="璇烽�夋嫨绫诲埆" clearable style="width: 150px;">
+ <el-option label="涓撳埄鏉�" value="patent" />
+ <el-option label="鍟嗘爣鏉�" value="trademark" />
+ <el-option label="钁椾綔鏉�" value="copyright" />
+ <el-option label="杞欢" value="software" />
+ <el-option label="鍦熷湴浣跨敤鏉�" value="land" />
+ <el-option label="鍏朵粬" value="other" />
+ </el-select>
+ </el-form-item>
+ <el-form-item label="鐘舵��:">
+ <el-select v-model="filters.status" placeholder="璇烽�夋嫨鐘舵��" clearable style="width: 150px;">
+ <el-option label="鍦ㄧ敤" value="in_use" />
+ <el-option label="闂茬疆" value="idle" />
+ <el-option label="宸叉憡閿�瀹屾瘯" value="amortized" />
+ </el-select>
+ </el-form-item>
+ <el-form-item>
+ <el-button type="primary" @click="getTableData">鎼滅储</el-button>
+ <el-button @click="resetFilters">閲嶇疆</el-button>
+ </el-form-item>
+ </el-form>
+ <div class="table_list">
+ <div class="actions">
+ <div>
+ <el-statistic title="璧勪骇鍘熷�煎悎璁�" :value="totalOriginalValue" precision="2" prefix="楼" />
+ <el-statistic title="绱鎽婇攢鍚堣" :value="totalAmortization" precision="2" prefix="楼" style="margin-left: 30px;" />
+ <el-statistic title="鍑�鍊煎悎璁�" :value="totalNetValue" precision="2" prefix="楼" style="margin-left: 30px;" />
+ </div>
+ <div>
+ <el-button type="primary" @click="add" icon="Plus">鏂板璧勪骇</el-button>
+ <el-button type="warning" @click="handleAmortization" icon="Money">鎽婇攢璁℃彁</el-button>
+ <el-button @click="handleOut" icon="Download">瀵煎嚭</el-button>
+ </div>
+ </div>
+ <PIMTable
+ rowKey="id"
+ :column="columns"
+ :tableData="dataList"
+ :page="{
+ current: pagination.currentPage,
+ size: pagination.pageSize,
+ total: pagination.total,
+ }"
+ @pagination="changePage"
+ >
+ <template #originalValue="{ row }">
+ <span class="text-primary">楼{{ formatMoney(row.originalValue) }}</span>
+ </template>
+ <template #accumulatedAmortization="{ row }">
+ <span class="text-warning">楼{{ formatMoney(row.accumulatedAmortization) }}</span>
+ </template>
+ <template #netValue="{ row }">
+ <span class="text-success">楼{{ formatMoney(row.netValue) }}</span>
+ </template>
+ <template #category="{ row }">
+ <el-tag>{{ getCategoryLabel(row.category) }}</el-tag>
+ </template>
+ <template #status="{ row }">
+ <el-tag :type="getStatusType(row.status)">{{ getStatusLabel(row.status) }}</el-tag>
+ </template>
+ <template #operation="{ row }">
+ <el-button type="primary" link @click="view(row)">鏌ョ湅</el-button>
+ <el-button type="primary" link @click="edit(row)">缂栬緫</el-button>
+ <el-button type="danger" link @click="handleDelete(row)">鍒犻櫎</el-button>
+ </template>
+ </PIMTable>
+ </div>
+
+ <el-dialog :title="dialogTitle" v-model="dialogVisible" width="800px" append-to-body>
+ <el-form :model="form" :rules="rules" ref="formRef" label-width="120px">
+ <el-row :gutter="20">
+ <el-col :span="12">
+ <el-form-item label="璧勪骇缂栧彿" prop="assetCode">
+ <el-input v-model="form.assetCode" placeholder="绯荤粺鑷姩鐢熸垚" disabled />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="璧勪骇鍚嶇О" prop="assetName">
+ <el-input v-model="form.assetName" placeholder="璇疯緭鍏ヨ祫浜у悕绉�" />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="20">
+ <el-col :span="12">
+ <el-form-item label="璧勪骇绫诲埆" prop="category">
+ <el-select v-model="form.category" placeholder="璇烽�夋嫨璧勪骇绫诲埆" style="width: 100%;">
+ <el-option label="涓撳埄鏉�" value="patent" />
+ <el-option label="鍟嗘爣鏉�" value="trademark" />
+ <el-option label="钁椾綔鏉�" value="copyright" />
+ <el-option label="杞欢" value="software" />
+ <el-option label="鍦熷湴浣跨敤鏉�" value="land" />
+ <el-option label="鍏朵粬" value="other" />
+ </el-select>
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="璇佷功缂栧彿" prop="certificateNo">
+ <el-input v-model="form.certificateNo" placeholder="璇疯緭鍏ヨ瘉涔︾紪鍙�" />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="20">
+ <el-col :span="12">
+ <el-form-item label="鍙栧緱鏃ユ湡" prop="acquisitionDate">
+ <el-date-picker v-model="form.acquisitionDate" type="date" placeholder="閫夋嫨鏃ユ湡" value-format="YYYY-MM-DD" style="width: 100%;" />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="璧勪骇鍘熷��" prop="originalValue">
+ <el-input-number v-model="form.originalValue" :min="0" :precision="2" style="width: 100%;" @change="calculateNetValue" />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="20">
+ <el-col :span="12">
+ <el-form-item label="鎽婇攢骞撮檺" prop="amortizationPeriod">
+ <el-input-number v-model="form.amortizationPeriod" :min="1" :max="50" style="width: 100%;" />
+ <span style="margin-left: 10px;">骞�</span>
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="娈嬪�肩巼" prop="residualRate">
+ <el-input-number v-model="form.residualRate" :min="0" :max="10" :precision="2" style="width: 100%;" />
+ <span style="margin-left: 10px;">%</span>
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="20">
+ <el-col :span="12">
+ <el-form-item label="绱鎽婇攢">
+ <el-input v-model="form.accumulatedAmortization" disabled />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="璧勪骇鍑�鍊�">
+ <el-input v-model="form.netValue" disabled />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="20">
+ <el-col :span="12">
+ <el-form-item label="鏈夋晥鏈熻嚦" prop="validityDate">
+ <el-date-picker v-model="form.validityDate" type="date" placeholder="閫夋嫨鏃ユ湡" value-format="YYYY-MM-DD" style="width: 100%;" />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="鐘舵��" prop="status">
+ <el-select v-model="form.status" placeholder="璇烽�夋嫨鐘舵��" style="width: 100%;">
+ <el-option label="鍦ㄧ敤" value="in_use" />
+ <el-option label="闂茬疆" value="idle" />
+ <el-option label="宸叉憡閿�瀹屾瘯" value="amortized" />
+ </el-select>
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-form-item label="璧勪骇鎻忚堪" prop="description">
+ <el-input v-model="form.description" type="textarea" :rows="3" placeholder="璇疯緭鍏ヨ祫浜ф弿杩�" />
+ </el-form-item>
+ <el-form-item label="澶囨敞" prop="remark">
+ <el-input v-model="form.remark" type="textarea" :rows="2" placeholder="璇疯緭鍏ュ娉�" />
+ </el-form-item>
+ </el-form>
+ <template #footer>
+ <el-button @click="dialogVisible = false">鍙栨秷</el-button>
+ <el-button type="primary" @click="submitForm">纭畾</el-button>
+ </template>
+ </el-dialog>
+ </div>
+</template>
+
+<script setup>
+import { ref, reactive, onMounted, computed } from "vue";
+import { ElMessage, ElMessageBox } from "element-plus";
+
+defineOptions({
+ name: "鏃犲舰璧勪骇",
+});
+
+const filters = reactive({
+ assetCode: "",
+ assetName: "",
+ category: "",
+ status: "",
+});
+
+const pagination = reactive({
+ currentPage: 1,
+ pageSize: 10,
+ total: 0,
+});
+
+const columns = [
+ { label: "璧勪骇缂栧彿", prop: "assetCode", width: "130" },
+ { label: "璧勪骇鍚嶇О", prop: "assetName", width: "150" },
+ { label: "璧勪骇绫诲埆", prop: "category", slot: "category" },
+ { label: "璇佷功缂栧彿", prop: "certificateNo", width: "150" },
+ { label: "璧勪骇鍘熷��", prop: "originalValue", slot: "originalValue" },
+ { label: "绱鎽婇攢", prop: "accumulatedAmortization", slot: "accumulatedAmortization" },
+ { label: "璧勪骇鍑�鍊�", prop: "netValue", slot: "netValue" },
+ { label: "鐘舵��", prop: "status", slot: "status" },
+ { label: "鎿嶄綔", prop: "operation", slot: "operation", width: "180", fixed: "right" },
+];
+
+const dataList = ref([]);
+const dialogVisible = ref(false);
+const dialogTitle = ref("");
+const formRef = ref(null);
+const isEdit = ref(false);
+const currentId = ref(null);
+
+const form = reactive({
+ assetCode: "",
+ assetName: "",
+ category: "",
+ certificateNo: "",
+ acquisitionDate: "",
+ originalValue: 0,
+ amortizationPeriod: 10,
+ residualRate: 0,
+ accumulatedAmortization: 0,
+ netValue: 0,
+ validityDate: "",
+ status: "in_use",
+ description: "",
+ remark: "",
+});
+
+const rules = {
+ assetName: [{ required: true, message: "璇疯緭鍏ヨ祫浜у悕绉�", trigger: "blur" }],
+ category: [{ required: true, message: "璇烽�夋嫨璧勪骇绫诲埆", trigger: "change" }],
+ acquisitionDate: [{ required: true, message: "璇烽�夋嫨鍙栧緱鏃ユ湡", trigger: "change" }],
+ originalValue: [{ required: true, message: "璇疯緭鍏ヨ祫浜у師鍊�", trigger: "blur" }],
+ amortizationPeriod: [{ required: true, message: "璇疯緭鍏ユ憡閿�骞撮檺", trigger: "blur" }],
+};
+
+const mockData = [
+ { id: 1, assetCode: "WX2024001", assetName: "ERP杞欢璁稿彲", category: "software", certificateNo: "SW-2023-001", acquisitionDate: "2023-01-01", originalValue: 50000, amortizationPeriod: 10, residualRate: 0, accumulatedAmortization: 5000, netValue: 45000, validityDate: "2033-01-01", status: "in_use", description: "浼佷笟璧勬簮璁″垝绠$悊绯荤粺", remark: "" },
+ { id: 2, assetCode: "WX2024002", assetName: "鍙戞槑涓撳埄", category: "patent", certificateNo: "ZL202210123456.7", acquisitionDate: "2022-06-15", originalValue: 100000, amortizationPeriod: 20, residualRate: 0, accumulatedAmortization: 3750, netValue: 96250, validityDate: "2042-06-15", status: "in_use", description: "涓�绉嶆柊鍨嬬敓浜у伐鑹�", remark: "" },
+ { id: 3, assetCode: "WX2024003", assetName: "鍟嗘爣鏉�", category: "trademark", certificateNo: "TM-2023-008", acquisitionDate: "2023-03-10", originalValue: 20000, amortizationPeriod: 10, residualRate: 0, accumulatedAmortization: 1500, netValue: 18500, validityDate: "2033-03-10", status: "in_use", description: "鍏徃鍝佺墝鍟嗘爣", remark: "" },
+ { id: 4, assetCode: "WX2024004", assetName: "鍦熷湴浣跨敤鏉�", category: "land", certificateNo: "鍦熷浗鐢�(2023)绗�001鍙�", acquisitionDate: "2023-07-01", originalValue: 500000, amortizationPeriod: 50, residualRate: 0, accumulatedAmortization: 5000, netValue: 495000, validityDate: "2073-07-01", status: "in_use", description: "宸ヤ笟鐢ㄥ湴浣跨敤鏉�", remark: "" },
+];
+
+const totalOriginalValue = computed(() => {
+ return dataList.value.reduce((sum, item) => sum + Number(item.originalValue), 0);
+});
+
+const totalAmortization = computed(() => {
+ return dataList.value.reduce((sum, item) => sum + Number(item.accumulatedAmortization), 0);
+});
+
+const totalNetValue = computed(() => {
+ return dataList.value.reduce((sum, item) => sum + Number(item.netValue), 0);
+});
+
+const formatMoney = (value) => {
+ if (value === undefined || value === null) return "0.00";
+ return Number(value).toFixed(2).replace(/\B(?=(\d{3})+(?!\d))/g, ",");
+};
+
+const getCategoryLabel = (category) => {
+ const map = {
+ patent: "涓撳埄鏉�",
+ trademark: "鍟嗘爣鏉�",
+ copyright: "钁椾綔鏉�",
+ software: "杞欢",
+ land: "鍦熷湴浣跨敤鏉�",
+ other: "鍏朵粬",
+ };
+ return map[category] || category;
+};
+
+const getStatusLabel = (status) => {
+ const map = { in_use: "鍦ㄧ敤", idle: "闂茬疆", amortized: "宸叉憡閿�瀹屾瘯" };
+ return map[status] || status;
+};
+
+const getStatusType = (status) => {
+ const map = { in_use: "success", idle: "warning", amortized: "info" };
+ return map[status] || "";
+};
+
+const calculateNetValue = () => {
+ form.netValue = Number((form.originalValue - form.accumulatedAmortization).toFixed(2));
+};
+
+const getTableData = () => {
+ let result = [...mockData];
+ if (filters.assetCode) {
+ result = result.filter(item => item.assetCode.includes(filters.assetCode));
+ }
+ if (filters.assetName) {
+ result = result.filter(item => item.assetName.includes(filters.assetName));
+ }
+ if (filters.category) {
+ result = result.filter(item => item.category === filters.category);
+ }
+ if (filters.status) {
+ result = result.filter(item => item.status === filters.status);
+ }
+ pagination.total = result.length;
+ dataList.value = result.slice((pagination.currentPage - 1) * pagination.pageSize, pagination.currentPage * pagination.pageSize);
+};
+
+const resetFilters = () => {
+ filters.assetCode = "";
+ filters.assetName = "";
+ filters.category = "";
+ filters.status = "";
+ pagination.currentPage = 1;
+ getTableData();
+};
+
+const changePage = ({ current, size }) => {
+ pagination.currentPage = current;
+ pagination.pageSize = size;
+ getTableData();
+};
+
+const add = () => {
+ isEdit.value = false;
+ dialogTitle.value = "鏂板鏃犲舰璧勪骇";
+ Object.assign(form, {
+ assetCode: "WX" + Date.now().toString().slice(-8),
+ assetName: "",
+ category: "",
+ certificateNo: "",
+ acquisitionDate: new Date().toISOString().split('T')[0],
+ originalValue: 0,
+ amortizationPeriod: 10,
+ residualRate: 0,
+ accumulatedAmortization: 0,
+ netValue: 0,
+ validityDate: "",
+ status: "in_use",
+ description: "",
+ remark: "",
+ });
+ dialogVisible.value = true;
+};
+
+const edit = (row) => {
+ isEdit.value = true;
+ currentId.value = row.id;
+ dialogTitle.value = "缂栬緫鏃犲舰璧勪骇";
+ Object.assign(form, row);
+ dialogVisible.value = true;
+};
+
+const view = (row) => {
+ ElMessage.info(`鏌ョ湅璧勪骇: ${row.assetName}`);
+};
+
+const handleDelete = (row) => {
+ ElMessageBox.confirm("纭鍒犻櫎璇ユ棤褰㈣祫浜у悧锛�", "鎻愮ず", {
+ confirmButtonText: "纭畾",
+ cancelButtonText: "鍙栨秷",
+ type: "warning",
+ }).then(() => {
+ const index = mockData.findIndex(item => item.id === row.id);
+ if (index !== -1) {
+ mockData.splice(index, 1);
+ }
+ ElMessage.success("鍒犻櫎鎴愬姛");
+ getTableData();
+ });
+};
+
+const handleAmortization = () => {
+ ElMessageBox.confirm("纭杩涜鏈湀鎽婇攢璁℃彁鍚楋紵", "鎻愮ず", {
+ confirmButtonText: "纭",
+ cancelButtonText: "鍙栨秷",
+ type: "info",
+ }).then(() => {
+ mockData.forEach(item => {
+ if (item.status === "in_use") {
+ const monthlyAmortization = (item.originalValue * (1 - item.residualRate / 100)) / (item.amortizationPeriod * 12);
+ item.accumulatedAmortization = Number((item.accumulatedAmortization + monthlyAmortization).toFixed(2));
+ item.netValue = Number((item.originalValue - item.accumulatedAmortization).toFixed(2));
+ if (item.netValue <= 0) {
+ item.status = "amortized";
+ item.netValue = 0;
+ }
+ }
+ });
+ ElMessage.success("鎽婇攢璁℃彁瀹屾垚");
+ getTableData();
+ });
+};
+
+const handleOut = () => {
+ ElMessage.success("瀵煎嚭鎴愬姛");
+};
+
+const submitForm = () => {
+ formRef.value.validate((valid) => {
+ if (valid) {
+ calculateNetValue();
+ if (isEdit.value) {
+ const index = mockData.findIndex(item => item.id === currentId.value);
+ if (index !== -1) {
+ mockData[index] = { ...mockData[index], ...form };
+ }
+ ElMessage.success("缂栬緫鎴愬姛");
+ } else {
+ const newId = mockData.length > 0 ? Math.max(...mockData.map(item => item.id)) + 1 : 1;
+ mockData.push({ id: newId, ...form });
+ ElMessage.success("鏂板鎴愬姛");
+ }
+ dialogVisible.value = false;
+ getTableData();
+ }
+ });
+};
+
+onMounted(() => {
+ getTableData();
+});
+</script>
+
+<style lang="scss" scoped>
+.actions {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 15px;
+
+ > div:first-child {
+ display: flex;
+ align-items: center;
+ }
+}
+
+.text-primary {
+ color: #409eff;
+ font-weight: bold;
+}
+
+.text-warning {
+ color: #e6a23c;
+ font-weight: bold;
+}
+
+.text-success {
+ color: #67c23a;
+ font-weight: bold;
+}
+</style>
diff --git a/src/views/financialManagement/generalLedger/index.vue b/src/views/financialManagement/generalLedger/index.vue
new file mode 100644
index 0000000..0b5d597
--- /dev/null
+++ b/src/views/financialManagement/generalLedger/index.vue
@@ -0,0 +1,290 @@
+<template>
+ <div class="app-container">
+ <el-form :model="filters" :inline="true">
+ <el-form-item label="绉戠洰缂栫爜:">
+ <el-input v-model="filters.subjectCode" placeholder="璇疯緭鍏ョ鐩紪鐮�" clearable style="width: 200px;" />
+ </el-form-item>
+ <el-form-item label="绉戠洰鍚嶇О:">
+ <el-input v-model="filters.subjectName" placeholder="璇疯緭鍏ョ鐩悕绉�" clearable style="width: 200px;" />
+ </el-form-item>
+ <el-form-item label="绉戠洰绫诲瀷:">
+ <el-select v-model="filters.subjectType" placeholder="璇烽�夋嫨" clearable style="width: 200px;">
+ <el-option label="璧勪骇绫�" value="asset" />
+ <el-option label="璐熷�虹被" value="liability" />
+ <el-option label="鏉冪泭绫�" value="equity" />
+ <el-option label="鎴愭湰绫�" value="cost" />
+ <el-option label="鎹熺泭绫�" value="profit_loss" />
+ </el-select>
+ </el-form-item>
+ <el-form-item>
+ <el-button type="primary" @click="getTableData">鎼滅储</el-button>
+ <el-button @click="resetFilters">閲嶇疆</el-button>
+ </el-form-item>
+ </el-form>
+ <div class="table_list">
+ <div class="actions">
+ <div></div>
+ <div>
+ <el-button type="primary" @click="add" icon="Plus">鏂板</el-button>
+ <el-button @click="handleOut" icon="Download">瀵煎嚭</el-button>
+ </div>
+ </div>
+ <PIMTable
+ rowKey="id"
+ :column="columns"
+ :tableData="dataList"
+ :page="{
+ current: pagination.currentPage,
+ size: pagination.pageSize,
+ total: pagination.total,
+ }"
+ @pagination="changePage"
+ >
+ <template #subjectType="{ row }">
+ <el-tag :type="getSubjectTypeType(row.subjectType)">{{ getSubjectTypeLabel(row.subjectType) }}</el-tag>
+ </template>
+ <template #balanceDirection="{ row }">
+ <el-tag :type="row.balanceDirection === 'debit' ? 'success' : 'danger'">
+ {{ row.balanceDirection === 'debit' ? '鍊熸柟' : '璐锋柟' }}
+ </el-tag>
+ </template>
+ <template #status="{ row }">
+ <el-tag :type="row.status === 'active' ? 'success' : 'info'">
+ {{ row.status === 'active' ? '鍚敤' : '绂佺敤' }}
+ </el-tag>
+ </template>
+ <template #operation="{ row }">
+ <el-button type="primary" link @click="edit(row)">缂栬緫</el-button>
+ <el-button type="danger" link @click="handleDelete(row)">鍒犻櫎</el-button>
+ </template>
+ </PIMTable>
+ </div>
+
+ <el-dialog :title="dialogTitle" v-model="dialogVisible" width="600px" append-to-body>
+ <el-form :model="form" :rules="rules" ref="formRef" label-width="100px">
+ <el-form-item label="绉戠洰缂栫爜" prop="subjectCode">
+ <el-input v-model="form.subjectCode" placeholder="璇疯緭鍏ョ鐩紪鐮�" />
+ </el-form-item>
+ <el-form-item label="绉戠洰鍚嶇О" prop="subjectName">
+ <el-input v-model="form.subjectName" placeholder="璇疯緭鍏ョ鐩悕绉�" />
+ </el-form-item>
+ <el-form-item label="绉戠洰绫诲瀷" prop="subjectType">
+ <el-select v-model="form.subjectType" placeholder="璇烽�夋嫨绉戠洰绫诲瀷" style="width: 100%;">
+ <el-option label="璧勪骇绫�" value="asset" />
+ <el-option label="璐熷�虹被" value="liability" />
+ <el-option label="鏉冪泭绫�" value="equity" />
+ <el-option label="鎴愭湰绫�" value="cost" />
+ <el-option label="鎹熺泭绫�" value="profit_loss" />
+ </el-select>
+ </el-form-item>
+ <el-form-item label="浣欓鏂瑰悜" prop="balanceDirection">
+ <el-radio-group v-model="form.balanceDirection">
+ <el-radio label="debit">鍊熸柟</el-radio>
+ <el-radio label="credit">璐锋柟</el-radio>
+ </el-radio-group>
+ </el-form-item>
+ <el-form-item label="鐘舵��" prop="status">
+ <el-radio-group v-model="form.status">
+ <el-radio label="active">鍚敤</el-radio>
+ <el-radio label="inactive">绂佺敤</el-radio>
+ </el-radio-group>
+ </el-form-item>
+ <el-form-item label="澶囨敞" prop="remark">
+ <el-input v-model="form.remark" type="textarea" :rows="3" placeholder="璇疯緭鍏ュ娉�" />
+ </el-form-item>
+ </el-form>
+ <template #footer>
+ <el-button @click="dialogVisible = false">鍙栨秷</el-button>
+ <el-button type="primary" @click="submitForm">纭畾</el-button>
+ </template>
+ </el-dialog>
+ </div>
+</template>
+
+<script setup>
+import { ref, reactive, onMounted } from "vue";
+import { ElMessage, ElMessageBox } from "element-plus";
+
+defineOptions({
+ name: "鎬诲笎绉戠洰",
+});
+
+const filters = reactive({
+ subjectCode: "",
+ subjectName: "",
+ subjectType: "",
+});
+
+const pagination = reactive({
+ currentPage: 1,
+ pageSize: 10,
+ total: 0,
+});
+
+const columns = [
+ { label: "绉戠洰缂栫爜", prop: "subjectCode", width: "120" },
+ { label: "绉戠洰鍚嶇О", prop: "subjectName", width: "150" },
+ { label: "绉戠洰绫诲瀷", prop: "subjectType", slot: "subjectType" },
+ { label: "浣欓鏂瑰悜", prop: "balanceDirection", slot: "balanceDirection" },
+ { label: "鐘舵��", prop: "status", slot: "status" },
+ { label: "澶囨敞", prop: "remark", showOverflowTooltip: true },
+ { label: "鎿嶄綔", prop: "operation", slot: "operation", width: "150", fixed: "right" },
+];
+
+const dataList = ref([]);
+const dialogVisible = ref(false);
+const dialogTitle = ref("");
+const formRef = ref(null);
+const isEdit = ref(false);
+const currentId = ref(null);
+
+const form = reactive({
+ subjectCode: "",
+ subjectName: "",
+ subjectType: "",
+ balanceDirection: "debit",
+ status: "active",
+ remark: "",
+});
+
+const rules = {
+ subjectCode: [{ required: true, message: "璇疯緭鍏ョ鐩紪鐮�", trigger: "blur" }],
+ subjectName: [{ required: true, message: "璇疯緭鍏ョ鐩悕绉�", trigger: "blur" }],
+ subjectType: [{ required: true, message: "璇烽�夋嫨绉戠洰绫诲瀷", trigger: "change" }],
+};
+
+const mockData = [
+ { id: 1, subjectCode: "1001", subjectName: "搴撳瓨鐜伴噾", subjectType: "asset", balanceDirection: "debit", status: "active", remark: "" },
+ { id: 2, subjectCode: "1002", subjectName: "閾惰瀛樻", subjectType: "asset", balanceDirection: "debit", status: "active", remark: "" },
+ { id: 3, subjectCode: "1122", subjectName: "搴旀敹璐︽", subjectType: "asset", balanceDirection: "debit", status: "active", remark: "" },
+ { id: 4, subjectCode: "2202", subjectName: "搴斾粯璐︽", subjectType: "liability", balanceDirection: "credit", status: "active", remark: "" },
+ { id: 5, subjectCode: "4001", subjectName: "瀹炴敹璧勬湰", subjectType: "equity", balanceDirection: "credit", status: "active", remark: "" },
+ { id: 6, subjectCode: "5001", subjectName: "鐢熶骇鎴愭湰", subjectType: "cost", balanceDirection: "debit", status: "active", remark: "" },
+ { id: 7, subjectCode: "6001", subjectName: "涓昏惀涓氬姟鏀跺叆", subjectType: "profit_loss", balanceDirection: "credit", status: "active", remark: "" },
+ { id: 8, subjectCode: "6401", subjectName: "涓昏惀涓氬姟鎴愭湰", subjectType: "profit_loss", balanceDirection: "debit", status: "active", remark: "" },
+];
+
+const getSubjectTypeLabel = (type) => {
+ const map = {
+ asset: "璧勪骇绫�",
+ liability: "璐熷�虹被",
+ equity: "鏉冪泭绫�",
+ cost: "鎴愭湰绫�",
+ profit_loss: "鎹熺泭绫�",
+ };
+ return map[type] || type;
+};
+
+const getSubjectTypeType = (type) => {
+ const map = {
+ asset: "success",
+ liability: "danger",
+ equity: "warning",
+ cost: "info",
+ profit_loss: "primary",
+ };
+ return map[type] || "";
+};
+
+const getTableData = () => {
+ let result = [...mockData];
+ if (filters.subjectCode) {
+ result = result.filter(item => item.subjectCode.includes(filters.subjectCode));
+ }
+ if (filters.subjectName) {
+ result = result.filter(item => item.subjectName.includes(filters.subjectName));
+ }
+ if (filters.subjectType) {
+ result = result.filter(item => item.subjectType === filters.subjectType);
+ }
+ pagination.total = result.length;
+ dataList.value = result.slice((pagination.currentPage - 1) * pagination.pageSize, pagination.currentPage * pagination.pageSize);
+};
+
+const resetFilters = () => {
+ filters.subjectCode = "";
+ filters.subjectName = "";
+ filters.subjectType = "";
+ pagination.currentPage = 1;
+ getTableData();
+};
+
+const changePage = ({ current, size }) => {
+ pagination.currentPage = current;
+ pagination.pageSize = size;
+ getTableData();
+};
+
+const add = () => {
+ isEdit.value = false;
+ dialogTitle.value = "鏂板绉戠洰";
+ Object.assign(form, {
+ subjectCode: "",
+ subjectName: "",
+ subjectType: "",
+ balanceDirection: "debit",
+ status: "active",
+ remark: "",
+ });
+ dialogVisible.value = true;
+};
+
+const edit = (row) => {
+ isEdit.value = true;
+ currentId.value = row.id;
+ dialogTitle.value = "缂栬緫绉戠洰";
+ Object.assign(form, row);
+ dialogVisible.value = true;
+};
+
+const submitForm = () => {
+ formRef.value.validate((valid) => {
+ if (valid) {
+ if (isEdit.value) {
+ const index = mockData.findIndex(item => item.id === currentId.value);
+ if (index !== -1) {
+ mockData[index] = { ...mockData[index], ...form };
+ }
+ ElMessage.success("缂栬緫鎴愬姛");
+ } else {
+ const newId = mockData.length > 0 ? Math.max(...mockData.map(item => item.id)) + 1 : 1;
+ mockData.push({ id: newId, ...form });
+ ElMessage.success("鏂板鎴愬姛");
+ }
+ dialogVisible.value = false;
+ getTableData();
+ }
+ });
+};
+
+const handleDelete = (row) => {
+ ElMessageBox.confirm("纭鍒犻櫎璇ョ鐩悧锛�", "鎻愮ず", {
+ confirmButtonText: "纭畾",
+ cancelButtonText: "鍙栨秷",
+ type: "warning",
+ }).then(() => {
+ const index = mockData.findIndex(item => item.id === row.id);
+ if (index !== -1) {
+ mockData.splice(index, 1);
+ }
+ ElMessage.success("鍒犻櫎鎴愬姛");
+ getTableData();
+ });
+};
+
+const handleOut = () => {
+ ElMessage.success("瀵煎嚭鎴愬姛");
+};
+
+onMounted(() => {
+ getTableData();
+});
+</script>
+
+<style lang="scss" scoped>
+.actions {
+ display: flex;
+ justify-content: space-between;
+ margin-bottom: 15px;
+}
+</style>
diff --git a/src/views/financialManagement/payable/input-invoice.vue b/src/views/financialManagement/payable/input-invoice.vue
new file mode 100644
index 0000000..8258e96
--- /dev/null
+++ b/src/views/financialManagement/payable/input-invoice.vue
@@ -0,0 +1,404 @@
+<template>
+ <div class="app-container">
+ <el-form :model="filters" :inline="true">
+ <el-form-item label="鍙戠エ浠g爜:">
+ <el-input v-model="filters.invoiceCode" placeholder="璇疯緭鍏ュ彂绁ㄤ唬鐮�" clearable style="width: 200px;" />
+ </el-form-item>
+ <el-form-item label="鍙戠エ鍙风爜:">
+ <el-input v-model="filters.invoiceNo" placeholder="璇疯緭鍏ュ彂绁ㄥ彿鐮�" clearable style="width: 200px;" />
+ </el-form-item>
+ <el-form-item label="渚涘簲鍟�:">
+ <el-select v-model="filters.supplierId" placeholder="璇烽�夋嫨渚涘簲鍟�" clearable style="width: 200px;">
+ <el-option v-for="item in supplierList" :key="item.id" :label="item.name" :value="item.id" />
+ </el-select>
+ </el-form-item>
+ <el-form-item label="璁よ瘉鐘舵��:">
+ <el-select v-model="filters.certifyStatus" placeholder="璇烽�夋嫨璁よ瘉鐘舵��" clearable style="width: 150px;">
+ <el-option label="鏈璇�" value="uncertified" />
+ <el-option label="宸茶璇�" value="certified" />
+ <el-option label="璁よ瘉澶辫触" value="failed" />
+ </el-select>
+ </el-form-item>
+ <el-form-item>
+ <el-button type="primary" @click="getTableData">鎼滅储</el-button>
+ <el-button @click="resetFilters">閲嶇疆</el-button>
+ </el-form-item>
+ </el-form>
+ <div class="table_list">
+ <div class="actions">
+ <div>
+ <el-button type="success" @click="handleBatchCertify" icon="Check" :disabled="selectedRows.length === 0">鎵归噺璁よ瘉</el-button>
+ </div>
+ <div>
+ <el-button type="primary" @click="add" icon="Plus">褰曞叆鍙戠エ</el-button>
+ <el-button @click="handleOut" icon="Download">瀵煎嚭</el-button>
+ </div>
+ </div>
+ <PIMTable
+ rowKey="id"
+ isSelection
+ :column="columns"
+ :tableData="dataList"
+ :page="{
+ current: pagination.currentPage,
+ size: pagination.pageSize,
+ total: pagination.total,
+ }"
+ @selection-change="handleSelectionChange"
+ @pagination="changePage"
+ >
+ <template #amount="{ row }">
+ <span class="text-primary">楼{{ formatMoney(row.amount) }}</span>
+ </template>
+ <template #taxAmount="{ row }">
+ <span class="text-danger">楼{{ formatMoney(row.taxAmount) }}</span>
+ </template>
+ <template #totalAmount="{ row }">
+ <span class="text-success">楼{{ formatMoney(row.totalAmount) }}</span>
+ </template>
+ <template #certifyStatus="{ row }">
+ <el-tag :type="getCertifyStatusType(row.certifyStatus)">{{ getCertifyStatusLabel(row.certifyStatus) }}</el-tag>
+ </template>
+ <template #operation="{ row }">
+ <el-button type="primary" link @click="view(row)">鏌ョ湅</el-button>
+ <el-button type="primary" link @click="edit(row)">缂栬緫</el-button>
+ <el-button type="success" link @click="handleCertify(row)" v-if="row.certifyStatus === 'uncertified'">璁よ瘉</el-button>
+ </template>
+ </PIMTable>
+ </div>
+
+ <el-dialog :title="dialogTitle" v-model="dialogVisible" width="800px" append-to-body>
+ <el-form :model="form" :rules="rules" ref="formRef" label-width="120px">
+ <el-row :gutter="20">
+ <el-col :span="12">
+ <el-form-item label="鍙戠エ浠g爜" prop="invoiceCode">
+ <el-input v-model="form.invoiceCode" placeholder="璇疯緭鍏ュ彂绁ㄤ唬鐮�" />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="鍙戠エ鍙风爜" prop="invoiceNo">
+ <el-input v-model="form.invoiceNo" placeholder="璇疯緭鍏ュ彂绁ㄥ彿鐮�" />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="20">
+ <el-col :span="12">
+ <el-form-item label="渚涘簲鍟�" prop="supplierId">
+ <el-select v-model="form.supplierId" placeholder="璇烽�夋嫨渚涘簲鍟�" style="width: 100%;">
+ <el-option v-for="item in supplierList" :key="item.id" :label="item.name" :value="item.id" />
+ </el-select>
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="寮�绁ㄦ棩鏈�" prop="invoiceDate">
+ <el-date-picker v-model="form.invoiceDate" type="date" placeholder="閫夋嫨鏃ユ湡" value-format="YYYY-MM-DD" style="width: 100%;" />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="20">
+ <el-col :span="8">
+ <el-form-item label="閲戦(涓嶅惈绋�)" prop="amount">
+ <el-input-number v-model="form.amount" :min="0" :precision="2" style="width: 100%;" @change="calculateTax" />
+ </el-form-item>
+ </el-col>
+ <el-col :span="8">
+ <el-form-item label="绋庣巼" prop="taxRate">
+ <el-select v-model="form.taxRate" placeholder="璇烽�夋嫨绋庣巼" style="width: 100%;" @change="calculateTax">
+ <el-option label="0%" :value="0" />
+ <el-option label="3%" :value="3" />
+ <el-option label="6%" :value="6" />
+ <el-option label="9%" :value="9" />
+ <el-option label="13%" :value="13" />
+ </el-select>
+ </el-form-item>
+ </el-col>
+ <el-col :span="8">
+ <el-form-item label="绋庨">
+ <el-input v-model="form.taxAmount" disabled />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="20">
+ <el-col :span="12">
+ <el-form-item label="璁よ瘉鐘舵��" prop="certifyStatus">
+ <el-select v-model="form.certifyStatus" placeholder="璇烽�夋嫨璁よ瘉鐘舵��" style="width: 100%;" disabled>
+ <el-option label="鏈璇�" value="uncertified" />
+ <el-option label="宸茶璇�" value="certified" />
+ <el-option label="璁よ瘉澶辫触" value="failed" />
+ </el-select>
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="璁よ瘉鏃ユ湡" prop="certifyDate">
+ <el-date-picker v-model="form.certifyDate" type="date" placeholder="閫夋嫨鏃ユ湡" value-format="YYYY-MM-DD" style="width: 100%;" disabled />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-form-item label="鍙戠エ鍐呭" prop="content">
+ <el-input v-model="form.content" type="textarea" :rows="3" placeholder="璇疯緭鍏ュ彂绁ㄥ唴瀹�" />
+ </el-form-item>
+ <el-form-item label="澶囨敞" prop="remark">
+ <el-input v-model="form.remark" type="textarea" :rows="2" placeholder="璇疯緭鍏ュ娉�" />
+ </el-form-item>
+ </el-form>
+ <template #footer>
+ <el-button @click="dialogVisible = false">鍙栨秷</el-button>
+ <el-button type="primary" @click="submitForm">纭畾</el-button>
+ </template>
+ </el-dialog>
+ </div>
+</template>
+
+<script setup>
+import { ref, reactive, onMounted } from "vue";
+import { ElMessage, ElMessageBox } from "element-plus";
+
+defineOptions({
+ name: "杩涢」鍙戠エ",
+});
+
+const filters = reactive({
+ invoiceCode: "",
+ invoiceNo: "",
+ supplierId: "",
+ certifyStatus: "",
+});
+
+const pagination = reactive({
+ currentPage: 1,
+ pageSize: 10,
+ total: 0,
+});
+
+const columns = [
+ { label: "鍙戠エ浠g爜", prop: "invoiceCode", width: "130" },
+ { label: "鍙戠エ鍙风爜", prop: "invoiceNo", width: "120" },
+ { label: "渚涘簲鍟�", prop: "supplierName", width: "180" },
+ { label: "寮�绁ㄦ棩鏈�", prop: "invoiceDate", width: "120" },
+ { label: "閲戦", prop: "amount", slot: "amount" },
+ { label: "绋庨", prop: "taxAmount", slot: "taxAmount" },
+ { label: "浠风◣鍚堣", prop: "totalAmount", slot: "totalAmount" },
+ { label: "璁よ瘉鐘舵��", prop: "certifyStatus", slot: "certifyStatus" },
+ { label: "鎿嶄綔", prop: "operation", slot: "operation", width: "180", fixed: "right" },
+];
+
+const dataList = ref([]);
+const selectedRows = ref([]);
+const dialogVisible = ref(false);
+const dialogTitle = ref("");
+const formRef = ref(null);
+const isEdit = ref(false);
+const currentId = ref(null);
+
+const supplierList = [
+ { id: 1, name: "鍖椾含鍘熸潗鏂欎緵搴斿晢" },
+ { id: 2, name: "涓婃捣鐢靛瓙鍏冨櫒浠跺叕鍙�" },
+ { id: 3, name: "骞垮窞鍖呰鏉愭枡鍘�" },
+ { id: 4, name: "娣卞湷浜旈噾閰嶄欢鍏徃" },
+];
+
+const form = reactive({
+ invoiceCode: "",
+ invoiceNo: "",
+ supplierId: "",
+ invoiceDate: "",
+ amount: 0,
+ taxRate: 13,
+ taxAmount: 0,
+ totalAmount: 0,
+ certifyStatus: "uncertified",
+ certifyDate: "",
+ content: "",
+ remark: "",
+});
+
+const rules = {
+ invoiceCode: [{ required: true, message: "璇疯緭鍏ュ彂绁ㄤ唬鐮�", trigger: "blur" }],
+ invoiceNo: [{ required: true, message: "璇疯緭鍏ュ彂绁ㄥ彿鐮�", trigger: "blur" }],
+ supplierId: [{ required: true, message: "璇烽�夋嫨渚涘簲鍟�", trigger: "change" }],
+ invoiceDate: [{ required: true, message: "璇烽�夋嫨寮�绁ㄦ棩鏈�", trigger: "change" }],
+ amount: [{ required: true, message: "璇疯緭鍏ラ噾棰�", trigger: "blur" }],
+ taxRate: [{ required: true, message: "璇烽�夋嫨绋庣巼", trigger: "change" }],
+};
+
+const mockData = [
+ { id: 1, invoiceCode: "0440021001", invoiceNo: "12345678", supplierId: 1, supplierName: "鍖椾含鍘熸潗鏂欎緵搴斿晢", invoiceDate: "2024-01-08", amount: 8000, taxRate: 13, taxAmount: 1040, totalAmount: 9040, certifyStatus: "certified", certifyDate: "2024-01-15", content: "鍘熸潗鏂欓噰璐�", remark: "" },
+ { id: 2, invoiceCode: "0440021002", invoiceNo: "87654321", supplierId: 2, supplierName: "涓婃捣鐢靛瓙鍏冨櫒浠跺叕鍙�", invoiceDate: "2024-01-10", amount: 12000, taxRate: 13, taxAmount: 1560, totalAmount: 13560, certifyStatus: "uncertified", certifyDate: "", content: "鐢靛瓙鍏冨櫒浠�", remark: "" },
+ { id: 3, invoiceCode: "0440021003", invoiceNo: "11112222", supplierId: 3, supplierName: "骞垮窞鍖呰鏉愭枡鍘�", invoiceDate: "2024-01-12", amount: 3500, taxRate: 13, taxAmount: 455, totalAmount: 3955, certifyStatus: "certified", certifyDate: "2024-01-18", content: "鍖呰鏉愭枡", remark: "" },
+];
+
+const formatMoney = (value) => {
+ if (value === undefined || value === null) return "0.00";
+ return Number(value).toFixed(2).replace(/\B(?=(\d{3})+(?!\d))/g, ",");
+};
+
+const calculateTax = () => {
+ form.taxAmount = Number((form.amount * form.taxRate / 100).toFixed(2));
+ form.totalAmount = Number((form.amount + form.taxAmount).toFixed(2));
+};
+
+const getCertifyStatusLabel = (status) => {
+ const map = { uncertified: "鏈璇�", certified: "宸茶璇�", failed: "璁よ瘉澶辫触" };
+ return map[status] || status;
+};
+
+const getCertifyStatusType = (status) => {
+ const map = { uncertified: "info", certified: "success", failed: "danger" };
+ return map[status] || "";
+};
+
+const getTableData = () => {
+ let result = [...mockData];
+ if (filters.invoiceCode) {
+ result = result.filter(item => item.invoiceCode.includes(filters.invoiceCode));
+ }
+ if (filters.invoiceNo) {
+ result = result.filter(item => item.invoiceNo.includes(filters.invoiceNo));
+ }
+ if (filters.supplierId) {
+ result = result.filter(item => item.supplierId === filters.supplierId);
+ }
+ if (filters.certifyStatus) {
+ result = result.filter(item => item.certifyStatus === filters.certifyStatus);
+ }
+ pagination.total = result.length;
+ dataList.value = result.slice((pagination.currentPage - 1) * pagination.pageSize, pagination.currentPage * pagination.pageSize);
+};
+
+const resetFilters = () => {
+ filters.invoiceCode = "";
+ filters.invoiceNo = "";
+ filters.supplierId = "";
+ filters.certifyStatus = "";
+ pagination.currentPage = 1;
+ getTableData();
+};
+
+const changePage = ({ current, size }) => {
+ pagination.currentPage = current;
+ pagination.pageSize = size;
+ getTableData();
+};
+
+const handleSelectionChange = (selection) => {
+ selectedRows.value = selection;
+};
+
+const add = () => {
+ isEdit.value = false;
+ dialogTitle.value = "褰曞叆鍙戠エ";
+ Object.assign(form, {
+ invoiceCode: "",
+ invoiceNo: "",
+ supplierId: "",
+ invoiceDate: new Date().toISOString().split('T')[0],
+ amount: 0,
+ taxRate: 13,
+ taxAmount: 0,
+ totalAmount: 0,
+ certifyStatus: "uncertified",
+ certifyDate: "",
+ content: "",
+ remark: "",
+ });
+ dialogVisible.value = true;
+};
+
+const edit = (row) => {
+ isEdit.value = true;
+ currentId.value = row.id;
+ dialogTitle.value = "缂栬緫鍙戠エ";
+ Object.assign(form, row);
+ dialogVisible.value = true;
+};
+
+const view = (row) => {
+ ElMessage.info(`鏌ョ湅鍙戠エ: ${row.invoiceCode}-${row.invoiceNo}`);
+};
+
+const handleCertify = (row) => {
+ ElMessageBox.confirm("纭璁よ瘉璇ュ彂绁ㄥ悧锛�", "鎻愮ず", {
+ confirmButtonText: "纭",
+ cancelButtonText: "鍙栨秷",
+ type: "info",
+ }).then(() => {
+ const index = mockData.findIndex(item => item.id === row.id);
+ if (index !== -1) {
+ mockData[index].certifyStatus = "certified";
+ mockData[index].certifyDate = new Date().toISOString().split('T')[0];
+ }
+ ElMessage.success("璁よ瘉鎴愬姛");
+ getTableData();
+ });
+};
+
+const handleBatchCertify = () => {
+ ElMessageBox.confirm(`纭鎵归噺璁よ瘉閫変腑鐨� ${selectedRows.value.length} 寮犲彂绁ㄥ悧锛焋, "鎻愮ず", {
+ confirmButtonText: "纭",
+ cancelButtonText: "鍙栨秷",
+ type: "info",
+ }).then(() => {
+ selectedRows.value.forEach(row => {
+ const index = mockData.findIndex(item => item.id === row.id);
+ if (index !== -1 && mockData[index].certifyStatus === "uncertified") {
+ mockData[index].certifyStatus = "certified";
+ mockData[index].certifyDate = new Date().toISOString().split('T')[0];
+ }
+ });
+ ElMessage.success("鎵归噺璁よ瘉鎴愬姛");
+ getTableData();
+ });
+};
+
+const handleOut = () => {
+ ElMessage.success("瀵煎嚭鎴愬姛");
+};
+
+const submitForm = () => {
+ formRef.value.validate((valid) => {
+ if (valid) {
+ const supplier = supplierList.find(item => item.id === form.supplierId);
+ if (isEdit.value) {
+ const index = mockData.findIndex(item => item.id === currentId.value);
+ if (index !== -1) {
+ mockData[index] = { ...mockData[index], ...form, supplierName: supplier?.name };
+ }
+ ElMessage.success("缂栬緫鎴愬姛");
+ } else {
+ const newId = mockData.length > 0 ? Math.max(...mockData.map(item => item.id)) + 1 : 1;
+ mockData.push({ id: newId, ...form, supplierName: supplier?.name });
+ ElMessage.success("褰曞叆鎴愬姛");
+ }
+ dialogVisible.value = false;
+ getTableData();
+ }
+ });
+};
+
+onMounted(() => {
+ getTableData();
+});
+</script>
+
+<style lang="scss" scoped>
+.actions {
+ display: flex;
+ justify-content: space-between;
+ margin-bottom: 15px;
+}
+
+.text-primary {
+ color: #409eff;
+ font-weight: bold;
+}
+
+.text-danger {
+ color: #f56c6c;
+ font-weight: bold;
+}
+
+.text-success {
+ color: #67c23a;
+ font-weight: bold;
+}
+</style>
diff --git a/src/views/financialManagement/payable/payment.vue b/src/views/financialManagement/payable/payment.vue
new file mode 100644
index 0000000..3ea0df8
--- /dev/null
+++ b/src/views/financialManagement/payable/payment.vue
@@ -0,0 +1,376 @@
+<template>
+ <div class="app-container">
+ <el-form :model="filters" :inline="true">
+ <el-form-item label="浠樻鍗曞彿:">
+ <el-input v-model="filters.paymentCode" placeholder="璇疯緭鍏ヤ粯娆惧崟鍙�" clearable style="width: 200px;" />
+ </el-form-item>
+ <el-form-item label="渚涘簲鍟�:">
+ <el-select v-model="filters.supplierId" placeholder="璇烽�夋嫨渚涘簲鍟�" clearable style="width: 200px;">
+ <el-option v-for="item in supplierList" :key="item.id" :label="item.name" :value="item.id" />
+ </el-select>
+ </el-form-item>
+ <el-form-item label="浠樻鏂瑰紡:">
+ <el-select v-model="filters.paymentMethod" placeholder="璇烽�夋嫨浠樻鏂瑰紡" clearable style="width: 150px;">
+ <el-option label="閾惰杞处" value="bank_transfer" />
+ <el-option label="鐜伴噾" value="cash" />
+ <el-option label="鏀エ" value="check" />
+ <el-option label="姹囩エ" value="draft" />
+ </el-select>
+ </el-form-item>
+ <el-form-item label="鐘舵��:">
+ <el-select v-model="filters.status" placeholder="璇烽�夋嫨鐘舵��" clearable style="width: 150px;">
+ <el-option label="寰呬粯娆�" value="pending" />
+ <el-option label="宸插畬鎴�" value="completed" />
+ <el-option label="宸插彇娑�" value="cancelled" />
+ </el-select>
+ </el-form-item>
+ <el-form-item>
+ <el-button type="primary" @click="getTableData">鎼滅储</el-button>
+ <el-button @click="resetFilters">閲嶇疆</el-button>
+ </el-form-item>
+ </el-form>
+ <div class="table_list">
+ <div class="actions">
+ <div>
+ <el-statistic title="鏈湡浠樻鍚堣" :value="totalPaymentAmount" precision="2" prefix="楼" />
+ </div>
+ <div>
+ <el-button type="primary" @click="add" icon="Plus">鏂板浠樻</el-button>
+ <el-button @click="handleOut" icon="Download">瀵煎嚭</el-button>
+ </div>
+ </div>
+ <PIMTable
+ rowKey="id"
+ :column="columns"
+ :tableData="dataList"
+ :page="{
+ current: pagination.currentPage,
+ size: pagination.pageSize,
+ total: pagination.total,
+ }"
+ @pagination="changePage"
+ >
+ <template #amount="{ row }">
+ <span class="text-danger">楼{{ formatMoney(row.amount) }}</span>
+ </template>
+ <template #paymentMethod="{ row }">
+ <el-tag>{{ getPaymentMethodLabel(row.paymentMethod) }}</el-tag>
+ </template>
+ <template #status="{ row }">
+ <el-tag :type="row.status === 'completed' ? 'success' : row.status === 'pending' ? 'warning' : 'info'">
+ {{ row.status === 'completed' ? '宸插畬鎴�' : row.status === 'pending' ? '寰呬粯娆�' : '宸插彇娑�' }}
+ </el-tag>
+ </template>
+ <template #operation="{ row }">
+ <el-button type="primary" link @click="view(row)">鏌ョ湅</el-button>
+ <el-button type="primary" link @click="edit(row)" v-if="row.status === 'pending'">缂栬緫</el-button>
+ <el-button type="success" link @click="handleComplete(row)" v-if="row.status === 'pending'">瀹屾垚</el-button>
+ <el-button type="danger" link @click="handleCancel(row)" v-if="row.status === 'pending'">鍙栨秷</el-button>
+ </template>
+ </PIMTable>
+ </div>
+
+ <el-dialog :title="dialogTitle" v-model="dialogVisible" width="800px" append-to-body>
+ <el-form :model="form" :rules="rules" ref="formRef" label-width="120px">
+ <el-row :gutter="20">
+ <el-col :span="12">
+ <el-form-item label="浠樻鍗曞彿" prop="paymentCode">
+ <el-input v-model="form.paymentCode" placeholder="绯荤粺鑷姩鐢熸垚" disabled />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="鍏宠仈鐢宠鍗�" prop="applyCode">
+ <el-select v-model="form.applyCode" placeholder="璇烽�夋嫨鍏宠仈鐢宠鍗�" style="width: 100%;" :disabled="isEdit">
+ <el-option v-for="item in applyList" :key="item.applyCode" :label="item.applyCode" :value="item.applyCode" />
+ </el-select>
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="20">
+ <el-col :span="12">
+ <el-form-item label="渚涘簲鍟�" prop="supplierId">
+ <el-select v-model="form.supplierId" placeholder="璇烽�夋嫨渚涘簲鍟�" style="width: 100%;" :disabled="isEdit">
+ <el-option v-for="item in supplierList" :key="item.id" :label="item.name" :value="item.id" />
+ </el-select>
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="浠樻鏃ユ湡" prop="paymentDate">
+ <el-date-picker v-model="form.paymentDate" type="date" placeholder="閫夋嫨鏃ユ湡" value-format="YYYY-MM-DD" style="width: 100%;" />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="20">
+ <el-col :span="12">
+ <el-form-item label="浠樻閲戦" prop="amount">
+ <el-input-number v-model="form.amount" :min="0" :precision="2" style="width: 100%;" />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="浠樻鏂瑰紡" prop="paymentMethod">
+ <el-select v-model="form.paymentMethod" placeholder="璇烽�夋嫨浠樻鏂瑰紡" style="width: 100%;">
+ <el-option label="閾惰杞处" value="bank_transfer" />
+ <el-option label="鐜伴噾" value="cash" />
+ <el-option label="鏀エ" value="check" />
+ <el-option label="姹囩エ" value="draft" />
+ </el-select>
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="20">
+ <el-col :span="12">
+ <el-form-item label="閾惰璐﹀彿" prop="bankAccount" v-if="form.paymentMethod === 'bank_transfer'">
+ <el-input v-model="form.bankAccount" placeholder="璇疯緭鍏ラ摱琛岃处鍙�" />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="寮�鎴疯" prop="bankName" v-if="form.paymentMethod === 'bank_transfer'">
+ <el-input v-model="form.bankName" placeholder="璇疯緭鍏ュ紑鎴疯" />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-form-item label="澶囨敞" prop="remark">
+ <el-input v-model="form.remark" type="textarea" :rows="3" placeholder="璇疯緭鍏ュ娉�" />
+ </el-form-item>
+ </el-form>
+ <template #footer>
+ <el-button @click="dialogVisible = false">鍙栨秷</el-button>
+ <el-button type="primary" @click="submitForm">纭畾</el-button>
+ </template>
+ </el-dialog>
+ </div>
+</template>
+
+<script setup>
+import { ref, reactive, onMounted, computed } from "vue";
+import { ElMessage, ElMessageBox } from "element-plus";
+
+defineOptions({
+ name: "浠樻鍗�",
+});
+
+const filters = reactive({
+ paymentCode: "",
+ supplierId: "",
+ paymentMethod: "",
+ status: "",
+});
+
+const pagination = reactive({
+ currentPage: 1,
+ pageSize: 10,
+ total: 0,
+});
+
+const columns = [
+ { label: "浠樻鍗曞彿", prop: "paymentCode", width: "150" },
+ { label: "鍏宠仈鐢宠鍗�", prop: "applyCode", width: "150" },
+ { label: "渚涘簲鍟�", prop: "supplierName", width: "180" },
+ { label: "浠樻鏃ユ湡", prop: "paymentDate", width: "120" },
+ { label: "浠樻閲戦", prop: "amount", slot: "amount" },
+ { label: "浠樻鏂瑰紡", prop: "paymentMethod", slot: "paymentMethod" },
+ { label: "鐘舵��", prop: "status", slot: "status" },
+ { label: "澶囨敞", prop: "remark", showOverflowTooltip: true },
+ { label: "鎿嶄綔", prop: "operation", slot: "operation", width: "220", fixed: "right" },
+];
+
+const dataList = ref([]);
+const dialogVisible = ref(false);
+const dialogTitle = ref("");
+const formRef = ref(null);
+const isEdit = ref(false);
+const currentId = ref(null);
+
+const supplierList = [
+ { id: 1, name: "鍖椾含鍘熸潗鏂欎緵搴斿晢" },
+ { id: 2, name: "涓婃捣鐢靛瓙鍏冨櫒浠跺叕鍙�" },
+ { id: 3, name: "骞垮窞鍖呰鏉愭枡鍘�" },
+ { id: 4, name: "娣卞湷浜旈噾閰嶄欢鍏徃" },
+];
+
+const applyList = [
+ { applyCode: "FK2024001", supplierId: 1, amount: 5000 },
+ { applyCode: "FK2024002", supplierId: 2, amount: 8000 },
+ { applyCode: "FK2024003", supplierId: 3, amount: 3000 },
+];
+
+const form = reactive({
+ paymentCode: "",
+ applyCode: "",
+ supplierId: "",
+ paymentDate: "",
+ amount: 0,
+ paymentMethod: "bank_transfer",
+ bankAccount: "",
+ bankName: "",
+ remark: "",
+});
+
+const rules = {
+ applyCode: [{ required: true, message: "璇烽�夋嫨鍏宠仈鐢宠鍗�", trigger: "change" }],
+ supplierId: [{ required: true, message: "璇烽�夋嫨渚涘簲鍟�", trigger: "change" }],
+ paymentDate: [{ required: true, message: "璇烽�夋嫨浠樻鏃ユ湡", trigger: "change" }],
+ amount: [{ required: true, message: "璇疯緭鍏ヤ粯娆鹃噾棰�", trigger: "blur" }],
+ paymentMethod: [{ required: true, message: "璇烽�夋嫨浠樻鏂瑰紡", trigger: "change" }],
+};
+
+const mockData = [
+ { id: 1, paymentCode: "FKD2024001", applyCode: "FK2024001", supplierId: 1, supplierName: "鍖椾含鍘熸潗鏂欎緵搴斿晢", paymentDate: "2024-01-15", amount: 5000, paymentMethod: "bank_transfer", status: "completed", bankAccount: "6222021234567890123", bankName: "宸ュ晢閾惰", remark: "" },
+ { id: 2, paymentCode: "FKD2024002", applyCode: "FK2024002", supplierId: 2, supplierName: "涓婃捣鐢靛瓙鍏冨櫒浠跺叕鍙�", paymentDate: "2024-01-18", amount: 8000, paymentMethod: "bank_transfer", status: "pending", bankAccount: "6222029876543210987", bankName: "寤鸿閾惰", remark: "" },
+ { id: 3, paymentCode: "FKD2024003", applyCode: "FK2024003", supplierId: 3, supplierName: "骞垮窞鍖呰鏉愭枡鍘�", paymentDate: "2024-01-20", amount: 3000, paymentMethod: "cash", status: "completed", remark: "" },
+];
+
+const totalPaymentAmount = computed(() => {
+ return dataList.value.reduce((sum, item) => sum + Number(item.amount), 0);
+});
+
+const formatMoney = (value) => {
+ if (value === undefined || value === null) return "0.00";
+ return Number(value).toFixed(2).replace(/\B(?=(\d{3})+(?!\d))/g, ",");
+};
+
+const getPaymentMethodLabel = (method) => {
+ const map = {
+ bank_transfer: "閾惰杞处",
+ cash: "鐜伴噾",
+ check: "鏀エ",
+ draft: "姹囩エ",
+ };
+ return map[method] || method;
+};
+
+const getTableData = () => {
+ let result = [...mockData];
+ if (filters.paymentCode) {
+ result = result.filter(item => item.paymentCode.includes(filters.paymentCode));
+ }
+ if (filters.supplierId) {
+ result = result.filter(item => item.supplierId === filters.supplierId);
+ }
+ if (filters.paymentMethod) {
+ result = result.filter(item => item.paymentMethod === filters.paymentMethod);
+ }
+ if (filters.status) {
+ result = result.filter(item => item.status === filters.status);
+ }
+ pagination.total = result.length;
+ dataList.value = result.slice((pagination.currentPage - 1) * pagination.pageSize, pagination.currentPage * pagination.pageSize);
+};
+
+const resetFilters = () => {
+ filters.paymentCode = "";
+ filters.supplierId = "";
+ filters.paymentMethod = "";
+ filters.status = "";
+ pagination.currentPage = 1;
+ getTableData();
+};
+
+const changePage = ({ current, size }) => {
+ pagination.currentPage = current;
+ pagination.pageSize = size;
+ getTableData();
+};
+
+const add = () => {
+ isEdit.value = false;
+ dialogTitle.value = "鏂板浠樻";
+ Object.assign(form, {
+ paymentCode: "FKD" + Date.now().toString().slice(-8),
+ applyCode: "",
+ supplierId: "",
+ paymentDate: new Date().toISOString().split('T')[0],
+ amount: 0,
+ paymentMethod: "bank_transfer",
+ bankAccount: "",
+ bankName: "",
+ remark: "",
+ });
+ dialogVisible.value = true;
+};
+
+const edit = (row) => {
+ isEdit.value = true;
+ currentId.value = row.id;
+ dialogTitle.value = "缂栬緫浠樻";
+ Object.assign(form, row);
+ dialogVisible.value = true;
+};
+
+const view = (row) => {
+ ElMessage.info(`鏌ョ湅浠樻鍗�: ${row.paymentCode}`);
+};
+
+const handleComplete = (row) => {
+ ElMessageBox.confirm("纭璇ヤ粯娆惧崟宸插畬鎴愬悧锛�", "鎻愮ず", {
+ confirmButtonText: "纭",
+ cancelButtonText: "鍙栨秷",
+ type: "info",
+ }).then(() => {
+ const index = mockData.findIndex(item => item.id === row.id);
+ if (index !== -1) {
+ mockData[index].status = "completed";
+ }
+ ElMessage.success("浠樻瀹屾垚");
+ getTableData();
+ });
+};
+
+const handleCancel = (row) => {
+ ElMessageBox.confirm("纭鍙栨秷璇ヤ粯娆惧崟鍚楋紵", "鎻愮ず", {
+ confirmButtonText: "纭",
+ cancelButtonText: "鍙栨秷",
+ type: "warning",
+ }).then(() => {
+ const index = mockData.findIndex(item => item.id === row.id);
+ if (index !== -1) {
+ mockData[index].status = "cancelled";
+ }
+ ElMessage.success("宸插彇娑�");
+ getTableData();
+ });
+};
+
+const handleOut = () => {
+ ElMessage.success("瀵煎嚭鎴愬姛");
+};
+
+const submitForm = () => {
+ formRef.value.validate((valid) => {
+ if (valid) {
+ const supplier = supplierList.find(item => item.id === form.supplierId);
+ if (isEdit.value) {
+ const index = mockData.findIndex(item => item.id === currentId.value);
+ if (index !== -1) {
+ mockData[index] = { ...mockData[index], ...form, supplierName: supplier?.name };
+ }
+ ElMessage.success("缂栬緫鎴愬姛");
+ } else {
+ const newId = mockData.length > 0 ? Math.max(...mockData.map(item => item.id)) + 1 : 1;
+ mockData.push({ id: newId, ...form, supplierName: supplier?.name, status: "pending" });
+ ElMessage.success("鏂板鎴愬姛");
+ }
+ dialogVisible.value = false;
+ getTableData();
+ }
+ });
+};
+
+onMounted(() => {
+ getTableData();
+});
+</script>
+
+<style lang="scss" scoped>
+.actions {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 15px;
+}
+
+.text-danger {
+ color: #f56c6c;
+ font-weight: bold;
+}
+</style>
diff --git a/src/views/financialManagement/payable/paymentApply.vue b/src/views/financialManagement/payable/paymentApply.vue
new file mode 100644
index 0000000..82981d7
--- /dev/null
+++ b/src/views/financialManagement/payable/paymentApply.vue
@@ -0,0 +1,359 @@
+<template>
+ <div class="app-container">
+ <el-form :model="filters" :inline="true">
+ <el-form-item label="鐢宠鍗曞彿:">
+ <el-input v-model="filters.applyCode" placeholder="璇疯緭鍏ョ敵璇峰崟鍙�" clearable style="width: 200px;" />
+ </el-form-item>
+ <el-form-item label="渚涘簲鍟�:">
+ <el-select v-model="filters.supplierId" placeholder="璇烽�夋嫨渚涘簲鍟�" clearable style="width: 200px;">
+ <el-option v-for="item in supplierList" :key="item.id" :label="item.name" :value="item.id" />
+ </el-select>
+ </el-form-item>
+ <el-form-item label="鐘舵��:">
+ <el-select v-model="filters.status" placeholder="璇烽�夋嫨鐘舵��" clearable style="width: 150px;">
+ <el-option label="寰呭鎵�" value="pending" />
+ <el-option label="宸插鎵�" value="approved" />
+ <el-option label="宸查┏鍥�" value="rejected" />
+ <el-option label="宸蹭粯娆�" value="paid" />
+ </el-select>
+ </el-form-item>
+ <el-form-item>
+ <el-button type="primary" @click="getTableData">鎼滅储</el-button>
+ <el-button @click="resetFilters">閲嶇疆</el-button>
+ </el-form-item>
+ </el-form>
+ <div class="table_list">
+ <div class="actions">
+ <div></div>
+ <div>
+ <el-button type="primary" @click="add" icon="Plus">鏂板鐢宠</el-button>
+ <el-button @click="handleBatchApply" icon="Document" :disabled="selectedRows.length === 0">鎵归噺鐢宠</el-button>
+ </div>
+ </div>
+ <PIMTable
+ rowKey="id"
+ isSelection
+ :column="columns"
+ :tableData="dataList"
+ :page="{
+ current: pagination.currentPage,
+ size: pagination.pageSize,
+ total: pagination.total,
+ }"
+ @selection-change="handleSelectionChange"
+ @pagination="changePage"
+ >
+ <template #amount="{ row }">
+ <span class="text-danger">楼{{ formatMoney(row.amount) }}</span>
+ </template>
+ <template #paymentMethod="{ row }">
+ <el-tag>{{ getPaymentMethodLabel(row.paymentMethod) }}</el-tag>
+ </template>
+ <template #status="{ row }">
+ <el-tag :type="getStatusType(row.status)">{{ getStatusLabel(row.status) }}</el-tag>
+ </template>
+ <template #operation="{ row }">
+ <el-button type="primary" link @click="view(row)">鏌ョ湅</el-button>
+ <el-button type="primary" link @click="edit(row)" v-if="row.status === 'pending'">缂栬緫</el-button>
+ <el-button type="success" link @click="handleAudit(row)" v-if="row.status === 'pending'">瀹℃壒</el-button>
+ </template>
+ </PIMTable>
+ </div>
+
+ <el-dialog :title="dialogTitle" v-model="dialogVisible" width="800px" append-to-body>
+ <el-form :model="form" :rules="rules" ref="formRef" label-width="120px">
+ <el-row :gutter="20">
+ <el-col :span="12">
+ <el-form-item label="鐢宠鍗曞彿" prop="applyCode">
+ <el-input v-model="form.applyCode" placeholder="绯荤粺鑷姩鐢熸垚" disabled />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="渚涘簲鍟�" prop="supplierId">
+ <el-select v-model="form.supplierId" placeholder="璇烽�夋嫨渚涘簲鍟�" style="width: 100%;" :disabled="isEdit">
+ <el-option v-for="item in supplierList" :key="item.id" :label="item.name" :value="item.id" />
+ </el-select>
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="20">
+ <el-col :span="12">
+ <el-form-item label="浠樻閲戦" prop="amount">
+ <el-input-number v-model="form.amount" :min="0" :precision="2" style="width: 100%;" />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="浠樻鏂瑰紡" prop="paymentMethod">
+ <el-select v-model="form.paymentMethod" placeholder="璇烽�夋嫨浠樻鏂瑰紡" style="width: 100%;">
+ <el-option label="閾惰杞处" value="bank_transfer" />
+ <el-option label="鐜伴噾" value="cash" />
+ <el-option label="鏀エ" value="check" />
+ <el-option label="姹囩エ" value="draft" />
+ </el-select>
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="20">
+ <el-col :span="12">
+ <el-form-item label="鐢宠鏃ユ湡" prop="applyDate">
+ <el-date-picker v-model="form.applyDate" type="date" placeholder="閫夋嫨鏃ユ湡" value-format="YYYY-MM-DD" style="width: 100%;" />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="鏈熸湜浠樻鏃ユ湡" prop="expectedDate">
+ <el-date-picker v-model="form.expectedDate" type="date" placeholder="閫夋嫨鏃ユ湡" value-format="YYYY-MM-DD" style="width: 100%;" />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-form-item label="鍏宠仈鍏ュ簱鍗�" prop="relatedDocs">
+ <el-select v-model="form.relatedDocs" multiple placeholder="璇烽�夋嫨鍏宠仈鍏ュ簱鍗�" style="width: 100%;">
+ <el-option v-for="item in inList" :key="item.inCode" :label="item.inCode" :value="item.inCode" />
+ </el-select>
+ </el-form-item>
+ <el-form-item label="浠樻浜嬬敱" prop="reason">
+ <el-input v-model="form.reason" type="textarea" :rows="3" placeholder="璇疯緭鍏ヤ粯娆句簨鐢�" />
+ </el-form-item>
+ <el-form-item label="澶囨敞" prop="remark">
+ <el-input v-model="form.remark" type="textarea" :rows="2" placeholder="璇疯緭鍏ュ娉�" />
+ </el-form-item>
+ </el-form>
+ <template #footer>
+ <el-button @click="dialogVisible = false">鍙栨秷</el-button>
+ <el-button type="primary" @click="submitForm">纭畾</el-button>
+ </template>
+ </el-dialog>
+ </div>
+</template>
+
+<script setup>
+import { ref, reactive, onMounted } from "vue";
+import { ElMessage, ElMessageBox } from "element-plus";
+
+defineOptions({
+ name: "浠樻鐢宠",
+});
+
+const filters = reactive({
+ applyCode: "",
+ supplierId: "",
+ status: "",
+});
+
+const pagination = reactive({
+ currentPage: 1,
+ pageSize: 10,
+ total: 0,
+});
+
+const columns = [
+ { label: "鐢宠鍗曞彿", prop: "applyCode", width: "150" },
+ { label: "渚涘簲鍟�", prop: "supplierName", width: "180" },
+ { label: "浠樻閲戦", prop: "amount", slot: "amount" },
+ { label: "浠樻鏂瑰紡", prop: "paymentMethod", slot: "paymentMethod" },
+ { label: "鐢宠鏃ユ湡", prop: "applyDate", width: "120" },
+ { label: "鏈熸湜浠樻鏃�", prop: "expectedDate", width: "120" },
+ { label: "鐘舵��", prop: "status", slot: "status" },
+ { label: "鎿嶄綔", prop: "operation", slot: "operation", width: "200", fixed: "right" },
+];
+
+const dataList = ref([]);
+const selectedRows = ref([]);
+const dialogVisible = ref(false);
+const dialogTitle = ref("");
+const formRef = ref(null);
+const isEdit = ref(false);
+const currentId = ref(null);
+
+const supplierList = [
+ { id: 1, name: "鍖椾含鍘熸潗鏂欎緵搴斿晢" },
+ { id: 2, name: "涓婃捣鐢靛瓙鍏冨櫒浠跺叕鍙�" },
+ { id: 3, name: "骞垮窞鍖呰鏉愭枡鍘�" },
+ { id: 4, name: "娣卞湷浜旈噾閰嶄欢鍏徃" },
+];
+
+const inList = [
+ { inCode: "RK2024001", supplierId: 1 },
+ { inCode: "RK2024002", supplierId: 2 },
+ { inCode: "RK2024003", supplierId: 3 },
+];
+
+const form = reactive({
+ applyCode: "",
+ supplierId: "",
+ amount: 0,
+ paymentMethod: "bank_transfer",
+ applyDate: "",
+ expectedDate: "",
+ relatedDocs: [],
+ reason: "",
+ remark: "",
+});
+
+const rules = {
+ supplierId: [{ required: true, message: "璇烽�夋嫨渚涘簲鍟�", trigger: "change" }],
+ amount: [{ required: true, message: "璇疯緭鍏ヤ粯娆鹃噾棰�", trigger: "blur" }],
+ paymentMethod: [{ required: true, message: "璇烽�夋嫨浠樻鏂瑰紡", trigger: "change" }],
+ applyDate: [{ required: true, message: "璇烽�夋嫨鐢宠鏃ユ湡", trigger: "change" }],
+ expectedDate: [{ required: true, message: "璇烽�夋嫨鏈熸湜浠樻鏃ユ湡", trigger: "change" }],
+};
+
+const mockData = [
+ { id: 1, applyCode: "FK2024001", supplierId: 1, supplierName: "鍖椾含鍘熸潗鏂欎緵搴斿晢", amount: 5000, paymentMethod: "bank_transfer", applyDate: "2024-01-12", expectedDate: "2024-01-15", status: "pending", relatedDocs: ["RK2024001"], reason: "鏀粯鍘熸潗鏂欒揣娆�", remark: "" },
+ { id: 2, applyCode: "FK2024002", supplierId: 2, supplierName: "涓婃捣鐢靛瓙鍏冨櫒浠跺叕鍙�", amount: 8000, paymentMethod: "bank_transfer", applyDate: "2024-01-14", expectedDate: "2024-01-18", status: "approved", relatedDocs: ["RK2024002"], reason: "鏀粯鐢靛瓙鍏冨櫒浠惰揣娆�", remark: "" },
+ { id: 3, applyCode: "FK2024003", supplierId: 3, supplierName: "骞垮窞鍖呰鏉愭枡鍘�", amount: 3000, paymentMethod: "cash", applyDate: "2024-01-16", expectedDate: "2024-01-20", status: "paid", relatedDocs: ["RK2024003"], reason: "鏀粯鍖呰鏉愭枡璐ф", remark: "" },
+];
+
+const formatMoney = (value) => {
+ if (value === undefined || value === null) return "0.00";
+ return Number(value).toFixed(2).replace(/\B(?=(\d{3})+(?!\d))/g, ",");
+};
+
+const getPaymentMethodLabel = (method) => {
+ const map = {
+ bank_transfer: "閾惰杞处",
+ cash: "鐜伴噾",
+ check: "鏀エ",
+ draft: "姹囩エ",
+ };
+ return map[method] || method;
+};
+
+const getStatusLabel = (status) => {
+ const map = { pending: "寰呭鎵�", approved: "宸插鎵�", rejected: "宸查┏鍥�", paid: "宸蹭粯娆�" };
+ return map[status] || status;
+};
+
+const getStatusType = (status) => {
+ const map = { pending: "warning", approved: "success", rejected: "danger", paid: "primary" };
+ return map[status] || "";
+};
+
+const getTableData = () => {
+ let result = [...mockData];
+ if (filters.applyCode) {
+ result = result.filter(item => item.applyCode.includes(filters.applyCode));
+ }
+ if (filters.supplierId) {
+ result = result.filter(item => item.supplierId === filters.supplierId);
+ }
+ if (filters.status) {
+ result = result.filter(item => item.status === filters.status);
+ }
+ pagination.total = result.length;
+ dataList.value = result.slice((pagination.currentPage - 1) * pagination.pageSize, pagination.currentPage * pagination.pageSize);
+};
+
+const resetFilters = () => {
+ filters.applyCode = "";
+ filters.supplierId = "";
+ filters.status = "";
+ pagination.currentPage = 1;
+ getTableData();
+};
+
+const changePage = ({ current, size }) => {
+ pagination.currentPage = current;
+ pagination.pageSize = size;
+ getTableData();
+};
+
+const handleSelectionChange = (selection) => {
+ selectedRows.value = selection;
+};
+
+const add = () => {
+ isEdit.value = false;
+ dialogTitle.value = "鏂板浠樻鐢宠";
+ Object.assign(form, {
+ applyCode: "FK" + Date.now().toString().slice(-8),
+ supplierId: "",
+ amount: 0,
+ paymentMethod: "bank_transfer",
+ applyDate: new Date().toISOString().split('T')[0],
+ expectedDate: "",
+ relatedDocs: [],
+ reason: "",
+ remark: "",
+ });
+ dialogVisible.value = true;
+};
+
+const edit = (row) => {
+ isEdit.value = true;
+ currentId.value = row.id;
+ dialogTitle.value = "缂栬緫浠樻鐢宠";
+ Object.assign(form, row);
+ dialogVisible.value = true;
+};
+
+const view = (row) => {
+ ElMessage.info(`鏌ョ湅鐢宠鍗�: ${row.applyCode}`);
+};
+
+const handleAudit = (row) => {
+ ElMessageBox.confirm("纭瀹℃壒閫氳繃璇ヤ粯娆剧敵璇峰悧锛�", "鎻愮ず", {
+ confirmButtonText: "閫氳繃",
+ cancelButtonText: "椹冲洖",
+ distinguishCancelAndClose: true,
+ type: "warning",
+ }).then(() => {
+ const index = mockData.findIndex(item => item.id === row.id);
+ if (index !== -1) {
+ mockData[index].status = "approved";
+ }
+ ElMessage.success("瀹℃壒閫氳繃");
+ getTableData();
+ }).catch((action) => {
+ if (action === "cancel") {
+ const index = mockData.findIndex(item => item.id === row.id);
+ if (index !== -1) {
+ mockData[index].status = "rejected";
+ }
+ ElMessage.warning("宸查┏鍥�");
+ getTableData();
+ }
+ });
+};
+
+const handleBatchApply = () => {
+ ElMessage.success(`鎵归噺鐢宠 ${selectedRows.value.length} 鏉¤褰昤);
+};
+
+const submitForm = () => {
+ formRef.value.validate((valid) => {
+ if (valid) {
+ const supplier = supplierList.find(item => item.id === form.supplierId);
+ if (isEdit.value) {
+ const index = mockData.findIndex(item => item.id === currentId.value);
+ if (index !== -1) {
+ mockData[index] = { ...mockData[index], ...form, supplierName: supplier?.name };
+ }
+ ElMessage.success("缂栬緫鎴愬姛");
+ } else {
+ const newId = mockData.length > 0 ? Math.max(...mockData.map(item => item.id)) + 1 : 1;
+ mockData.push({ id: newId, ...form, supplierName: supplier?.name, status: "pending" });
+ ElMessage.success("鏂板鎴愬姛");
+ }
+ dialogVisible.value = false;
+ getTableData();
+ }
+ });
+};
+
+onMounted(() => {
+ getTableData();
+});
+</script>
+
+<style lang="scss" scoped>
+.actions {
+ display: flex;
+ justify-content: space-between;
+ margin-bottom: 15px;
+}
+
+.text-danger {
+ color: #f56c6c;
+ font-weight: bold;
+}
+</style>
diff --git a/src/views/financialManagement/payable/purchaseIn.vue b/src/views/financialManagement/payable/purchaseIn.vue
new file mode 100644
index 0000000..131fe49
--- /dev/null
+++ b/src/views/financialManagement/payable/purchaseIn.vue
@@ -0,0 +1,331 @@
+<template>
+ <div class="app-container">
+ <el-form :model="filters" :inline="true">
+ <el-form-item label="鍏ュ簱鍗曞彿:">
+ <el-input v-model="filters.inCode" placeholder="璇疯緭鍏ュ叆搴撳崟鍙�" clearable style="width: 200px;" />
+ </el-form-item>
+ <el-form-item label="渚涘簲鍟�:">
+ <el-select v-model="filters.supplierId" placeholder="璇烽�夋嫨渚涘簲鍟�" clearable style="width: 200px;">
+ <el-option v-for="item in supplierList" :key="item.id" :label="item.name" :value="item.id" />
+ </el-select>
+ </el-form-item>
+ <el-form-item label="鍏ュ簱鏃ユ湡:">
+ <el-date-picker v-model="filters.dateRange" value-format="YYYY-MM-DD" format="YYYY-MM-DD" type="daterange" range-separator="鑷�" start-placeholder="寮�濮嬫棩鏈�" end-placeholder="缁撴潫鏃ユ湡" clearable />
+ </el-form-item>
+ <el-form-item>
+ <el-button type="primary" @click="getTableData">鎼滅储</el-button>
+ <el-button @click="resetFilters">閲嶇疆</el-button>
+ </el-form-item>
+ </el-form>
+ <div class="table_list">
+ <div class="actions">
+ <div></div>
+ <div>
+ <el-button type="primary" @click="add" icon="Plus">鏂板鍏ュ簱</el-button>
+ <el-button @click="handleOut" icon="Download">瀵煎嚭</el-button>
+ </div>
+ </div>
+ <PIMTable
+ rowKey="id"
+ :column="columns"
+ :tableData="dataList"
+ :page="{
+ current: pagination.currentPage,
+ size: pagination.pageSize,
+ total: pagination.total,
+ }"
+ @pagination="changePage"
+ >
+ <template #amount="{ row }">
+ <span class="text-primary">楼{{ formatMoney(row.amount) }}</span>
+ </template>
+ <template #status="{ row }">
+ <el-tag :type="getStatusType(row.status)">{{ getStatusLabel(row.status) }}</el-tag>
+ </template>
+ <template #operation="{ row }">
+ <el-button type="primary" link @click="view(row)">鏌ョ湅</el-button>
+ <el-button type="primary" link @click="edit(row)" v-if="row.status === 'pending'">缂栬緫</el-button>
+ <el-button type="danger" link @click="handleDelete(row)" v-if="row.status === 'pending'">鍒犻櫎</el-button>
+ </template>
+ </PIMTable>
+ </div>
+
+ <el-dialog :title="dialogTitle" v-model="dialogVisible" width="800px" append-to-body>
+ <el-form :model="form" :rules="rules" ref="formRef" label-width="100px">
+ <el-row :gutter="20">
+ <el-col :span="12">
+ <el-form-item label="鍏ュ簱鍗曞彿" prop="inCode">
+ <el-input v-model="form.inCode" placeholder="璇疯緭鍏ュ叆搴撳崟鍙�" :disabled="isEdit" />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="渚涘簲鍟�" prop="supplierId">
+ <el-select v-model="form.supplierId" placeholder="璇烽�夋嫨渚涘簲鍟�" style="width: 100%;" :disabled="isEdit">
+ <el-option v-for="item in supplierList" :key="item.id" :label="item.name" :value="item.id" />
+ </el-select>
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="20">
+ <el-col :span="12">
+ <el-form-item label="鍏ュ簱鏃ユ湡" prop="inDate">
+ <el-date-picker v-model="form.inDate" type="date" placeholder="閫夋嫨鏃ユ湡" value-format="YYYY-MM-DD" style="width: 100%;" />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="鍏ュ簱閲戦" prop="amount">
+ <el-input-number v-model="form.amount" :min="0" :precision="2" style="width: 100%;" />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-form-item label="鍏ュ簱鏄庣粏" prop="details">
+ <el-table :data="form.details" border style="width: 100%">
+ <el-table-column prop="materialName" label="鐗╂枡鍚嶇О" width="150">
+ <template #default="{ $index }">
+ <el-input v-model="form.details[$index].materialName" placeholder="鐗╂枡鍚嶇О" />
+ </template>
+ </el-table-column>
+ <el-table-column prop="spec" label="瑙勬牸" width="120">
+ <template #default="{ $index }">
+ <el-input v-model="form.details[$index].spec" placeholder="瑙勬牸" />
+ </template>
+ </el-table-column>
+ <el-table-column prop="quantity" label="鏁伴噺" width="100">
+ <template #default="{ $index }">
+ <el-input-number v-model="form.details[$index].quantity" :min="0" style="width: 100%;" />
+ </template>
+ </el-table-column>
+ <el-table-column prop="unitPrice" label="鍗曚环" width="120">
+ <template #default="{ $index }">
+ <el-input-number v-model="form.details[$index].unitPrice" :min="0" :precision="2" style="width: 100%;" />
+ </template>
+ </el-table-column>
+ <el-table-column prop="total" label="閲戦" width="120">
+ <template #default="{ row }">
+ <span>楼{{ formatMoney(row.quantity * row.unitPrice) }}</span>
+ </template>
+ </el-table-column>
+ <el-table-column label="鎿嶄綔" width="80">
+ <template #default="{ $index }">
+ <el-button type="danger" link @click="removeDetail($index)">鍒犻櫎</el-button>
+ </template>
+ </el-table-column>
+ </el-table>
+ <el-button type="primary" link @click="addDetail" style="margin-top: 10px;">+ 娣诲姞鏄庣粏</el-button>
+ </el-form-item>
+ <el-form-item label="澶囨敞" prop="remark">
+ <el-input v-model="form.remark" type="textarea" :rows="3" placeholder="璇疯緭鍏ュ娉�" />
+ </el-form-item>
+ </el-form>
+ <template #footer>
+ <el-button @click="dialogVisible = false">鍙栨秷</el-button>
+ <el-button type="primary" @click="submitForm">纭畾</el-button>
+ </template>
+ </el-dialog>
+ </div>
+</template>
+
+<script setup>
+import { ref, reactive, onMounted } from "vue";
+import { ElMessage, ElMessageBox } from "element-plus";
+
+defineOptions({
+ name: "閲囪喘鍏ュ簱",
+});
+
+const filters = reactive({
+ inCode: "",
+ supplierId: "",
+ dateRange: [],
+});
+
+const pagination = reactive({
+ currentPage: 1,
+ pageSize: 10,
+ total: 0,
+});
+
+const columns = [
+ { label: "鍏ュ簱鍗曞彿", prop: "inCode", width: "150" },
+ { label: "渚涘簲鍟�", prop: "supplierName", width: "180" },
+ { label: "鍏ュ簱鏃ユ湡", prop: "inDate", width: "120" },
+ { label: "鍏ュ簱閲戦", prop: "amount", slot: "amount" },
+ { label: "鐘舵��", prop: "status", slot: "status" },
+ { label: "澶囨敞", prop: "remark", showOverflowTooltip: true },
+ { label: "鎿嶄綔", prop: "operation", slot: "operation", width: "200", fixed: "right" },
+];
+
+const dataList = ref([]);
+const dialogVisible = ref(false);
+const dialogTitle = ref("");
+const formRef = ref(null);
+const isEdit = ref(false);
+const currentId = ref(null);
+
+const supplierList = [
+ { id: 1, name: "鍖椾含鍘熸潗鏂欎緵搴斿晢" },
+ { id: 2, name: "涓婃捣鐢靛瓙鍏冨櫒浠跺叕鍙�" },
+ { id: 3, name: "骞垮窞鍖呰鏉愭枡鍘�" },
+ { id: 4, name: "娣卞湷浜旈噾閰嶄欢鍏徃" },
+];
+
+const form = reactive({
+ inCode: "",
+ supplierId: "",
+ inDate: "",
+ amount: 0,
+ details: [],
+ remark: "",
+});
+
+const rules = {
+ inCode: [{ required: true, message: "璇疯緭鍏ュ叆搴撳崟鍙�", trigger: "blur" }],
+ supplierId: [{ required: true, message: "璇烽�夋嫨渚涘簲鍟�", trigger: "change" }],
+ inDate: [{ required: true, message: "璇烽�夋嫨鍏ュ簱鏃ユ湡", trigger: "change" }],
+ amount: [{ required: true, message: "璇疯緭鍏ュ叆搴撻噾棰�", trigger: "blur" }],
+};
+
+const mockData = [
+ { id: 1, inCode: "RK2024001", supplierId: 1, supplierName: "鍖椾含鍘熸潗鏂欎緵搴斿晢", inDate: "2024-01-10", amount: 8000, status: "approved", details: [{ materialName: "閽㈡潗", spec: "Q235", quantity: 10, unitPrice: 500 }], remark: "" },
+ { id: 2, inCode: "RK2024002", supplierId: 2, supplierName: "涓婃捣鐢靛瓙鍏冨櫒浠跺叕鍙�", inDate: "2024-01-12", amount: 12000, status: "pending", details: [{ materialName: "鑺墖", spec: "STM32", quantity: 100, unitPrice: 80 }], remark: "" },
+ { id: 3, inCode: "RK2024003", supplierId: 3, supplierName: "骞垮窞鍖呰鏉愭枡鍘�", inDate: "2024-01-15", amount: 3500, status: "approved", details: [{ materialName: "绾哥", spec: "50*40*30", quantity: 500, unitPrice: 5 }], remark: "" },
+];
+
+const formatMoney = (value) => {
+ if (value === undefined || value === null) return "0.00";
+ return Number(value).toFixed(2).replace(/\B(?=(\d{3})+(?!\d))/g, ",");
+};
+
+const getStatusLabel = (status) => {
+ const map = { pending: "寰呭鏍�", approved: "宸插鏍�", rejected: "宸查┏鍥�" };
+ return map[status] || status;
+};
+
+const getStatusType = (status) => {
+ const map = { pending: "warning", approved: "success", rejected: "danger" };
+ return map[status] || "";
+};
+
+const getTableData = () => {
+ let result = [...mockData];
+ if (filters.inCode) {
+ result = result.filter(item => item.inCode.includes(filters.inCode));
+ }
+ if (filters.supplierId) {
+ result = result.filter(item => item.supplierId === filters.supplierId);
+ }
+ if (filters.dateRange && filters.dateRange.length === 2) {
+ result = result.filter(item => item.inDate >= filters.dateRange[0] && item.inDate <= filters.dateRange[1]);
+ }
+ pagination.total = result.length;
+ dataList.value = result.slice((pagination.currentPage - 1) * pagination.pageSize, pagination.currentPage * pagination.pageSize);
+};
+
+const resetFilters = () => {
+ filters.inCode = "";
+ filters.supplierId = "";
+ filters.dateRange = [];
+ pagination.currentPage = 1;
+ getTableData();
+};
+
+const changePage = ({ current, size }) => {
+ pagination.currentPage = current;
+ pagination.pageSize = size;
+ getTableData();
+};
+
+const addDetail = () => {
+ form.details.push({ materialName: "", spec: "", quantity: 0, unitPrice: 0 });
+};
+
+const removeDetail = (index) => {
+ form.details.splice(index, 1);
+};
+
+const add = () => {
+ isEdit.value = false;
+ dialogTitle.value = "鏂板鍏ュ簱";
+ Object.assign(form, {
+ inCode: "RK" + Date.now().toString().slice(-8),
+ supplierId: "",
+ inDate: new Date().toISOString().split('T')[0],
+ amount: 0,
+ details: [{ materialName: "", spec: "", quantity: 0, unitPrice: 0 }],
+ remark: "",
+ });
+ dialogVisible.value = true;
+};
+
+const edit = (row) => {
+ isEdit.value = true;
+ currentId.value = row.id;
+ dialogTitle.value = "缂栬緫鍏ュ簱";
+ Object.assign(form, row);
+ if (!form.details || form.details.length === 0) {
+ form.details = [{ materialName: "", spec: "", quantity: 0, unitPrice: 0 }];
+ }
+ dialogVisible.value = true;
+};
+
+const view = (row) => {
+ ElMessage.info(`鏌ョ湅鍏ュ簱鍗�: ${row.inCode}`);
+};
+
+const handleDelete = (row) => {
+ ElMessageBox.confirm("纭鍒犻櫎璇ュ叆搴撳崟鍚楋紵", "鎻愮ず", {
+ confirmButtonText: "纭畾",
+ cancelButtonText: "鍙栨秷",
+ type: "warning",
+ }).then(() => {
+ const index = mockData.findIndex(item => item.id === row.id);
+ if (index !== -1) {
+ mockData.splice(index, 1);
+ }
+ ElMessage.success("鍒犻櫎鎴愬姛");
+ getTableData();
+ });
+};
+
+const handleOut = () => {
+ ElMessage.success("瀵煎嚭鎴愬姛");
+};
+
+const submitForm = () => {
+ formRef.value.validate((valid) => {
+ if (valid) {
+ const supplier = supplierList.find(item => item.id === form.supplierId);
+ if (isEdit.value) {
+ const index = mockData.findIndex(item => item.id === currentId.value);
+ if (index !== -1) {
+ mockData[index] = { ...mockData[index], ...form, supplierName: supplier?.name };
+ }
+ ElMessage.success("缂栬緫鎴愬姛");
+ } else {
+ const newId = mockData.length > 0 ? Math.max(...mockData.map(item => item.id)) + 1 : 1;
+ mockData.push({ id: newId, ...form, supplierName: supplier?.name, status: "pending" });
+ ElMessage.success("鏂板鎴愬姛");
+ }
+ dialogVisible.value = false;
+ getTableData();
+ }
+ });
+};
+
+onMounted(() => {
+ getTableData();
+});
+</script>
+
+<style lang="scss" scoped>
+.actions {
+ display: flex;
+ justify-content: space-between;
+ margin-bottom: 15px;
+}
+
+.text-primary {
+ color: #409eff;
+ font-weight: bold;
+}
+</style>
diff --git a/src/views/financialManagement/payable/reconciliation.vue b/src/views/financialManagement/payable/reconciliation.vue
new file mode 100644
index 0000000..06854b8
--- /dev/null
+++ b/src/views/financialManagement/payable/reconciliation.vue
@@ -0,0 +1,254 @@
+<template>
+ <div class="app-container">
+ <el-form :model="filters" :inline="true">
+ <el-form-item label="渚涘簲鍟�:">
+ <el-select v-model="filters.supplierId" placeholder="璇烽�夋嫨渚涘簲鍟�" clearable style="width: 200px;">
+ <el-option v-for="item in supplierList" :key="item.id" :label="item.name" :value="item.id" />
+ </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-form-item>
+ </el-form>
+ <div class="table_list">
+ <div class="actions">
+ <div>
+ <el-button type="primary" @click="generateStatement" icon="Document">鐢熸垚瀵硅处鍗�</el-button>
+ </div>
+ <div>
+ <el-button @click="handleOut" icon="Download">瀵煎嚭瀵硅处鍗�</el-button>
+ </div>
+ </div>
+ <PIMTable
+ rowKey="id"
+ :column="columns"
+ :tableData="dataList"
+ :page="{
+ current: pagination.currentPage,
+ size: pagination.pageSize,
+ total: pagination.total,
+ }"
+ @pagination="changePage"
+ >
+ <template #beginBalance="{ row }">
+ <span :class="row.beginBalance >= 0 ? 'text-success' : 'text-danger'">楼{{ formatMoney(row.beginBalance) }}</span>
+ </template>
+ <template #currentPayable="{ row }">
+ <span class="text-danger">楼{{ formatMoney(row.currentPayable) }}</span>
+ </template>
+ <template #currentPayment="{ row }">
+ <span class="text-success">楼{{ formatMoney(row.currentPayment) }}</span>
+ </template>
+ <template #endBalance="{ row }">
+ <span :class="row.endBalance >= 0 ? 'text-success' : 'text-danger'">楼{{ formatMoney(row.endBalance) }}</span>
+ </template>
+ <template #operation="{ row }">
+ <el-button type="primary" link @click="viewDetail(row)">鏌ョ湅鏄庣粏</el-button>
+ <el-button type="primary" link @click="printStatement(row)">鎵撳嵃</el-button>
+ </template>
+ </PIMTable>
+ </div>
+
+ <el-dialog title="瀵硅处鏄庣粏" v-model="detailDialogVisible" width="900px" append-to-body>
+ <div class="statement-header">
+ <h3>{{ currentSupplier }} 搴斾粯瀵硅处鍗�</h3>
+ <p>瀵硅处鏈熼棿: {{ currentPeriod }}</p>
+ </div>
+ <el-table :data="detailData" border style="width: 100%">
+ <el-table-column prop="date" label="鏃ユ湡" width="120" />
+ <el-table-column prop="type" label="绫诲瀷" width="100">
+ <template #default="{ row }">
+ <el-tag :type="row.type === '鍏ュ簱' ? 'success' : row.type === '閫�璐�' ? 'danger' : 'primary'">{{ row.type }}</el-tag>
+ </template>
+ </el-table-column>
+ <el-table-column prop="code" label="鍗曟嵁缂栧彿" width="150" />
+ <el-table-column prop="debit" label="鍊熸柟(浠樻)" width="120">
+ <template #default="{ row }">
+ <span v-if="row.debit > 0" class="text-success">楼{{ formatMoney(row.debit) }}</span>
+ <span v-else>-</span>
+ </template>
+ </el-table-column>
+ <el-table-column prop="credit" label="璐锋柟(搴斾粯)" width="120">
+ <template #default="{ row }">
+ <span v-if="row.credit > 0" class="text-danger">楼{{ formatMoney(row.credit) }}</span>
+ <span v-else>-</span>
+ </template>
+ </el-table-column>
+ <el-table-column prop="balance" label="浣欓" width="120">
+ <template #default="{ row }">
+ <span :class="row.balance >= 0 ? 'text-success' : 'text-danger'">楼{{ formatMoney(row.balance) }}</span>
+ </template>
+ </el-table-column>
+ <el-table-column prop="remark" label="澶囨敞" show-overflow-tooltip />
+ </el-table>
+ <template #footer>
+ <el-button @click="detailDialogVisible = false">鍏抽棴</el-button>
+ <el-button type="primary" @click="printDetail">鎵撳嵃</el-button>
+ </template>
+ </el-dialog>
+ </div>
+</template>
+
+<script setup>
+import { ref, reactive, onMounted } from "vue";
+import { ElMessage } from "element-plus";
+
+defineOptions({
+ name: "搴斾粯瀵硅处",
+});
+
+const filters = reactive({
+ supplierId: "",
+ startMonth: "",
+ endMonth: "",
+});
+
+const pagination = reactive({
+ currentPage: 1,
+ pageSize: 10,
+ total: 0,
+});
+
+const columns = [
+ { label: "瀵硅处鍗曞彿", prop: "statementCode", width: "150" },
+ { label: "渚涘簲鍟�", prop: "supplierName", width: "180" },
+ { label: "瀵硅处鏈熼棿", prop: "period", width: "150" },
+ { label: "鏈熷垵浣欓", prop: "beginBalance", slot: "beginBalance" },
+ { label: "鏈湡搴斾粯", prop: "currentPayable", slot: "currentPayable" },
+ { label: "鏈湡浠樻", prop: "currentPayment", slot: "currentPayment" },
+ { label: "鏈熸湯浣欓", prop: "endBalance", slot: "endBalance" },
+ { label: "鎿嶄綔", prop: "operation", slot: "operation", width: "150", fixed: "right" },
+];
+
+const dataList = ref([]);
+const detailDialogVisible = ref(false);
+const currentSupplier = ref("");
+const currentPeriod = ref("");
+const detailData = ref([]);
+
+const supplierList = [
+ { id: 1, name: "鍖椾含鍘熸潗鏂欎緵搴斿晢" },
+ { id: 2, name: "涓婃捣鐢靛瓙鍏冨櫒浠跺叕鍙�" },
+ { id: 3, name: "骞垮窞鍖呰鏉愭枡鍘�" },
+ { id: 4, name: "娣卞湷浜旈噾閰嶄欢鍏徃" },
+];
+
+const mockData = [
+ { id: 1, statementCode: "DZ202401001", supplierId: 1, supplierName: "鍖椾含鍘熸潗鏂欎緵搴斿晢", period: "2024-01", beginBalance: 20000, currentPayable: 15000, currentPayment: 10000, endBalance: 25000 },
+ { id: 2, statementCode: "DZ202401002", supplierId: 2, supplierName: "涓婃捣鐢靛瓙鍏冨櫒浠跺叕鍙�", period: "2024-01", beginBalance: 10000, currentPayable: 20000, currentPayment: 15000, endBalance: 15000 },
+ { id: 3, statementCode: "DZ202402001", supplierId: 1, supplierName: "鍖椾含鍘熸潗鏂欎緵搴斿晢", period: "2024-02", beginBalance: 25000, currentPayable: 18000, currentPayment: 20000, endBalance: 23000 },
+];
+
+const formatMoney = (value) => {
+ if (value === undefined || value === null) return "0.00";
+ return Number(value).toFixed(2).replace(/\B(?=(\d{3})+(?!\d))/g, ",");
+};
+
+const getTableData = () => {
+ let result = [...mockData];
+ if (filters.supplierId) {
+ result = result.filter(item => item.supplierId === filters.supplierId);
+ }
+ if (filters.startMonth && filters.endMonth) {
+ result = result.filter(item => item.period >= filters.startMonth && item.period <= filters.endMonth);
+ }
+ pagination.total = result.length;
+ dataList.value = result.slice((pagination.currentPage - 1) * pagination.pageSize, pagination.currentPage * pagination.pageSize);
+};
+
+const resetFilters = () => {
+ filters.supplierId = "";
+ filters.startMonth = "";
+ filters.endMonth = "";
+ pagination.currentPage = 1;
+ getTableData();
+};
+
+const changePage = ({ current, size }) => {
+ pagination.currentPage = current;
+ pagination.pageSize = size;
+ getTableData();
+};
+
+const generateStatement = () => {
+ ElMessage.success("瀵硅处鍗曠敓鎴愭垚鍔�");
+ const newId = mockData.length > 0 ? Math.max(...mockData.map(item => item.id)) + 1 : 1;
+ const supplier = supplierList[Math.floor(Math.random() * supplierList.length)];
+ mockData.unshift({
+ id: newId,
+ statementCode: "DZ" + Date.now(),
+ supplierId: supplier.id,
+ supplierName: supplier.name,
+ period: "2024-03",
+ beginBalance: Math.floor(Math.random() * 20000),
+ currentPayable: Math.floor(Math.random() * 25000),
+ currentPayment: Math.floor(Math.random() * 20000),
+ endBalance: Math.floor(Math.random() * 25000),
+ });
+ getTableData();
+};
+
+const viewDetail = (row) => {
+ currentSupplier.value = row.supplierName;
+ currentPeriod.value = row.period;
+ detailData.value = [
+ { date: row.period + "-01", type: "鏈熷垵", code: "-", debit: 0, credit: 0, balance: row.beginBalance, remark: "鏈熷垵浣欓" },
+ { date: row.period + "-05", type: "鍏ュ簱", code: "RK2024001", debit: 0, credit: 8000, balance: row.beginBalance + 8000, remark: "" },
+ { date: row.period + "-10", type: "浠樻", code: "FK2024001", debit: 5000, credit: 0, balance: row.beginBalance + 3000, remark: "" },
+ { date: row.period + "-15", type: "鍏ュ簱", code: "RK2024002", credit: 12000, balance: row.beginBalance + 15000, remark: "" },
+ { date: row.period + "-20", type: "閫�璐�", code: "TH2024001", debit: 2000, credit: 0, balance: row.beginBalance + 13000, remark: "" },
+ { date: row.period + "-25", type: "浠樻", code: "FK2024002", debit: row.currentPayment - 5000, balance: row.endBalance, remark: "" },
+ ];
+ detailDialogVisible.value = true;
+};
+
+const printStatement = (row) => {
+ ElMessage.info(`鎵撳嵃瀵硅处鍗�: ${row.statementCode}`);
+};
+
+const printDetail = () => {
+ ElMessage.info("鎵撳嵃鏄庣粏");
+};
+
+const handleOut = () => {
+ ElMessage.success("瀵煎嚭鎴愬姛");
+};
+
+onMounted(() => {
+ getTableData();
+});
+</script>
+
+<style lang="scss" scoped>
+.actions {
+ display: flex;
+ justify-content: space-between;
+ margin-bottom: 15px;
+}
+
+.text-success {
+ color: #67c23a;
+}
+
+.text-danger {
+ color: #f56c6c;
+}
+
+.statement-header {
+ text-align: center;
+ margin-bottom: 20px;
+ h3 {
+ margin: 0 0 10px 0;
+ }
+ p {
+ color: #909399;
+ margin: 0;
+ }
+}
+</style>
diff --git a/src/views/financialManagement/receivable/invoiceApply.vue b/src/views/financialManagement/receivable/invoiceApply.vue
new file mode 100644
index 0000000..e637a3e
--- /dev/null
+++ b/src/views/financialManagement/receivable/invoiceApply.vue
@@ -0,0 +1,358 @@
+<template>
+ <div class="app-container">
+ <el-form :model="filters" :inline="true">
+ <el-form-item label="鐢宠鍗曞彿:">
+ <el-input v-model="filters.applyCode" placeholder="璇疯緭鍏ョ敵璇峰崟鍙�" clearable style="width: 200px;" />
+ </el-form-item>
+ <el-form-item label="瀹㈡埛:">
+ <el-select v-model="filters.customerId" placeholder="璇烽�夋嫨瀹㈡埛" clearable style="width: 200px;">
+ <el-option v-for="item in customerList" :key="item.id" :label="item.name" :value="item.id" />
+ </el-select>
+ </el-form-item>
+ <el-form-item label="鐘舵��:">
+ <el-select v-model="filters.status" placeholder="璇烽�夋嫨鐘舵��" clearable style="width: 150px;">
+ <el-option label="寰呭鏍�" value="pending" />
+ <el-option label="宸插鏍�" value="approved" />
+ <el-option label="宸查┏鍥�" value="rejected" />
+ <el-option label="宸插紑绁�" value="invoiced" />
+ </el-select>
+ </el-form-item>
+ <el-form-item>
+ <el-button type="primary" @click="getTableData">鎼滅储</el-button>
+ <el-button @click="resetFilters">閲嶇疆</el-button>
+ </el-form-item>
+ </el-form>
+ <div class="table_list">
+ <div class="actions">
+ <div></div>
+ <div>
+ <el-button type="primary" @click="add" icon="Plus">鏂板鐢宠</el-button>
+ <el-button @click="handleBatchApply" icon="Document" :disabled="selectedRows.length === 0">鎵归噺鐢宠</el-button>
+ </div>
+ </div>
+ <PIMTable
+ rowKey="id"
+ isSelection
+ :column="columns"
+ :tableData="dataList"
+ :page="{
+ current: pagination.currentPage,
+ size: pagination.pageSize,
+ total: pagination.total,
+ }"
+ @selection-change="handleSelectionChange"
+ @pagination="changePage"
+ >
+ <template #amount="{ row }">
+ <span class="text-primary">楼{{ formatMoney(row.amount) }}</span>
+ </template>
+ <template #taxRate="{ row }">
+ <span>{{ row.taxRate }}%</span>
+ </template>
+ <template #status="{ row }">
+ <el-tag :type="getStatusType(row.status)">{{ getStatusLabel(row.status) }}</el-tag>
+ </template>
+ <template #operation="{ row }">
+ <el-button type="primary" link @click="view(row)">鏌ョ湅</el-button>
+ <el-button type="primary" link @click="edit(row)" v-if="row.status === 'pending'">缂栬緫</el-button>
+ <el-button type="success" link @click="handleAudit(row)" v-if="row.status === 'pending'">瀹℃牳</el-button>
+ <el-button type="warning" link @click="handleInvoice(row)" v-if="row.status === 'approved'">寮�绁�</el-button>
+ </template>
+ </PIMTable>
+ </div>
+
+ <el-dialog :title="dialogTitle" v-model="dialogVisible" width="800px" append-to-body>
+ <el-form :model="form" :rules="rules" ref="formRef" label-width="120px">
+ <el-row :gutter="20">
+ <el-col :span="12">
+ <el-form-item label="鐢宠鍗曞彿" prop="applyCode">
+ <el-input v-model="form.applyCode" placeholder="绯荤粺鑷姩鐢熸垚" disabled />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="瀹㈡埛" prop="customerId">
+ <el-select v-model="form.customerId" placeholder="璇烽�夋嫨瀹㈡埛" style="width: 100%;" :disabled="isEdit">
+ <el-option v-for="item in customerList" :key="item.id" :label="item.name" :value="item.id" />
+ </el-select>
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="20">
+ <el-col :span="12">
+ <el-form-item label="寮�绁ㄩ噾棰�" prop="amount">
+ <el-input-number v-model="form.amount" :min="0" :precision="2" style="width: 100%;" />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="绋庣巼" prop="taxRate">
+ <el-select v-model="form.taxRate" placeholder="璇烽�夋嫨绋庣巼" style="width: 100%;">
+ <el-option label="0%" :value="0" />
+ <el-option label="3%" :value="3" />
+ <el-option label="6%" :value="6" />
+ <el-option label="9%" :value="9" />
+ <el-option label="13%" :value="13" />
+ </el-select>
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="20">
+ <el-col :span="12">
+ <el-form-item label="鍙戠エ绫诲瀷" prop="invoiceType">
+ <el-select v-model="form.invoiceType" placeholder="璇烽�夋嫨鍙戠エ绫诲瀷" style="width: 100%;">
+ <el-option label="澧炲�肩◣涓撶敤鍙戠エ" value="special" />
+ <el-option label="澧炲�肩◣鏅�氬彂绁�" value="normal" />
+ <el-option label="鐢靛瓙鍙戠エ" value="electronic" />
+ </el-select>
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="鐢宠鏃ユ湡" prop="applyDate">
+ <el-date-picker v-model="form.applyDate" type="date" placeholder="閫夋嫨鏃ユ湡" value-format="YYYY-MM-DD" style="width: 100%;" />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-form-item label="鍙戠エ鍐呭" prop="content">
+ <el-input v-model="form.content" type="textarea" :rows="3" placeholder="璇疯緭鍏ュ彂绁ㄥ唴瀹�" />
+ </el-form-item>
+ <el-form-item label="澶囨敞" prop="remark">
+ <el-input v-model="form.remark" type="textarea" :rows="2" placeholder="璇疯緭鍏ュ娉�" />
+ </el-form-item>
+ </el-form>
+ <template #footer>
+ <el-button @click="dialogVisible = false">鍙栨秷</el-button>
+ <el-button type="primary" @click="submitForm">纭畾</el-button>
+ </template>
+ </el-dialog>
+ </div>
+</template>
+
+<script setup>
+import { ref, reactive, onMounted } from "vue";
+import { ElMessage, ElMessageBox } from "element-plus";
+
+defineOptions({
+ name: "寮�绁ㄧ敵璇�",
+});
+
+const filters = reactive({
+ applyCode: "",
+ customerId: "",
+ status: "",
+});
+
+const pagination = reactive({
+ currentPage: 1,
+ pageSize: 10,
+ total: 0,
+});
+
+const columns = [
+ { label: "鐢宠鍗曞彿", prop: "applyCode", width: "150" },
+ { label: "瀹㈡埛鍚嶇О", prop: "customerName", width: "180" },
+ { label: "寮�绁ㄩ噾棰�", prop: "amount", slot: "amount" },
+ { label: "绋庣巼", prop: "taxRate", slot: "taxRate" },
+ { label: "鍙戠エ绫诲瀷", prop: "invoiceTypeLabel", width: "130" },
+ { label: "鐢宠鏃ユ湡", prop: "applyDate", width: "120" },
+ { label: "鐘舵��", prop: "status", slot: "status" },
+ { label: "鎿嶄綔", prop: "operation", slot: "operation", width: "200", fixed: "right" },
+];
+
+const dataList = ref([]);
+const selectedRows = ref([]);
+const dialogVisible = ref(false);
+const dialogTitle = ref("");
+const formRef = ref(null);
+const isEdit = ref(false);
+const currentId = ref(null);
+
+const customerList = [
+ { id: 1, name: "鍖椾含绉戞妧鏈夐檺鍏徃" },
+ { id: 2, name: "涓婃捣璐告槗鍏徃" },
+ { id: 3, name: "骞垮窞瀹炰笟鏈夐檺鍏徃" },
+ { id: 4, name: "娣卞湷鐢靛瓙鍏徃" },
+];
+
+const form = reactive({
+ applyCode: "",
+ customerId: "",
+ amount: 0,
+ taxRate: 13,
+ invoiceType: "special",
+ applyDate: "",
+ content: "",
+ remark: "",
+});
+
+const rules = {
+ customerId: [{ required: true, message: "璇烽�夋嫨瀹㈡埛", trigger: "change" }],
+ amount: [{ required: true, message: "璇疯緭鍏ュ紑绁ㄩ噾棰�", trigger: "blur" }],
+ taxRate: [{ required: true, message: "璇烽�夋嫨绋庣巼", trigger: "change" }],
+ invoiceType: [{ required: true, message: "璇烽�夋嫨鍙戠エ绫诲瀷", trigger: "change" }],
+ applyDate: [{ required: true, message: "璇烽�夋嫨鐢宠鏃ユ湡", trigger: "change" }],
+};
+
+const mockData = [
+ { id: 1, applyCode: "KP2024001", customerId: 1, customerName: "鍖椾含绉戞妧鏈夐檺鍏徃", amount: 5000, taxRate: 13, invoiceType: "special", invoiceTypeLabel: "澧炲�肩◣涓撶敤鍙戠エ", applyDate: "2024-01-15", status: "pending", content: "杞欢鏈嶅姟璐�", remark: "" },
+ { id: 2, applyCode: "KP2024002", customerId: 2, customerName: "涓婃捣璐告槗鍏徃", amount: 8000, taxRate: 13, invoiceType: "normal", invoiceTypeLabel: "澧炲�肩◣鏅�氬彂绁�", applyDate: "2024-01-16", status: "approved", content: "鍟嗗搧閿�鍞�", remark: "" },
+ { id: 3, applyCode: "KP2024003", customerId: 3, customerName: "骞垮窞瀹炰笟鏈夐檺鍏徃", amount: 12000, taxRate: 6, invoiceType: "electronic", invoiceTypeLabel: "鐢靛瓙鍙戠エ", applyDate: "2024-01-18", status: "invoiced", content: "鎶�鏈湇鍔¤垂", remark: "" },
+];
+
+const formatMoney = (value) => {
+ if (value === undefined || value === null) return "0.00";
+ return Number(value).toFixed(2).replace(/\B(?=(\d{3})+(?!\d))/g, ",");
+};
+
+const getStatusLabel = (status) => {
+ const map = { pending: "寰呭鏍�", approved: "宸插鏍�", rejected: "宸查┏鍥�", invoiced: "宸插紑绁�" };
+ return map[status] || status;
+};
+
+const getStatusType = (status) => {
+ const map = { pending: "warning", approved: "success", rejected: "danger", invoiced: "primary" };
+ return map[status] || "";
+};
+
+const getTableData = () => {
+ let result = [...mockData];
+ if (filters.applyCode) {
+ result = result.filter(item => item.applyCode.includes(filters.applyCode));
+ }
+ if (filters.customerId) {
+ result = result.filter(item => item.customerId === filters.customerId);
+ }
+ if (filters.status) {
+ result = result.filter(item => item.status === filters.status);
+ }
+ pagination.total = result.length;
+ dataList.value = result.slice((pagination.currentPage - 1) * pagination.pageSize, pagination.currentPage * pagination.pageSize);
+};
+
+const resetFilters = () => {
+ filters.applyCode = "";
+ filters.customerId = "";
+ filters.status = "";
+ pagination.currentPage = 1;
+ getTableData();
+};
+
+const changePage = ({ current, size }) => {
+ pagination.currentPage = current;
+ pagination.pageSize = size;
+ getTableData();
+};
+
+const handleSelectionChange = (selection) => {
+ selectedRows.value = selection;
+};
+
+const add = () => {
+ isEdit.value = false;
+ dialogTitle.value = "鏂板寮�绁ㄧ敵璇�";
+ Object.assign(form, {
+ applyCode: "KP" + Date.now().toString().slice(-8),
+ customerId: "",
+ amount: 0,
+ taxRate: 13,
+ invoiceType: "special",
+ applyDate: new Date().toISOString().split('T')[0],
+ content: "",
+ remark: "",
+ });
+ dialogVisible.value = true;
+};
+
+const edit = (row) => {
+ isEdit.value = true;
+ currentId.value = row.id;
+ dialogTitle.value = "缂栬緫寮�绁ㄧ敵璇�";
+ Object.assign(form, row);
+ dialogVisible.value = true;
+};
+
+const view = (row) => {
+ ElMessage.info(`鏌ョ湅鐢宠鍗�: ${row.applyCode}`);
+};
+
+const handleAudit = (row) => {
+ ElMessageBox.confirm("纭瀹℃牳閫氳繃璇ュ紑绁ㄧ敵璇峰悧锛�", "鎻愮ず", {
+ confirmButtonText: "閫氳繃",
+ cancelButtonText: "椹冲洖",
+ distinguishCancelAndClose: true,
+ type: "warning",
+ }).then(() => {
+ const index = mockData.findIndex(item => item.id === row.id);
+ if (index !== -1) {
+ mockData[index].status = "approved";
+ }
+ ElMessage.success("瀹℃牳閫氳繃");
+ getTableData();
+ }).catch((action) => {
+ if (action === "cancel") {
+ const index = mockData.findIndex(item => item.id === row.id);
+ if (index !== -1) {
+ mockData[index].status = "rejected";
+ }
+ ElMessage.warning("宸查┏鍥�");
+ getTableData();
+ }
+ });
+};
+
+const handleInvoice = (row) => {
+ ElMessageBox.confirm("纭宸插紑鍏峰彂绁紵", "鎻愮ず", {
+ confirmButtonText: "纭",
+ cancelButtonText: "鍙栨秷",
+ type: "info",
+ }).then(() => {
+ const index = mockData.findIndex(item => item.id === row.id);
+ if (index !== -1) {
+ mockData[index].status = "invoiced";
+ }
+ ElMessage.success("寮�绁ㄥ畬鎴�");
+ getTableData();
+ });
+};
+
+const handleBatchApply = () => {
+ ElMessage.success(`鎵归噺鐢宠 ${selectedRows.value.length} 鏉¤褰昤);
+};
+
+const submitForm = () => {
+ formRef.value.validate((valid) => {
+ if (valid) {
+ const customer = customerList.find(item => item.id === form.customerId);
+ const invoiceTypeMap = { special: "澧炲�肩◣涓撶敤鍙戠エ", normal: "澧炲�肩◣鏅�氬彂绁�", electronic: "鐢靛瓙鍙戠エ" };
+ if (isEdit.value) {
+ const index = mockData.findIndex(item => item.id === currentId.value);
+ if (index !== -1) {
+ mockData[index] = { ...mockData[index], ...form, customerName: customer?.name, invoiceTypeLabel: invoiceTypeMap[form.invoiceType] };
+ }
+ ElMessage.success("缂栬緫鎴愬姛");
+ } else {
+ const newId = mockData.length > 0 ? Math.max(...mockData.map(item => item.id)) + 1 : 1;
+ mockData.push({ id: newId, ...form, customerName: customer?.name, invoiceTypeLabel: invoiceTypeMap[form.invoiceType], status: "pending" });
+ ElMessage.success("鏂板鎴愬姛");
+ }
+ dialogVisible.value = false;
+ getTableData();
+ }
+ });
+};
+
+onMounted(() => {
+ getTableData();
+});
+</script>
+
+<style lang="scss" scoped>
+.actions {
+ display: flex;
+ justify-content: space-between;
+ margin-bottom: 15px;
+}
+
+.text-primary {
+ color: #409eff;
+ font-weight: bold;
+}
+</style>
diff --git a/src/views/financialManagement/receivable/outputInvoice.vue b/src/views/financialManagement/receivable/outputInvoice.vue
new file mode 100644
index 0000000..289905a
--- /dev/null
+++ b/src/views/financialManagement/receivable/outputInvoice.vue
@@ -0,0 +1,368 @@
+<template>
+ <div class="app-container">
+ <el-form :model="filters" :inline="true">
+ <el-form-item label="鍙戠エ浠g爜:">
+ <el-input v-model="filters.invoiceCode" placeholder="璇疯緭鍏ュ彂绁ㄤ唬鐮�" clearable style="width: 200px;" />
+ </el-form-item>
+ <el-form-item label="鍙戠エ鍙风爜:">
+ <el-input v-model="filters.invoiceNo" placeholder="璇疯緭鍏ュ彂绁ㄥ彿鐮�" clearable style="width: 200px;" />
+ </el-form-item>
+ <el-form-item label="瀹㈡埛:">
+ <el-select v-model="filters.customerId" placeholder="璇烽�夋嫨瀹㈡埛" clearable style="width: 200px;">
+ <el-option v-for="item in customerList" :key="item.id" :label="item.name" :value="item.id" />
+ </el-select>
+ </el-form-item>
+ <el-form-item>
+ <el-button type="primary" @click="getTableData">鎼滅储</el-button>
+ <el-button @click="resetFilters">閲嶇疆</el-button>
+ </el-form-item>
+ </el-form>
+ <div class="table_list">
+ <div class="actions">
+ <div></div>
+ <div>
+ <el-button type="primary" @click="add" icon="Plus">褰曞叆鍙戠エ</el-button>
+ <el-button @click="handleImport" icon="Upload">瀵煎叆</el-button>
+ <el-button @click="handleOut" icon="Download">瀵煎嚭</el-button>
+ </div>
+ </div>
+ <PIMTable
+ rowKey="id"
+ :column="columns"
+ :tableData="dataList"
+ :page="{
+ current: pagination.currentPage,
+ size: pagination.pageSize,
+ total: pagination.total,
+ }"
+ @pagination="changePage"
+ >
+ <template #amount="{ row }">
+ <span class="text-primary">楼{{ formatMoney(row.amount) }}</span>
+ </template>
+ <template #taxAmount="{ row }">
+ <span class="text-danger">楼{{ formatMoney(row.taxAmount) }}</span>
+ </template>
+ <template #totalAmount="{ row }">
+ <span class="text-success">楼{{ formatMoney(row.totalAmount) }}</span>
+ </template>
+ <template #invoiceType="{ row }">
+ <el-tag :type="row.invoiceType === 'special' ? 'danger' : 'primary'">{{ row.invoiceTypeLabel }}</el-tag>
+ </template>
+ <template #operation="{ row }">
+ <el-button type="primary" link @click="view(row)">鏌ョ湅</el-button>
+ <el-button type="primary" link @click="edit(row)">缂栬緫</el-button>
+ <el-button type="danger" link @click="handleDelete(row)">浣滃簾</el-button>
+ </template>
+ </PIMTable>
+ </div>
+
+ <el-dialog :title="dialogTitle" v-model="dialogVisible" width="800px" append-to-body>
+ <el-form :model="form" :rules="rules" ref="formRef" label-width="120px">
+ <el-row :gutter="20">
+ <el-col :span="12">
+ <el-form-item label="鍙戠エ浠g爜" prop="invoiceCode">
+ <el-input v-model="form.invoiceCode" placeholder="璇疯緭鍏ュ彂绁ㄤ唬鐮�" />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="鍙戠エ鍙风爜" prop="invoiceNo">
+ <el-input v-model="form.invoiceNo" placeholder="璇疯緭鍏ュ彂绁ㄥ彿鐮�" />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="20">
+ <el-col :span="12">
+ <el-form-item label="瀹㈡埛" prop="customerId">
+ <el-select v-model="form.customerId" placeholder="璇烽�夋嫨瀹㈡埛" style="width: 100%;">
+ <el-option v-for="item in customerList" :key="item.id" :label="item.name" :value="item.id" />
+ </el-select>
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="寮�绁ㄦ棩鏈�" prop="invoiceDate">
+ <el-date-picker v-model="form.invoiceDate" type="date" placeholder="閫夋嫨鏃ユ湡" value-format="YYYY-MM-DD" style="width: 100%;" />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="20">
+ <el-col :span="12">
+ <el-form-item label="鍙戠エ绫诲瀷" prop="invoiceType">
+ <el-select v-model="form.invoiceType" placeholder="璇烽�夋嫨鍙戠エ绫诲瀷" style="width: 100%;" @change="handleInvoiceTypeChange">
+ <el-option label="澧炲�肩◣涓撶敤鍙戠エ" value="special" />
+ <el-option label="澧炲�肩◣鏅�氬彂绁�" value="normal" />
+ <el-option label="鐢靛瓙鍙戠エ" value="electronic" />
+ </el-select>
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="绋庣巼" prop="taxRate">
+ <el-select v-model="form.taxRate" placeholder="璇烽�夋嫨绋庣巼" style="width: 100%;" @change="calculateTax">
+ <el-option label="0%" :value="0" />
+ <el-option label="3%" :value="3" />
+ <el-option label="6%" :value="6" />
+ <el-option label="9%" :value="9" />
+ <el-option label="13%" :value="13" />
+ </el-select>
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="20">
+ <el-col :span="8">
+ <el-form-item label="閲戦(涓嶅惈绋�)" prop="amount">
+ <el-input-number v-model="form.amount" :min="0" :precision="2" style="width: 100%;" @change="calculateTax" />
+ </el-form-item>
+ </el-col>
+ <el-col :span="8">
+ <el-form-item label="绋庨">
+ <el-input v-model="form.taxAmount" disabled />
+ </el-form-item>
+ </el-col>
+ <el-col :span="8">
+ <el-form-item label="浠风◣鍚堣">
+ <el-input v-model="form.totalAmount" disabled />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-form-item label="鍙戠エ鍐呭" prop="content">
+ <el-input v-model="form.content" type="textarea" :rows="3" placeholder="璇疯緭鍏ュ彂绁ㄥ唴瀹�" />
+ </el-form-item>
+ <el-form-item label="澶囨敞" prop="remark">
+ <el-input v-model="form.remark" type="textarea" :rows="2" placeholder="璇疯緭鍏ュ娉�" />
+ </el-form-item>
+ </el-form>
+ <template #footer>
+ <el-button @click="dialogVisible = false">鍙栨秷</el-button>
+ <el-button type="primary" @click="submitForm">纭畾</el-button>
+ </template>
+ </el-dialog>
+ </div>
+</template>
+
+<script setup>
+import { ref, reactive, onMounted, computed } from "vue";
+import { ElMessage, ElMessageBox } from "element-plus";
+
+defineOptions({
+ name: "閿�椤瑰彂绁�",
+});
+
+const filters = reactive({
+ invoiceCode: "",
+ invoiceNo: "",
+ customerId: "",
+});
+
+const pagination = reactive({
+ currentPage: 1,
+ pageSize: 10,
+ total: 0,
+});
+
+const columns = [
+ { label: "鍙戠エ浠g爜", prop: "invoiceCode", width: "130" },
+ { label: "鍙戠エ鍙风爜", prop: "invoiceNo", width: "120" },
+ { label: "瀹㈡埛鍚嶇О", prop: "customerName", width: "180" },
+ { label: "寮�绁ㄦ棩鏈�", prop: "invoiceDate", width: "120" },
+ { label: "閲戦", prop: "amount", slot: "amount" },
+ { label: "绋庨", prop: "taxAmount", slot: "taxAmount" },
+ { label: "浠风◣鍚堣", prop: "totalAmount", slot: "totalAmount" },
+ { label: "鍙戠エ绫诲瀷", prop: "invoiceType", slot: "invoiceType" },
+ { label: "鎿嶄綔", prop: "operation", slot: "operation", width: "180", fixed: "right" },
+];
+
+const dataList = ref([]);
+const dialogVisible = ref(false);
+const dialogTitle = ref("");
+const formRef = ref(null);
+const isEdit = ref(false);
+const currentId = ref(null);
+
+const customerList = [
+ { id: 1, name: "鍖椾含绉戞妧鏈夐檺鍏徃" },
+ { id: 2, name: "涓婃捣璐告槗鍏徃" },
+ { id: 3, name: "骞垮窞瀹炰笟鏈夐檺鍏徃" },
+ { id: 4, name: "娣卞湷鐢靛瓙鍏徃" },
+];
+
+const form = reactive({
+ invoiceCode: "",
+ invoiceNo: "",
+ customerId: "",
+ invoiceDate: "",
+ invoiceType: "special",
+ taxRate: 13,
+ amount: 0,
+ taxAmount: 0,
+ totalAmount: 0,
+ content: "",
+ remark: "",
+});
+
+const rules = {
+ invoiceCode: [{ required: true, message: "璇疯緭鍏ュ彂绁ㄤ唬鐮�", trigger: "blur" }],
+ invoiceNo: [{ required: true, message: "璇疯緭鍏ュ彂绁ㄥ彿鐮�", trigger: "blur" }],
+ customerId: [{ required: true, message: "璇烽�夋嫨瀹㈡埛", trigger: "change" }],
+ invoiceDate: [{ required: true, message: "璇烽�夋嫨寮�绁ㄦ棩鏈�", trigger: "change" }],
+ invoiceType: [{ required: true, message: "璇烽�夋嫨鍙戠エ绫诲瀷", trigger: "change" }],
+ taxRate: [{ required: true, message: "璇烽�夋嫨绋庣巼", trigger: "change" }],
+ amount: [{ required: true, message: "璇疯緭鍏ラ噾棰�", trigger: "blur" }],
+};
+
+const mockData = [
+ { id: 1, invoiceCode: "0440021001", invoiceNo: "12345678", customerId: 1, customerName: "鍖椾含绉戞妧鏈夐檺鍏徃", invoiceDate: "2024-01-15", amount: 5000, taxRate: 13, taxAmount: 650, totalAmount: 5650, invoiceType: "special", invoiceTypeLabel: "澧炲�肩◣涓撶敤鍙戠エ", content: "杞欢鏈嶅姟璐�", remark: "" },
+ { id: 2, invoiceCode: "0440021002", invoiceNo: "87654321", customerId: 2, customerName: "涓婃捣璐告槗鍏徃", invoiceDate: "2024-01-16", amount: 8000, taxRate: 13, taxAmount: 1040, totalAmount: 9040, invoiceType: "normal", invoiceTypeLabel: "澧炲�肩◣鏅�氬彂绁�", content: "鍟嗗搧閿�鍞�", remark: "" },
+ { id: 3, invoiceCode: "0440021003", invoiceNo: "11112222", customerId: 3, customerName: "骞垮窞瀹炰笟鏈夐檺鍏徃", invoiceDate: "2024-01-18", amount: 12000, taxRate: 6, taxAmount: 720, totalAmount: 12720, invoiceType: "electronic", invoiceTypeLabel: "鐢靛瓙鍙戠エ", content: "鎶�鏈湇鍔¤垂", remark: "" },
+];
+
+const formatMoney = (value) => {
+ if (value === undefined || value === null) return "0.00";
+ return Number(value).toFixed(2).replace(/\B(?=(\d{3})+(?!\d))/g, ",");
+};
+
+const calculateTax = () => {
+ form.taxAmount = Number((form.amount * form.taxRate / 100).toFixed(2));
+ form.totalAmount = Number((form.amount + form.taxAmount).toFixed(2));
+};
+
+const handleInvoiceTypeChange = () => {
+ if (form.invoiceType === "special") {
+ form.taxRate = 13;
+ } else {
+ form.taxRate = 13;
+ }
+ calculateTax();
+};
+
+const getTableData = () => {
+ let result = [...mockData];
+ if (filters.invoiceCode) {
+ result = result.filter(item => item.invoiceCode.includes(filters.invoiceCode));
+ }
+ if (filters.invoiceNo) {
+ result = result.filter(item => item.invoiceNo.includes(filters.invoiceNo));
+ }
+ if (filters.customerId) {
+ result = result.filter(item => item.customerId === filters.customerId);
+ }
+ pagination.total = result.length;
+ dataList.value = result.slice((pagination.currentPage - 1) * pagination.pageSize, pagination.currentPage * pagination.pageSize);
+};
+
+const resetFilters = () => {
+ filters.invoiceCode = "";
+ filters.invoiceNo = "";
+ filters.customerId = "";
+ pagination.currentPage = 1;
+ getTableData();
+};
+
+const changePage = ({ current, size }) => {
+ pagination.currentPage = current;
+ pagination.pageSize = size;
+ getTableData();
+};
+
+const add = () => {
+ isEdit.value = false;
+ dialogTitle.value = "褰曞叆鍙戠エ";
+ Object.assign(form, {
+ invoiceCode: "",
+ invoiceNo: "",
+ customerId: "",
+ invoiceDate: new Date().toISOString().split('T')[0],
+ invoiceType: "special",
+ taxRate: 13,
+ amount: 0,
+ taxAmount: 0,
+ totalAmount: 0,
+ content: "",
+ remark: "",
+ });
+ dialogVisible.value = true;
+};
+
+const edit = (row) => {
+ isEdit.value = true;
+ currentId.value = row.id;
+ dialogTitle.value = "缂栬緫鍙戠エ";
+ Object.assign(form, row);
+ dialogVisible.value = true;
+};
+
+const view = (row) => {
+ ElMessage.info(`鏌ョ湅鍙戠エ: ${row.invoiceCode}-${row.invoiceNo}`);
+};
+
+const handleDelete = (row) => {
+ ElMessageBox.confirm("纭浣滃簾璇ュ彂绁ㄥ悧锛�", "鎻愮ず", {
+ confirmButtonText: "纭畾",
+ cancelButtonText: "鍙栨秷",
+ type: "warning",
+ }).then(() => {
+ const index = mockData.findIndex(item => item.id === row.id);
+ if (index !== -1) {
+ mockData.splice(index, 1);
+ }
+ ElMessage.success("浣滃簾鎴愬姛");
+ getTableData();
+ });
+};
+
+const handleImport = () => {
+ ElMessage.info("瀵煎叆鍔熻兘");
+};
+
+const handleOut = () => {
+ ElMessage.success("瀵煎嚭鎴愬姛");
+};
+
+const submitForm = () => {
+ formRef.value.validate((valid) => {
+ if (valid) {
+ const customer = customerList.find(item => item.id === form.customerId);
+ const invoiceTypeMap = { special: "澧炲�肩◣涓撶敤鍙戠エ", normal: "澧炲�肩◣鏅�氬彂绁�", electronic: "鐢靛瓙鍙戠エ" };
+ if (isEdit.value) {
+ const index = mockData.findIndex(item => item.id === currentId.value);
+ if (index !== -1) {
+ mockData[index] = { ...mockData[index], ...form, customerName: customer?.name, invoiceTypeLabel: invoiceTypeMap[form.invoiceType] };
+ }
+ ElMessage.success("缂栬緫鎴愬姛");
+ } else {
+ const newId = mockData.length > 0 ? Math.max(...mockData.map(item => item.id)) + 1 : 1;
+ mockData.push({ id: newId, ...form, customerName: customer?.name, invoiceTypeLabel: invoiceTypeMap[form.invoiceType] });
+ ElMessage.success("褰曞叆鎴愬姛");
+ }
+ dialogVisible.value = false;
+ getTableData();
+ }
+ });
+};
+
+onMounted(() => {
+ getTableData();
+});
+</script>
+
+<style lang="scss" scoped>
+.actions {
+ display: flex;
+ justify-content: space-between;
+ margin-bottom: 15px;
+}
+
+.text-primary {
+ color: #409eff;
+ font-weight: bold;
+}
+
+.text-danger {
+ color: #f56c6c;
+ font-weight: bold;
+}
+
+.text-success {
+ color: #67c23a;
+ font-weight: bold;
+}
+</style>
diff --git a/src/views/financialManagement/receivable/receipt.vue b/src/views/financialManagement/receivable/receipt.vue
new file mode 100644
index 0000000..51bb1f2
--- /dev/null
+++ b/src/views/financialManagement/receivable/receipt.vue
@@ -0,0 +1,355 @@
+<template>
+ <div class="app-container">
+ <el-form :model="filters" :inline="true">
+ <el-form-item label="鏀舵鍗曞彿:">
+ <el-input v-model="filters.receiptCode" placeholder="璇疯緭鍏ユ敹娆惧崟鍙�" clearable style="width: 200px;" />
+ </el-form-item>
+ <el-form-item label="瀹㈡埛:">
+ <el-select v-model="filters.customerId" placeholder="璇烽�夋嫨瀹㈡埛" clearable style="width: 200px;">
+ <el-option v-for="item in customerList" :key="item.id" :label="item.name" :value="item.id" />
+ </el-select>
+ </el-form-item>
+ <el-form-item label="鏀舵鏂瑰紡:">
+ <el-select v-model="filters.receiptMethod" placeholder="璇烽�夋嫨鏀舵鏂瑰紡" clearable style="width: 150px;">
+ <el-option label="閾惰杞处" value="bank_transfer" />
+ <el-option label="鐜伴噾" value="cash" />
+ <el-option label="鏀エ" value="check" />
+ <el-option label="姹囩エ" value="draft" />
+ <el-option label="鏀粯瀹�" value="alipay" />
+ <el-option label="寰俊" value="wechat" />
+ </el-select>
+ </el-form-item>
+ <el-form-item>
+ <el-button type="primary" @click="getTableData">鎼滅储</el-button>
+ <el-button @click="resetFilters">閲嶇疆</el-button>
+ </el-form-item>
+ </el-form>
+ <div class="table_list">
+ <div class="actions">
+ <div>
+ <el-statistic title="鏈湡鏀舵鍚堣" :value="totalReceiptAmount" precision="2" prefix="楼" />
+ </div>
+ <div>
+ <el-button type="primary" @click="add" icon="Plus">鏂板鏀舵</el-button>
+ <el-button @click="handleOut" icon="Download">瀵煎嚭</el-button>
+ </div>
+ </div>
+ <PIMTable
+ rowKey="id"
+ :column="columns"
+ :tableData="dataList"
+ :page="{
+ current: pagination.currentPage,
+ size: pagination.pageSize,
+ total: pagination.total,
+ }"
+ @pagination="changePage"
+ >
+ <template #amount="{ row }">
+ <span class="text-success">楼{{ formatMoney(row.amount) }}</span>
+ </template>
+ <template #receiptMethod="{ row }">
+ <el-tag>{{ getReceiptMethodLabel(row.receiptMethod) }}</el-tag>
+ </template>
+ <template #status="{ row }">
+ <el-tag :type="row.status === 'confirmed' ? 'success' : 'warning'">{{ row.status === 'confirmed' ? '宸茬‘璁�' : '寰呯‘璁�' }}</el-tag>
+ </template>
+ <template #operation="{ row }">
+ <el-button type="primary" link @click="view(row)">鏌ョ湅</el-button>
+ <el-button type="primary" link @click="edit(row)" v-if="row.status === 'pending'">缂栬緫</el-button>
+ <el-button type="success" link @click="handleConfirm(row)" v-if="row.status === 'pending'">纭</el-button>
+ <el-button type="danger" link @click="handleDelete(row)" v-if="row.status === 'pending'">鍒犻櫎</el-button>
+ </template>
+ </PIMTable>
+ </div>
+
+ <el-dialog :title="dialogTitle" v-model="dialogVisible" width="800px" append-to-body>
+ <el-form :model="form" :rules="rules" ref="formRef" label-width="120px">
+ <el-row :gutter="20">
+ <el-col :span="12">
+ <el-form-item label="鏀舵鍗曞彿" prop="receiptCode">
+ <el-input v-model="form.receiptCode" placeholder="绯荤粺鑷姩鐢熸垚" disabled />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="瀹㈡埛" prop="customerId">
+ <el-select v-model="form.customerId" placeholder="璇烽�夋嫨瀹㈡埛" style="width: 100%;" :disabled="isEdit">
+ <el-option v-for="item in customerList" :key="item.id" :label="item.name" :value="item.id" />
+ </el-select>
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="20">
+ <el-col :span="12">
+ <el-form-item label="鏀舵鏃ユ湡" prop="receiptDate">
+ <el-date-picker v-model="form.receiptDate" type="date" placeholder="閫夋嫨鏃ユ湡" value-format="YYYY-MM-DD" style="width: 100%;" />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="鏀舵閲戦" prop="amount">
+ <el-input-number v-model="form.amount" :min="0" :precision="2" style="width: 100%;" />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="20">
+ <el-col :span="12">
+ <el-form-item label="鏀舵鏂瑰紡" prop="receiptMethod">
+ <el-select v-model="form.receiptMethod" placeholder="璇烽�夋嫨鏀舵鏂瑰紡" style="width: 100%;">
+ <el-option label="閾惰杞处" value="bank_transfer" />
+ <el-option label="鐜伴噾" value="cash" />
+ <el-option label="鏀エ" value="check" />
+ <el-option label="姹囩エ" value="draft" />
+ <el-option label="鏀粯瀹�" value="alipay" />
+ <el-option label="寰俊" value="wechat" />
+ </el-select>
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="閾惰璐﹀彿" prop="bankAccount" v-if="form.receiptMethod === 'bank_transfer'">
+ <el-input v-model="form.bankAccount" placeholder="璇疯緭鍏ラ摱琛岃处鍙�" />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-form-item label="鍏宠仈鍗曟嵁" prop="relatedDocs">
+ <el-select v-model="form.relatedDocs" multiple placeholder="璇烽�夋嫨鍏宠仈鍗曟嵁" style="width: 100%;">
+ <el-option v-for="item in outList" :key="item.outCode" :label="item.outCode" :value="item.outCode" />
+ </el-select>
+ </el-form-item>
+ <el-form-item label="澶囨敞" prop="remark">
+ <el-input v-model="form.remark" type="textarea" :rows="3" placeholder="璇疯緭鍏ュ娉�" />
+ </el-form-item>
+ </el-form>
+ <template #footer>
+ <el-button @click="dialogVisible = false">鍙栨秷</el-button>
+ <el-button type="primary" @click="submitForm">纭畾</el-button>
+ </template>
+ </el-dialog>
+ </div>
+</template>
+
+<script setup>
+import { ref, reactive, onMounted, computed } from "vue";
+import { ElMessage, ElMessageBox } from "element-plus";
+
+defineOptions({
+ name: "鏀舵鍗�",
+});
+
+const filters = reactive({
+ receiptCode: "",
+ customerId: "",
+ receiptMethod: "",
+});
+
+const pagination = reactive({
+ currentPage: 1,
+ pageSize: 10,
+ total: 0,
+});
+
+const columns = [
+ { label: "鏀舵鍗曞彿", prop: "receiptCode", width: "150" },
+ { label: "瀹㈡埛鍚嶇О", prop: "customerName", width: "180" },
+ { label: "鏀舵鏃ユ湡", prop: "receiptDate", width: "120" },
+ { label: "鏀舵閲戦", prop: "amount", slot: "amount" },
+ { label: "鏀舵鏂瑰紡", prop: "receiptMethod", slot: "receiptMethod" },
+ { label: "鐘舵��", prop: "status", slot: "status" },
+ { label: "澶囨敞", prop: "remark", showOverflowTooltip: true },
+ { label: "鎿嶄綔", prop: "operation", slot: "operation", width: "220", fixed: "right" },
+];
+
+const dataList = ref([]);
+const dialogVisible = ref(false);
+const dialogTitle = ref("");
+const formRef = ref(null);
+const isEdit = ref(false);
+const currentId = ref(null);
+
+const customerList = [
+ { id: 1, name: "鍖椾含绉戞妧鏈夐檺鍏徃" },
+ { id: 2, name: "涓婃捣璐告槗鍏徃" },
+ { id: 3, name: "骞垮窞瀹炰笟鏈夐檺鍏徃" },
+ { id: 4, name: "娣卞湷鐢靛瓙鍏徃" },
+];
+
+const outList = [
+ { outCode: "CK2024001", customerId: 1 },
+ { outCode: "CK2024002", customerId: 2 },
+ { outCode: "CK2024003", customerId: 3 },
+];
+
+const form = reactive({
+ receiptCode: "",
+ customerId: "",
+ receiptDate: "",
+ amount: 0,
+ receiptMethod: "bank_transfer",
+ bankAccount: "",
+ relatedDocs: [],
+ remark: "",
+});
+
+const rules = {
+ customerId: [{ required: true, message: "璇烽�夋嫨瀹㈡埛", trigger: "change" }],
+ receiptDate: [{ required: true, message: "璇烽�夋嫨鏀舵鏃ユ湡", trigger: "change" }],
+ amount: [{ required: true, message: "璇疯緭鍏ユ敹娆鹃噾棰�", trigger: "blur" }],
+ receiptMethod: [{ required: true, message: "璇烽�夋嫨鏀舵鏂瑰紡", trigger: "change" }],
+};
+
+const mockData = [
+ { id: 1, receiptCode: "SK2024001", customerId: 1, customerName: "鍖椾含绉戞妧鏈夐檺鍏徃", receiptDate: "2024-01-16", amount: 3000, receiptMethod: "bank_transfer", status: "confirmed", relatedDocs: ["CK2024001"], remark: "" },
+ { id: 2, receiptCode: "SK2024002", customerId: 2, customerName: "涓婃捣璐告槗鍏徃", receiptDate: "2024-01-18", amount: 5000, receiptMethod: "cash", status: "pending", relatedDocs: ["CK2024002"], remark: "" },
+ { id: 3, receiptCode: "SK2024003", customerId: 3, customerName: "骞垮窞瀹炰笟鏈夐檺鍏徃", receiptDate: "2024-01-20", amount: 8000, receiptMethod: "alipay", status: "confirmed", relatedDocs: ["CK2024003"], remark: "" },
+];
+
+const totalReceiptAmount = computed(() => {
+ return dataList.value.reduce((sum, item) => sum + Number(item.amount), 0);
+});
+
+const formatMoney = (value) => {
+ if (value === undefined || value === null) return "0.00";
+ return Number(value).toFixed(2).replace(/\B(?=(\d{3})+(?!\d))/g, ",");
+};
+
+const getReceiptMethodLabel = (method) => {
+ const map = {
+ bank_transfer: "閾惰杞处",
+ cash: "鐜伴噾",
+ check: "鏀エ",
+ draft: "姹囩エ",
+ alipay: "鏀粯瀹�",
+ wechat: "寰俊",
+ };
+ return map[method] || method;
+};
+
+const getTableData = () => {
+ let result = [...mockData];
+ if (filters.receiptCode) {
+ result = result.filter(item => item.receiptCode.includes(filters.receiptCode));
+ }
+ if (filters.customerId) {
+ result = result.filter(item => item.customerId === filters.customerId);
+ }
+ if (filters.receiptMethod) {
+ result = result.filter(item => item.receiptMethod === filters.receiptMethod);
+ }
+ pagination.total = result.length;
+ dataList.value = result.slice((pagination.currentPage - 1) * pagination.pageSize, pagination.currentPage * pagination.pageSize);
+};
+
+const resetFilters = () => {
+ filters.receiptCode = "";
+ filters.customerId = "";
+ filters.receiptMethod = "";
+ pagination.currentPage = 1;
+ getTableData();
+};
+
+const changePage = ({ current, size }) => {
+ pagination.currentPage = current;
+ pagination.pageSize = size;
+ getTableData();
+};
+
+const add = () => {
+ isEdit.value = false;
+ dialogTitle.value = "鏂板鏀舵";
+ Object.assign(form, {
+ receiptCode: "SK" + Date.now().toString().slice(-8),
+ customerId: "",
+ receiptDate: new Date().toISOString().split('T')[0],
+ amount: 0,
+ receiptMethod: "bank_transfer",
+ bankAccount: "",
+ relatedDocs: [],
+ remark: "",
+ });
+ dialogVisible.value = true;
+};
+
+const edit = (row) => {
+ isEdit.value = true;
+ currentId.value = row.id;
+ dialogTitle.value = "缂栬緫鏀舵";
+ Object.assign(form, row);
+ dialogVisible.value = true;
+};
+
+const view = (row) => {
+ ElMessage.info(`鏌ョ湅鏀舵鍗�: ${row.receiptCode}`);
+};
+
+const handleConfirm = (row) => {
+ ElMessageBox.confirm("纭璇ユ敹娆惧崟鍚楋紵", "鎻愮ず", {
+ confirmButtonText: "纭",
+ cancelButtonText: "鍙栨秷",
+ type: "info",
+ }).then(() => {
+ const index = mockData.findIndex(item => item.id === row.id);
+ if (index !== -1) {
+ mockData[index].status = "confirmed";
+ }
+ ElMessage.success("纭鎴愬姛");
+ getTableData();
+ });
+};
+
+const handleDelete = (row) => {
+ ElMessageBox.confirm("纭鍒犻櫎璇ユ敹娆惧崟鍚楋紵", "鎻愮ず", {
+ confirmButtonText: "纭畾",
+ cancelButtonText: "鍙栨秷",
+ type: "warning",
+ }).then(() => {
+ const index = mockData.findIndex(item => item.id === row.id);
+ if (index !== -1) {
+ mockData.splice(index, 1);
+ }
+ ElMessage.success("鍒犻櫎鎴愬姛");
+ getTableData();
+ });
+};
+
+const handleOut = () => {
+ ElMessage.success("瀵煎嚭鎴愬姛");
+};
+
+const submitForm = () => {
+ formRef.value.validate((valid) => {
+ if (valid) {
+ const customer = customerList.find(item => item.id === form.customerId);
+ if (isEdit.value) {
+ const index = mockData.findIndex(item => item.id === currentId.value);
+ if (index !== -1) {
+ mockData[index] = { ...mockData[index], ...form, customerName: customer?.name };
+ }
+ ElMessage.success("缂栬緫鎴愬姛");
+ } else {
+ const newId = mockData.length > 0 ? Math.max(...mockData.map(item => item.id)) + 1 : 1;
+ mockData.push({ id: newId, ...form, customerName: customer?.name, status: "pending" });
+ ElMessage.success("鏂板鎴愬姛");
+ }
+ dialogVisible.value = false;
+ getTableData();
+ }
+ });
+};
+
+onMounted(() => {
+ getTableData();
+});
+</script>
+
+<style lang="scss" scoped>
+.actions {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 15px;
+}
+
+.text-success {
+ color: #67c23a;
+ font-weight: bold;
+}
+</style>
diff --git a/src/views/financialManagement/receivable/reconciliation.vue b/src/views/financialManagement/receivable/reconciliation.vue
new file mode 100644
index 0000000..fc6d7c1
--- /dev/null
+++ b/src/views/financialManagement/receivable/reconciliation.vue
@@ -0,0 +1,258 @@
+<template>
+ <div class="app-container">
+ <el-form :model="filters" :inline="true">
+ <el-form-item label="瀹㈡埛:">
+ <el-select v-model="filters.customerId" placeholder="璇烽�夋嫨瀹㈡埛" clearable style="width: 200px;">
+ <el-option v-for="item in customerList" :key="item.id" :label="item.name" :value="item.id" />
+ </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-form-item>
+ </el-form>
+ <div class="table_list">
+ <div class="actions">
+ <div>
+ <el-button type="primary" @click="generateStatement" icon="Document">鐢熸垚瀵硅处鍗�</el-button>
+ </div>
+ <div>
+ <el-button @click="handleOut" icon="Download">瀵煎嚭瀵硅处鍗�</el-button>
+ </div>
+ </div>
+ <PIMTable
+ rowKey="id"
+ :column="columns"
+ :tableData="dataList"
+ :page="{
+ current: pagination.currentPage,
+ size: pagination.pageSize,
+ total: pagination.total,
+ }"
+ @pagination="changePage"
+ >
+ <template #beginBalance="{ row }">
+ <span :class="row.beginBalance >= 0 ? 'text-success' : 'text-danger'">楼{{ formatMoney(row.beginBalance) }}</span>
+ </template>
+ <template #currentReceivable="{ row }">
+ <span class="text-primary">楼{{ formatMoney(row.currentReceivable) }}</span>
+ </template>
+ <template #currentReceipt="{ row }">
+ <span class="text-success">楼{{ formatMoney(row.currentReceipt) }}</span>
+ </template>
+ <template #endBalance="{ row }">
+ <span :class="row.endBalance >= 0 ? 'text-success' : 'text-danger'">楼{{ formatMoney(row.endBalance) }}</span>
+ </template>
+ <template #operation="{ row }">
+ <el-button type="primary" link @click="viewDetail(row)">鏌ョ湅鏄庣粏</el-button>
+ <el-button type="primary" link @click="printStatement(row)">鎵撳嵃</el-button>
+ </template>
+ </PIMTable>
+ </div>
+
+ <el-dialog title="瀵硅处鏄庣粏" v-model="detailDialogVisible" width="900px" append-to-body>
+ <div class="statement-header">
+ <h3>{{ currentCustomer }} 搴旀敹瀵硅处鍗�</h3>
+ <p>瀵硅处鏈熼棿: {{ currentPeriod }}</p>
+ </div>
+ <el-table :data="detailData" border style="width: 100%">
+ <el-table-column prop="date" label="鏃ユ湡" width="120" />
+ <el-table-column prop="type" label="绫诲瀷" width="100">
+ <template #default="{ row }">
+ <el-tag :type="row.type === '鍑哄簱' ? 'success' : row.type === '閫�璐�' ? 'danger' : 'primary'">{{ row.type }}</el-tag>
+ </template>
+ </el-table-column>
+ <el-table-column prop="code" label="鍗曟嵁缂栧彿" width="150" />
+ <el-table-column prop="debit" label="鍊熸柟(搴旀敹)" width="120">
+ <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="120">
+ <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 prop="balance" label="浣欓" width="120">
+ <template #default="{ row }">
+ <span :class="row.balance >= 0 ? 'text-success' : 'text-danger'">楼{{ formatMoney(row.balance) }}</span>
+ </template>
+ </el-table-column>
+ <el-table-column prop="remark" label="澶囨敞" show-overflow-tooltip />
+ </el-table>
+ <template #footer>
+ <el-button @click="detailDialogVisible = false">鍏抽棴</el-button>
+ <el-button type="primary" @click="printDetail">鎵撳嵃</el-button>
+ </template>
+ </el-dialog>
+ </div>
+</template>
+
+<script setup>
+import { ref, reactive, onMounted } from "vue";
+import { ElMessage } from "element-plus";
+
+defineOptions({
+ name: "搴旀敹瀵硅处",
+});
+
+const filters = reactive({
+ customerId: "",
+ startMonth: "",
+ endMonth: "",
+});
+
+const pagination = reactive({
+ currentPage: 1,
+ pageSize: 10,
+ total: 0,
+});
+
+const columns = [
+ { label: "瀵硅处鍗曞彿", prop: "statementCode", width: "150" },
+ { label: "瀹㈡埛鍚嶇О", prop: "customerName", width: "180" },
+ { label: "瀵硅处鏈熼棿", prop: "period", width: "150" },
+ { label: "鏈熷垵浣欓", prop: "beginBalance", slot: "beginBalance" },
+ { label: "鏈湡搴旀敹", prop: "currentReceivable", slot: "currentReceivable" },
+ { label: "鏈湡鏀舵", prop: "currentReceipt", slot: "currentReceipt" },
+ { label: "鏈熸湯浣欓", prop: "endBalance", slot: "endBalance" },
+ { label: "鎿嶄綔", prop: "operation", slot: "operation", width: "150", fixed: "right" },
+];
+
+const dataList = ref([]);
+const detailDialogVisible = ref(false);
+const currentCustomer = ref("");
+const currentPeriod = ref("");
+const detailData = ref([]);
+
+const customerList = [
+ { id: 1, name: "鍖椾含绉戞妧鏈夐檺鍏徃" },
+ { id: 2, name: "涓婃捣璐告槗鍏徃" },
+ { id: 3, name: "骞垮窞瀹炰笟鏈夐檺鍏徃" },
+ { id: 4, name: "娣卞湷鐢靛瓙鍏徃" },
+];
+
+const mockData = [
+ { id: 1, statementCode: "DZ202401001", customerId: 1, customerName: "鍖椾含绉戞妧鏈夐檺鍏徃", period: "2024-01", beginBalance: 10000, currentReceivable: 15000, currentReceipt: 8000, endBalance: 17000 },
+ { id: 2, statementCode: "DZ202401002", customerId: 2, customerName: "涓婃捣璐告槗鍏徃", period: "2024-01", beginBalance: 5000, currentReceivable: 12000, currentReceipt: 10000, endBalance: 7000 },
+ { id: 3, statementCode: "DZ202402001", customerId: 1, customerName: "鍖椾含绉戞妧鏈夐檺鍏徃", period: "2024-02", beginBalance: 17000, currentReceivable: 20000, currentReceipt: 15000, endBalance: 22000 },
+];
+
+const formatMoney = (value) => {
+ if (value === undefined || value === null) return "0.00";
+ return Number(value).toFixed(2).replace(/\B(?=(\d{3})+(?!\d))/g, ",");
+};
+
+const getTableData = () => {
+ let result = [...mockData];
+ if (filters.customerId) {
+ result = result.filter(item => item.customerId === filters.customerId);
+ }
+ if (filters.startMonth && filters.endMonth) {
+ result = result.filter(item => item.period >= filters.startMonth && item.period <= filters.endMonth);
+ }
+ pagination.total = result.length;
+ dataList.value = result.slice((pagination.currentPage - 1) * pagination.pageSize, pagination.currentPage * pagination.pageSize);
+};
+
+const resetFilters = () => {
+ filters.customerId = "";
+ filters.startMonth = "";
+ filters.endMonth = "";
+ pagination.currentPage = 1;
+ getTableData();
+};
+
+const changePage = ({ current, size }) => {
+ pagination.currentPage = current;
+ pagination.pageSize = size;
+ getTableData();
+};
+
+const generateStatement = () => {
+ ElMessage.success("瀵硅处鍗曠敓鎴愭垚鍔�");
+ const newId = mockData.length > 0 ? Math.max(...mockData.map(item => item.id)) + 1 : 1;
+ const customer = customerList[Math.floor(Math.random() * customerList.length)];
+ mockData.unshift({
+ id: newId,
+ statementCode: "DZ" + Date.now(),
+ customerId: customer.id,
+ customerName: customer.name,
+ period: "2024-03",
+ beginBalance: Math.floor(Math.random() * 10000),
+ currentReceivable: Math.floor(Math.random() * 20000),
+ currentReceipt: Math.floor(Math.random() * 15000),
+ endBalance: Math.floor(Math.random() * 20000),
+ });
+ getTableData();
+};
+
+const viewDetail = (row) => {
+ currentCustomer.value = row.customerName;
+ currentPeriod.value = row.period;
+ detailData.value = [
+ { date: row.period + "-01", type: "鏈熷垵", code: "-", debit: 0, credit: 0, balance: row.beginBalance, remark: "鏈熷垵浣欓" },
+ { date: row.period + "-05", type: "鍑哄簱", code: "CK2024001", debit: 5000, credit: 0, balance: row.beginBalance + 5000, remark: "" },
+ { date: row.period + "-10", type: "鏀舵", code: "SK2024001", debit: 0, credit: 3000, balance: row.beginBalance + 2000, remark: "" },
+ { date: row.period + "-15", type: "鍑哄簱", code: "CK2024002", debit: 8000, credit: 0, balance: row.beginBalance + 10000, remark: "" },
+ { date: row.period + "-20", type: "閫�璐�", code: "TH2024001", debit: 0, credit: 2000, balance: row.beginBalance + 8000, remark: "" },
+ { date: row.period + "-25", type: "鏀舵", code: "SK2024002", credit: row.currentReceipt - 3000, balance: row.endBalance, remark: "" },
+ ];
+ detailDialogVisible.value = true;
+};
+
+const printStatement = (row) => {
+ ElMessage.info(`鎵撳嵃瀵硅处鍗�: ${row.statementCode}`);
+};
+
+const printDetail = () => {
+ ElMessage.info("鎵撳嵃鏄庣粏");
+};
+
+const handleOut = () => {
+ ElMessage.success("瀵煎嚭鎴愬姛");
+};
+
+onMounted(() => {
+ getTableData();
+});
+</script>
+
+<style lang="scss" scoped>
+.actions {
+ display: flex;
+ justify-content: space-between;
+ margin-bottom: 15px;
+}
+
+.text-success {
+ color: #67c23a;
+}
+
+.text-danger {
+ color: #f56c6c;
+}
+
+.text-primary {
+ color: #409eff;
+}
+
+.statement-header {
+ text-align: center;
+ margin-bottom: 20px;
+ h3 {
+ margin: 0 0 10px 0;
+ }
+ p {
+ color: #909399;
+ margin: 0;
+ }
+}
+</style>
diff --git a/src/views/financialManagement/receivable/salesOut.vue b/src/views/financialManagement/receivable/salesOut.vue
new file mode 100644
index 0000000..15e5106
--- /dev/null
+++ b/src/views/financialManagement/receivable/salesOut.vue
@@ -0,0 +1,271 @@
+<template>
+ <div class="app-container">
+ <el-form :model="filters" :inline="true">
+ <el-form-item label="鍑哄簱鍗曞彿:">
+ <el-input v-model="filters.outCode" placeholder="璇疯緭鍏ュ嚭搴撳崟鍙�" clearable style="width: 200px;" />
+ </el-form-item>
+ <el-form-item label="瀹㈡埛:">
+ <el-select v-model="filters.customerId" placeholder="璇烽�夋嫨瀹㈡埛" clearable style="width: 200px;">
+ <el-option v-for="item in customerList" :key="item.id" :label="item.name" :value="item.id" />
+ </el-select>
+ </el-form-item>
+ <el-form-item label="鍑哄簱鏃ユ湡:">
+ <el-date-picker v-model="filters.dateRange" value-format="YYYY-MM-DD" format="YYYY-MM-DD" type="daterange" range-separator="鑷�" start-placeholder="寮�濮嬫棩鏈�" end-placeholder="缁撴潫鏃ユ湡" clearable />
+ </el-form-item>
+ <el-form-item>
+ <el-button type="primary" @click="getTableData">鎼滅储</el-button>
+ <el-button @click="resetFilters">閲嶇疆</el-button>
+ </el-form-item>
+ </el-form>
+ <div class="table_list">
+ <div class="actions">
+ <div></div>
+ <div>
+ <el-button type="primary" @click="add" icon="Plus">鏂板鍑哄簱</el-button>
+ <el-button @click="handleOut" icon="Download">瀵煎嚭</el-button>
+ </div>
+ </div>
+ <PIMTable
+ rowKey="id"
+ :column="columns"
+ :tableData="dataList"
+ :page="{
+ current: pagination.currentPage,
+ size: pagination.pageSize,
+ total: pagination.total,
+ }"
+ @pagination="changePage"
+ >
+ <template #status="{ row }">
+ <el-tag :type="getStatusType(row.status)">{{ getStatusLabel(row.status) }}</el-tag>
+ </template>
+ <template #operation="{ row }">
+ <el-button type="primary" link @click="view(row)">鏌ョ湅</el-button>
+ <el-button type="primary" link @click="edit(row)" v-if="row.status === 'pending'">缂栬緫</el-button>
+ <el-button type="danger" link @click="handleDelete(row)" v-if="row.status === 'pending'">鍒犻櫎</el-button>
+ </template>
+ </PIMTable>
+ </div>
+
+ <el-dialog :title="dialogTitle" v-model="dialogVisible" width="800px" append-to-body>
+ <el-form :model="form" :rules="rules" ref="formRef" label-width="100px">
+ <el-row :gutter="20">
+ <el-col :span="12">
+ <el-form-item label="鍑哄簱鍗曞彿" prop="outCode">
+ <el-input v-model="form.outCode" placeholder="璇疯緭鍏ュ嚭搴撳崟鍙�" :disabled="isEdit" />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="瀹㈡埛" prop="customerId">
+ <el-select v-model="form.customerId" placeholder="璇烽�夋嫨瀹㈡埛" style="width: 100%;" :disabled="isEdit">
+ <el-option v-for="item in customerList" :key="item.id" :label="item.name" :value="item.id" />
+ </el-select>
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="20">
+ <el-col :span="12">
+ <el-form-item label="鍑哄簱鏃ユ湡" prop="outDate">
+ <el-date-picker v-model="form.outDate" type="date" placeholder="閫夋嫨鏃ユ湡" value-format="YYYY-MM-DD" style="width: 100%;" />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="閲戦" prop="amount">
+ <el-input-number v-model="form.amount" :min="0" :precision="2" style="width: 100%;" />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-form-item label="澶囨敞" prop="remark">
+ <el-input v-model="form.remark" type="textarea" :rows="3" placeholder="璇疯緭鍏ュ娉�" />
+ </el-form-item>
+ </el-form>
+ <template #footer>
+ <el-button @click="dialogVisible = false">鍙栨秷</el-button>
+ <el-button type="primary" @click="submitForm">纭畾</el-button>
+ </template>
+ </el-dialog>
+ </div>
+</template>
+
+<script setup>
+import { ref, reactive, onMounted } from "vue";
+import { ElMessage, ElMessageBox } from "element-plus";
+
+defineOptions({
+ name: "閿�鍞嚭搴�",
+});
+
+const filters = reactive({
+ outCode: "",
+ customerId: "",
+ dateRange: [],
+});
+
+const pagination = reactive({
+ currentPage: 1,
+ pageSize: 10,
+ total: 0,
+});
+
+const columns = [
+ { label: "鍑哄簱鍗曞彿", prop: "outCode", width: "150" },
+ { label: "瀹㈡埛鍚嶇О", prop: "customerName", width: "180" },
+ { label: "鍑哄簱鏃ユ湡", prop: "outDate", width: "120" },
+ { label: "閲戦", prop: "amount", width: "120" },
+ { label: "鐘舵��", prop: "status", slot: "status" },
+ { label: "澶囨敞", prop: "remark", showOverflowTooltip: true },
+ { label: "鎿嶄綔", prop: "operation", slot: "operation", width: "200", fixed: "right" },
+];
+
+const dataList = ref([]);
+const dialogVisible = ref(false);
+const dialogTitle = ref("");
+const formRef = ref(null);
+const isEdit = ref(false);
+const currentId = ref(null);
+
+const customerList = [
+ { id: 1, name: "鍖椾含绉戞妧鏈夐檺鍏徃" },
+ { id: 2, name: "涓婃捣璐告槗鍏徃" },
+ { id: 3, name: "骞垮窞瀹炰笟鏈夐檺鍏徃" },
+ { id: 4, name: "娣卞湷鐢靛瓙鍏徃" },
+];
+
+const form = reactive({
+ outCode: "",
+ customerId: "",
+ outDate: "",
+ amount: 0,
+ remark: "",
+});
+
+const rules = {
+ outCode: [{ required: true, message: "璇疯緭鍏ュ嚭搴撳崟鍙�", trigger: "blur" }],
+ customerId: [{ required: true, message: "璇烽�夋嫨瀹㈡埛", trigger: "change" }],
+ outDate: [{ required: true, message: "璇烽�夋嫨鍑哄簱鏃ユ湡", trigger: "change" }],
+ amount: [{ required: true, message: "璇疯緭鍏ラ噾棰�", trigger: "blur" }],
+};
+
+const mockData = [
+ { id: 1, outCode: "CK2024001", customerId: 1, customerName: "鍖椾含绉戞妧鏈夐檺鍏徃", outDate: "2024-01-15", amount: 5000, status: "approved", remark: "" },
+ { id: 2, outCode: "CK2024002", customerId: 2, customerName: "涓婃捣璐告槗鍏徃", outDate: "2024-01-16", amount: 8000, status: "pending", remark: "" },
+ { id: 3, outCode: "CK2024003", customerId: 3, customerName: "骞垮窞瀹炰笟鏈夐檺鍏徃", outDate: "2024-01-18", amount: 12000, status: "approved", remark: "" },
+ { id: 4, outCode: "CK2024004", customerId: 4, customerName: "娣卞湷鐢靛瓙鍏徃", outDate: "2024-01-20", amount: 3500, status: "pending", remark: "" },
+];
+
+const getStatusLabel = (status) => {
+ const map = { pending: "寰呭鏍�", approved: "宸插鏍�", rejected: "宸查┏鍥�" };
+ return map[status] || status;
+};
+
+const getStatusType = (status) => {
+ const map = { pending: "warning", approved: "success", rejected: "danger" };
+ return map[status] || "";
+};
+
+const getTableData = () => {
+ let result = [...mockData];
+ if (filters.outCode) {
+ result = result.filter(item => item.outCode.includes(filters.outCode));
+ }
+ if (filters.customerId) {
+ result = result.filter(item => item.customerId === filters.customerId);
+ }
+ if (filters.dateRange && filters.dateRange.length === 2) {
+ result = result.filter(item => item.outDate >= filters.dateRange[0] && item.outDate <= filters.dateRange[1]);
+ }
+ pagination.total = result.length;
+ dataList.value = result.slice((pagination.currentPage - 1) * pagination.pageSize, pagination.currentPage * pagination.pageSize);
+};
+
+const resetFilters = () => {
+ filters.outCode = "";
+ filters.customerId = "";
+ filters.dateRange = [];
+ pagination.currentPage = 1;
+ getTableData();
+};
+
+const changePage = ({ current, size }) => {
+ pagination.currentPage = current;
+ pagination.pageSize = size;
+ getTableData();
+};
+
+const add = () => {
+ isEdit.value = false;
+ dialogTitle.value = "鏂板鍑哄簱";
+ Object.assign(form, {
+ outCode: "CK" + Date.now(),
+ customerId: "",
+ outDate: "",
+ amount: 0,
+ remark: "",
+ });
+ dialogVisible.value = true;
+};
+
+const edit = (row) => {
+ isEdit.value = true;
+ currentId.value = row.id;
+ dialogTitle.value = "缂栬緫鍑哄簱";
+ Object.assign(form, row);
+ dialogVisible.value = true;
+};
+
+const view = (row) => {
+ ElMessage.info(`鏌ョ湅鍑哄簱鍗�: ${row.outCode}`);
+};
+
+const submitForm = () => {
+ formRef.value.validate((valid) => {
+ if (valid) {
+ const customer = customerList.find(item => item.id === form.customerId);
+ if (isEdit.value) {
+ const index = mockData.findIndex(item => item.id === currentId.value);
+ if (index !== -1) {
+ mockData[index] = { ...mockData[index], ...form, customerName: customer?.name };
+ }
+ ElMessage.success("缂栬緫鎴愬姛");
+ } else {
+ const newId = mockData.length > 0 ? Math.max(...mockData.map(item => item.id)) + 1 : 1;
+ mockData.push({ id: newId, ...form, customerName: customer?.name, status: "pending" });
+ ElMessage.success("鏂板鎴愬姛");
+ }
+ dialogVisible.value = false;
+ getTableData();
+ }
+ });
+};
+
+const handleDelete = (row) => {
+ ElMessageBox.confirm("纭鍒犻櫎璇ュ嚭搴撳崟鍚楋紵", "鎻愮ず", {
+ confirmButtonText: "纭畾",
+ cancelButtonText: "鍙栨秷",
+ type: "warning",
+ }).then(() => {
+ const index = mockData.findIndex(item => item.id === row.id);
+ if (index !== -1) {
+ mockData.splice(index, 1);
+ }
+ ElMessage.success("鍒犻櫎鎴愬姛");
+ getTableData();
+ });
+};
+
+const handleOut = () => {
+ ElMessage.success("瀵煎嚭鎴愬姛");
+};
+
+onMounted(() => {
+ getTableData();
+});
+</script>
+
+<style lang="scss" scoped>
+.actions {
+ display: flex;
+ justify-content: space-between;
+ margin-bottom: 15px;
+}
+</style>
diff --git a/src/views/financialManagement/receivable/salesReturn.vue b/src/views/financialManagement/receivable/salesReturn.vue
new file mode 100644
index 0000000..7077b97
--- /dev/null
+++ b/src/views/financialManagement/receivable/salesReturn.vue
@@ -0,0 +1,305 @@
+<template>
+ <div class="app-container">
+ <el-form :model="filters" :inline="true">
+ <el-form-item label="閫�璐у崟鍙�:">
+ <el-input v-model="filters.returnCode" placeholder="璇疯緭鍏ラ��璐у崟鍙�" clearable style="width: 200px;" />
+ </el-form-item>
+ <el-form-item label="瀹㈡埛:">
+ <el-select v-model="filters.customerId" placeholder="璇烽�夋嫨瀹㈡埛" clearable style="width: 200px;">
+ <el-option v-for="item in customerList" :key="item.id" :label="item.name" :value="item.id" />
+ </el-select>
+ </el-form-item>
+ <el-form-item label="閫�璐ф棩鏈�:">
+ <el-date-picker v-model="filters.dateRange" value-format="YYYY-MM-DD" format="YYYY-MM-DD" type="daterange" range-separator="鑷�" start-placeholder="寮�濮嬫棩鏈�" end-placeholder="缁撴潫鏃ユ湡" clearable />
+ </el-form-item>
+ <el-form-item>
+ <el-button type="primary" @click="getTableData">鎼滅储</el-button>
+ <el-button @click="resetFilters">閲嶇疆</el-button>
+ </el-form-item>
+ </el-form>
+ <div class="table_list">
+ <div class="actions">
+ <div></div>
+ <div>
+ <el-button type="primary" @click="add" icon="Plus">鏂板閫�璐�</el-button>
+ <el-button @click="handleOut" icon="Download">瀵煎嚭</el-button>
+ </div>
+ </div>
+ <PIMTable
+ rowKey="id"
+ :column="columns"
+ :tableData="dataList"
+ :page="{
+ current: pagination.currentPage,
+ size: pagination.pageSize,
+ total: pagination.total,
+ }"
+ @pagination="changePage"
+ >
+ <template #status="{ row }">
+ <el-tag :type="getStatusType(row.status)">{{ getStatusLabel(row.status) }}</el-tag>
+ </template>
+ <template #operation="{ row }">
+ <el-button type="primary" link @click="view(row)">鏌ョ湅</el-button>
+ <el-button type="primary" link @click="edit(row)" v-if="row.status === 'pending'">缂栬緫</el-button>
+ <el-button type="success" link @click="handleAudit(row)" v-if="row.status === 'pending'">瀹℃牳</el-button>
+ </template>
+ </PIMTable>
+ </div>
+
+ <el-dialog :title="dialogTitle" v-model="dialogVisible" width="800px" append-to-body>
+ <el-form :model="form" :rules="rules" ref="formRef" label-width="120px">
+ <el-row :gutter="20">
+ <el-col :span="12">
+ <el-form-item label="閫�璐у崟鍙�" prop="returnCode">
+ <el-input v-model="form.returnCode" placeholder="璇疯緭鍏ラ��璐у崟鍙�" :disabled="isEdit" />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="鍏宠仈鍑哄簱鍗�" prop="outCode">
+ <el-select v-model="form.outCode" placeholder="璇烽�夋嫨鍑哄簱鍗�" style="width: 100%;" :disabled="isEdit">
+ <el-option v-for="item in outList" :key="item.outCode" :label="item.outCode" :value="item.outCode" />
+ </el-select>
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="20">
+ <el-col :span="12">
+ <el-form-item label="瀹㈡埛" prop="customerId">
+ <el-select v-model="form.customerId" placeholder="璇烽�夋嫨瀹㈡埛" style="width: 100%;" :disabled="isEdit">
+ <el-option v-for="item in customerList" :key="item.id" :label="item.name" :value="item.id" />
+ </el-select>
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="閫�璐ф棩鏈�" prop="returnDate">
+ <el-date-picker v-model="form.returnDate" type="date" placeholder="閫夋嫨鏃ユ湡" value-format="YYYY-MM-DD" style="width: 100%;" />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="20">
+ <el-col :span="12">
+ <el-form-item label="閫�璐ч噾棰�" prop="amount">
+ <el-input-number v-model="form.amount" :min="0" :precision="2" style="width: 100%;" />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="閫�璐у師鍥�" prop="reason">
+ <el-input v-model="form.reason" placeholder="璇疯緭鍏ラ��璐у師鍥�" />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-form-item label="澶囨敞" prop="remark">
+ <el-input v-model="form.remark" type="textarea" :rows="3" placeholder="璇疯緭鍏ュ娉�" />
+ </el-form-item>
+ </el-form>
+ <template #footer>
+ <el-button @click="dialogVisible = false">鍙栨秷</el-button>
+ <el-button type="primary" @click="submitForm">纭畾</el-button>
+ </template>
+ </el-dialog>
+ </div>
+</template>
+
+<script setup>
+import { ref, reactive, onMounted } from "vue";
+import { ElMessage, ElMessageBox } from "element-plus";
+
+defineOptions({
+ name: "閿�鍞��璐�",
+});
+
+const filters = reactive({
+ returnCode: "",
+ customerId: "",
+ dateRange: [],
+});
+
+const pagination = reactive({
+ currentPage: 1,
+ pageSize: 10,
+ total: 0,
+});
+
+const columns = [
+ { label: "閫�璐у崟鍙�", prop: "returnCode", width: "150" },
+ { label: "瀹㈡埛鍚嶇О", prop: "customerName", width: "180" },
+ { label: "鍏宠仈鍑哄簱鍗�", prop: "outCode", width: "150" },
+ { label: "閫�璐ф棩鏈�", prop: "returnDate", width: "120" },
+ { label: "閫�璐ч噾棰�", prop: "amount", width: "120" },
+ { label: "閫�璐у師鍥�", prop: "reason", width: "150", showOverflowTooltip: true },
+ { label: "鐘舵��", prop: "status", slot: "status" },
+ { label: "鎿嶄綔", prop: "operation", slot: "operation", width: "200", fixed: "right" },
+];
+
+const dataList = ref([]);
+const dialogVisible = ref(false);
+const dialogTitle = ref("");
+const formRef = ref(null);
+const isEdit = ref(false);
+const currentId = ref(null);
+
+const customerList = [
+ { id: 1, name: "鍖椾含绉戞妧鏈夐檺鍏徃" },
+ { id: 2, name: "涓婃捣璐告槗鍏徃" },
+ { id: 3, name: "骞垮窞瀹炰笟鏈夐檺鍏徃" },
+ { id: 4, name: "娣卞湷鐢靛瓙鍏徃" },
+];
+
+const outList = [
+ { outCode: "CK2024001", customerId: 1 },
+ { outCode: "CK2024002", customerId: 2 },
+ { outCode: "CK2024003", customerId: 3 },
+];
+
+const form = reactive({
+ returnCode: "",
+ outCode: "",
+ customerId: "",
+ returnDate: "",
+ amount: 0,
+ reason: "",
+ remark: "",
+});
+
+const rules = {
+ returnCode: [{ required: true, message: "璇疯緭鍏ラ��璐у崟鍙�", trigger: "blur" }],
+ outCode: [{ required: true, message: "璇烽�夋嫨鍏宠仈鍑哄簱鍗�", trigger: "change" }],
+ customerId: [{ required: true, message: "璇烽�夋嫨瀹㈡埛", trigger: "change" }],
+ returnDate: [{ required: true, message: "璇烽�夋嫨閫�璐ф棩鏈�", trigger: "change" }],
+ amount: [{ required: true, message: "璇疯緭鍏ラ��璐ч噾棰�", trigger: "blur" }],
+};
+
+const mockData = [
+ { id: 1, returnCode: "TH2024001", outCode: "CK2024001", customerId: 1, customerName: "鍖椾含绉戞妧鏈夐檺鍏徃", returnDate: "2024-01-20", amount: 1000, reason: "璐ㄩ噺闂", status: "approved", remark: "" },
+ { id: 2, returnCode: "TH2024002", outCode: "CK2024002", customerId: 2, customerName: "涓婃捣璐告槗鍏徃", returnDate: "2024-01-22", amount: 500, reason: "瑙勬牸涓嶇", status: "pending", remark: "" },
+];
+
+const getStatusLabel = (status) => {
+ const map = { pending: "寰呭鏍�", approved: "宸插鏍�", rejected: "宸查┏鍥�" };
+ return map[status] || status;
+};
+
+const getStatusType = (status) => {
+ const map = { pending: "warning", approved: "success", rejected: "danger" };
+ return map[status] || "";
+};
+
+const getTableData = () => {
+ let result = [...mockData];
+ if (filters.returnCode) {
+ result = result.filter(item => item.returnCode.includes(filters.returnCode));
+ }
+ if (filters.customerId) {
+ result = result.filter(item => item.customerId === filters.customerId);
+ }
+ if (filters.dateRange && filters.dateRange.length === 2) {
+ result = result.filter(item => item.returnDate >= filters.dateRange[0] && item.returnDate <= filters.dateRange[1]);
+ }
+ pagination.total = result.length;
+ dataList.value = result.slice((pagination.currentPage - 1) * pagination.pageSize, pagination.currentPage * pagination.pageSize);
+};
+
+const resetFilters = () => {
+ filters.returnCode = "";
+ filters.customerId = "";
+ filters.dateRange = [];
+ pagination.currentPage = 1;
+ getTableData();
+};
+
+const changePage = ({ current, size }) => {
+ pagination.currentPage = current;
+ pagination.pageSize = size;
+ getTableData();
+};
+
+const add = () => {
+ isEdit.value = false;
+ dialogTitle.value = "鏂板閫�璐�";
+ Object.assign(form, {
+ returnCode: "TH" + Date.now(),
+ outCode: "",
+ customerId: "",
+ returnDate: "",
+ amount: 0,
+ reason: "",
+ remark: "",
+ });
+ dialogVisible.value = true;
+};
+
+const edit = (row) => {
+ isEdit.value = true;
+ currentId.value = row.id;
+ dialogTitle.value = "缂栬緫閫�璐�";
+ Object.assign(form, row);
+ dialogVisible.value = true;
+};
+
+const view = (row) => {
+ ElMessage.info(`鏌ョ湅閫�璐у崟: ${row.returnCode}`);
+};
+
+const handleAudit = (row) => {
+ ElMessageBox.confirm("纭瀹℃牳閫氳繃璇ラ��璐у崟鍚楋紵", "鎻愮ず", {
+ confirmButtonText: "閫氳繃",
+ cancelButtonText: "椹冲洖",
+ distinguishCancelAndClose: true,
+ type: "warning",
+ }).then(() => {
+ const index = mockData.findIndex(item => item.id === row.id);
+ if (index !== -1) {
+ mockData[index].status = "approved";
+ }
+ ElMessage.success("瀹℃牳閫氳繃");
+ getTableData();
+ }).catch((action) => {
+ if (action === "cancel") {
+ const index = mockData.findIndex(item => item.id === row.id);
+ if (index !== -1) {
+ mockData[index].status = "rejected";
+ }
+ ElMessage.warning("宸查┏鍥�");
+ getTableData();
+ }
+ });
+};
+
+const submitForm = () => {
+ formRef.value.validate((valid) => {
+ if (valid) {
+ const customer = customerList.find(item => item.id === form.customerId);
+ if (isEdit.value) {
+ const index = mockData.findIndex(item => item.id === currentId.value);
+ if (index !== -1) {
+ mockData[index] = { ...mockData[index], ...form, customerName: customer?.name };
+ }
+ ElMessage.success("缂栬緫鎴愬姛");
+ } else {
+ const newId = mockData.length > 0 ? Math.max(...mockData.map(item => item.id)) + 1 : 1;
+ mockData.push({ id: newId, ...form, customerName: customer?.name, status: "pending" });
+ ElMessage.success("鏂板鎴愬姛");
+ }
+ dialogVisible.value = false;
+ getTableData();
+ }
+ });
+};
+
+const handleOut = () => {
+ ElMessage.success("瀵煎嚭鎴愬姛");
+};
+
+onMounted(() => {
+ getTableData();
+});
+</script>
+
+<style lang="scss" scoped>
+.actions {
+ display: flex;
+ justify-content: space-between;
+ margin-bottom: 15px;
+}
+</style>
diff --git a/src/views/financialManagement/voucher/detailLedger.vue b/src/views/financialManagement/voucher/detailLedger.vue
new file mode 100644
index 0000000..7f85790
--- /dev/null
+++ b/src/views/financialManagement/voucher/detailLedger.vue
@@ -0,0 +1,289 @@
+<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' }" 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="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>
+ </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 label="鍊熸柟" width="150">
+ <template #default="{ row }">
+ <span v-if="row.debit > 0" class="text-danger">楼{{ formatMoney(row.debit) }}</span>
+ <span v-else>-</span>
+ </template>
+ </el-table-column>
+ <el-table-column label="璐锋柟" width="150">
+ <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 { ElMessage } from "element-plus";
+
+defineOptions({
+ name: "绉戠洰鏄庣粏璐�",
+});
+
+const filters = reactive({
+ subject: [],
+ auxiliary: "",
+ auxiliaryItem: "",
+ startMonth: "2024-01",
+ endMonth: "2024-03",
+});
+
+const dataList = ref([]);
+
+const subjectOptions = [
+ {
+ code: "1122",
+ name: "搴旀敹璐︽",
+ children: [
+ { code: "112201", name: "鍖椾含绉戞妧鏈夐檺鍏徃" },
+ { code: "112202", name: "涓婃捣璐告槗鍏徃" },
+ { code: "112203", name: "骞垮窞瀹炰笟鏈夐檺鍏徃" },
+ ],
+ },
+ {
+ code: "2202",
+ name: "搴斾粯璐︽",
+ children: [
+ { code: "220201", name: "鍖椾含鍘熸潗鏂欎緵搴斿晢" },
+ { code: "220202", name: "涓婃捣鐢靛瓙鍏冨櫒浠跺叕鍙�" },
+ { code: "220203", name: "骞垮窞鍖呰鏉愭枡鍘�" },
+ ],
+ },
+ {
+ code: "6602",
+ name: "绠$悊璐圭敤",
+ children: [
+ { code: "660201", name: "鍔炲叕璐�" },
+ { code: "660202", name: "宸梾璐�" },
+ { code: "660203", name: "涓氬姟鎷涘緟璐�" },
+ ],
+ },
+];
+
+const 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(() => {
+ if (!filters.subject || filters.subject.length === 0) return null;
+ const code = filters.subject[filters.subject.length - 1];
+ return findSubject(subjectOptions, code);
+});
+
+const findSubject = (options, code) => {
+ for (const item of options) {
+ if (item.code === code) return item;
+ if (item.children && item.children.length > 0) {
+ const found = findSubject(item.children, code);
+ if (found) return found;
+ }
+ }
+ return null;
+};
+
+const getAuxiliaryLabel = () => {
+ const item = auxiliaryItems.value.find(i => i.value === filters.auxiliaryItem);
+ return item ? item.label : "";
+};
+
+const formatMoney = (value) => {
+ if (value === undefined || value === null) return "0.00";
+ return Number(value).toFixed(2).replace(/\B(?=(\d{3})+(?!\d))/g, ",");
+};
+
+const mockData = [
+ { date: "2024-01-01", voucherNo: "-", summary: "鏈熷垵浣欓", debit: 0, credit: 0, direction: "鍊�", balance: 10000 },
+ { date: "2024-01-05", voucherNo: "璁�-0001", summary: "閿�鍞嚭搴�", debit: 5000, credit: 0, direction: "鍊�", balance: 15000 },
+ { date: "2024-01-10", voucherNo: "璁�-0002", summary: "鏀跺埌璐ф", debit: 0, credit: 3000, direction: "鍊�", balance: 12000 },
+ { date: "2024-01-15", voucherNo: "璁�-0003", summary: "閿�鍞嚭搴�", debit: 8000, credit: 0, direction: "鍊�", balance: 20000 },
+ { date: "2024-01-20", voucherNo: "璁�-0004", summary: "閿�鍞��璐�", debit: 0, credit: 2000, direction: "鍊�", balance: 18000 },
+ { date: "2024-01-25", voucherNo: "璁�-0005", summary: "鏀跺埌璐ф", debit: 0, credit: 5000, direction: "鍊�", balance: 13000 },
+ { date: "2024-01-31", voucherNo: "-", summary: "鏈湀鍚堣", debit: 13000, credit: 10000, direction: "鍊�", balance: 13000 },
+ { date: "2024-02-01", voucherNo: "-", summary: "鏈熷垵浣欓", debit: 0, credit: 0, direction: "鍊�", balance: 13000 },
+ { date: "2024-02-10", voucherNo: "璁�-0006", summary: "閿�鍞嚭搴�", debit: 6000, credit: 0, direction: "鍊�", balance: 19000 },
+ { date: "2024-02-15", voucherNo: "璁�-0007", summary: "鏀跺埌璐ф", debit: 0, credit: 4000, direction: "鍊�", balance: 15000 },
+ { date: "2024-02-28", voucherNo: "-", summary: "鏈湀鍚堣", debit: 6000, credit: 4000, direction: "鍊�", balance: 15000 },
+ { date: "2024-03-01", voucherNo: "-", summary: "鏈熷垵浣欓", debit: 0, credit: 0, direction: "鍊�", balance: 15000 },
+ { date: "2024-03-05", voucherNo: "璁�-0008", summary: "閿�鍞嚭搴�", debit: 7000, credit: 0, direction: "鍊�", balance: 22000 },
+ { date: "2024-03-10", voucherNo: "璁�-0009", summary: "鏀跺埌璐ф", debit: 0, credit: 6000, direction: "鍊�", balance: 16000 },
+ { date: "2024-03-31", voucherNo: "-", summary: "鏈湀鍚堣", debit: 7000, credit: 6000, direction: "鍊�", balance: 16000 },
+ { date: "2024-03-31", voucherNo: "-", summary: "鏈勾绱", debit: 26000, credit: 20000, direction: "鍊�", balance: 16000 },
+];
+
+const getTableData = () => {
+ if (!currentSubject.value) {
+ dataList.value = [];
+ return;
+ }
+ dataList.value = [...mockData];
+};
+
+const resetFilters = () => {
+ filters.subject = [];
+ filters.auxiliary = "";
+ filters.auxiliaryItem = "";
+ filters.startMonth = "2024-01";
+ filters.endMonth = "2024-03";
+ 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;
+};
+
+const handlePrint = () => {
+ ElMessage.info("鎵撳嵃鍔熻兘");
+};
+
+const handleOut = () => {
+ ElMessage.success("瀵煎嚭鎴愬姛");
+};
+
+onMounted(() => {
+ // 榛樿涓嶅姞杞芥暟鎹紝闇�瑕侀�夋嫨绉戠洰
+});
+</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;
+ }
+}
+
+.text-primary {
+ color: #409eff;
+ font-weight: bold;
+}
+
+.text-success {
+ color: #67c23a;
+ font-weight: bold;
+}
+
+.text-danger {
+ color: #f56c6c;
+ font-weight: bold;
+}
+
+.text-warning {
+ color: #e6a23c;
+ font-weight: bold;
+}
+</style>
diff --git a/src/views/financialManagement/voucher/generalLedger.vue b/src/views/financialManagement/voucher/generalLedger.vue
new file mode 100644
index 0000000..5da2d70
--- /dev/null
+++ b/src/views/financialManagement/voucher/generalLedger.vue
@@ -0,0 +1,230 @@
+<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' }" 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="ledger-header" v-if="currentSubject">
+ <h2>绉戠洰鎬昏处</h2>
+ <p>绉戠洰: {{ currentSubject.code }} {{ currentSubject.name }}</p>
+ <p>鏈熼棿: {{ filters.startMonth }} 鑷� {{ filters.endMonth }}</p>
+ </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 label="鍊熸柟" width="150">
+ <template #default="{ row }">
+ <span v-if="row.debit > 0" class="text-danger">楼{{ formatMoney(row.debit) }}</span>
+ <span v-else>-</span>
+ </template>
+ </el-table-column>
+ <el-table-column label="璐锋柟" width="150">
+ <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 { ElMessage } from "element-plus";
+
+defineOptions({
+ name: "绉戠洰鎬昏处",
+});
+
+const filters = reactive({
+ subject: [],
+ startMonth: "2024-01",
+ endMonth: "2024-03",
+});
+
+const dataList = ref([]);
+
+const subjectOptions = [
+ {
+ code: "1001",
+ name: "搴撳瓨鐜伴噾",
+ children: [],
+ },
+ {
+ code: "1002",
+ name: "閾惰瀛樻",
+ children: [
+ { code: "100201", name: "宸ュ晢閾惰" },
+ { code: "100202", name: "寤鸿閾惰" },
+ ],
+ },
+ {
+ code: "1122",
+ name: "搴旀敹璐︽",
+ children: [],
+ },
+ {
+ code: "2202",
+ name: "搴斾粯璐︽",
+ children: [],
+ },
+ {
+ code: "6001",
+ name: "涓昏惀涓氬姟鏀跺叆",
+ children: [],
+ },
+];
+
+const currentSubject = computed(() => {
+ if (!filters.subject || filters.subject.length === 0) return null;
+ const code = filters.subject[filters.subject.length - 1];
+ return findSubject(subjectOptions, code);
+});
+
+const findSubject = (options, code) => {
+ for (const item of options) {
+ if (item.code === code) return item;
+ if (item.children && item.children.length > 0) {
+ const found = findSubject(item.children, code);
+ if (found) return found;
+ }
+ }
+ return null;
+};
+
+const formatMoney = (value) => {
+ if (value === undefined || value === null) return "0.00";
+ return Number(value).toFixed(2).replace(/\B(?=(\d{3})+(?!\d))/g, ",");
+};
+
+const mockData = [
+ { date: "2024-01-01", voucherNo: "-", summary: "鏈熷垵浣欓", debit: 0, credit: 0, direction: "鍊�", balance: 100000 },
+ { date: "2024-01-05", voucherNo: "璁�-0001", summary: "閿�鍞敹鍏�", debit: 5650, credit: 0, direction: "鍊�", balance: 105650 },
+ { date: "2024-01-10", voucherNo: "璁�-0002", summary: "閲囪喘鏀嚭", debit: 0, credit: 8000, direction: "鍊�", balance: 97650 },
+ { date: "2024-01-15", voucherNo: "璁�-0003", summary: "鏀跺埌璐ф", debit: 10000, credit: 0, direction: "鍊�", balance: 107650 },
+ { date: "2024-01-20", voucherNo: "璁�-0004", summary: "鏀粯璐圭敤", debit: 0, credit: 5000, direction: "鍊�", balance: 102650 },
+ { date: "2024-01-31", voucherNo: "-", summary: "鏈湀鍚堣", debit: 15650, credit: 13000, direction: "鍊�", balance: 102650 },
+ { date: "2024-02-01", voucherNo: "-", summary: "鏈熷垵浣欓", debit: 0, credit: 0, direction: "鍊�", balance: 102650 },
+ { date: "2024-02-10", voucherNo: "璁�-0005", summary: "閿�鍞敹鍏�", debit: 8000, credit: 0, direction: "鍊�", balance: 110650 },
+ { date: "2024-02-15", voucherNo: "璁�-0006", summary: "閲囪喘鏀嚭", debit: 0, credit: 12000, direction: "鍊�", balance: 98650 },
+ { date: "2024-02-28", voucherNo: "-", summary: "鏈湀鍚堣", debit: 8000, credit: 12000, direction: "鍊�", balance: 98650 },
+ { date: "2024-03-01", voucherNo: "-", summary: "鏈熷垵浣欓", debit: 0, credit: 0, direction: "鍊�", balance: 98650 },
+ { date: "2024-03-05", voucherNo: "璁�-0007", summary: "閿�鍞敹鍏�", debit: 12000, credit: 0, direction: "鍊�", balance: 110650 },
+ { date: "2024-03-10", voucherNo: "璁�-0008", summary: "鏀粯宸ヨ祫", debit: 0, credit: 15000, direction: "鍊�", balance: 95650 },
+ { date: "2024-03-31", voucherNo: "-", summary: "鏈湀鍚堣", debit: 12000, credit: 15000, direction: "鍊�", balance: 95650 },
+ { date: "2024-03-31", voucherNo: "-", summary: "鏈勾绱", debit: 35650, credit: 40000, direction: "鍊�", balance: 95650 },
+];
+
+const getTableData = () => {
+ if (!currentSubject.value) {
+ dataList.value = [];
+ return;
+ }
+ dataList.value = [...mockData];
+};
+
+const resetFilters = () => {
+ filters.subject = [];
+ filters.startMonth = "2024-01";
+ filters.endMonth = "2024-03";
+ 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;
+};
+
+const handlePrint = () => {
+ ElMessage.info("鎵撳嵃鍔熻兘");
+};
+
+const handleOut = () => {
+ ElMessage.success("瀵煎嚭鎴愬姛");
+};
+
+onMounted(() => {
+ // 榛樿涓嶅姞杞芥暟鎹紝闇�瑕侀�夋嫨绉戠洰
+});
+</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;
+ }
+}
+
+.text-primary {
+ color: #409eff;
+ font-weight: bold;
+}
+
+.text-success {
+ color: #67c23a;
+ font-weight: bold;
+}
+
+.text-danger {
+ color: #f56c6c;
+ font-weight: bold;
+}
+
+.text-warning {
+ color: #e6a23c;
+ font-weight: bold;
+}
+</style>
diff --git a/src/views/financialManagement/voucher/index.vue b/src/views/financialManagement/voucher/index.vue
new file mode 100644
index 0000000..440336f
--- /dev/null
+++ b/src/views/financialManagement/voucher/index.vue
@@ -0,0 +1,418 @@
+<template>
+ <div class="app-container">
+ <el-form :model="filters" :inline="true">
+ <el-form-item label="鍑瘉瀛楀彿:">
+ <el-input v-model="filters.voucherNo" placeholder="璇疯緭鍏ュ嚟璇佸瓧鍙�" clearable style="width: 200px;" />
+ </el-form-item>
+ <el-form-item label="鍑瘉鏃ユ湡:">
+ <el-date-picker v-model="filters.dateRange" value-format="YYYY-MM-DD" format="YYYY-MM-DD" type="daterange" range-separator="鑷�" start-placeholder="寮�濮嬫棩鏈�" end-placeholder="缁撴潫鏃ユ湡" clearable />
+ </el-form-item>
+ <el-form-item label="鍒跺崟浜�:">
+ <el-select v-model="filters.creator" placeholder="璇烽�夋嫨鍒跺崟浜�" clearable style="width: 150px;">
+ <el-option label="寮犱笁" value="寮犱笁" />
+ <el-option label="鏉庡洓" value="鏉庡洓" />
+ <el-option label="鐜嬩簲" value="鐜嬩簲" />
+ </el-select>
+ </el-form-item>
+ <el-form-item label="鐘舵��:">
+ <el-select v-model="filters.status" placeholder="璇烽�夋嫨鐘舵��" clearable style="width: 150px;">
+ <el-option label="鏈繃璐�" value="unposted" />
+ <el-option label="宸茶繃璐�" value="posted" />
+ <el-option label="宸蹭綔搴�" value="cancelled" />
+ </el-select>
+ </el-form-item>
+ <el-form-item>
+ <el-button type="primary" @click="getTableData">鎼滅储</el-button>
+ <el-button @click="resetFilters">閲嶇疆</el-button>
+ </el-form-item>
+ </el-form>
+ <div class="table_list">
+ <div class="actions">
+ <div>
+ <el-statistic title="鍊熸柟鍚堣" :value="totalDebit" precision="2" prefix="楼" />
+ <el-statistic title="璐锋柟鍚堣" :value="totalCredit" precision="2" prefix="楼" style="margin-left: 30px;" />
+ </div>
+ <div>
+ <el-button type="primary" @click="add" icon="Plus">鏂板鍑瘉</el-button>
+ <el-button @click="handleImport" icon="Upload">瀵煎叆</el-button>
+ <el-button @click="handleOut" icon="Download">瀵煎嚭</el-button>
+ </div>
+ </div>
+ <PIMTable
+ rowKey="id"
+ :column="columns"
+ :tableData="dataList"
+ :page="{
+ current: pagination.currentPage,
+ size: pagination.pageSize,
+ total: pagination.total,
+ }"
+ @pagination="changePage"
+ >
+ <template #debit="{ row }">
+ <span class="text-danger" v-if="row.debit > 0">楼{{ formatMoney(row.debit) }}</span>
+ <span v-else>-</span>
+ </template>
+ <template #credit="{ row }">
+ <span class="text-success" v-if="row.credit > 0">楼{{ formatMoney(row.credit) }}</span>
+ <span v-else>-</span>
+ </template>
+ <template #status="{ row }">
+ <el-tag :type="getStatusType(row.status)">{{ getStatusLabel(row.status) }}</el-tag>
+ </template>
+ <template #operation="{ row }">
+ <el-button type="primary" link @click="view(row)">鏌ョ湅</el-button>
+ <el-button type="primary" link @click="edit(row)" v-if="row.status === 'unposted'">缂栬緫</el-button>
+ <el-button type="success" link @click="handlePost(row)" v-if="row.status === 'unposted'">杩囪处</el-button>
+ <el-button type="danger" link @click="handleCancel(row)" v-if="row.status === 'unposted'">浣滃簾</el-button>
+ </template>
+ </PIMTable>
+ </div>
+
+ <el-dialog :title="dialogTitle" v-model="dialogVisible" width="900px" append-to-body>
+ <el-form :model="form" :rules="rules" ref="formRef" label-width="100px">
+ <el-row :gutter="20">
+ <el-col :span="8">
+ <el-form-item label="鍑瘉瀛楀彿" prop="voucherNo">
+ <el-input v-model="form.voucherNo" placeholder="绯荤粺鑷姩鐢熸垚" disabled />
+ </el-form-item>
+ </el-col>
+ <el-col :span="8">
+ <el-form-item label="鍑瘉鏃ユ湡" prop="voucherDate">
+ <el-date-picker v-model="form.voucherDate" type="date" placeholder="閫夋嫨鏃ユ湡" value-format="YYYY-MM-DD" style="width: 100%;" />
+ </el-form-item>
+ </el-col>
+ <el-col :span="8">
+ <el-form-item label="闄勪欢寮犳暟" prop="attachmentCount">
+ <el-input-number v-model="form.attachmentCount" :min="0" style="width: 100%;" />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-form-item label="鍑瘉鍒嗗綍" prop="entries">
+ <el-table :data="form.entries" border style="width: 100%">
+ <el-table-column type="index" label="搴忓彿" width="60" />
+ <el-table-column prop="subjectCode" label="绉戠洰缂栫爜" width="120">
+ <template #default="{ $index }">
+ <el-select v-model="form.entries[$index].subjectCode" placeholder="閫夋嫨绉戠洰" filterable style="width: 100%;" @change="(val) => handleSubjectChange(val, $index)">
+ <el-option v-for="item in subjectList" :key="item.code" :label="item.code" :value="item.code" />
+ </el-select>
+ </template>
+ </el-table-column>
+ <el-table-column prop="subjectName" label="绉戠洰鍚嶇О" width="150">
+ <template #default="{ $index }">
+ <el-input v-model="form.entries[$index].subjectName" disabled />
+ </template>
+ </el-table-column>
+ <el-table-column prop="summary" label="鎽樿">
+ <template #default="{ $index }">
+ <el-input v-model="form.entries[$index].summary" placeholder="璇疯緭鍏ユ憳瑕�" />
+ </template>
+ </el-table-column>
+ <el-table-column prop="debit" label="鍊熸柟閲戦" width="130">
+ <template #default="{ $index }">
+ <el-input-number v-model="form.entries[$index].debit" :min="0" :precision="2" style="width: 100%;" @change="calculateTotal" />
+ </template>
+ </el-table-column>
+ <el-table-column prop="credit" label="璐锋柟閲戦" width="130">
+ <template #default="{ $index }">
+ <el-input-number v-model="form.entries[$index].credit" :min="0" :precision="2" style="width: 100%;" @change="calculateTotal" />
+ </template>
+ </el-table-column>
+ <el-table-column label="鎿嶄綔" width="80">
+ <template #default="{ $index }">
+ <el-button type="danger" link @click="removeEntry($index)">鍒犻櫎</el-button>
+ </template>
+ </el-table-column>
+ </el-table>
+ <div style="display: flex; justify-content: space-between; margin-top: 10px;">
+ <el-button type="primary" link @click="addEntry">+ 娣诲姞鍒嗗綍</el-button>
+ <div>
+ <span style="margin-right: 20px;">鍚堣: 鍊熸柟 <span :class="totalDebitEntry === totalCreditEntry ? 'text-success' : 'text-danger'">楼{{ formatMoney(totalDebitEntry) }}</span></span>
+ <span>璐锋柟 <span :class="totalDebitEntry === totalCreditEntry ? 'text-success' : 'text-danger'">楼{{ formatMoney(totalCreditEntry) }}</span></span>
+ </div>
+ </div>
+ </el-form-item>
+ <el-form-item label="鍒跺崟浜�" prop="creator">
+ <el-input v-model="form.creator" disabled />
+ </el-form-item>
+ <el-form-item label="澶囨敞" prop="remark">
+ <el-input v-model="form.remark" type="textarea" :rows="2" placeholder="璇疯緭鍏ュ娉�" />
+ </el-form-item>
+ </el-form>
+ <template #footer>
+ <el-button @click="dialogVisible = false">鍙栨秷</el-button>
+ <el-button type="primary" @click="submitForm" :disabled="totalDebitEntry !== totalCreditEntry">纭畾</el-button>
+ </template>
+ </el-dialog>
+ </div>
+</template>
+
+<script setup>
+import { ref, reactive, onMounted, computed } from "vue";
+import { ElMessage, ElMessageBox } from "element-plus";
+
+defineOptions({
+ name: "鍑瘉绠$悊",
+});
+
+const filters = reactive({
+ voucherNo: "",
+ dateRange: [],
+ creator: "",
+ status: "",
+});
+
+const pagination = reactive({
+ currentPage: 1,
+ pageSize: 10,
+ total: 0,
+});
+
+const columns = [
+ { label: "鍑瘉瀛楀彿", prop: "voucherNo", width: "120" },
+ { label: "鍑瘉鏃ユ湡", prop: "voucherDate", width: "120" },
+ { label: "鎽樿", prop: "summary", showOverflowTooltip: true },
+ { label: "鍊熸柟閲戦", prop: "debit", slot: "debit" },
+ { label: "璐锋柟閲戦", prop: "credit", slot: "credit" },
+ { label: "鍒跺崟浜�", prop: "creator", width: "100" },
+ { label: "鐘舵��", prop: "status", slot: "status" },
+ { label: "鎿嶄綔", prop: "operation", slot: "operation", width: "220", fixed: "right" },
+];
+
+const dataList = ref([]);
+const dialogVisible = ref(false);
+const dialogTitle = ref("");
+const formRef = ref(null);
+const isEdit = ref(false);
+const currentId = ref(null);
+
+const subjectList = [
+ { code: "1001", name: "搴撳瓨鐜伴噾" },
+ { code: "1002", name: "閾惰瀛樻" },
+ { code: "1122", name: "搴旀敹璐︽" },
+ { code: "2202", name: "搴斾粯璐︽" },
+ { code: "5001", name: "鐢熶骇鎴愭湰" },
+ { code: "6001", name: "涓昏惀涓氬姟鏀跺叆" },
+ { code: "6401", name: "涓昏惀涓氬姟鎴愭湰" },
+];
+
+const form = reactive({
+ voucherNo: "",
+ voucherDate: "",
+ attachmentCount: 0,
+ entries: [],
+ creator: "寮犱笁",
+ remark: "",
+});
+
+const rules = {
+ voucherDate: [{ required: true, message: "璇烽�夋嫨鍑瘉鏃ユ湡", trigger: "change" }],
+};
+
+const mockData = [
+ { id: 1, voucherNo: "璁�-0001", voucherDate: "2024-01-15", summary: "閿�鍞敹鍏�", debit: 5650, credit: 5650, creator: "寮犱笁", status: "posted", entries: [{ subjectCode: "1002", subjectName: "閾惰瀛樻", summary: "閿�鍞敹鍏�", debit: 5650, credit: 0 }, { subjectCode: "6001", subjectName: "涓昏惀涓氬姟鏀跺叆", summary: "閿�鍞敹鍏�", debit: 0, credit: 5000 }, { subjectCode: "2221", subjectName: "搴斾氦绋庤垂", summary: "閿�椤圭◣棰�", debit: 0, credit: 650 }] },
+ { id: 2, voucherNo: "璁�-0002", voucherDate: "2024-01-16", summary: "閲囪喘鍘熸潗鏂�", debit: 9040, credit: 9040, creator: "鏉庡洓", status: "unposted", entries: [{ subjectCode: "5001", subjectName: "鐢熶骇鎴愭湰", summary: "閲囪喘鍘熸潗鏂�", debit: 8000, credit: 0 }, { subjectCode: "2221", subjectName: "搴斾氦绋庤垂", summary: "杩涢」绋庨", debit: 1040, credit: 0 }, { subjectCode: "2202", subjectName: "搴斾粯璐︽", summary: "閲囪喘鍘熸潗鏂�", debit: 0, credit: 9040 }] },
+ { id: 3, voucherNo: "璁�-0003", voucherDate: "2024-01-18", summary: "鏀粯璐ф", debit: 5000, credit: 5000, creator: "寮犱笁", status: "posted", entries: [{ subjectCode: "2202", subjectName: "搴斾粯璐︽", summary: "鏀粯璐ф", debit: 5000, credit: 0 }, { subjectCode: "1002", subjectName: "閾惰瀛樻", summary: "鏀粯璐ф", debit: 0, credit: 5000 }] },
+];
+
+const totalDebit = computed(() => {
+ return dataList.value.reduce((sum, item) => sum + Number(item.debit), 0);
+});
+
+const totalCredit = computed(() => {
+ return dataList.value.reduce((sum, item) => sum + Number(item.credit), 0);
+});
+
+const totalDebitEntry = computed(() => {
+ return form.entries.reduce((sum, item) => sum + Number(item.debit || 0), 0);
+});
+
+const totalCreditEntry = computed(() => {
+ return form.entries.reduce((sum, item) => sum + Number(item.credit || 0), 0);
+});
+
+const formatMoney = (value) => {
+ if (value === undefined || value === null) return "0.00";
+ return Number(value).toFixed(2).replace(/\B(?=(\d{3})+(?!\d))/g, ",");
+};
+
+const getStatusLabel = (status) => {
+ const map = { unposted: "鏈繃璐�", posted: "宸茶繃璐�", cancelled: "宸蹭綔搴�" };
+ return map[status] || status;
+};
+
+const getStatusType = (status) => {
+ const map = { unposted: "warning", posted: "success", cancelled: "info" };
+ return map[status] || "";
+};
+
+const getTableData = () => {
+ let result = [...mockData];
+ if (filters.voucherNo) {
+ result = result.filter(item => item.voucherNo.includes(filters.voucherNo));
+ }
+ if (filters.dateRange && filters.dateRange.length === 2) {
+ result = result.filter(item => item.voucherDate >= filters.dateRange[0] && item.voucherDate <= filters.dateRange[1]);
+ }
+ if (filters.creator) {
+ result = result.filter(item => item.creator === filters.creator);
+ }
+ if (filters.status) {
+ result = result.filter(item => item.status === filters.status);
+ }
+ pagination.total = result.length;
+ dataList.value = result.slice((pagination.currentPage - 1) * pagination.pageSize, pagination.currentPage * pagination.pageSize);
+};
+
+const resetFilters = () => {
+ filters.voucherNo = "";
+ filters.dateRange = [];
+ filters.creator = "";
+ filters.status = "";
+ pagination.currentPage = 1;
+ getTableData();
+};
+
+const changePage = ({ current, size }) => {
+ pagination.currentPage = current;
+ pagination.pageSize = size;
+ getTableData();
+};
+
+const addEntry = () => {
+ form.entries.push({ subjectCode: "", subjectName: "", summary: "", debit: 0, credit: 0 });
+};
+
+const removeEntry = (index) => {
+ form.entries.splice(index, 1);
+ calculateTotal();
+};
+
+const handleSubjectChange = (val, index) => {
+ const subject = subjectList.find(item => item.code === val);
+ if (subject) {
+ form.entries[index].subjectName = subject.name;
+ }
+};
+
+const calculateTotal = () => {
+ // 鑷姩璁$畻锛岀敱computed灞炴�у鐞�
+};
+
+const add = () => {
+ isEdit.value = false;
+ dialogTitle.value = "鏂板鍑瘉";
+ Object.assign(form, {
+ voucherNo: "璁�-" + String(mockData.length + 1).padStart(4, "0"),
+ voucherDate: new Date().toISOString().split('T')[0],
+ attachmentCount: 0,
+ entries: [{ subjectCode: "", subjectName: "", summary: "", debit: 0, credit: 0 }],
+ creator: "寮犱笁",
+ remark: "",
+ });
+ dialogVisible.value = true;
+};
+
+const edit = (row) => {
+ isEdit.value = true;
+ currentId.value = row.id;
+ dialogTitle.value = "缂栬緫鍑瘉";
+ Object.assign(form, row);
+ dialogVisible.value = true;
+};
+
+const view = (row) => {
+ ElMessage.info(`鏌ョ湅鍑瘉: ${row.voucherNo}`);
+};
+
+const handlePost = (row) => {
+ ElMessageBox.confirm("纭杩囪处璇ュ嚟璇佸悧锛�", "鎻愮ず", {
+ confirmButtonText: "纭",
+ cancelButtonText: "鍙栨秷",
+ type: "info",
+ }).then(() => {
+ const index = mockData.findIndex(item => item.id === row.id);
+ if (index !== -1) {
+ mockData[index].status = "posted";
+ }
+ ElMessage.success("杩囪处鎴愬姛");
+ getTableData();
+ });
+};
+
+const handleCancel = (row) => {
+ ElMessageBox.confirm("纭浣滃簾璇ュ嚟璇佸悧锛�", "鎻愮ず", {
+ confirmButtonText: "纭",
+ cancelButtonText: "鍙栨秷",
+ type: "warning",
+ }).then(() => {
+ const index = mockData.findIndex(item => item.id === row.id);
+ if (index !== -1) {
+ mockData[index].status = "cancelled";
+ }
+ ElMessage.success("浣滃簾鎴愬姛");
+ getTableData();
+ });
+};
+
+const handleImport = () => {
+ ElMessage.info("瀵煎叆鍔熻兘");
+};
+
+const handleOut = () => {
+ ElMessage.success("瀵煎嚭鎴愬姛");
+};
+
+const submitForm = () => {
+ formRef.value.validate((valid) => {
+ if (valid) {
+ if (totalDebitEntry.value !== totalCreditEntry.value) {
+ ElMessage.error("鍊熻捶涓嶅钩琛★紝璇锋鏌ュ垎褰�");
+ return;
+ }
+ const summary = form.entries.find(e => e.debit > 0)?.summary || "";
+ if (isEdit.value) {
+ const index = mockData.findIndex(item => item.id === currentId.value);
+ if (index !== -1) {
+ mockData[index] = { ...mockData[index], ...form, summary, debit: totalDebitEntry.value, credit: totalCreditEntry.value };
+ }
+ ElMessage.success("缂栬緫鎴愬姛");
+ } else {
+ const newId = mockData.length > 0 ? Math.max(...mockData.map(item => item.id)) + 1 : 1;
+ mockData.push({ id: newId, ...form, summary, debit: totalDebitEntry.value, credit: totalCreditEntry.value, status: "unposted" });
+ ElMessage.success("鏂板鎴愬姛");
+ }
+ dialogVisible.value = false;
+ getTableData();
+ }
+ });
+};
+
+onMounted(() => {
+ getTableData();
+});
+</script>
+
+<style lang="scss" scoped>
+.actions {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 15px;
+
+ > div:first-child {
+ display: flex;
+ align-items: center;
+ }
+}
+
+.text-success {
+ color: #67c23a;
+ font-weight: bold;
+}
+
+.text-danger {
+ color: #f56c6c;
+ font-weight: bold;
+}
+</style>
--
Gitblit v1.9.3