From 8bc17a9ea84a6af0b7d01e451c702f404a3ff895 Mon Sep 17 00:00:00 2001
From: zouyu <2723363702@qq.com>
Date: 星期六, 10 一月 2026 11:36:34 +0800
Subject: [PATCH] Merge branch 'dev_tide' into dev_tide_cbsglxt
---
src/views/financialManagement/accounting/index.vue | 547 ++
src/views/index.vue | 1361 ++--
src/api/lavorissce/ledger.js | 55
src/views/inventoryManagement/stockManagement/components/FormDiaPurchase.vue | 147
src/api/productionManagement/productionOrder.js | 27
src/views/productionManagement/productionDispatching/components/formDia.vue | 167
src/api/inventoryManagement/stockIn.js | 78
src/views/inventoryManagement/receiptManagement/components/formDiaProduct.vue | 300 +
src/views/inventoryManagement/stockWarning/index.vue | 943 +++
src/views/procurementManagement/paymentEntry/index.vue | 575 ++
src/views/productionManagement/productionDispatching/index.vue | 164
src/views/salesManagement/salesLedger/index.vue | 1018 +++
src/views/environmentAccess/accessManagement/index.vue | 347 +
src/api/inventoryManagement/stockManage.js | 20
src/api/inventoryManagement/stockWarning.js | 15
src/api/inventoryManagement/stockReport.js | 55
src/views/inventoryManagement/receiptManagement/index.vue | 212
index.html | 2
src/views/financialManagement/inventoryAccounting/index.vue | 390 +
src/views/inventoryManagement/stockManagement/index.vue | 387 +
src/views/environmentAccess/vehicleInformationCollection/index.vue | 577 ++
src/views/inventoryManagement/dispatchLog/index.vue | 371 +
src/views/financialManagement/financialStatements/index.vue | 507 +
src/views/procurementManagement/procurementInvoiceLedger/Form/EditForm.vue | 196
src/views/productionManagement/operationScheduling/components/formDia.vue | 181
src/views/salesManagement/receiptPayment/index.vue | 640 ++
src/views/productionManagement/productionCosting/index.vue | 166
src/views/environmentAccess/intelligentInspectionManagement/index.vue | 581 ++
src/views/environmentAccess/remoteMonitoringOfEquipment/index.vue | 633 ++
src/views/inventoryManagement/index.vue | 309 +
src/views/inventoryManagement/issueManagement/index.vue | 337 +
src/views/productionManagement/operationScheduling/index.vue | 228
src/views/financialManagement/expenseManagement/index.vue | 276 +
src/views/procurementManagement/procurementLedger/index.vue | 1131 ++++
src/views/productionManagement/productionReporting/index.vue | 401 +
src/api/inventoryManagement/stockOut.js | 12
src/views/productionManagement/productionOrder/index.vue | 374 +
src/views/inventoryManagement/receiptManagement/components/formDiaManual.vue | 310 +
src/utils/index.js | 13
src/views/inventoryManagement/receiptManagement/components/formDia.vue | 401 +
src/views/inventoryManagement/stockReport/index.vue | 713 ++
src/views/salesManagement/receiptPaymentHistory/index.vue | 208
src/views/inventoryManagement/stockManagement/components/FormDiaManual.vue | 154
src/views/inventoryManagement/stockManagement/components/FormDiaProduction.vue | 147
44 files changed, 15,041 insertions(+), 635 deletions(-)
diff --git a/index.html b/index.html
index f375898..2e6580e 100644
--- a/index.html
+++ b/index.html
@@ -7,7 +7,7 @@
<meta name="renderer" content="webkit">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
<link rel="icon" href="/favicon.ico">
- <title>鎵垮寘鍟嗙鐞嗙郴缁�</title>
+ <title>%VITE_APP_TITLE%</title>
<!--[if lt IE 11]><script>window.location.href='/html/ie.html';</script><![endif]-->
<style>
html,
diff --git a/src/api/inventoryManagement/stockIn.js b/src/api/inventoryManagement/stockIn.js
index 5e104f7..70f07a7 100644
--- a/src/api/inventoryManagement/stockIn.js
+++ b/src/api/inventoryManagement/stockIn.js
@@ -9,6 +9,41 @@
});
};
+// 鏌ヨ鐢熶骇鍏ュ簱淇℃伅鍒楄〃
+export const getStockInPageByProduction = (params) => {
+ return request({
+ url: "/stockin/listPageByProduction",
+ method: "get",
+ params,
+ });
+};
+
+// 鍑哄簱鍙拌处-鏌ヨ鑷畾涔夊叆搴撲俊鎭垪琛�
+export const getStockInPageByCustom = (params) => {
+ return request({
+ url: "/stockmanagement/listPageByCustom",
+ method: "get",
+ params,
+ });
+};
+// 鍏ュ簱绠$悊-鏌ヨ鑷畾涔夊叆搴撲俊鎭垪琛�
+export const getInPageByCustom = (params) => {
+ return request({
+ url: "/stockin/listPageByCustom",
+ method: "get",
+ params,
+ });
+};
+
+// 鍑哄簱鍙拌处-鏌ヨ鐢熶骇鍑哄簱淇℃伅鍒楄〃
+export const getStockInPageByProduct = (params) => {
+ return request({
+ url: "/stockmanagement/listPageByProduct",
+ method: "get",
+ params,
+ });
+};
+
// 淇敼鍏ュ簱瀛樹俊鎭�
export const updateStockIn = (data) => {
return request({
@@ -26,6 +61,14 @@
data,
});
};
+// 淇敼鏉愭枡搴撳瓨淇℃伅
+export const updateManagementByCustom = (data) => {
+ return request({
+ url: "/stockin/updateManagementByCustom ",
+ method: "post",
+ data,
+ });
+};
// 鏂板鍟嗗搧鍏ュ簱淇℃伅
export function addSutockIn(data) {
@@ -36,6 +79,32 @@
})
}
+// 鏂板鑷畾涔夊叆搴撲俊鎭�
+export function addStockInCustom(data) {
+ return request({
+ url: '/stockin/addCustom',
+ method: 'post',
+ data: data
+ })
+}
+
+// 缂栬緫鑷畾涔夊叆搴撲俊鎭�
+export function updateStockInCustom(data) {
+ return request({
+ url: '/stockin/updateCustom',
+ method: 'post',
+ data: data
+ })
+}
+// 缂栬緫鎴愬搧鍏ュ簱淇℃伅
+export function updateProduct(data) {
+ return request({
+ url: '/stockin/update',
+ method: 'post',
+ data: data
+ })
+}
+
// 鍒犻櫎鍏ュ簱淇℃伅
export function delStockIn(ids) {
return request({
@@ -45,6 +114,15 @@
})
}
+// 鍒犻櫎鑷畾涔夊叆搴撲俊鎭�
+export function delStockInCustom(ids) {
+ return request({
+ url: '/stockin/delteCustom',
+ method: 'post',
+ data: ids
+ })
+}
+
// 瀵煎嚭鍏ュ簱淇℃伅
export function exportStockIn(query) {
return request({
diff --git a/src/api/inventoryManagement/stockManage.js b/src/api/inventoryManagement/stockManage.js
index bb2081b..4f5d957 100644
--- a/src/api/inventoryManagement/stockManage.js
+++ b/src/api/inventoryManagement/stockManage.js
@@ -9,6 +9,24 @@
});
};
+// 鏌ヨ鐢熶骇鍏ュ簱搴撳瓨淇℃伅鍒楄〃
+export const getStockManagePageByProduction = (params) => {
+ return request({
+ url: "/stockin/listPageCopyByProduction",
+ method: "get",
+ params,
+ });
+};
+
+// 鏌ヨ鑷畾涔夊叆搴撳簱瀛樹俊鎭垪琛�
+export const getStockManagePageByCustom = (params) => {
+ return request({
+ url: "/stockin/listPageCopyByCustom",
+ method: "get",
+ params,
+ });
+};
+
// 淇敼搴撳瓨淇℃伅
export const updateStockManage = (data) => {
@@ -38,7 +56,7 @@
})
}
-//鍑哄簱鎺ュ彛
+// 鍑哄簱绠$悊-棰嗙敤鎺ュ彛
export const stockOut = (data) => {
return request({
url: '/stockmanagement/stockout',
diff --git a/src/api/inventoryManagement/stockOut.js b/src/api/inventoryManagement/stockOut.js
index 5d410d9..7d188af 100644
--- a/src/api/inventoryManagement/stockOut.js
+++ b/src/api/inventoryManagement/stockOut.js
@@ -1,6 +1,6 @@
import request from "@/utils/request";
-//鏌ヨ鍑哄簱鍒楄〃
+// 鍑哄簱鍙拌处-閲囪喘鍑哄簱鏌ヨ鍑哄簱鍒楄〃
export const getStockOutPage = (params) => {
return request({
url: "/stockmanagement/listPage",
@@ -35,13 +35,3 @@
data: ids
})
}
-
-//瀵煎嚭鍑哄簱淇℃伅
-export const exportStockOut = (query) => {
- return request({
- url: '/stockmanagement/export',
- method: 'get',
- params: query,
- responseType: 'blob'
- })
-}
\ No newline at end of file
diff --git a/src/api/inventoryManagement/stockReport.js b/src/api/inventoryManagement/stockReport.js
new file mode 100644
index 0000000..6d1a3ce
--- /dev/null
+++ b/src/api/inventoryManagement/stockReport.js
@@ -0,0 +1,55 @@
+import request from "@/utils/request";
+
+// 鑾峰彇搴撳瓨鏃ユ姤缁熻
+export const getStockDailyReport = (params) => {
+ return request({
+ url: "/stockin/getReportList",
+ method: "get",
+ params,
+ });
+};
+// 鑾峰彇搴撳瓨鏈堟姤缁熻
+export const getStockMonthlyReport = (params) => {
+ return request({
+ url: "/stockin/getReportList",
+ method: "get",
+ params,
+ });
+};
+
+// 鑾峰彇浣滀笟鎶ヨ〃缁熻
+export const getWorkReport = (params) => {
+ return request({
+ url: "/stockin/getReportList",
+ method: "get",
+ params,
+ });
+};
+
+// 鑾峰彇搴撳瓨杩涘嚭瀛樼粺璁�
+export const getStockInOutReport = (params) => {
+ return request({
+ url: "/stockin/getReportList",
+ method: "get",
+ params,
+ });
+};
+
+// 瀵煎嚭搴撳瓨鎶ヨ〃
+export const exportStockReport = (params) => {
+ return request({
+ url: "/stockin/exportCopy",
+ method: "post",
+ params,
+ responseType: 'blob'
+ });
+};
+
+// 鑾峰彇搴撳瓨瓒嬪娍鏁版嵁
+export const getStockTrendData = (params) => {
+ return request({
+ url: "/stockreport/trend",
+ method: "get",
+ params,
+ });
+};
diff --git a/src/api/inventoryManagement/stockWarning.js b/src/api/inventoryManagement/stockWarning.js
index 092fb80..65e641a 100644
--- a/src/api/inventoryManagement/stockWarning.js
+++ b/src/api/inventoryManagement/stockWarning.js
@@ -1,11 +1,14 @@
import request from "@/utils/request";
// 鏌ヨ鍌ㄦ皵缃愰璀﹀垪琛�
-export const getStockWarningPage = (params) => {
+export const getStockWarningPage = (page, params) => {
return request({
url: "/gasTankWarning/listPage",
method: "get",
- params,
+ params: {
+ ...page,
+ ...params
+ },
});
};
@@ -14,7 +17,7 @@
return request({
url: "/gasTankWarning/add",
method: "post",
- data,
+ data: data,
});
};
@@ -22,8 +25,8 @@
export const updateStockWarning = (data) => {
return request({
url: "/gasTankWarning/update",
- method: "put",
- data,
+ method: "post",
+ data: data,
});
};
@@ -32,7 +35,7 @@
return request({
url: "/gasTankWarning/delete",
method: "delete",
- data: { ids },
+ data: ids,
});
};
diff --git a/src/api/lavorissce/ledger.js b/src/api/lavorissce/ledger.js
new file mode 100644
index 0000000..f4f710c
--- /dev/null
+++ b/src/api/lavorissce/ledger.js
@@ -0,0 +1,55 @@
+import request from '@/utils/request'
+
+// 鍒嗛〉鏌ヨ
+export function listPage(query) {
+ return request({
+ url: '/lavorIssue/listPage',
+ method: 'get',
+ params: query
+ })
+}
+
+// 鍒嗛〉鏌ヨ
+export function statistics(params) {
+ return request({
+ url: '/lavorIssue/statistics',
+ method: 'get',
+ params
+ })
+}
+
+export function statisticsList(params) {
+ return request({
+ url: '/lavorIssue/statisticsList',
+ method: 'get',
+ params
+ })
+}
+
+// 娣诲姞
+export function add(data) {
+ return request({
+ url: '/lavorIssue/add',
+ method: 'post',
+ data
+ })
+}
+
+// 淇敼
+export function update(data) {
+ return request({
+ url: '/lavorIssue/update',
+ method: 'post',
+ data
+ })
+}
+
+// 鍒犻櫎
+export function deleteLedger(data) {
+ return request({
+ url: '/lavorIssue/delete',
+ method: 'delete',
+ data
+ })
+}
+
diff --git a/src/api/productionManagement/productionOrder.js b/src/api/productionManagement/productionOrder.js
index ab3dc06..29cff35 100644
--- a/src/api/productionManagement/productionOrder.js
+++ b/src/api/productionManagement/productionOrder.js
@@ -4,7 +4,7 @@
// 鍒嗛〉鏌ヨ
export function schedulingListPage(query) {
return request({
- url: "/salesLedger/scheduling/listPage",
+ url: "/productionOrder/listPage",
method: "get",
params: query,
});
@@ -16,4 +16,29 @@
method: "post",
data: query,
});
+}
+
+// 鏂板鐢熶骇璁㈠崟
+export function addProductionOrder(query) {
+ return request({
+ url: "/productionOrder/addProductionOrder",
+ method: "post",
+ data: query,
+ });
+}
+// 淇敼鐢熶骇璁㈠崟
+export function updateProductionOrder(query) {
+ return request({
+ url: "/productionOrder/updateProductionOrder",
+ method: "post",
+ data: query,
+ });
+}
+// 鍒犻櫎鐢熶骇璁㈠崟
+export function deleteProductionOrder(query) {
+ return request({
+ url: "/productionOrder/deleteProductionOrder",
+ method: "delete",
+ data: query,
+ });
}
\ No newline at end of file
diff --git a/src/utils/index.js b/src/utils/index.js
index e522c3c..809593f 100644
--- a/src/utils/index.js
+++ b/src/utils/index.js
@@ -396,3 +396,16 @@
export function isEqual(obj1, obj2) {
return JSON.stringify(obj1) === JSON.stringify(obj2);
}
+
+/**
+ * 鑾峰彇褰撳墠鏃ユ湡骞舵牸寮忓寲涓� YYYY-MM-DD
+ * @returns {string} 鏍煎紡鍖栫殑鏃ユ湡瀛楃涓�
+ */
+export function getCurrentDate() {
+ const today = new Date();
+ const year = today.getFullYear();
+ const month = String(today.getMonth() + 1).padStart(2, '0'); // 鏈堜唤浠�0寮�濮�
+ const day = String(today.getDate()).padStart(2, '0');
+ return `${year}-${month}-${day}`;
+}
+
diff --git a/src/views/environmentAccess/accessManagement/index.vue b/src/views/environmentAccess/accessManagement/index.vue
new file mode 100644
index 0000000..587dc78
--- /dev/null
+++ b/src/views/environmentAccess/accessManagement/index.vue
@@ -0,0 +1,347 @@
+<template>
+ <div class="app-container">
+ <el-form :model="filters" :inline="true">
+ <el-form-item label="闂ㄧ鍚嶇О">
+ <el-input
+ v-model="filters.name"
+ style="width: 240px"
+ placeholder="璇疯緭鍏ラ棬绂佸悕绉�"
+ clearable
+ :prefix-icon="Search"
+ @change="getTableData"
+ />
+ </el-form-item>
+ <el-form-item label="闂ㄧ鐘舵��">
+ <el-select
+ v-model="filters.status"
+ style="width: 240px"
+ placeholder="璇烽�夋嫨闂ㄧ鐘舵��"
+ clearable
+ @change="getTableData"
+ >
+ <el-option label="姝e父" value="1"></el-option>
+ <el-option label="寮傚父" value="0"></el-option>
+ </el-select>
+ </el-form-item>
+ <el-form-item label="鍖哄煙">
+ <el-input
+ v-model="filters.area"
+ style="width: 240px"
+ placeholder="璇疯緭鍏ュ尯鍩�"
+ clearable
+ :prefix-icon="Search"
+ @change="getTableData"
+ />
+ </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 type="danger" icon="Delete" :disabled="multipleList.length <= 0" @click="batchDelete">鎵归噺鍒犻櫎</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"
+ >
+ </PIMTable>
+ </div>
+ <!-- 鏂板缂栬緫寮圭獥 -->
+ <el-dialog v-model="dialogVisible" :title="dialogTitle" width="600px">
+ <el-form :model="formData" label-width="100px">
+ <el-form-item label="闂ㄧ鍚嶇О" required>
+ <el-input v-model="formData.name" placeholder="璇疯緭鍏ラ棬绂佸悕绉�"></el-input>
+ </el-form-item>
+ <el-form-item label="鍖哄煙" required>
+ <el-input v-model="formData.area" placeholder="璇疯緭鍏ュ尯鍩�"></el-input>
+ </el-form-item>
+ <el-form-item label="浣嶇疆" required>
+ <el-input v-model="formData.location" placeholder="璇疯緭鍏ュ叿浣撲綅缃�"></el-input>
+ </el-form-item>
+ <el-form-item label="鐘舵��" required>
+ <el-select v-model="formData.status" placeholder="璇烽�夋嫨鐘舵��">
+ <el-option label="姝e父" value="1"></el-option>
+ <el-option label="寮傚父" value="0"></el-option>
+ </el-select>
+ </el-form-item>
+ <el-form-item label="鎻忚堪">
+ <el-input v-model="formData.description" type="textarea" rows="3" placeholder="璇疯緭鍏ユ弿杩颁俊鎭�"></el-input>
+ </el-form-item>
+ </el-form>
+ <template #footer>
+ <span class="dialog-footer">
+ <el-button @click="dialogVisible = false">鍙栨秷</el-button>
+ <el-button type="primary" @click="saveData">纭畾</el-button>
+ </span>
+ </template>
+ </el-dialog>
+ </div>
+</template>
+
+<script setup>
+import { ref, reactive, computed, onMounted } from 'vue';
+import { Search, Plus, Delete } from '@element-plus/icons-vue';
+import { ElMessage, ElMessageBox } from 'element-plus';
+import dayjs from 'dayjs';
+
+// 瀹氫箟鍋囨暟鎹�
+const mockData = [
+ { id: 1, name: '涓滈棬闂ㄧ', area: '鐢熶骇鍖�', location: '涓滈棬鍏ュ彛', status: '1', lastUpdate: '2025-12-30 08:30:00', description: '涓昏鐢ㄤ簬鍛樺伐涓婁笅鐝�氳' },
+ { id: 2, name: '瑗块棬闂ㄧ', area: '浠撳偍鍖�', location: '瑗块棬鍏ュ彛', status: '1', lastUpdate: '2025-12-30 09:15:00', description: '涓昏鐢ㄤ簬鐗╂祦杞﹁締閫氳' },
+ { id: 3, name: '鍗楅棬闂ㄧ', area: '鍔炲叕鍖�', location: '鍗楅棬鍏ュ彛', status: '0', lastUpdate: '2025-12-30 10:20:00', description: '涓昏鐢ㄤ簬璁垮閫氳锛屽綋鍓嶆晠闅�' },
+ { id: 4, name: '鍖楅棬闂ㄧ', area: '鐢熶骇鍖�', location: '鍖楅棬鍏ュ彛', status: '1', lastUpdate: '2025-12-30 11:45:00', description: '涓昏鐢ㄤ簬鍘熸潗鏂欒繍杈撹溅杈嗛�氳' },
+ { id: 5, name: '涓帶瀹ら棬绂�', area: '涓帶鍖�', location: '涓帶瀹ら棬鍙�', status: '1', lastUpdate: '2025-12-30 13:20:00', description: '浠呴檺鎺堟潈浜哄憳杩涘叆' },
+];
+
+// 鍝嶅簲寮忔暟鎹�
+const filters = reactive({
+ name: '',
+ status: '',
+ area: ''
+});
+
+const dataList = ref([]);
+const pagination = reactive({
+ currentPage: 1,
+ pageSize: 10,
+ total: 0
+});
+
+const multipleList = ref([]);
+const dialogVisible = ref(false);
+const dialogTitle = ref('鏂板闂ㄧ');
+const formData = reactive({
+ id: '',
+ name: '',
+ area: '',
+ location: '',
+ status: '1',
+ description: ''
+});
+
+// 琛ㄦ牸鍒楅厤缃�
+const columns = [
+ {
+ label: '闂ㄧ鍚嶇О',
+ align: 'center',
+ prop: 'name',
+ },
+ {
+ label: '鍖哄煙',
+ align: 'center',
+ prop: 'area',
+ },
+ {
+ label: '浣嶇疆',
+ align: 'center',
+ prop: 'location',
+ },
+ {
+ label: '鐘舵��',
+ align: 'center',
+ prop: 'status',
+ formatter: (row) => {
+ return row.status === '1' ? '<el-tag type="success">姝e父</el-tag>' : '<el-tag type="danger">寮傚父</el-tag>';
+ }
+ },
+ {
+ label: '鏈�鍚庢洿鏂�',
+ align: 'center',
+ prop: 'lastUpdate',
+ },
+ {
+ label: '鎻忚堪',
+ align: 'center',
+ prop: 'description',
+ },
+ {
+ dataType: 'action',
+ label: '鎿嶄綔',
+ align: 'center',
+ fixed: 'right',
+ width: 140,
+ operation: [
+ {
+ name: '缂栬緫',
+ type: 'text',
+ clickFun: (row) => {
+ edit(row);
+ },
+ },
+ {
+ name: '鍒犻櫎',
+ type: 'text',
+ clickFun: (row) => {
+ deleteRow(row.id);
+ },
+ },
+ ],
+ },
+];
+
+// 杩囨护鍚庣殑鏁版嵁
+const filteredData = computed(() => {
+ return mockData.filter(item => {
+ const nameMatch = !filters.name || item.name.includes(filters.name);
+ const statusMatch = !filters.status || item.status === filters.status;
+ const areaMatch = !filters.area || item.area.includes(filters.area);
+ return nameMatch && statusMatch && areaMatch;
+ });
+});
+
+// 鑾峰彇琛ㄦ牸鏁版嵁
+const getTableData = () => {
+ pagination.total = filteredData.value.length;
+ const start = (pagination.currentPage - 1) * pagination.pageSize;
+ const end = start + pagination.pageSize;
+ dataList.value = filteredData.value.slice(start, end);
+};
+
+// 閲嶇疆杩囨护鍣�
+const resetFilters = () => {
+ filters.name = '';
+ filters.status = '';
+ filters.area = '';
+ pagination.currentPage = 1;
+ getTableData();
+};
+
+// 鍒嗛〉鍙樺寲
+const changePage = ({ page, limit }) => {
+ pagination.currentPage = page;
+ pagination.pageSize = limit;
+ getTableData();
+};
+
+// 澶氶�夊鐞�
+const handleSelectionChange = (selectionList) => {
+ multipleList.value = selectionList;
+};
+
+// 鏂板
+const add = () => {
+ dialogTitle.value = '鏂板闂ㄧ';
+ formData.id = '';
+ formData.name = '';
+ formData.area = '';
+ formData.location = '';
+ formData.status = '1';
+ formData.description = '';
+ dialogVisible.value = true;
+};
+
+// 缂栬緫
+const edit = (row) => {
+ dialogTitle.value = '缂栬緫闂ㄧ';
+ formData.id = row.id;
+ formData.name = row.name;
+ formData.area = row.area;
+ formData.location = row.location;
+ formData.status = row.status;
+ formData.description = row.description;
+ dialogVisible.value = true;
+};
+
+// 淇濆瓨鏁版嵁
+const saveData = () => {
+ if (!formData.name || !formData.area || !formData.location) {
+ ElMessage.warning('璇峰~鍐欏繀濉瓧娈�');
+ return;
+ }
+
+ const currentTime = dayjs().format('YYYY-MM-DD HH:mm:ss');
+ if (formData.id) {
+ // 缂栬緫
+ const index = mockData.findIndex(item => item.id === formData.id);
+ if (index !== -1) {
+ mockData[index] = {
+ ...formData,
+ lastUpdate: currentTime
+ };
+ ElMessage.success('缂栬緫鎴愬姛');
+ }
+ } else {
+ // 鏂板
+ const newId = Math.max(...mockData.map(item => item.id), 0) + 1;
+ mockData.unshift({
+ ...formData,
+ id: newId,
+ lastUpdate: currentTime
+ });
+ ElMessage.success('鏂板鎴愬姛');
+ }
+ dialogVisible.value = false;
+ getTableData();
+};
+
+// 鍒犻櫎
+const deleteRow = (id) => {
+ ElMessageBox.confirm('姝ゆ搷浣滃皢姘镐箙鍒犻櫎璇ラ棬绂佽褰�, 鏄惁缁х画?', '鎻愮ず', {
+ confirmButtonText: '纭畾',
+ cancelButtonText: '鍙栨秷',
+ type: 'warning',
+ }).then(() => {
+ const index = mockData.findIndex(item => item.id === id);
+ if (index !== -1) {
+ mockData.splice(index, 1);
+ ElMessage.success('鍒犻櫎鎴愬姛');
+ getTableData();
+ }
+ }).catch(() => {});
+};
+
+// 鎵归噺鍒犻櫎
+const batchDelete = () => {
+ if (multipleList.value.length === 0) {
+ ElMessage.warning('璇烽�夋嫨瑕佸垹闄ょ殑鏁版嵁');
+ return;
+ }
+
+ ElMessageBox.confirm('姝ゆ搷浣滃皢姘镐箙鍒犻櫎鎵�閫夐棬绂佽褰�, 鏄惁缁х画?', '鎻愮ず', {
+ confirmButtonText: '纭畾',
+ cancelButtonText: '鍙栨秷',
+ type: 'warning',
+ }).then(() => {
+ const ids = multipleList.value.map(item => item.id);
+ mockData.forEach((item, index) => {
+ if (ids.includes(item.id)) {
+ mockData.splice(index, 1);
+ }
+ });
+ ElMessage.success('鍒犻櫎鎴愬姛');
+ getTableData();
+ multipleList.value = [];
+ }).catch(() => {});
+};
+
+// 鍒濆鍖栨暟鎹�
+onMounted(() => {
+ getTableData();
+});
+</script>
+
+<style lang="scss" scoped>
+.table_list {
+ margin-top: unset;
+}
+.actions {
+ display: flex;
+ justify-content: space-between;
+ margin-bottom: 10px;
+}
+</style>
diff --git a/src/views/environmentAccess/intelligentInspectionManagement/index.vue b/src/views/environmentAccess/intelligentInspectionManagement/index.vue
new file mode 100644
index 0000000..47fe867
--- /dev/null
+++ b/src/views/environmentAccess/intelligentInspectionManagement/index.vue
@@ -0,0 +1,581 @@
+<template>
+ <div class="app-container">
+ <el-form :model="filters" :inline="true">
+ <el-form-item label="宸℃鍚嶇О">
+ <el-input
+ v-model="filters.name"
+ style="width: 240px"
+ placeholder="璇疯緭鍏ュ贰妫�鍚嶇О"
+ clearable
+ :prefix-icon="Search"
+ @change="getTableData"
+ />
+ </el-form-item>
+ <el-form-item label="宸℃鐘舵��">
+ <el-select
+ v-model="filters.status"
+ style="width: 240px"
+ placeholder="璇烽�夋嫨宸℃鐘舵��"
+ clearable
+ @change="getTableData"
+ >
+ <el-option label="寰呮墽琛�" value="0"></el-option>
+ <el-option label="鎵ц涓�" value="1"></el-option>
+ <el-option label="宸插畬鎴�" value="2"></el-option>
+ <el-option label="宸插彇娑�" value="3"></el-option>
+ </el-select>
+ </el-form-item>
+ <el-form-item label="宸℃绫诲瀷">
+ <el-select
+ v-model="filters.type"
+ style="width: 240px"
+ placeholder="璇烽�夋嫨宸℃绫诲瀷"
+ clearable
+ @change="getTableData"
+ >
+ <el-option label="瀹氭湡宸℃" value="0"></el-option>
+ <el-option label="涓存椂宸℃" value="1"></el-option>
+ <el-option label="鏁呴殰宸℃" value="2"></el-option>
+ </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
+ type="danger"
+ icon="Delete"
+ :disabled="multipleList.length <= 0"
+ @click="batchDelete"
+ >鎵归噺鍒犻櫎</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"
+ >
+ </PIMTable>
+ </div>
+ <!-- 鏂板缂栬緫寮圭獥 -->
+ <el-dialog v-model="dialogVisible" :title="dialogTitle" width="600px">
+ <el-form :model="formData" label-width="100px">
+ <el-form-item label="宸℃鍚嶇О" required>
+ <el-input
+ v-model="formData.name"
+ placeholder="璇疯緭鍏ュ贰妫�鍚嶇О"
+ ></el-input>
+ </el-form-item>
+ <el-form-item label="宸℃绫诲瀷" required>
+ <el-select v-model="formData.type" placeholder="璇烽�夋嫨宸℃绫诲瀷">
+ <el-option label="瀹氭湡宸℃" value="0"></el-option>
+ <el-option label="涓存椂宸℃" value="1"></el-option>
+ <el-option label="鏁呴殰宸℃" value="2"></el-option>
+ </el-select>
+ </el-form-item>
+ <el-form-item label="宸℃鍖哄煙" required>
+ <el-input
+ v-model="formData.area"
+ placeholder="璇疯緭鍏ュ贰妫�鍖哄煙"
+ ></el-input>
+ </el-form-item>
+ <el-form-item label="宸℃浜哄憳" required>
+ <el-input
+ v-model="formData.inspector"
+ placeholder="璇疯緭鍏ュ贰妫�浜哄憳"
+ ></el-input>
+ </el-form-item>
+ <el-form-item label="璁″垝寮�濮嬫椂闂�" required>
+ <el-date-picker
+ v-model="formData.planStartTime"
+ type="datetime"
+ placeholder="璇烽�夋嫨璁″垝寮�濮嬫椂闂�"
+ style="width: 100%"
+ ></el-date-picker>
+ </el-form-item>
+ <el-form-item label="璁″垝缁撴潫鏃堕棿" required>
+ <el-date-picker
+ v-model="formData.planEndTime"
+ type="datetime"
+ placeholder="璇烽�夋嫨璁″垝缁撴潫鏃堕棿"
+ style="width: 100%"
+ ></el-date-picker>
+ </el-form-item>
+ <el-form-item label="宸℃鍐呭">
+ <el-input
+ v-model="formData.content"
+ type="textarea"
+ rows="3"
+ placeholder="璇疯緭鍏ュ贰妫�鍐呭"
+ ></el-input>
+ </el-form-item>
+ </el-form>
+ <template #footer>
+ <span class="dialog-footer">
+ <el-button @click="dialogVisible = false">鍙栨秷</el-button>
+ <el-button type="primary" @click="saveData">纭畾</el-button>
+ </span>
+ </template>
+ </el-dialog>
+ </div>
+</template>
+
+<script setup>
+import { ref, reactive, computed, onMounted } from "vue";
+import { Search, Plus, Delete } from "@element-plus/icons-vue";
+import { ElMessage, ElMessageBox } from "element-plus";
+import dayjs from "dayjs";
+
+// 瀹氫箟鍋囨暟鎹�
+const mockData = [
+ {
+ id: 1,
+ name: "涓滈棬鍖哄煙鏃ュ父宸℃",
+ type: "0",
+ status: "2",
+ planStartTime: "2025-12-30 08:00:00",
+ planEndTime: "2025-12-30 09:00:00",
+ actualStartTime: "2025-12-30 08:05:00",
+ actualEndTime: "2025-12-30 08:55:00",
+ inspector: "寮犱笁",
+ area: "涓滈棬鍖哄煙",
+ content: "妫�鏌ラ棬绂佽澶囪繍琛岀姸鎬併�佺幆澧冪洃娴嬭澶囨暟鎹�",
+ },
+ {
+ id: 2,
+ name: "瑗块棬鍖哄煙涓存椂宸℃",
+ type: "1",
+ status: "2",
+ planStartTime: "2025-12-30 10:00:00",
+ planEndTime: "2025-12-30 11:00:00",
+ actualStartTime: "2025-12-30 10:00:00",
+ actualEndTime: "2025-12-30 10:45:00",
+ inspector: "鏉庡洓",
+ area: "瑗块棬鍖哄煙",
+ content: "妫�鏌ュ紓甯告姤璀﹁澶�",
+ },
+ {
+ id: 3,
+ name: "鍗楅棬闂ㄧ鏁呴殰宸℃",
+ type: "2",
+ status: "1",
+ planStartTime: "2025-12-30 13:00:00",
+ planEndTime: "2025-12-30 14:00:00",
+ actualStartTime: "2025-12-30 13:10:00",
+ actualEndTime: "",
+ inspector: "鐜嬩簲",
+ area: "鍗楅棬鍖哄煙",
+ content: "淇闂ㄧ鏁呴殰",
+ },
+ {
+ id: 4,
+ name: "涓帶瀹ゅ畾鏈熷贰妫�",
+ type: "0",
+ status: "0",
+ planStartTime: "2025-12-31 09:00:00",
+ planEndTime: "2025-12-31 10:00:00",
+ actualStartTime: "",
+ actualEndTime: "",
+ inspector: "璧靛叚",
+ area: "涓帶鍖哄煙",
+ content: "妫�鏌ョ洃鎺ц澶囥�佹湇鍔″櫒杩愯鐘舵��",
+ },
+ {
+ id: 5,
+ name: "鍖楅棬鍖哄煙鏃ュ父宸℃",
+ type: "0",
+ status: "2",
+ planStartTime: "2025-12-30 15:00:00",
+ planEndTime: "2025-12-30 16:00:00",
+ actualStartTime: "2025-12-30 15:00:00",
+ actualEndTime: "2025-12-30 15:50:00",
+ inspector: "寮犱笁",
+ area: "鍖楅棬鍖哄煙",
+ content: "妫�鏌ヨ溅杈嗚瘑鍒澶囥�侀亾闂歌繍琛岀姸鎬�",
+ },
+];
+
+// 鍝嶅簲寮忔暟鎹�
+const filters = reactive({
+ name: "",
+ status: "",
+ type: "",
+});
+
+const dataList = ref([]);
+const pagination = reactive({
+ currentPage: 1,
+ pageSize: 10,
+ total: 0,
+});
+
+const multipleList = ref([]);
+const dialogVisible = ref(false);
+const dialogTitle = ref("鏂板宸℃");
+const formData = reactive({
+ id: "",
+ name: "",
+ type: "0",
+ status: "0",
+ planStartTime: "",
+ planEndTime: "",
+ actualStartTime: "",
+ actualEndTime: "",
+ inspector: "",
+ area: "",
+ content: "",
+});
+
+// 鐘舵�佹槧灏�
+const statusMap = {
+ '0': '<el-tag type="warning">寰呮墽琛�</el-tag>',
+ '1': '<el-tag type="primary">鎵ц涓�</el-tag>',
+ '2': '<el-tag type="success">宸插畬鎴�</el-tag>',
+ '3': '<el-tag type="danger">宸插彇娑�</el-tag>',
+};
+
+// 绫诲瀷鏄犲皠
+const typeMap = {
+ 0: "瀹氭湡宸℃",
+ 1: "涓存椂宸℃",
+ 2: "鏁呴殰宸℃",
+};
+
+// 琛ㄦ牸鍒楅厤缃�
+const columns = [
+ {
+ label: "宸℃鍚嶇О",
+ align: "center",
+ prop: "name",
+ },
+ {
+ label: "宸℃绫诲瀷",
+ align: "center",
+ prop: "type",
+ formatter: (row) => {
+ return typeMap[row.type];
+ },
+ },
+ {
+ label: "宸℃鐘舵��",
+ align: "center",
+ prop: "status",
+ formatter: (row) => {
+ return statusMap[row.status];
+ },
+ },
+ {
+ label: "璁″垝寮�濮嬫椂闂�",
+ align: "center",
+ prop: "planStartTime",
+ },
+ {
+ label: "璁″垝缁撴潫鏃堕棿",
+ align: "center",
+ prop: "planEndTime",
+ },
+ {
+ label: "瀹為檯寮�濮嬫椂闂�",
+ align: "center",
+ prop: "actualStartTime",
+ formatter: (row) => {
+ return row.actualStartTime || "-";
+ },
+ },
+ {
+ label: "瀹為檯缁撴潫鏃堕棿",
+ align: "center",
+ prop: "actualEndTime",
+ formatter: (row) => {
+ return row.actualEndTime || "-";
+ },
+ },
+ {
+ label: "宸℃浜哄憳",
+ align: "center",
+ prop: "inspector",
+ },
+ {
+ label: "宸℃鍖哄煙",
+ align: "center",
+ prop: "area",
+ },
+ {
+ dataType: "action",
+ label: "鎿嶄綔",
+ align: "center",
+ fixed: "right",
+ width: 180,
+ operation: [
+ {
+ name: "缂栬緫",
+ type: "text",
+ clickFun: (row) => {
+ edit(row);
+ },
+ },
+ {
+ name: "寮�濮嬫墽琛�",
+ type: "text",
+ clickFun: (row) => {
+ startInspection(row);
+ },
+ visible: (row) => {
+ return row.status === "0";
+ },
+ },
+ {
+ name: "瀹屾垚宸℃",
+ type: "text",
+ clickFun: (row) => {
+ completeInspection(row);
+ },
+ visible: (row) => {
+ return row.status === "1";
+ },
+ },
+ {
+ name: "鍙栨秷宸℃",
+ type: "text",
+ clickFun: (row) => {
+ cancelInspection(row);
+ },
+ visible: (row) => {
+ return ["0", "1"].includes(row.status);
+ },
+ },
+ {
+ name: "鍒犻櫎",
+ type: "text",
+ clickFun: (row) => {
+ deleteRow(row.id);
+ },
+ },
+ ],
+ },
+];
+
+// 杩囨护鍚庣殑鏁版嵁
+const filteredData = computed(() => {
+ return mockData.filter((item) => {
+ const nameMatch = !filters.name || item.name.includes(filters.name);
+ const statusMatch = !filters.status || item.status === filters.status;
+ const typeMatch = !filters.type || item.type === filters.type;
+ return nameMatch && statusMatch && typeMatch;
+ });
+});
+
+// 鑾峰彇琛ㄦ牸鏁版嵁
+const getTableData = () => {
+ pagination.total = filteredData.value.length;
+ const start = (pagination.currentPage - 1) * pagination.pageSize;
+ const end = start + pagination.pageSize;
+ dataList.value = filteredData.value.slice(start, end);
+};
+
+// 閲嶇疆杩囨护鍣�
+const resetFilters = () => {
+ filters.name = "";
+ filters.status = "";
+ filters.type = "";
+ pagination.currentPage = 1;
+ getTableData();
+};
+
+// 鍒嗛〉鍙樺寲
+const changePage = ({ page, limit }) => {
+ pagination.currentPage = page;
+ pagination.pageSize = limit;
+ getTableData();
+};
+
+// 澶氶�夊鐞�
+const handleSelectionChange = (selectionList) => {
+ multipleList.value = selectionList;
+};
+
+// 鏂板
+const add = () => {
+ dialogTitle.value = "鏂板宸℃";
+ formData.id = "";
+ formData.name = "";
+ formData.type = "0";
+ formData.status = "0";
+ formData.planStartTime = "";
+ formData.planEndTime = "";
+ formData.actualStartTime = "";
+ formData.actualEndTime = "";
+ formData.inspector = "";
+ formData.area = "";
+ formData.content = "";
+ dialogVisible.value = true;
+};
+
+// 缂栬緫
+const edit = (row) => {
+ dialogTitle.value = "缂栬緫宸℃";
+ formData.id = row.id;
+ formData.name = row.name;
+ formData.type = row.type;
+ formData.status = row.status;
+ formData.planStartTime = row.planStartTime;
+ formData.planEndTime = row.planEndTime;
+ formData.actualStartTime = row.actualStartTime;
+ formData.actualEndTime = row.actualEndTime;
+ formData.inspector = row.inspector;
+ formData.area = row.area;
+ formData.content = row.content;
+ dialogVisible.value = true;
+};
+
+// 淇濆瓨鏁版嵁
+const saveData = () => {
+ if (
+ !formData.name ||
+ !formData.planStartTime ||
+ !formData.planEndTime ||
+ !formData.inspector ||
+ !formData.area
+ ) {
+ ElMessage.warning("璇峰~鍐欏繀濉瓧娈�");
+ return;
+ }
+
+ if (formData.id) {
+ // 缂栬緫
+ const index = mockData.findIndex((item) => item.id === formData.id);
+ if (index !== -1) {
+ mockData[index] = {
+ ...mockData[index],
+ ...formData,
+ };
+ ElMessage.success("缂栬緫鎴愬姛");
+ }
+ } else {
+ // 鏂板
+ const newId = Math.max(...mockData.map((item) => item.id), 0) + 1;
+ mockData.unshift({
+ ...formData,
+ id: newId,
+ });
+ ElMessage.success("鏂板鎴愬姛");
+ }
+ dialogVisible.value = false;
+ getTableData();
+};
+
+// 寮�濮嬪贰妫�
+const startInspection = (row) => {
+ const index = mockData.findIndex((item) => item.id === row.id);
+ if (index !== -1) {
+ mockData[index].status = "1";
+ mockData[index].actualStartTime = dayjs().format("YYYY-MM-DD HH:mm:ss");
+ ElMessage.success("宸℃宸插紑濮�");
+ getTableData();
+ }
+};
+
+// 瀹屾垚宸℃
+const completeInspection = (row) => {
+ const index = mockData.findIndex((item) => item.id === row.id);
+ if (index !== -1) {
+ mockData[index].status = "2";
+ mockData[index].actualEndTime = dayjs().format("YYYY-MM-DD HH:mm:ss");
+ ElMessage.success("宸℃宸插畬鎴�");
+ getTableData();
+ }
+};
+
+// 鍙栨秷宸℃
+const cancelInspection = (row) => {
+ ElMessageBox.confirm("纭畾瑕佸彇娑堣宸℃鍚�?", "鎻愮ず", {
+ confirmButtonText: "纭畾",
+ cancelButtonText: "鍙栨秷",
+ type: "warning",
+ })
+ .then(() => {
+ const index = mockData.findIndex((item) => item.id === row.id);
+ if (index !== -1) {
+ mockData[index].status = "3";
+ ElMessage.success("宸℃宸插彇娑�");
+ getTableData();
+ }
+ })
+ .catch(() => {});
+};
+
+// 鍒犻櫎
+const deleteRow = (id) => {
+ ElMessageBox.confirm("姝ゆ搷浣滃皢姘镐箙鍒犻櫎璇ュ贰妫�璁板綍, 鏄惁缁х画?", "鎻愮ず", {
+ confirmButtonText: "纭畾",
+ cancelButtonText: "鍙栨秷",
+ type: "warning",
+ })
+ .then(() => {
+ const index = mockData.findIndex((item) => item.id === id);
+ if (index !== -1) {
+ mockData.splice(index, 1);
+ ElMessage.success("鍒犻櫎鎴愬姛");
+ getTableData();
+ }
+ })
+ .catch(() => {});
+};
+
+// 鎵归噺鍒犻櫎
+const batchDelete = () => {
+ if (multipleList.value.length === 0) {
+ ElMessage.warning("璇烽�夋嫨瑕佸垹闄ょ殑鏁版嵁");
+ return;
+ }
+
+ ElMessageBox.confirm("姝ゆ搷浣滃皢姘镐箙鍒犻櫎鎵�閫夊贰妫�璁板綍, 鏄惁缁х画?", "鎻愮ず", {
+ confirmButtonText: "纭畾",
+ cancelButtonText: "鍙栨秷",
+ type: "warning",
+ })
+ .then(() => {
+ const ids = multipleList.value.map((item) => item.id);
+ mockData.forEach((item, index) => {
+ if (ids.includes(item.id)) {
+ mockData.splice(index, 1);
+ }
+ });
+ ElMessage.success("鍒犻櫎鎴愬姛");
+ getTableData();
+ multipleList.value = [];
+ })
+ .catch(() => {});
+};
+
+// 鍒濆鍖栨暟鎹�
+onMounted(() => {
+ getTableData();
+});
+</script>
+
+<style lang="scss" scoped>
+.table_list {
+ margin-top: unset;
+}
+.actions {
+ display: flex;
+ justify-content: space-between;
+ margin-bottom: 10px;
+}
+</style>
diff --git a/src/views/environmentAccess/remoteMonitoringOfEquipment/index.vue b/src/views/environmentAccess/remoteMonitoringOfEquipment/index.vue
new file mode 100644
index 0000000..bdd0a7b
--- /dev/null
+++ b/src/views/environmentAccess/remoteMonitoringOfEquipment/index.vue
@@ -0,0 +1,633 @@
+<template>
+ <div class="app-container">
+ <el-form :model="filters" :inline="true">
+ <el-form-item label="璁惧鍚嶇О">
+ <el-input
+ v-model="filters.name"
+ style="width: 240px"
+ placeholder="璇疯緭鍏ヨ澶囧悕绉�"
+ clearable
+ :prefix-icon="Search"
+ @change="getTableData"
+ />
+ </el-form-item>
+ <el-form-item label="璁惧鐘舵��">
+ <el-select
+ v-model="filters.status"
+ style="width: 240px"
+ placeholder="璇烽�夋嫨璁惧鐘舵��"
+ clearable
+ @change="getTableData"
+ >
+ <el-option label="鍦ㄧ嚎" value="1"></el-option>
+ <el-option label="绂荤嚎" value="0"></el-option>
+ <el-option label="鏁呴殰" value="2"></el-option>
+ </el-select>
+ </el-form-item>
+ <el-form-item label="璁惧绫诲瀷">
+ <el-select
+ v-model="filters.type"
+ style="width: 240px"
+ placeholder="璇烽�夋嫨璁惧绫诲瀷"
+ clearable
+ @change="getTableData"
+ >
+ <el-option label="闂ㄧ璁惧" value="0"></el-option>
+ <el-option label="鐜鐩戞祴璁惧" value="1"></el-option>
+ <el-option label="瑙嗛鐩戞帶璁惧" value="2"></el-option>
+ <el-option label="鏅鸿兘閬撻椄" value="3"></el-option>
+ </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="monitor-container">
+ <div class="monitor-stats">
+ <div class="stat-card">
+ <div class="stat-title">鍦ㄧ嚎璁惧</div>
+ <div class="stat-value online">{{ onlineCount }}</div>
+ </div>
+ <div class="stat-card">
+ <div class="stat-title">绂荤嚎璁惧</div>
+ <div class="stat-value offline">{{ offlineCount }}</div>
+ </div>
+ <div class="stat-card">
+ <div class="stat-title">鏁呴殰璁惧</div>
+ <div class="stat-value error">{{ errorCount }}</div>
+ </div>
+ <div class="stat-card">
+ <div class="stat-title">璁惧鎬绘暟</div>
+ <div class="stat-value total">{{ totalCount }}</div>
+ </div>
+ </div>
+
+ <div class="table_list">
+ <PIMTable
+ rowKey="id"
+ :column="columns"
+ :tableData="dataList"
+ :page="{
+ current: pagination.currentPage,
+ size: pagination.pageSize,
+ total: pagination.total,
+ }"
+ @pagination="changePage"
+ >
+ </PIMTable>
+ </div>
+ </div>
+
+ <!-- 璁惧璇︽儏寮圭獥 -->
+ <el-dialog
+ v-model="detailVisible"
+ :title="`璁惧璇︽儏 - ${selectedDevice.name}`"
+ width="800px"
+ >
+ <div v-if="selectedDevice" class="device-detail">
+ <div class="detail-section">
+ <h3>鍩烘湰淇℃伅</h3>
+ <el-descriptions :column="2" border>
+ <el-descriptions-item label="璁惧鍚嶇О">{{
+ selectedDevice.name
+ }}</el-descriptions-item>
+ <el-descriptions-item label="璁惧绫诲瀷">{{
+ typeMap[selectedDevice.type]
+ }}</el-descriptions-item>
+ <el-descriptions-item label="璁惧鐘舵��">{{
+ statusMap[selectedDevice.status]
+ }}</el-descriptions-item>
+ <el-descriptions-item label="璁惧缂栧彿">{{
+ selectedDevice.code
+ }}</el-descriptions-item>
+ <el-descriptions-item label="瀹夎浣嶇疆">{{
+ selectedDevice.location
+ }}</el-descriptions-item>
+ <el-descriptions-item label="瀹夎鏃堕棿">{{
+ selectedDevice.installTime
+ }}</el-descriptions-item>
+ <el-descriptions-item label="鏈�鍚庣淮鎶ゆ椂闂�">{{
+ selectedDevice.maintainTime
+ }}</el-descriptions-item>
+ <el-descriptions-item label="璐d换浜�">{{
+ selectedDevice.responsiblePerson
+ }}</el-descriptions-item>
+ </el-descriptions>
+ </div>
+
+ <div class="detail-section">
+ <h3>瀹炴椂鏁版嵁</h3>
+ <div class="realtime-data">
+ <div
+ v-for="data in selectedDevice.realtimeData"
+ :key="data.name"
+ class="data-item"
+ >
+ <div class="data-name">{{ data.name }}</div>
+ <div class="data-value">{{ data.value }}{{ data.unit }}</div>
+ <div class="data-status" :class="data.status">
+ {{ data.status === "normal" ? "姝e父" : "寮傚父" }}
+ </div>
+ </div>
+ </div>
+ </div>
+
+ <div class="detail-section">
+ <h3>鎺у埗鎿嶄綔</h3>
+ <div class="control-buttons">
+ <el-button
+ type="primary"
+ @click="controlDevice('restart')"
+ :disabled="selectedDevice.status !== '1'"
+ >閲嶅惎璁惧</el-button
+ >
+ <el-button
+ type="warning"
+ @click="controlDevice('reset')"
+ :disabled="selectedDevice.status !== '1'"
+ >閲嶇疆璁惧</el-button
+ >
+ <el-button
+ type="danger"
+ @click="controlDevice('shutdown')"
+ :disabled="selectedDevice.status !== '1'"
+ >鍏抽棴璁惧</el-button
+ >
+ <el-button
+ type="success"
+ @click="controlDevice('update')"
+ :disabled="selectedDevice.status !== '1'"
+ >鍥轰欢鏇存柊</el-button
+ >
+ </div>
+ </div>
+ </div>
+ <template #footer>
+ <span class="dialog-footer">
+ <el-button @click="detailVisible = false">鍏抽棴</el-button>
+ </span>
+ </template>
+ </el-dialog>
+ </div>
+</template>
+
+<script setup>
+import { ref, reactive, computed, onMounted } from "vue";
+import {
+ Search
+} from "@element-plus/icons-vue";
+import { ElMessage, ElMessageBox } from "element-plus";
+
+// 瀹氫箟鍋囨暟鎹�
+const mockDevices = [
+ {
+ id: 1,
+ name: "涓滈棬闂ㄧ鎺у埗鍣�",
+ code: "DEV-20250001",
+ type: "0",
+ status: "1",
+ location: "涓滈棬鍏ュ彛",
+ installTime: "2025-01-15",
+ maintainTime: "2025-12-20",
+ responsiblePerson: "寮犱笁",
+ realtimeData: [
+ { name: "娓╁害", value: 23.5, unit: "鈩�", status: "normal" },
+ { name: "婀垮害", value: 45, unit: "%", status: "normal" },
+ { name: "杩愯鏃堕暱", value: 12560, unit: "h", status: "normal" },
+ { name: "闂ㄧ寮�鍏虫鏁�", value: 234, unit: "娆�/澶�", status: "normal" },
+ ],
+ },
+ {
+ id: 2,
+ name: "瑗块棬瑙嗛鐩戞帶",
+ code: "DEV-20250002",
+ type: "2",
+ status: "1",
+ location: "瑗块棬鍑哄彛",
+ installTime: "2025-02-20",
+ maintainTime: "2025-11-15",
+ responsiblePerson: "鏉庡洓",
+ realtimeData: [
+ { name: "瑙嗛娓呮櫚搴�", value: 1080, unit: "P", status: "normal" },
+ { name: "甯х巼", value: 25, unit: "fps", status: "normal" },
+ { name: "瀛樺偍绌洪棿", value: 78, unit: "%", status: "warning" },
+ { name: "缃戠粶寤惰繜", value: 12, unit: "ms", status: "normal" },
+ ],
+ },
+ {
+ id: 3,
+ name: "鍗楅棬鐜鐩戞祴浠�",
+ code: "DEV-20250003",
+ type: "1",
+ status: "2",
+ location: "鍗楅棬骞垮満",
+ installTime: "2025-03-10",
+ maintainTime: "2025-10-05",
+ responsiblePerson: "鐜嬩簲",
+ realtimeData: [
+ { name: "PM2.5", value: 85, unit: "渭g/m鲁", status: "error" },
+ { name: "PM10", value: 120, unit: "渭g/m鲁", status: "error" },
+ { name: "鍣0", value: 65, unit: "dB", status: "normal" },
+ { name: "椋庨��", value: 1.2, unit: "m/s", status: "normal" },
+ ],
+ },
+ {
+ id: 4,
+ name: "鍖楅棬鏅鸿兘閬撻椄",
+ code: "DEV-20250004",
+ type: "3",
+ status: "0",
+ location: "鍖楅棬鍏ュ彛",
+ installTime: "2025-04-05",
+ maintainTime: "2025-09-20",
+ responsiblePerson: "璧靛叚",
+ realtimeData: [
+ { name: "寮�鍏虫鏁�", value: 156, unit: "娆�/澶�", status: "normal" },
+ { name: "杩愯鐢靛帇", value: 220, unit: "V", status: "normal" },
+ { name: "杩愯鐢垫祦", value: 5.2, unit: "A", status: "normal" },
+ { name: "鏁呴殰浠g爜", value: "E001", unit: "", status: "error" },
+ ],
+ },
+ {
+ id: 5,
+ name: "涓帶瀹ゆ湇鍔″櫒",
+ code: "DEV-20250005",
+ type: "1",
+ status: "1",
+ location: "涓帶瀹�",
+ installTime: "2025-05-15",
+ maintainTime: "2025-12-01",
+ responsiblePerson: "瀛欎竷",
+ realtimeData: [
+ { name: "CPU浣跨敤鐜�", value: 45, unit: "%", status: "normal" },
+ { name: "鍐呭瓨浣跨敤鐜�", value: 68, unit: "%", status: "warning" },
+ { name: "纾佺洏浣跨敤鐜�", value: 82, unit: "%", status: "warning" },
+ { name: "缃戠粶娴侀噺", value: 125, unit: "Mbps", status: "normal" },
+ ],
+ },
+];
+
+// 鍝嶅簲寮忔暟鎹�
+const filters = reactive({
+ name: "",
+ status: "",
+ type: "",
+});
+
+const dataList = ref([]);
+const pagination = reactive({
+ currentPage: 1,
+ pageSize: 10,
+ total: 0,
+});
+
+const detailVisible = ref(false);
+const selectedDevice = ref({});
+
+// 鐘舵�佹槧灏�
+const statusMap = {
+ 0: '<el-tag type="warning">绂荤嚎</el-tag>',
+ 1: '<el-tag type="success">鍦ㄧ嚎</el-tag>',
+ 2: '<el-tag type="danger">鏁呴殰</el-tag>',
+};
+
+// 绫诲瀷鏄犲皠
+const typeMap = {
+ 0: "闂ㄧ璁惧",
+ 1: "鐜鐩戞祴璁惧",
+ 2: "瑙嗛鐩戞帶璁惧",
+ 3: "鏅鸿兘閬撻椄",
+};
+
+// 璁惧鏁伴噺缁熻
+const onlineCount = computed(() => {
+ return mockDevices.filter((device) => device.status === "1").length;
+});
+
+const offlineCount = computed(() => {
+ return mockDevices.filter((device) => device.status === "0").length;
+});
+
+const errorCount = computed(() => {
+ return mockDevices.filter((device) => device.status === "2").length;
+});
+
+const totalCount = computed(() => {
+ return mockDevices.length;
+});
+
+// 琛ㄦ牸鍒楅厤缃�
+const columns = [
+ {
+ label: "璁惧鍚嶇О",
+ align: "center",
+ prop: "name",
+ },
+ {
+ label: "璁惧缂栧彿",
+ align: "center",
+ prop: "code",
+ },
+ {
+ label: "璁惧绫诲瀷",
+ align: "center",
+ prop: "type",
+ formatter: (row) => {
+ return typeMap[row.type];
+ },
+ },
+ {
+ label: "璁惧鐘舵��",
+ align: "center",
+ prop: "status",
+ formatter: (row) => {
+ return statusMap[row.status];
+ },
+ },
+ {
+ label: "瀹夎浣嶇疆",
+ align: "center",
+ prop: "location",
+ },
+ {
+ label: "璐d换浜�",
+ align: "center",
+ prop: "responsiblePerson",
+ },
+ {
+ label: "鏈�鍚庣淮鎶ゆ椂闂�",
+ align: "center",
+ prop: "maintainTime",
+ },
+ {
+ dataType: "action",
+ label: "鎿嶄綔",
+ align: "center",
+ fixed: "right",
+ width: 140,
+ operation: [
+ {
+ name: "鏌ョ湅璇︽儏",
+ type: "text",
+ clickFun: (row) => {
+ viewDetail(row);
+ },
+ },
+ {
+ name: "瀹炴椂鐩戞帶",
+ type: "text",
+ clickFun: (row) => {
+ realtimeMonitor(row);
+ },
+ visible: (row) => {
+ return row.status === "1";
+ },
+ },
+ {
+ name: "閲嶅惎璁惧",
+ type: "text",
+ clickFun: (row) => {
+ controlDevice("restart", row);
+ },
+ visible: (row) => {
+ return row.status === "1";
+ },
+ },
+ ],
+ },
+];
+
+// 杩囨护鍚庣殑鏁版嵁
+const filteredData = computed(() => {
+ return mockDevices.filter((item) => {
+ const nameMatch = !filters.name || item.name.includes(filters.name);
+ const statusMatch = !filters.status || item.status === filters.status;
+ const typeMatch = !filters.type || item.type === filters.type;
+ return nameMatch && statusMatch && typeMatch;
+ });
+});
+
+// 鑾峰彇琛ㄦ牸鏁版嵁
+const getTableData = () => {
+ pagination.total = filteredData.value.length;
+ const start = (pagination.currentPage - 1) * pagination.pageSize;
+ const end = start + pagination.pageSize;
+ dataList.value = filteredData.value.slice(start, end);
+};
+
+// 閲嶇疆杩囨护鍣�
+const resetFilters = () => {
+ filters.name = "";
+ filters.status = "";
+ filters.type = "";
+ pagination.currentPage = 1;
+ getTableData();
+};
+
+// 鍒嗛〉鍙樺寲
+const changePage = ({ page, limit }) => {
+ pagination.currentPage = page;
+ pagination.pageSize = limit;
+ getTableData();
+};
+
+// 鏌ョ湅璁惧璇︽儏
+const viewDetail = (row) => {
+ selectedDevice.value = row;
+ detailVisible.value = true;
+};
+
+// 瀹炴椂鐩戞帶
+const realtimeMonitor = (row) => {
+ ElMessage.info(`姝e湪璺宠浆鍒�${row.name}鐨勫疄鏃剁洃鎺ч〉闈�...`);
+ // 杩欓噷鍙互璺宠浆鍒板疄鏃剁洃鎺ч〉闈㈡垨鎵撳紑鐩戞帶绐楀彛
+};
+
+// 璁惧鎺у埗
+const controlDevice = (action, row = selectedDevice.value) => {
+ const actionMap = {
+ restart: "閲嶅惎",
+ reset: "閲嶇疆",
+ shutdown: "鍏抽棴",
+ update: "鍥轰欢鏇存柊",
+ };
+
+ ElMessageBox.confirm(
+ `纭畾瑕佸璁惧 ${row.name} 鎵ц${actionMap[action]}鎿嶄綔鍚�?`,
+ "鎻愮ず",
+ {
+ confirmButtonText: "纭畾",
+ cancelButtonText: "鍙栨秷",
+ type: "warning",
+ }
+ )
+ .then(() => {
+ ElMessage.success(`璁惧${actionMap[action]}鎿嶄綔宸插彂閫乣);
+ // 妯℃嫙璁惧鍝嶅簲
+ setTimeout(() => {
+ ElMessage.success(`璁惧${actionMap[action]}鎿嶄綔鎵ц鎴愬姛`);
+ }, 1000);
+ })
+ .catch(() => {});
+};
+
+// 瀹氭椂鏇存柊璁惧鐘舵�侊紙妯℃嫙瀹炴椂鍙樺寲锛�
+const updateDeviceStatus = () => {
+ // 闅忔満鏇存柊涓�浜涜澶囩殑鐘舵�佸拰瀹炴椂鏁版嵁
+ mockDevices.forEach((device) => {
+ if (Math.random() > 0.8) {
+ // 80%姒傜巼涓嶆洿鏂�
+ return;
+ }
+
+ // 闅忔満鏇存柊鐘舵��
+ const statuses = ["0", "1", "2"];
+ device.status = statuses[Math.floor(Math.random() * statuses.length)];
+
+ // 鏇存柊瀹炴椂鏁版嵁
+ device.realtimeData.forEach((data) => {
+ if (data.name === "娓╁害") {
+ data.value = (20 + Math.random() * 10).toFixed(1);
+ data.status = data.value > 30 ? "error" : "normal";
+ } else if (data.name === "婀垮害") {
+ data.value = Math.floor(30 + Math.random() * 50);
+ data.status = data.value > 80 ? "error" : "normal";
+ } else if (data.name === "CPU浣跨敤鐜�") {
+ data.value = Math.floor(20 + Math.random() * 60);
+ data.status =
+ data.value > 80 ? "error" : data.value > 60 ? "warning" : "normal";
+ }
+ });
+ });
+
+ // 鍒锋柊琛ㄦ牸鏁版嵁
+ getTableData();
+};
+
+// 鍒濆鍖栨暟鎹�
+onMounted(() => {
+ getTableData();
+ // 姣�5绉掓洿鏂颁竴娆¤澶囩姸鎬�
+ const interval = setInterval(updateDeviceStatus, 5000);
+
+ // 娓呯悊瀹氭椂鍣�
+ return () => clearInterval(interval);
+});
+</script>
+
+<style lang="scss" scoped>
+.table_list {
+ margin-top: unset;
+}
+
+.monitor-container {
+ .monitor-stats {
+ display: flex;
+ gap: 20px;
+ margin-bottom: 20px;
+
+ .stat-card {
+ flex: 1;
+ background: #fff;
+ padding: 20px;
+ border-radius: 8px;
+ box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
+ text-align: center;
+
+ .stat-title {
+ font-size: 14px;
+ color: #606266;
+ margin-bottom: 10px;
+ }
+
+ .stat-value {
+ font-size: 32px;
+ font-weight: bold;
+
+ &.online {
+ color: #67c23a;
+ }
+
+ &.offline {
+ color: #e6a23c;
+ }
+
+ &.error {
+ color: #f56c6c;
+ }
+
+ &.total {
+ color: #409eff;
+ }
+ }
+ }
+ }
+}
+
+.device-detail {
+ .detail-section {
+ margin-bottom: 20px;
+
+ h3 {
+ font-size: 16px;
+ font-weight: bold;
+ margin-bottom: 10px;
+ color: #303133;
+ }
+ }
+
+ .realtime-data {
+ display: grid;
+ grid-template-columns: repeat(2, 1fr);
+ gap: 15px;
+
+ .data-item {
+ background: #f5f7fa;
+ padding: 15px;
+ border-radius: 8px;
+
+ .data-name {
+ font-size: 14px;
+ color: #606266;
+ margin-bottom: 5px;
+ }
+
+ .data-value {
+ font-size: 24px;
+ font-weight: bold;
+ color: #303133;
+ margin-bottom: 5px;
+ }
+
+ .data-status {
+ font-size: 12px;
+ padding: 2px 8px;
+ border-radius: 10px;
+
+ &.normal {
+ background: #f0f9eb;
+ color: #67c23a;
+ }
+
+ &.warning {
+ background: #fdf6ec;
+ color: #e6a23c;
+ }
+
+ &.error {
+ background: #fef0f0;
+ color: #f56c6c;
+ }
+ }
+ }
+ }
+
+ .control-buttons {
+ display: flex;
+ gap: 10px;
+ margin-top: 10px;
+ }
+}
+</style>
diff --git a/src/views/environmentAccess/vehicleInformationCollection/index.vue b/src/views/environmentAccess/vehicleInformationCollection/index.vue
new file mode 100644
index 0000000..5669bfa
--- /dev/null
+++ b/src/views/environmentAccess/vehicleInformationCollection/index.vue
@@ -0,0 +1,577 @@
+<template>
+ <div class="app-container">
+ <el-form :model="filters" :inline="true">
+ <el-form-item label="杞︾墝鍙风爜">
+ <el-input
+ v-model="filters.plateNumber"
+ style="width: 240px"
+ placeholder="璇疯緭鍏ヨ溅鐗屽彿鐮�"
+ clearable
+ :prefix-icon="Search"
+ @change="getTableData"
+ />
+ </el-form-item>
+ <el-form-item label="杞﹁締绫诲瀷">
+ <el-select
+ v-model="filters.vehicleType"
+ style="width: 240px"
+ placeholder="璇烽�夋嫨杞﹁締绫诲瀷"
+ clearable
+ @change="getTableData"
+ >
+ <el-option label="灏忓瀷杞�" value="0"></el-option>
+ <el-option label="涓瀷杞�" value="1"></el-option>
+ <el-option label="澶у瀷杞�" value="2"></el-option>
+ <el-option label="鐗圭杞�" value="3"></el-option>
+ </el-select>
+ </el-form-item>
+ <el-form-item label="杩涘嚭鏂瑰悜">
+ <el-select
+ v-model="filters.direction"
+ style="width: 240px"
+ placeholder="璇烽�夋嫨杩涘嚭鏂瑰悜"
+ clearable
+ @change="getTableData"
+ >
+ <el-option label="杩�" value="0"></el-option>
+ <el-option label="鍑�" value="1"></el-option>
+ </el-select>
+ </el-form-item>
+ <el-form-item label="杩涘嚭鏃堕棿">
+ <el-date-picker
+ v-model="filters.timeRange"
+ type="daterange"
+ start-placeholder="寮�濮嬫椂闂�"
+ end-placeholder="缁撴潫鏃堕棿"
+ value-format="YYYY-MM-DD HH:mm:ss"
+ format="YYYY-MM-DD HH:mm"
+ clearable
+ @change="changeTimeRange"
+ />
+ </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="vehicle-stats">
+ <div class="stat-card">
+ <div class="stat-title">浠婃棩杩涜溅鏁�</div>
+ <div class="stat-value in">{{ todayInCount }}</div>
+ </div>
+ <div class="stat-card">
+ <div class="stat-title">浠婃棩鍑鸿溅鏁�</div>
+ <div class="stat-value out">{{ todayOutCount }}</div>
+ </div>
+ <div class="stat-card">
+ <div class="stat-title">褰撳墠鍦ㄥ満杞﹁締</div>
+ <div class="stat-value present">{{ presentCount }}</div>
+ </div>
+ <div class="stat-card">
+ <div class="stat-title">浠婃棩鎬绘祦閲�</div>
+ <div class="stat-value total">{{ todayTotalCount }}</div>
+ </div>
+ </div>
+
+ <div class="table_list">
+ <PIMTable
+ rowKey="id"
+ :column="columns"
+ :tableData="dataList"
+ :page="{
+ current: pagination.currentPage,
+ size: pagination.pageSize,
+ total: pagination.total,
+ }"
+ @pagination="changePage"
+ >
+ </PIMTable>
+ </div>
+
+ <!-- 杞﹁締璇︽儏寮圭獥 -->
+ <el-dialog
+ v-model="detailVisible"
+ :title="`杞﹁締璇︽儏 - ${selectedVehicle.plateNumber}`"
+ width="700px"
+ >
+ <div v-if="selectedVehicle" class="vehicle-detail">
+ <div class="detail-section">
+ <h3>鍩烘湰淇℃伅</h3>
+ <el-descriptions :column="2" border>
+ <el-descriptions-item label="杞︾墝鍙风爜">{{
+ selectedVehicle.plateNumber
+ }}</el-descriptions-item>
+ <el-descriptions-item label="杞﹁締绫诲瀷">{{
+ typeMap[selectedVehicle.vehicleType]
+ }}</el-descriptions-item>
+ <el-descriptions-item label="杞﹁締棰滆壊">{{
+ selectedVehicle.color
+ }}</el-descriptions-item>
+ <el-descriptions-item label="杩涘嚭鏂瑰悜">{{
+ selectedVehicle.direction === "0" ? "杩�" : "鍑�"
+ }}</el-descriptions-item>
+ <el-descriptions-item label="杩涘嚭鏃堕棿">{{
+ selectedVehicle.timestamp
+ }}</el-descriptions-item>
+ <el-descriptions-item label="閫氶亾鍚嶇О">{{
+ selectedVehicle.channelName
+ }}</el-descriptions-item>
+ <el-descriptions-item label="璇嗗埆鏂瑰紡">{{
+ selectedVehicle.identifyMethod
+ }}</el-descriptions-item>
+ <el-descriptions-item label="杞﹁締鐘舵��">{{
+ statusMap[selectedVehicle.status]
+ }}</el-descriptions-item>
+ </el-descriptions>
+ </div>
+
+ <div class="detail-section">
+ <h3>杞﹁締鍥剧墖</h3>
+ <div class="vehicle-images">
+ <div class="image-item">
+ <div class="image-title">姝i潰鍥剧墖</div>
+ <el-image
+ :src="selectedVehicle.frontImage"
+ fit="cover"
+ style="width: 100%; height: 200px"
+ :preview-src-list="[
+ selectedVehicle.frontImage,
+ selectedVehicle.sideImage,
+ ]"
+ ></el-image>
+ </div>
+ <div class="image-item">
+ <div class="image-title">渚ч潰鍥剧墖</div>
+ <el-image
+ :src="selectedVehicle.sideImage"
+ fit="cover"
+ style="width: 100%; height: 200px"
+ :preview-src-list="[
+ selectedVehicle.sideImage,
+ selectedVehicle.frontImage,
+ ]"
+ ></el-image>
+ </div>
+ </div>
+ </div>
+ </div>
+ <template #footer>
+ <span class="dialog-footer">
+ <el-button @click="detailVisible = false">鍏抽棴</el-button>
+ </span>
+ </template>
+ </el-dialog>
+ </div>
+</template>
+
+<script setup>
+import { ref, reactive, computed, onMounted } from "vue";
+import { Search } from "@element-plus/icons-vue";
+import dayjs from "dayjs";
+
+// 瀹氫箟鍋囨暟鎹�
+const mockVehicles = [
+ {
+ id: 1,
+ plateNumber: "浜珹12345",
+ vehicleType: "0",
+ color: "鐧借壊",
+ direction: "0",
+ timestamp: "2025-12-31 08:15:30",
+ channelName: "涓滈棬閫氶亾",
+ identifyMethod: "杞︾墝璇嗗埆",
+ status: "0",
+ frontImage: "https://picsum.photos/id/1024/800/600",
+ sideImage: "https://picsum.photos/id/1025/800/600",
+ duration: "00:00:00",
+ present: true,
+ },
+ {
+ id: 2,
+ plateNumber: "娌狟67890",
+ vehicleType: "1",
+ color: "榛戣壊",
+ direction: "1",
+ timestamp: "2025-12-31 08:10:20",
+ channelName: "瑗块棬閫氶亾",
+ identifyMethod: "杞︾墝璇嗗埆",
+ status: "1",
+ frontImage: "https://picsum.photos/id/1026/800/600",
+ sideImage: "https://picsum.photos/id/1027/800/600",
+ duration: "01:25:30",
+ present: false,
+ },
+ {
+ id: 3,
+ plateNumber: "绮54321",
+ vehicleType: "0",
+ color: "绾㈣壊",
+ direction: "0",
+ timestamp: "2025-12-31 08:05:15",
+ channelName: "鍗楅棬閫氶亾",
+ identifyMethod: "IC鍗¤瘑鍒�",
+ status: "0",
+ frontImage: "https://picsum.photos/id/1028/800/600",
+ sideImage: "https://picsum.photos/id/1029/800/600",
+ duration: "00:05:45",
+ present: true,
+ },
+ {
+ id: 4,
+ plateNumber: "宸滵98765",
+ vehicleType: "2",
+ color: "钃濊壊",
+ direction: "0",
+ timestamp: "2025-12-31 08:00:00",
+ channelName: "鍖楅棬閫氶亾",
+ identifyMethod: "杞︾墝璇嗗埆",
+ status: "0",
+ frontImage: "https://picsum.photos/id/1030/800/600",
+ sideImage: "https://picsum.photos/id/1031/800/600",
+ duration: "00:10:30",
+ present: true,
+ },
+ {
+ id: 5,
+ plateNumber: "鑻廍13579",
+ vehicleType: "0",
+ color: "閾惰壊",
+ direction: "1",
+ timestamp: "2025-12-31 07:55:45",
+ channelName: "涓滈棬閫氶亾",
+ identifyMethod: "杞︾墝璇嗗埆",
+ status: "1",
+ frontImage: "https://picsum.photos/id/1032/800/600",
+ sideImage: "https://picsum.photos/id/1033/800/600",
+ duration: "02:30:15",
+ present: false,
+ },
+ {
+ id: 6,
+ plateNumber: "娴橣24680",
+ vehicleType: "3",
+ color: "榛勮壊",
+ direction: "0",
+ timestamp: "2025-12-31 07:50:30",
+ channelName: "鍗楅棬閫氶亾",
+ identifyMethod: "浜哄伐鐧昏",
+ status: "0",
+ frontImage: "https://picsum.photos/id/1034/800/600",
+ sideImage: "https://picsum.photos/id/1035/800/600",
+ duration: "00:15:30",
+ present: true,
+ },
+ {
+ id: 7,
+ plateNumber: "椴丟97531",
+ vehicleType: "1",
+ color: "缁胯壊",
+ direction: "1",
+ timestamp: "2025-12-31 07:45:15",
+ channelName: "瑗块棬閫氶亾",
+ identifyMethod: "杞︾墝璇嗗埆",
+ status: "1",
+ frontImage: "https://picsum.photos/id/1036/800/600",
+ sideImage: "https://picsum.photos/id/1037/800/600",
+ duration: "01:45:30",
+ present: false,
+ },
+ {
+ id: 8,
+ plateNumber: "璞獺86420",
+ vehicleType: "0",
+ color: "鐏拌壊",
+ direction: "0",
+ timestamp: "2025-12-31 07:40:00",
+ channelName: "鍖楅棬閫氶亾",
+ identifyMethod: "IC鍗¤瘑鍒�",
+ status: "0",
+ frontImage: "https://picsum.photos/id/1038/800/600",
+ sideImage: "https://picsum.photos/id/1039/800/600",
+ duration: "00:20:15",
+ present: true,
+ },
+];
+
+// 鍝嶅簲寮忔暟鎹�
+const filters = reactive({
+ plateNumber: "",
+ vehicleType: "",
+ direction: "",
+ timeRange: [],
+});
+
+const dataList = ref([]);
+const pagination = reactive({
+ currentPage: 1,
+ pageSize: 10,
+ total: 0,
+});
+
+const detailVisible = ref(false);
+const selectedVehicle = ref({});
+
+// 鏃堕棿鑼冨洿
+const timeRange = reactive({
+ startTime: "",
+ endTime: "",
+});
+
+// 鐘舵�佹槧灏�
+const statusMap = {
+ '0': '<el-tag type="success">姝e父</el-tag>',
+ '1': '<el-tag type="info">宸茬寮�</el-tag>',
+ '2': '<el-tag type="warning">寮傚父</el-tag>',
+};
+
+// 绫诲瀷鏄犲皠
+const typeMap = {
+ 0: "灏忓瀷杞�",
+ 1: "涓瀷杞�",
+ 2: "澶у瀷杞�",
+ 3: "鐗圭杞�",
+};
+
+// 缁熻鏁版嵁
+const todayInCount = computed(() => {
+ const today = dayjs().format("YYYY-MM-DD");
+ return mockVehicles.filter((vehicle) => {
+ return vehicle.direction === "0" && vehicle.timestamp.startsWith(today);
+ }).length;
+});
+
+const todayOutCount = computed(() => {
+ const today = dayjs().format("YYYY-MM-DD");
+ return mockVehicles.filter((vehicle) => {
+ return vehicle.direction === "1" && vehicle.timestamp.startsWith(today);
+ }).length;
+});
+
+const presentCount = computed(() => {
+ return mockVehicles.filter((vehicle) => vehicle.present).length;
+});
+
+const todayTotalCount = computed(() => {
+ const today = dayjs().format("YYYY-MM-DD");
+ return mockVehicles.filter((vehicle) => {
+ return vehicle.timestamp.startsWith(today);
+ }).length;
+});
+
+// 琛ㄦ牸鍒楅厤缃�
+const columns = [
+ {
+ label: "杞︾墝鍙风爜",
+ align: "center",
+ prop: "plateNumber",
+ },
+ {
+ label: "杞﹁締绫诲瀷",
+ align: "center",
+ prop: "vehicleType",
+ formatter: (row) => {
+ return typeMap[row.vehicleType];
+ },
+ },
+ {
+ label: "杞﹁締棰滆壊",
+ align: "center",
+ prop: "color",
+ },
+ {
+ label: "杩涘嚭鏂瑰悜",
+ align: "center",
+ prop: "direction",
+ formatter: (row) => {
+ return row.direction === "0" ? '<el-tag type="success">杩�</el-tag>' : '<el-tag type="info">鍑�</el-tag>';
+ },
+ },
+ {
+ label: "杩涘嚭鏃堕棿",
+ align: "center",
+ prop: "timestamp",
+ },
+ {
+ label: "閫氶亾鍚嶇О",
+ align: "center",
+ prop: "channelName",
+ },
+ {
+ label: "璇嗗埆鏂瑰紡",
+ align: "center",
+ prop: "identifyMethod",
+ },
+ {
+ label: "杞﹁締鐘舵��",
+ align: "center",
+ prop: "status",
+ formatter: (row) => {
+ return statusMap[row.status];
+ },
+ },
+ {
+ dataType: "action",
+ label: "鎿嶄綔",
+ align: "center",
+ fixed: "right",
+ width: 140,
+ operation: [
+ {
+ name: "鏌ョ湅璇︽儏",
+ type: "text",
+ clickFun: (row) => {
+ viewDetail(row);
+ },
+ },
+ ],
+ },
+];
+
+// 杩囨护鍚庣殑鏁版嵁
+const filteredData = computed(() => {
+ return mockVehicles.filter((item) => {
+ const plateMatch =
+ !filters.plateNumber || item.plateNumber.includes(filters.plateNumber);
+ const typeMatch =
+ !filters.vehicleType || item.vehicleType === filters.vehicleType;
+ const directionMatch =
+ !filters.direction || item.direction === filters.direction;
+ const timeMatch =
+ !timeRange.startTime ||
+ (item.timestamp >= timeRange.startTime &&
+ item.timestamp <= timeRange.endTime);
+ return plateMatch && typeMatch && directionMatch && timeMatch;
+ });
+});
+
+// 鑾峰彇琛ㄦ牸鏁版嵁
+const getTableData = () => {
+ pagination.total = filteredData.value.length;
+ const start = (pagination.currentPage - 1) * pagination.pageSize;
+ const end = start + pagination.pageSize;
+ dataList.value = filteredData.value.slice(start, end);
+};
+
+// 閲嶇疆杩囨护鍣�
+const resetFilters = () => {
+ filters.plateNumber = "";
+ filters.vehicleType = "";
+ filters.direction = "";
+ filters.timeRange = [];
+ timeRange.startTime = "";
+ timeRange.endTime = "";
+ pagination.currentPage = 1;
+ getTableData();
+};
+
+// 鍒嗛〉鍙樺寲
+const changePage = ({ page, limit }) => {
+ pagination.currentPage = page;
+ pagination.pageSize = limit;
+ getTableData();
+};
+
+// 鏃堕棿鑼冨洿鍙樺寲
+const changeTimeRange = (value) => {
+ if (value && value.length === 2) {
+ timeRange.startTime = value[0];
+ timeRange.endTime = value[1];
+ } else {
+ timeRange.startTime = "";
+ timeRange.endTime = "";
+ }
+ getTableData();
+};
+
+// 鏌ョ湅杞﹁締璇︽儏
+const viewDetail = (row) => {
+ selectedVehicle.value = row;
+ detailVisible.value = true;
+};
+
+// 鍒濆鍖栨暟鎹�
+onMounted(() => {
+ getTableData();
+});
+</script>
+
+<style lang="scss" scoped>
+.table_list {
+ margin-top: unset;
+}
+
+.vehicle-stats {
+ display: flex;
+ gap: 20px;
+ margin-bottom: 20px;
+
+ .stat-card {
+ flex: 1;
+ background: #fff;
+ padding: 20px;
+ border-radius: 8px;
+ box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
+ text-align: center;
+
+ .stat-title {
+ font-size: 14px;
+ color: #606266;
+ margin-bottom: 10px;
+ }
+
+ .stat-value {
+ font-size: 32px;
+ font-weight: bold;
+
+ &.in {
+ color: #67c23a;
+ }
+
+ &.out {
+ color: #409eff;
+ }
+
+ &.present {
+ color: #e6a23c;
+ }
+
+ &.total {
+ color: #909399;
+ }
+ }
+ }
+}
+
+.vehicle-detail {
+ .detail-section {
+ margin-bottom: 20px;
+
+ h3 {
+ font-size: 16px;
+ font-weight: bold;
+ margin-bottom: 10px;
+ color: #303133;
+ }
+ }
+
+ .vehicle-images {
+ display: grid;
+ grid-template-columns: repeat(2, 1fr);
+ gap: 15px;
+
+ .image-item {
+ background: #f5f7fa;
+ padding: 15px;
+ border-radius: 8px;
+
+ .image-title {
+ font-size: 14px;
+ color: #606266;
+ margin-bottom: 10px;
+ text-align: center;
+ }
+ }
+ }
+}
+</style>
diff --git a/src/views/financialManagement/accounting/index.vue b/src/views/financialManagement/accounting/index.vue
new file mode 100644
index 0000000..91588fd
--- /dev/null
+++ b/src/views/financialManagement/accounting/index.vue
@@ -0,0 +1,547 @@
+<template>
+ <div style="padding: 20px;">
+ <!-- 椤甸潰鏍囬鍜岀瓫閫夋潯浠� -->
+ <div class="w-full md:w-auto flex items-center gap-3" style="margin-bottom: 20px;">
+ <el-button
+ type="primary"
+ icon="Refresh"
+ @click="resetFilters"
+ size="default"
+ >
+ 鏌ヨ
+ </el-button>
+ </div>
+
+ <main class="container mx-auto px-4 pb-10">
+ <!-- 鍥哄畾璧勪骇鎸囨爣鍗$墖 -->
+ <div class="grid-container">
+ <!-- 璁惧鎬绘暟 -->
+ <el-card class="bg2">
+ <p>璁惧鎬绘暟</p>
+ <h3>
+ {{ assetInfo.totalEquipment }}
+ </h3>
+ </el-card>
+
+ <!-- 璧勪骇鍘熷�� -->
+ <el-card class="bg3">
+ <p>璧勪骇鍘熷��</p>
+ <h3>
+ 楼{{ assetInfo.totalOriginalValue }}
+ </h3>
+ </el-card>
+
+ <!-- 绱鎶樻棫 -->
+ <el-card class="bg4">
+ <p>绱鎶樻棫</p>
+ <h3>
+ 楼{{ assetInfo.totalDepreciation }}
+ </h3>
+ </el-card>
+
+ <!-- 鍑�鍊� -->
+ <el-card class="bg5">
+ <p>鍑�鍊�</p>
+ <h3>
+ 楼{{ assetInfo.totalNetValue }}
+ </h3>
+ </el-card>
+ </div>
+
+ <!-- 鍥哄畾璧勪骇缁熻鍥捐〃 -->
+ <div class="grid-layout">
+ <!-- 鎸夎澶囩被鍨嬬粺璁� -->
+ <el-card style="margin-bottom: 20px;">
+ <h2 class="section-title">璁惧绫诲瀷鍒嗗竷</h2>
+ <div class="echarts">
+ <Echarts
+ :legend="typeDistributionLegend"
+ :chartStyle="chartStylePie"
+ :series="typeDistributionSeries"
+ :tooltip="pieTooltip"
+ style="height: 260px; width: 35%;">
+ <div class="chart-num">
+ <span style="font-size: 22px;">璁惧绫诲瀷</span>
+ <span style="font-size: 36px; font-weight: 500; font-family: 'MyCustomFont', sans-serif;">{{ assetInfo.totalEquipment }}</span>
+ </div>
+ </Echarts>
+ <Echarts
+ ref="chart"
+ :chartStyle="chartStyle"
+ :grid="grid"
+ :legend="lineLegend"
+ :series="typeDistributionLineSeries"
+ :tooltip="tooltip"
+ :xAxis="xAxis"
+ :yAxis="yAxis"
+ style="height: 260px; width: 64%;"></Echarts>
+ </div>
+ </el-card>
+ </div>
+ <!-- 璁惧鍙拌处琛ㄦ牸 -->
+ <el-card style="margin-bottom: 20px;">
+ <el-table
+ :data="equipmentList"
+ stripe
+ style="width: 100%"
+ :header-cell-style="{ background: '#f5f7fa', color: '#606266' }"
+ >
+ <el-table-column prop="id" label="璧勪骇缂栧彿" width="120" />
+ <el-table-column prop="deviceName" label="璁惧鍚嶇О" width="250" />
+ <el-table-column prop="deviceModel" label="鍨嬪彿瑙勬牸" min-width="150" />
+ <el-table-column prop="supplierName" label="渚涘簲鍟�" min-width="120" />
+ <el-table-column prop="unit" label="鍗曚綅" width="120" />
+ <el-table-column prop="number" label="鏁伴噺" width="120" />
+ <el-table-column prop="originalValue" label="鍘熷��(鍏�)" width="120">
+ <template #default="{ row }">
+ 楼{{ formatCurrency(row.taxIncludingPriceTotal) }}
+ </template>
+ </el-table-column>
+ <el-table-column prop="depreciation" label="绱鎶樻棫(鍏�)" width="140">
+ <template #default="{ row }">
+ 楼{{ formatCurrency(row.taxIncludingPriceTotal-row.unTaxIncludingPriceTotal) }}
+ </template>
+ </el-table-column>
+ <el-table-column prop="netValue" label="鍑�鍊�(鍏�)" width="120">
+ <template #default="{ row }">
+ 楼{{ formatCurrency(row.unTaxIncludingPriceTotal) }}
+ </template>
+ </el-table-column>
+ <el-table-column prop="status" label="鐘舵��" width="100">
+ <template #default="{ row }">
+ <el-tag
+ :type="getStatusTagType(row.status)"
+ size="small"
+ >
+ {{ row.status }}
+ </el-tag>
+ </template>
+ </el-table-column>
+ </el-table>
+
+ <!-- 鍒嗛〉 -->
+ <div class="pagination-container">
+ <el-pagination
+ @size-change="handleSizeChange"
+ @current-change="handleCurrentChange"
+ :current-page="pagination.currentPage"
+ :page-sizes="[10, 20, 50, 100]"
+ :page-size="pagination.pageSize"
+ layout="total, sizes, prev, pager, next, jumper"
+ :total="pagination.total"
+ />
+ </div>
+ </el-card>
+ </main>
+
+ </div>
+</template>
+
+<script setup>
+import { ref, computed, onMounted, reactive } from 'vue';
+import 'element-plus/dist/index.css';
+import Echarts from "@/components/Echarts/echarts.vue";
+import { getLedgerPage } from "@/api/equipmentManagement/ledger";
+import dayjs from "dayjs";
+
+// 绛涢�夋潯浠�
+const dateRange = ref(null);
+const equipmentType = ref('');
+
+
+// 鍥哄畾璧勪骇淇℃伅
+const assetInfo = ref({
+ totalEquipment: 0,
+ totalOriginalValue: 0,
+ totalDepreciation: 0,
+ totalNetValue: 0
+});
+
+// 璁惧鍒楄〃
+const equipmentList = ref([]);
+const pagination = ref({
+ currentPage: 1,
+ pageSize: 10,
+ total: 0
+});
+
+// 鍥捐〃閰嶇疆
+const chartStyle = {
+ width: '100%',
+ height: '100%',
+ position: 'relative',
+};
+
+const grid = {
+ left: '3%',
+ right: '4%',
+ bottom: '3%',
+ containLabel: true
+};
+
+const lineLegend = {
+ show: false,
+};
+
+// 鎶樼嚎鍥炬彁绀烘
+const tooltip = reactive({
+ trigger: 'axis',
+ axisPointer: {
+ type: 'line',
+ lineStyle: { color: '#aaa' }
+ },
+ // 鑷畾涔夊唴瀹�
+ formatter: function (params) {
+ if (!params || !params.length) return '';
+ const axisLabel = params[0].axisValueLabel || params[0].axisValue || '';
+ const rows = params
+ .map(p => {
+ const colorDot = `<span style="display:inline-block;margin-right:6px;width:8px;height:8px;border-radius:50%;background:${p.color}"></span>`;
+ return `${colorDot}${p.seriesName}: ${p.value}`;
+ })
+ .join('<br/>');
+ return `<div>${axisLabel}</div><div>${rows}</div>`;
+ }
+});
+
+const xAxis = ref([
+ {
+ type: 'category',
+ axisTick: { show: true, alignWithLabel: true },
+ data: [],
+ },
+]);
+
+const yAxis = [
+ {
+ type: 'value',
+ name: '鏁伴噺/閲戦', // 宸︿晶y杞�
+ position: 'left',
+ min: 0,
+ // 鍧愭爣杞村悕绉版牱寮�
+ nameTextStyle: {
+ color: '#000',
+ fontSize: 14,
+ },
+ }
+];
+
+const chartStylePie = {
+ width: '100%',
+ height: '100%' // 璁剧疆鍥捐〃瀹瑰櫒鐨勯珮搴�
+};
+
+const pieColors = ['#F04864', '#FACC14', '#8543E0', '#1890FF', '#13C2C2', '#2FC25B']; // 鍙牴鎹疄闄呰皟鏁�
+
+// 楗煎浘鏁版嵁
+const typeDistributionData = ref([]);
+const departmentDistributionData = ref([]);
+
+// 楗煎浘鍥句緥
+const typeDistributionLegend = computed(() => ({
+ show: true,
+ top: 'center',
+ left: '60%',
+ orient: 'vertical',
+ icon: 'circle',
+ data: typeDistributionData.value.map(item => item.name),
+ formatter: function(name) {
+ const item = typeDistributionData.value.find(i => i.name === name);
+ if (!item) return name;
+ return `${name} | ${item.count} 鍙� | ${item.amount}`;
+ },
+ textStyle: {
+ color: '#333',
+ fontSize: 14,
+ lineHeight: 26,
+ }
+}));
+
+
+// 楗煎浘绯诲垪
+const typeDistributionSeries = computed(() => [
+ {
+ type: 'pie',
+ radius: ['50%', '65%'],
+ center: ['25%', '50%'],
+ avoidLabelOverlap: false,
+ itemStyle: {
+ borderColor: '#fff',
+ borderWidth: 2
+ },
+ label: {
+ show: false
+ },
+ data: typeDistributionData.value,
+ color: pieColors
+ }
+]);
+
+// 鎶樼嚎鍥炬暟鎹�
+const typeDistributionLineSeries = ref([]);
+
+
+// 楗煎浘鎻愮ず妗�
+const pieTooltip = reactive({
+ trigger: 'item',
+ formatter: function(params) {
+ // 妫�鏌ユ暟鎹槸鍚﹀瓨鍦�
+ if (!params.data) return params.name;
+ // 鎷兼帴瀹屾暣鍐呭
+ return `
+ <div>
+ <div style="color:${params.color};font-size:16px;">鈼�</div>
+ <div>${params.name}</div>
+ <div>鏁伴噺锛�${params.data.count} 鍙�</div>
+ <div>閲戦锛�${params.data.amount}</div>
+ </div>
+ `;
+ }
+});
+
+// 閫夐」鏁版嵁
+const equipmentTypeOptions = ref([]);
+
+// 鑾峰彇鏁版嵁
+const fetchData = async () => {
+ try {
+ // 鑾峰彇鍥哄畾璧勪骇姹囨�讳俊鎭�
+ const assetInfoRes = await getAssetInfo({
+ startDate: dateRange.value ? dateRange.value[0] : null,
+ endDate: dateRange.value ? dateRange.value[1] : null,
+ equipmentType: equipmentType.value
+ });
+
+ if (assetInfoRes.code === 200) {
+ assetInfo.value = assetInfoRes.data;
+ }
+
+ // 鑾峰彇璁惧鍒楄〃
+ const equipmentListRes = await getLedgerPage({
+ current: pagination.value.currentPage,
+ size: pagination.value.pageSize,
+ startDate: dateRange.value ? dateRange.value[0] : null,
+ endDate: dateRange.value ? dateRange.value[1] : null,
+ equipmentType: equipmentType.value
+ });
+
+ if (equipmentListRes.code === 200) {
+ equipmentList.value = equipmentListRes.data.records;
+ pagination.value.total = equipmentListRes.data.total;
+
+ // 鏍规嵁 equipmentList 鎸� deviceName 杩涜鍒嗙被缁熻
+ const deviceNameMap = {};
+ equipmentList.value.forEach(item => {
+ const deviceName = item.deviceName;
+ if (!deviceNameMap[deviceName]) {
+ deviceNameMap[deviceName] = {
+ name: deviceName,
+ count: 0,
+ totalValue: 0
+ };
+ }
+ deviceNameMap[deviceName].count += item.number || 1; // 鍋囪 number 涓鸿澶囨暟閲�
+ deviceNameMap[deviceName].totalValue += item.taxIncludingPriceTotal || 0; // 绱姞鍚◣鎬讳环
+ });
+
+ // 杞崲涓� typeDistributionData 鏍煎紡
+ typeDistributionData.value = Object.values(deviceNameMap).map(item => ({
+ name: item.name,
+ value: item.count,
+ count: item.count,
+ amount: `楼${formatCurrency(item.totalValue)}`
+ }));
+
+ // 鏇存柊x杞存暟鎹�
+ xAxis.value[0].data = typeDistributionData.value.map(item => item.name);
+
+ // 鏋勫缓鎶樼嚎鍥炬暟鎹�
+ typeDistributionLineSeries.value = [
+ {
+ name: '璁惧鏁伴噺',
+ type: 'line',
+ data: typeDistributionData.value.map(item => item.count)
+ }
+ ];
+ }
+ } catch (error) {
+ console.error('鑾峰彇鍥哄畾璧勪骇鏁版嵁澶辫触锛�', error);
+ }
+};
+
+// 鍒濆鍖�
+onMounted(() => {
+ // 鑾峰彇鍒楄〃鏁版嵁
+ fetchData();
+});
+
+// 鏍煎紡鍖栬揣甯�
+const formatCurrency = (value) => {
+ if (!value) return '0.00';
+ return Number(value).toFixed(2).replace(/\B(?=(\d{3})+(?!\d))/g, ',');
+};
+
+// 鑾峰彇鐘舵�佹爣绛剧被鍨�
+const getStatusTagType = (status) => {
+ switch (status) {
+ case '鍦ㄧ敤':
+ return 'success';
+ case '闂茬疆':
+ return 'info';
+ case '缁翠慨涓�':
+ return 'warning';
+ case '鎶ュ簾':
+ return 'danger';
+ default:
+ return 'info';
+ }
+};
+
+// 閲嶇疆绛涢�夋潯浠�
+const resetFilters = () => {
+ dateRange.value = null;
+ equipmentType.value = '';
+ fetchData();
+};
+
+// 鍒嗛〉澶勭悊
+const handleSizeChange = (size) => {
+ pagination.value.pageSize = size;
+ fetchData();
+};
+
+const handleCurrentChange = (page) => {
+ pagination.value.currentPage = page;
+ fetchData();
+};
+</script>
+
+<style scoped lang="scss">
+/* 鍩虹鏍峰紡琛ュ厖 */
+:root {
+ --el-color-primary: #4f46e5;
+}
+
+.el-card {
+ position: relative;
+ border-radius: 12px;
+ padding: 14px 10px 10px 10px;
+ box-shadow: 0 2px 8px #eee;
+
+ :deep(.el-card__body) {
+ padding: 10px 20px !important;
+ }
+
+ &.bg1 {
+ background: url(@/assets/icons/png/1.png) no-repeat 100% 100% !important;
+ }
+
+ &.bg2 {
+ background: url(@/assets/icons/png/2.png) no-repeat 100% 100% !important;
+ }
+
+ &.bg3 {
+ background: url(@/assets/icons/png/3.png) no-repeat 100% 100% !important;
+ }
+
+ &.bg4 {
+ background: url(@/assets/icons/png/4.png) no-repeat 100% 100% !important;
+ }
+
+ &.bg5 {
+ background: url(@/assets/icons/png/5.png) no-repeat 100% 100% !important;
+ }
+}
+
+.grid-container {
+ /* grid 瀹瑰櫒鍩虹鏍峰紡 */
+ display: grid;
+ gap: 1rem; /* gap-4 瀵瑰簲 1rem (16px) */
+ margin-bottom: 2rem; /* mb-8 瀵瑰簲 2rem (32px) */
+
+ p {
+ font-size: 22px;
+ margin-top: 0px;
+ color: #fff;
+ }
+
+ h3 {
+ font-size: 36px;
+ font-weight: 500;
+ font-family: 'MyCustomFont', sans-serif;
+ margin: 10px 0;
+ color: #fff;
+ }
+}
+
+/* 绉诲姩绔粯璁ゆ牱寮� (grid-cols-1) */
+.grid-container {
+ grid-template-columns: repeat(1, minmax(0, 1fr));
+}
+
+/* 灏忓睆骞曞強浠ヤ笂 (sm:grid-cols-2) */
+@media (min-width: 640px) {
+ .grid-container {
+ grid-template-columns: repeat(2, minmax(0, 1fr));
+ }
+}
+
+/* 澶у睆骞曞強浠ヤ笂 (lg:grid-cols-5) */
+@media (min-width: 1024px) {
+ .grid-container {
+ grid-template-columns: repeat(5, minmax(0, 1fr));
+ }
+}
+
+/* 鍗$墖鎮仠鏁堟灉澧炲己 */
+.el-card:hover {
+ transform: translateY(-2px);
+}
+
+.echarts {
+ display: flex;
+ justify-content: space-between;
+}
+
+/* 鍥捐〃瀹瑰櫒鏍峰紡 */
+.el-chart {
+ width: 100%;
+ height: 100%;
+}
+
+.section-title {
+ position: relative;
+ font-size: 18px;
+ color: #333;
+ padding-left: 10px;
+ margin-bottom: 10px;
+ font-weight: 700;
+}
+
+.section-title::before {
+ position: absolute;
+ left: 0;
+ top: 0px;
+ content: '';
+ width: 4px;
+ height: 18px;
+ background-color: #002FA7;
+ border-radius: 2px;
+}
+
+.chart-num {
+ position: absolute;
+ z-index: 3;
+ top: 92px;
+ left: 92px;
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+}
+
+.pagination-container {
+ margin-top: 20px;
+ display: flex;
+ justify-content: center;
+}
+</style>
diff --git a/src/views/financialManagement/expenseManagement/index.vue b/src/views/financialManagement/expenseManagement/index.vue
new file mode 100644
index 0000000..a45c32d
--- /dev/null
+++ b/src/views/financialManagement/expenseManagement/index.vue
@@ -0,0 +1,276 @@
+<template>
+ <div class="app-container">
+ <el-form :model="filters" :inline="true">
+ <el-form-item label="鏀嚭鏃ユ湡:">
+ <el-date-picker v-model="filters.entryDate" value-format="YYYY-MM-DD" format="YYYY-MM-DD" type="daterange"
+ placeholder="璇烽�夋嫨" clearable @change="changeDaterange" />
+ </el-form-item>
+ <el-form-item label="浠樻鏂瑰紡:">
+ <el-select
+ v-model="filters.expenseMethod"
+ placeholder="璇烽�夋嫨"
+ clearable
+ style="width: 200px;"
+ >
+ <el-option
+ v-for="item in checkout_payment"
+ :key="item.value"
+ :label="item.label"
+ :value="item.value"
+ />
+ </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>
+ <el-button
+ type="danger"
+ icon="Delete"
+ :disabled="multipleList.length <= 0"
+ @click="deleteRow(multipleList.map((item) => item.id))"
+ >
+ 鎵归噺鍒犻櫎
+ </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 #operation="{ row }">
+ <el-button type="primary" text @click="edit(row.id)" icon="editPen">
+ 缂栬緫
+ </el-button>
+ <el-button
+ type="primary"
+ text
+ @click="openFilesFormDia(row)"
+ >
+ 闄勪欢
+ </el-button>
+ </template>
+ </PIMTable>
+ </div>
+ <Modal ref="modalRef" @success="getTableData"></Modal>
+ <files-dia ref="filesDia"></files-dia>
+ </div>
+</template>
+
+<script setup>
+import { usePaginationApi } from "@/hooks/usePaginationApi";
+import { listPage, delAccountExpense } from "@/api/financialManagement/expenseManagement";
+import { onMounted, getCurrentInstance } from "vue";
+import Modal from "./Modal.vue";
+import { ElMessageBox, ElMessage } from "element-plus";
+import dayjs from "dayjs";
+import FilesDia from "../revenueManagement/filesDia.vue";
+
+defineOptions({
+ name: "鏀嚭绠$悊",
+});
+
+// 琛ㄦ牸澶氶�夋閫変腑椤�
+const multipleList = ref([]);
+const { proxy } = getCurrentInstance();
+const modalRef = ref();
+const { checkout_payment } = proxy.useDict("checkout_payment");
+const { expense_types } = proxy.useDict("expense_types");
+const filesDia = ref()
+
+const {
+ filters,
+ columns,
+ dataList,
+ pagination,
+ getTableData,
+ resetFilters,
+ onCurrentChange,
+} = usePaginationApi(
+ listPage,
+ {
+ expenseMethod: undefined,
+ entryDate: undefined,
+ },
+ [
+ {
+ label: "鏀嚭鏃ユ湡",
+ align: "center",
+ prop: "expenseDate",
+ },
+ {
+ label: "鏀嚭绫诲瀷",
+ align: "center",
+ prop: "expenseType",
+ dataType: "tag",
+ formatData: (params) => {
+ if (expense_types.value.find((m) => m.value == params)) {
+ return expense_types.value.find((m) => m.value == params).label;
+ } else {
+ return null
+ }
+ },
+ },
+ {
+ label: "渚涘簲鍟嗗悕绉�",
+ align: "center",
+ prop: "supplierName",
+
+ },
+ {
+ label: "鏀嚭閲戦",
+ align: "center",
+ prop: "expenseMoney",
+
+ },
+ {
+ label: "鏀嚭鎻忚堪",
+ align: "center",
+ prop: "expenseDescribed",
+
+ },
+ {
+ label: "浠樻鏂瑰紡",
+ align: "center",
+ prop: "expenseMethod",
+ dataType: "tag",
+ formatData: (params) => {
+ if (checkout_payment.value.find((m) => m.value == params)) {
+ return checkout_payment.value.find((m) => m.value == params).label;
+ } else {
+ return null
+ }
+ },
+ },
+ {
+ label: "鍙戠エ鍙风爜",
+ align: "center",
+ prop: "invoiceNumber",
+
+ },
+ {
+ label: "澶囨敞",
+ align: "center",
+ prop: "note",
+
+ },
+ {
+ label: "褰曞叆浜�",
+ align: "center",
+ prop: "inputUser",
+ },
+ {
+ label: "褰曞叆鏃ユ湡",
+ align: "center",
+ prop: "inputTime",
+
+ },
+ {
+ fixed: "right",
+ label: "鎿嶄綔",
+ dataType: "slot",
+ slot: "operation",
+ align: "center",
+ width: "200px",
+ },
+ ]
+);
+
+// 澶氶�夊悗鍋氫粈涔�
+const handleSelectionChange = (selectionList) => {
+ multipleList.value = selectionList;
+};
+
+const add = () => {
+ modalRef.value.openModal();
+};
+const edit = (id) => {
+ modalRef.value.loadForm(id);
+};
+const changePage = ({ page, limit }) => {
+ pagination.currentPage = page;
+ pagination.pageSize = limit;
+ onCurrentChange(page);
+};
+const deleteRow = (id) => {
+ ElMessageBox.confirm("姝ゆ搷浣滃皢姘镐箙鍒犻櫎璇ユ暟鎹�, 鏄惁缁х画?", "鎻愮ず", {
+ confirmButtonText: "纭畾",
+ cancelButtonText: "鍙栨秷",
+ type: "warning",
+ }).then(async () => {
+ const { code } = await delAccountExpense(id);
+ if (code == 200) {
+ ElMessage({
+ type: "success",
+ message: "鍒犻櫎鎴愬姛",
+ });
+ getTableData();
+ }
+ });
+};
+
+const changeDaterange = (value) => {
+ if (value) {
+ filters.entryDate = value;
+ filters.entryDateStart = dayjs(value[0]).format("YYYY-MM-DD");
+ filters.entryDateEnd = dayjs(value[1]).format("YYYY-MM-DD");
+ } else {
+ filters.entryDate = null;
+ filters.entryDateStart = undefined;
+ filters.entryDateEnd = undefined;
+ }
+ getTableData();
+};
+
+const handleOut = () => {
+ ElMessageBox.confirm("閫変腑鐨勫唴瀹瑰皢琚鍑猴紝鏄惁纭瀵煎嚭锛�", "瀵煎嚭", {
+ confirmButtonText: "纭",
+ cancelButtonText: "鍙栨秷",
+ type: "warning",
+ })
+ .then(() => {
+ proxy.download(`/account/accountExpense/export`, {}, "鏀嚭鍙拌处.xlsx");
+ })
+ .catch(() => {
+ proxy.$modal.msg("宸插彇娑�");
+ });
+};
+// 鎵撳紑闄勪欢寮规
+const openFilesFormDia = (row) => {
+ nextTick(() => {
+ filesDia.value?.openDialog( row,'鏀嚭')
+ })
+};
+
+onMounted(() => {
+ getTableData();
+});
+</script>
+
+<style lang="scss" scoped>
+.table_list {
+ margin-top: unset;
+}
+.actions {
+ display: flex;
+ justify-content: space-between;
+ margin-bottom: 10px;
+}
+</style>
+
diff --git a/src/views/financialManagement/financialStatements/index.vue b/src/views/financialManagement/financialStatements/index.vue
new file mode 100644
index 0000000..e5f9b23
--- /dev/null
+++ b/src/views/financialManagement/financialStatements/index.vue
@@ -0,0 +1,507 @@
+ <template>
+ <div style="padding: 20px;">
+ <!-- 椤甸潰鏍囬鍜屾棩鏈熺瓫閫� -->
+ <div class="w-full md:w-auto flex items-center gap-3" style="margin-bottom: 20px;">
+ <el-date-picker
+ v-model="dateRange"
+ type="daterange"
+ format="YYYY-MM-DD"
+ value-format="YYYY-MM-DD"
+ range-separator="鑷�"
+ start-placeholder="寮�濮嬫棩鏈�"
+ end-placeholder="缁撴潫鏃ユ湡"
+ clearable
+ @change="handleDateChange"
+ class="w-full md:w-auto"
+ style="margin-right: 30px;"
+ />
+
+ <el-button
+ type="primary"
+ icon="Refresh"
+ @click="resetDateRange"
+ size="default"
+ >
+ 閲嶇疆
+ </el-button>
+ </div>
+
+ <main class="container mx-auto px-4 pb-10">
+ <!-- 璐㈠姟鎸囨爣鍗$墖 -->
+ <div class="grid-container">
+ <!-- 鎬绘敹鍏� -->
+ <el-card class="bg1">
+ <p>鎬绘敹鍏�</p>
+ <h3>
+ 楼{{ pageInfo.totalIncome }}
+ </h3>
+ </el-card>
+
+ <!-- 鏀跺叆绗旀暟 -->
+ <el-card class="bg2">
+ <p>鏀跺叆绗旀暟</p>
+ <h3>
+ {{ pageInfo.incomeNumber }}
+ </h3>
+ </el-card>
+
+ <!-- 鎬绘敮鍑� -->
+ <el-card class="bg3">
+ <p>鎬绘敮鍑�</p>
+ <h3>
+ 楼{{ pageInfo.totalExpense }}
+ </h3>
+ </el-card>
+
+ <!-- 鏀嚭绗旀暟 -->
+ <el-card class="bg4">
+ <p>鏀嚭绗旀暟</p>
+ <h3>
+ {{ pageInfo.expenseNumber }}
+ </h3>
+ </el-card>
+
+ <!-- 鍑�鏀跺叆 -->
+ <el-card class="bg5">
+ <p>鍑�鏀跺叆</p>
+ <h3>
+ 楼{{ pageInfo.netRevenue }}
+ </h3>
+ </el-card>
+ </div>
+
+ <!-- 鏀跺叆缁熻鍥捐〃 -->
+ <div class="grid-layout">
+ <el-card style="margin-bottom: 20px;">
+ <h2 class="section-title">鏀跺叆缁熻(鍏�)</h2>
+ <div class="echarts">
+ <Echarts :legend="pieLegend0" :chartStyle="chartStylePie"
+ :series="materialPieSeries0"
+ :tooltip="pieTooltip" style="height: 260px;width: 35%;">
+ <div class="chart-num">
+ <span style="font-size: 22px;">鏀跺叆</span>
+ <span style="font-size: 36px;
+ font-weight: 500;
+ font-family: 'MyCustomFont', sans-serif;">{{ pageInfo.totalIncome }}</span>
+ </div>
+ </Echarts>
+ <Echarts ref="chart"
+ :chartStyle="chartStyle"
+ :grid="grid"
+ :legend="lineLegend"
+ :series="lineSeries0"
+ :tooltip="tooltip"
+ :xAxis="xAxis0"
+ :yAxis="yAxis0"
+ style="height: 260px;width: 64%;"></Echarts>
+ </div>
+ </el-card>
+
+ <!-- 鏀嚭缁熻鍥捐〃 -->
+ <el-card>
+ <h2 class="section-title">鏀嚭缁熻(鍏�)</h2>
+ <div class="echarts">
+ <Echarts ref="chart"
+ :legend="pieLegend1"
+ :chartStyle="chartStylePie"
+ :series="materialPieSeries1"
+ :tooltip="pieTooltip"
+ style="height: 260px;width: 35%;">
+ <div class="chart-num">
+ <span style="font-size: 22px;">鏀嚭</span>
+ <span style="font-size: 36px;
+ font-weight: 500;
+ font-family: 'MyCustomFont', sans-serif;">{{ pageInfo.totalExpense }}</span>
+ </div></Echarts>
+ <Echarts ref="chart"
+ :chartStyle="chartStyle"
+ :grid="grid"
+ :legend="lineLegend"
+ :series="lineSeries1"
+ :tooltip="tooltip"
+ :xAxis="xAxis1"
+ :yAxis="yAxis1"
+ style="height: 260px;width: 64%;"></Echarts>
+ </div>
+ </el-card>
+ </div>
+ </main>
+ </div>
+</template>
+
+<script setup>
+import { ref, computed, onMounted, reactive } from 'vue';
+import 'element-plus/dist/index.css';
+import Echarts from "@/components/Echarts/echarts.vue";
+import { reportForms,reportIncome,reportExpense } from "@/api/financialManagement/financialStatements";
+import dayjs from "dayjs";
+
+// 鏃ユ湡鑼冨洿
+const dateRange = ref(null);
+const chartStyle = {
+ width: '100%',
+ height: '100%', // 璁剧疆鍥捐〃瀹瑰櫒鐨勯珮搴�
+ position:'relative',
+}
+const grid = {
+ left: '3%',
+ right: '4%',
+ bottom: '3%',
+ containLabel: true
+}
+const lineLegend = {
+ show: false,
+}
+// 鎶樼嚎鍥炬彁绀烘
+const tooltip = reactive({
+ trigger: 'axis',
+ axisPointer: {
+ type: 'line',
+ lineStyle: { color: '#aaa' }
+ },
+ // 鑷畾涔夊唴瀹�
+ formatter: function (params) {
+ if (!params || !params.length) return ''
+ const axisLabel = params[0].axisValueLabel || params[0].axisValue || ''
+ const rows = params
+ .map(p => {
+ const colorDot = `<span style="display:inline-block;margin-right:6px;width:8px;height:8px;border-radius:50%;background:${p.color}"></span>`
+ return `${colorDot}${p.seriesName}: ${p.value}`
+ })
+ .join('<br/>')
+ return `<div>${axisLabel}</div><div>${rows}</div>`
+ }
+})
+const months = ['1鏈�','2鏈�','3鏈�','4鏈�','5鏈�','6鏈�','7鏈�','8鏈�','9鏈�','10鏈�','11鏈�','12鏈�'];
+const lineSeries0 = ref([])
+const lineSeries1 = ref([])
+
+const xAxis0 = ref([
+ {
+ type: 'category',
+ axisTick: { show: true, alignWithLabel: true },
+ data: months,
+ },
+]);
+const xAxis1 = ref([
+ {
+ type: 'category',
+ axisTick: { show: true, alignWithLabel: true },
+ data: months,
+ },
+]);
+const yAxis0 = [
+{
+ type: 'value',
+ name: '鏀跺叆缁熻', // 宸︿晶y杞�
+ position: 'left',
+ min: 0,
+ // 鍧愭爣杞村悕绉版牱寮�
+ nameTextStyle: {
+ color: '#000',
+ fontSize: 14,
+ },
+ }
+]
+
+const yAxis1 = [
+{
+ type: 'value',
+ name: '鏀嚭缁熻', // 宸︿晶y杞�
+ position: 'left',
+ min: 0,
+ // 鍧愭爣杞村悕绉版牱寮�
+ nameTextStyle: {
+ color: '#000',
+ fontSize: 14,
+ },
+ }
+]
+
+const chartStylePie = {
+ width: '100%',
+ height: '100%' // 璁剧疆鍥捐〃瀹瑰櫒鐨勯珮搴�
+}
+const pieColors = ['#F04864','#FACC14', '#8543E0', '#1890FF', '#13C2C2','#2FC25B']; // 鍙牴鎹疄闄呰皟鏁�
+const pieData0 = ref([]);
+const pieData1 = ref([]);
+
+const pieLegend0 = computed(() => ({
+ show: true,
+ top: 'center',
+ left: '60%',
+ orient: 'vertical',
+ icon: 'circle',
+ data: pieData0.value.map(item => item.name),
+ formatter: function(name) {
+ const item = pieData0.value.find(i => i.name === name);
+ if (!item) return name;
+ return `${name} | ${item.percent} ${item.amount}`;
+ },
+ textStyle: {
+ color: '#333',
+ fontSize: 14,
+ lineHeight: 26,
+ }
+}));
+const pieLegend1 = computed(() => ({
+ show: true,
+ top: 'center',
+ left: '60%',
+ orient: 'vertical',
+ icon: 'circle',
+ data: pieData1.value.map(item => item.name),
+ formatter: function(name) {
+ const item = pieData1.value.find(i => i.name === name);
+ if (!item) return name;
+ return `${name} | ${item.percent} ${item.amount}`;
+ },
+ textStyle: {
+ color: '#333',
+ fontSize: 14,
+ lineHeight: 26,
+ }
+}));
+
+const materialPieSeries0 = computed(() => [
+ {
+ type: 'pie',
+ radius: ['50%', '65%'],
+ center: ['25%', '50%'],
+ avoidLabelOverlap: false,
+ itemStyle: {
+ borderColor: '#fff',
+ borderWidth: 2
+ },
+ label: {
+ show: false
+ },
+ data: pieData0.value,
+ color: pieColors
+ }
+]);
+const materialPieSeries1 = computed(() => [
+ {
+ type: 'pie',
+ radius: ['50%', '65%'],
+ center: ['25%', '50%'],
+ avoidLabelOverlap: false,
+ itemStyle: {
+ borderColor: '#fff',
+ borderWidth: 2
+ },
+ label: {
+ show: false
+ },
+ data: pieData1.value,
+ color: pieColors
+ }
+]);
+const pieTooltip = reactive({
+ trigger: 'item',
+ formatter: function(params) {
+ // 妫�鏌ユ暟鎹槸鍚﹀瓨鍦�
+ if (!params.data) return params.name;
+ // 鎷兼帴瀹屾暣鍐呭
+ return `
+ <div>
+ <div style="color:${params.color};font-size:16px;">鈼�</div>
+ <div>${params.name}</div>
+ <div>鍗犳瘮锛�${params.data.percent}</div>
+ <div>閲戦锛�${params.data.amount}</div>
+ </div>
+ `;
+ }
+})
+
+
+const pageInfo = ref({
+})
+
+const getData = async () => {
+ if (!dateRange.value || !dateRange.value.length) {
+ return;
+ }
+ try {
+ const {code,data} = await reportForms({entryDateStart:dateRange.value[0], entryDateEnd:dateRange.value[1]});
+ if(code === 200) {
+ pageInfo.value = data
+ pieData0.value = data.incomeType.map(item=>({
+ name:item.typeName,
+ value:item.account,
+ percent:`${item.proportion*100}%`,
+ amount:`楼${item.account}`
+ }))
+ pieData1.value = data.expenseType.map(item=>({
+ name:item.typeName,
+ value:item.account,
+ percent:`${item.proportion*100}%`,
+ amount:`楼${item.account}`
+ }))
+
+ }
+ } catch (error) {
+ console.error('鑾峰彇璐㈠姟鎸囨爣鏁版嵁澶辫触锛�', error);
+ }
+ try{
+ const {code,data} = await reportIncome();
+ if(code==200){
+ lineSeries0.value = data.map(item=>({
+ name:item.typeName,
+ type: 'line',
+ data:item.account.map(item=>Number(item))
+ }))
+
+ }
+ }catch (error) {
+ console.error('鑾峰彇璐㈠姟鎸囨爣鏁版嵁澶辫触锛�', error);
+ }
+ try{
+ const {code,data} = await reportExpense();
+ if(code==200){
+ lineSeries1.value = data.map(item=>({
+ name:item.typeName,
+ type: 'line',
+ data:item.account.map(item=>Number(item))
+ }))
+
+ }
+ }catch (error) {
+ console.error('鑾峰彇璐㈠姟鎸囨爣鏁版嵁澶辫触锛�', error);
+ }
+};
+
+
+// 鍒濆鍖�
+onMounted(() => {
+ // 涓嶈缃粯璁ゆ棩鏈燂紝鐢辩敤鎴锋墜鍔ㄩ�夋嫨
+});
+
+// 澶勭悊鏃ユ湡鑼冨洿鍙樺寲
+const handleDateChange = (newRange) => {
+ dateRange.value = newRange;
+ if (newRange && newRange.length === 2) {
+ getData()
+ }
+};
+
+// 閲嶇疆鏃ユ湡鑼冨洿
+const resetDateRange = () => {
+ dateRange.value = null;
+};
+
+</script>
+
+<style scoped lang="scss">
+/* 鍩虹鏍峰紡琛ュ厖 */
+:root {
+ --el-color-primary: #4f46e5;
+}
+.el-card{
+ position: relative;
+ border-radius: 12px;
+ padding: 14px 10px 10px 10px;
+ box-shadow: 0 2px 8px #eee;
+ :deep(.el-card__body){
+ padding: 10px 20px !important;
+ }
+ &.bg1{
+ background: url(@/assets/icons/png/1.png) no-repeat 100% 100% !important;
+ }
+ &.bg2{
+ background: url(@/assets/icons/png/2.png) no-repeat 100% 100% !important;
+ }
+ &.bg3{
+ background: url(@/assets/icons/png/3.png) no-repeat 100% 100% !important;
+ }
+ &.bg4{
+ background: url(@/assets/icons/png/4.png) no-repeat 100% 100% !important;
+ }
+ &.bg5{
+ background: url(@/assets/icons/png/5.png) no-repeat 100% 100% !important;
+ }
+}
+
+.grid-container {
+ /* grid 瀹瑰櫒鍩虹鏍峰紡 */
+ display: grid;
+ gap: 1rem; /* gap-4 瀵瑰簲 1rem (16px) */
+ margin-bottom: 2rem; /* mb-8 瀵瑰簲 2rem (32px) */
+
+ p{
+ font-size: 22px;
+ margin-top: 0px;
+ color: #fff;
+ }
+ h3{
+ font-size: 36px;
+ font-weight: 500;
+ font-family: 'MyCustomFont', sans-serif;
+ margin: 10px 0;
+ color: #fff;
+ }
+
+}
+
+/* 绉诲姩绔粯璁ゆ牱寮� (grid-cols-1) */
+.grid-container {
+ grid-template-columns: repeat(1, minmax(0, 1fr));
+}
+
+/* 灏忓睆骞曞強浠ヤ笂 (sm:grid-cols-2) */
+@media (min-width: 640px) {
+ .grid-container {
+ grid-template-columns: repeat(2, minmax(0, 1fr));
+ }
+}
+
+/* 澶у睆骞曞強浠ヤ笂 (lg:grid-cols-5) */
+@media (min-width: 1024px) {
+ .grid-container {
+ grid-template-columns: repeat(5, minmax(0, 1fr));
+ }
+}
+
+/* 鍗$墖鎮仠鏁堟灉澧炲己 */
+.el-card:hover {
+ transform: translateY(-2px);
+}
+.echarts{
+ display: flex;
+ justify-content: space-between;
+}
+
+/* 鍥捐〃瀹瑰櫒鏍峰紡 */
+.el-chart {
+ width: 100%;
+ height: 100%;
+}
+.section-title {
+ position: relative;
+ font-size: 18px;
+ color: #333;
+ padding-left: 10px;
+ margin-bottom: 10px;
+ font-weight: 700;
+}
+
+.section-title::before {
+ position: absolute;
+ left: 0;
+ top: 0px;
+ content: '';
+ width: 4px;
+ height: 18px;
+ background-color: #002FA7;
+ border-radius: 2px;
+}
+.chart-num{
+ position: absolute;
+ z-index: 3;
+ top: 92px;
+ left: 92px;
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+}
+</style>
diff --git a/src/views/financialManagement/inventoryAccounting/index.vue b/src/views/financialManagement/inventoryAccounting/index.vue
new file mode 100644
index 0000000..cca6861
--- /dev/null
+++ b/src/views/financialManagement/inventoryAccounting/index.vue
@@ -0,0 +1,390 @@
+<template>
+ <div class="inventory-statistics">
+ <!-- 绛涢�夎〃鍗� -->
+ <div class="filter-form">
+ <el-form :model="filterForm" inline>
+<!-- <el-form-item label="鏃堕棿鑼冨洿">-->
+<!-- <el-date-picker-->
+<!-- v-model="filterForm.dateRange"-->
+<!-- type="daterange"-->
+<!-- range-separator="鑷�"-->
+<!-- start-placeholder="寮�濮嬫棩鏈�"-->
+<!-- end-placeholder="缁撴潫鏃ユ湡"-->
+<!-- />-->
+<!-- </el-form-item>-->
+<!-- <el-form-item label="渚涘簲鍟嗗悕绉�">-->
+<!-- <el-input v-model="filterForm.supplierName" style="width: 240px" placeholder="璇疯緭鍏�" clearable prefix-icon="Search" />-->
+<!-- </el-form-item>-->
+<!-- <el-form-item label="浜у搧鍚嶇О">-->
+<!-- <el-input v-model="filterForm.productCategory" style="width: 240px" placeholder="璇疯緭鍏�" clearable prefix-icon="Search" />-->
+<!-- </el-form-item>-->
+ <el-form-item>
+ <el-button type="primary" @click="handleSearch">鏌ヨ</el-button>
+<!-- <el-button @click="handleReset">閲嶇疆</el-button>-->
+<!-- <el-button type="success" @click="handleExport">瀵煎嚭</el-button>-->
+ </el-form-item>
+ </el-form>
+ </div>
+
+ <!-- 缁熻姹囨�诲崱鐗� -->
+ <div class="summary-cards">
+ <el-row :gutter="20">
+ <el-col :span="6">
+ <el-card class="summary-card">
+ <div class="summary-item">
+ <p class="summary-title">鎬诲簱瀛樻暟閲�</p>
+ <p class="summary-value">{{ summaryData.totalInventoryCount }}</p>
+ </div>
+ </el-card>
+ </el-col>
+ <el-col :span="6">
+ <el-card class="summary-card">
+ <div class="summary-item">
+ <p class="summary-title">鎬诲簱瀛橀噾棰�</p>
+ <p class="summary-value">楼{{ summaryData.totalInventoryValue }}</p>
+ </div>
+ </el-card>
+ </el-col>
+ <el-col :span="6">
+ <el-card class="summary-card">
+ <div class="summary-item">
+ <p class="summary-title">搴撳瓨鍙樺姩鏁伴噺</p>
+ <p class="summary-value">{{ summaryData.inventoryChangeCount }}</p>
+ </div>
+ </el-card>
+ </el-col>
+ <el-col :span="6">
+ <el-card class="summary-card">
+ <div class="summary-item">
+ <p class="summary-title">搴撳瓨鍙樺姩閲戦</p>
+ <p class="summary-value">楼{{ summaryData.inventoryChangeValue }}</p>
+ </div>
+ </el-card>
+ </el-col>
+ </el-row>
+ </div>
+
+ <!-- 鍥捐〃鍖哄煙 -->
+ <div class="chart-section">
+ <el-row :gutter="20">
+ <el-col :span="12">
+ <el-card class="chart-card">
+ <template #header>
+ <div class="card-header">
+ <span>搴撳瓨鍒嗙被鍗犳瘮</span>
+ </div>
+ </template>
+ <div id="category-pie-chart" style="height: 400px;"></div>
+ </el-card>
+ </el-col>
+ <el-col :span="12">
+ <el-card class="chart-card">
+ <template #header>
+ <div class="card-header">
+ <span>搴撳瓨閲戦瓒嬪娍</span>
+ </div>
+ </template>
+ <div id="amount-trend-chart" style="height: 400px;"></div>
+ </el-card>
+ </el-col>
+ </el-row>
+ </div>
+
+ <!-- 鏁版嵁琛ㄦ牸 -->
+ <div class="table_list">
+ <el-table
+ :data="tableData"
+ v-loading="loading"
+ border
+ style="width: 100%"
+ :header-cell-style="{ background: '#f5f7fa', color: '#606266' }"
+ >
+ <el-table-column align="center" type="selection" width="55" />
+ <el-table-column align="center" label="搴忓彿" type="index" width="60" />
+ <el-table-column label="渚涘簲鍟嗗悕绉�" prop="supplierName" width="240" show-overflow-tooltip />
+ <el-table-column label="浜у搧" prop="productCategory" min-width="100" show-overflow-tooltip />
+ <el-table-column label="瑙勬牸鍨嬪彿" prop="specificationModel" min-width="200" show-overflow-tooltip />
+ <el-table-column label="鍗曚綅" prop="unit" width="70" show-overflow-tooltip />
+ <el-table-column label="鍏ュ簱鏁伴噺" prop="inboundNum" width="90" show-overflow-tooltip />
+ <el-table-column label="搴撳瓨鏁伴噺" prop="inboundNum0" width="90" show-overflow-tooltip />
+ <el-table-column label="鍚◣鍗曚环" prop="taxInclusiveUnitPrice" width="100" show-overflow-tooltip />
+ <el-table-column label="鍚◣鎬讳环" prop="taxInclusiveTotalPrice" width="100" show-overflow-tooltip />
+ <el-table-column label="绋庣巼(%)" prop="taxRate" width="80" show-overflow-tooltip />
+ <el-table-column label="涓嶅惈绋庢�讳环" prop="taxExclusiveTotalPrice" width="100" show-overflow-tooltip />
+ <el-table-column label="鍏ュ簱浜�" prop="createBy" width="100" show-overflow-tooltip />
+ </el-table>
+ <pagination v-show="total > 0" :total="total" layout="total, sizes, prev, pager, next, jumper" :page="page.current" :limit="page.size" @pagination="paginationChange" />
+ </div>
+ </div>
+</template>
+
+<script setup>
+import { ref, reactive, onMounted, nextTick } from 'vue'
+import * as echarts from 'echarts'
+import {getStockInPage} from "@/api/inventoryManagement/stockIn.js";
+
+// 鐘舵�佸彉閲�
+const loading = ref(false)
+const total = ref(0)
+const tableData = ref([])
+const summaryData = ref({})
+const page = reactive({
+ current: 1,
+ size: 100,
+})
+
+// 鍥捐〃瀹炰緥
+const categoryPieChart = ref(null)
+const amountTrendChart = ref(null)
+
+// 绛涢�夎〃鍗�
+const filterForm = reactive({
+ dateRange: [],
+ supplierName: '',
+ productCategory: ''
+})
+
+const paginationChange = (obj) => {
+ page.current = obj.page;
+ page.size = obj.limit;
+ loadData()
+}
+
+// 鍒濆鍖栨暟鎹�
+onMounted(() => {
+ loadSummaryData()
+ loadData()
+})
+
+// 鍔犺浇缁熻姹囨�绘暟鎹�
+const loadSummaryData = () => {
+ getStockInChartData().then(res => {
+ summaryData.value = res.data
+ })
+}
+
+// 鍔犺浇搴撳瓨鏁版嵁
+const loadData = () => {
+ loading.value = true
+ getStockInPage({ ...filterForm, ...page }).then(res => {
+ loading.value = false
+ tableData.value = res.data.records
+ total.value = res.data.total
+ console.log('res', res.data.records)
+
+ // 鏁版嵁鍔犺浇瀹屾垚鍚庢覆鏌撳浘琛�
+ nextTick(() => {
+ renderCategoryPieChart()
+ renderAmountTrendChart()
+ })
+ }).catch(() => {
+ loading.value = false
+ })
+}
+
+// 娓叉煋鍒嗙被鍗犳瘮楗煎浘
+const renderCategoryPieChart = () => {
+ if (!categoryPieChart.value) {
+ categoryPieChart.value = echarts.init(document.getElementById('category-pie-chart'))
+ }
+ // 鏍规嵁 tableData 鎸� productCategory 鍒嗙被骞惰绠� inboundNum0 鏁伴噺鎬诲拰
+ const categoryMap = tableData.value.reduce((acc, cur) => {
+ acc[cur.productCategory] = (acc[cur.productCategory] || 0) + cur.inboundNum0
+ return acc
+ }, {})
+
+ // 灏嗗垎绫荤粨鏋滆浆鎹负 ECharts 楗煎浘鎵�闇�鐨勬暟鎹牸寮�
+ const categoryData = Object.entries(categoryMap).map(([name, value]) => ({
+ name: name,
+ value: value
+ }))
+ const option = {
+ title: {
+ text: '搴撳瓨鍒嗙被鍗犳瘮',
+ left: 'center'
+ },
+ tooltip: {
+ trigger: 'item',
+ formatter: '{a} <br/>{b}: {c} ({d}%)'
+ },
+ legend: {
+ orient: 'vertical',
+ left: 'left'
+ },
+ series: [
+ {
+ name: '搴撳瓨鍒嗙被',
+ type: 'pie',
+ radius: ['40%', '70%'],
+ avoidLabelOverlap: false,
+ itemStyle: {
+ borderRadius: 10,
+ borderColor: '#fff',
+ borderWidth: 2
+ },
+ label: {
+ show: true,
+ formatter: '{b}: {d}%'
+ },
+ emphasis: {
+ label: {
+ show: true,
+ fontSize: '16',
+ fontWeight: 'bold'
+ }
+ },
+ data: categoryData
+ }
+ ]
+ }
+
+ categoryPieChart.value.setOption(option)
+}
+// 娓叉煋閲戦瓒嬪娍鎶樼嚎鍥�
+const renderAmountTrendChart = () => {
+ if (!amountTrendChart.value) {
+ amountTrendChart.value = echarts.init(document.getElementById('amount-trend-chart'))
+ }
+ // 鎸夋湀浠藉垎缁勫苟璁$畻taxInclusiveTotalPrice鎬诲拰
+ const monthlyAmounts = tableData.value.reduce((acc, cur) => {
+ const date = new Date(cur.createTime);
+ const month = date.getMonth() + 1;
+
+ // 纭繚month鍦�1-12鑼冨洿鍐�
+ if (month >= 1 && month <= 12) {
+ acc[month] = (acc[month] || 0) + cur.taxInclusiveTotalPrice;
+ }
+ return acc;
+ }, {});
+
+ // 鐢熸垚12涓湀鐨勬暟鎹紝缂哄け鏈堜唤鐢�0浠f浛
+ const amounts = [];
+ for (let i = 1; i <= 12; i++) {
+ amounts.push(monthlyAmounts[i] || 0);
+ }
+ const dates = ['1鏈�', '2鏈�', '3鏈�', '4鏈�', '5鏈�', '6鏈�', '7鏈�', '8鏈�', '9鏈�', '10鏈�', '11鏈�', '12鏈�']
+
+ const option = {
+ title: {
+ text: '搴撳瓨閲戦瓒嬪娍',
+ left: 'center'
+ },
+ tooltip: {
+ trigger: 'axis',
+ formatter: '{b}: 楼{c}'
+ },
+ xAxis: {
+ type: 'category',
+ data: dates
+ },
+ yAxis: {
+ type: 'value',
+ axisLabel: {
+ formatter: '楼{value}'
+ }
+ },
+ series: [
+ {
+ name: '搴撳瓨閲戦',
+ type: 'line',
+ data: amounts,
+ smooth: true,
+ areaStyle: {}
+ }
+ ]
+ }
+
+ amountTrendChart.value.setOption(option)
+}
+
+// 鏌ヨ鎿嶄綔
+const handleSearch = () => {
+ loadData()
+}
+
+// 閲嶇疆鎿嶄綔
+const handleReset = () => {
+ filterForm.dateRange = []
+ filterForm.supplierName = ''
+ filterForm.productCategory = ''
+ loadData()
+}
+
+// 瀵煎嚭鎿嶄綔
+const handleExport = () => {
+ console.log('瀵煎嚭鏁版嵁')
+}
+
+// 绐楀彛澶у皬鏀瑰彉鏃讹紝閲嶆柊璋冩暣鍥捐〃澶у皬
+window.addEventListener('resize', () => {
+ if (categoryPieChart.value) categoryPieChart.value.resize()
+ if (amountTrendChart.value) amountTrendChart.value.resize()
+})
+</script>
+
+<style scoped>
+.inventory-statistics {
+ padding: 20px;
+}
+
+.filter-form {
+ margin-bottom: 20px;
+}
+
+.summary-cards {
+ margin-bottom: 20px;
+}
+
+.summary-card {
+ text-align: center;
+ height: 100px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+.summary-item {
+ width: 100%;
+}
+
+.summary-title {
+ font-size: 14px;
+ color: #606266;
+ margin-bottom: 5px;
+}
+
+.summary-value {
+ font-size: 24px;
+ font-weight: bold;
+ color: #303133;
+}
+
+.summary-value.warning {
+ color: #e6a23c;
+}
+
+.summary-value.danger {
+ color: #f56c6c;
+}
+
+.chart-section {
+ margin-bottom: 20px;
+}
+
+.chart-card {
+ height: 460px;
+}
+
+.card-header {
+ font-weight: bold;
+}
+
+.table_list {
+ margin-top: 20px;
+}
+
+.pagination {
+ text-align: right;
+ margin-top: 20px;
+}
+</style>
diff --git a/src/views/index.vue b/src/views/index.vue
index dfd56e3..e06a40b 100644
--- a/src/views/index.vue
+++ b/src/views/index.vue
@@ -1,674 +1,805 @@
<template>
- <div class="dashboard">
- <!-- 椤堕儴妯悜涓ゆ爮 -->
- <div class="dashboard-top">
- <!-- 宸︼細绯荤粺姒傝+鏁版嵁鍗$墖 -->
- <div class="top-left">
- <div class="system-info">
- <div class="section-title">鎵垮寘鍟嗙鐞嗙郴缁�</div>
- <div style="display: flex; align-items: center; gap: 20px">
- <div class="system-card">
- <div class="system-name">鎵垮寘鍟嗙鐞嗙郴缁�</div>
- <div class="system-meta">璧勮川瀹℃牳 路 鍚堝悓绠$悊 路 缁╂晥璇勪及</div>
- </div>
- <div style="display: flex; align-items: center; gap: 8px">
- <el-icon color="#5053B5" size="22"><Clock /></el-icon>
- <span>褰撳墠鏃堕棿锛歿{ currentTime }}</span>
- </div>
- </div>
- </div>
- <div class="data-cards">
- <div class="data-card total">
- <div class="data-title">鎬绘壙鍖呭晢鏁�</div>
- <div class="data-value">{{ contractorStats.total }}</div>
- <div class="data-desc">
- 宸插鏍� {{ contractorStats.approved }} | 寰呭鏍�
- {{ contractorStats.pending }}
- </div>
- </div>
- <div class="data-card pending">
- <div class="data-title">寰呭鏍告壙鍖呭晢</div>
- <div class="data-value">{{ contractorStats.pending }}</div>
- <div class="data-desc">
- A绾� {{ contractorStats.aPending }} | B绾�
- {{ contractorStats.bPending }} | C绾�
- {{ contractorStats.cPending }}
- </div>
- </div>
- <div class="data-card today">
- <div class="data-title">鏈湀鏂板</div>
- <div class="data-value">{{ contractorStats.monthly }}</div>
- <div class="data-desc">
- 鍚屾瘮 {{ contractorStats.monthly鍚屾瘮 }}% | 鐜瘮
- {{ contractorStats.monthly鐜瘮 }}%
- </div>
- </div>
- </div>
- </div>
- <!-- 鍙筹細寰呭鐞嗘姤璀﹀垪琛� -->
- <div class="alarm-panel">
- <div class="section-title">寰呭鏍告壙鍖呭晢</div>
- <ul class="alarm-list" v-if="pendingContractors.length > 0">
- <li v-for="item in pendingContractors" :key="item.id">
- <div
- style="
- display: flex;
- flex-direction: column;
- justify-content: space-between;
- width: 100%;
- gap: 10px;
- "
- >
- <div
- style="
- display: flex;
- justify-content: space-between;
- align-items: center;
- "
- >
- <div class="alarm-title">{{ item.name }} - {{ item.type }}</div>
- <el-tag :type="getContractorLevelType(item.level)"
- >{{ item.level }}绾�</el-tag
- >
- </div>
- <div
- style="
- display: flex;
- justify-content: space-between;
- align-items: center;
- "
- >
- <div class="alarm-value">
- 鑱旂郴浜�: {{ item.contact }} | 娉ㄥ唽鏃堕棿: {{ item.registerDate }}
- </div>
- <div class="alarm-time">{{ item.applyDate }}</div>
- </div>
- </div>
- </li>
- </ul>
- <div v-else style="text-align: center; color: #909399; padding: 20px">
- 鏆傛棤寰呭鏍告壙鍖呭晢
- </div>
- </div>
- </div>
-
- <!-- 涓儴妯悜涓ゆ爮 -->
- <div class="dashboard-row">
- <div class="main-panel">
- <div class="section-title">鎵垮寘鍟嗗闀胯秼鍔�</div>
- <Echarts
- ref="chart"
- :chartStyle="chartStyle"
- :grid="grid"
- :legend="lineLegend"
- :series="lineSeries"
- :tooltip="tooltipLine"
- :xAxis="xAxis"
- :yAxis="yAxis"
- style="height: 300px"
- ></Echarts>
- </div>
- <div class="main-panel">
- <div class="section-title">鎵垮寘鍟嗚祫璐ㄥ垎甯�</div>
- <div
- style="
- display: flex;
- align-items: center;
- gap: 20px;
- justify-content: space-evenly;
- height: 300px;
- "
- >
- <div style="width: 50%">
- <Echarts
- ref="chart"
- :legend="pieLegend"
- :chartStyle="chartStylePie"
- :series="levelPieSeries"
- :tooltip="pieTooltip"
- ></Echarts>
- </div>
- <ul class="level-list" style="width: 50%">
- <li v-for="item in levelPieSeries[0].data" :key="item.name">
- <div
- style="
- display: flex;
- align-items: center;
- justify-content: space-between;
- width: 100%;
- "
- >
- <div class="line" :style="{ color: item.itemStyle.color }">
- 鈼弡{ item.name }}
- </div>
- <div style="width: 60px">{{ item.value }}瀹�</div>
- <div style="width: 60px">{{ item.rate }}%</div>
- </div>
- </li>
- </ul>
- </div>
- </div>
- </div>
-
- <!-- 搴曢儴妯悜涓ゆ爮 -->
- <div class="dashboard-row">
- <div class="main-panel">
- <div class="section-title">鎵垮寘鍟嗙被鍨嬪垎甯�</div>
- <Echarts
- ref="chart"
- :color="barColors"
- :chartStyle="chartStyle"
- :grid="grid"
- :series="equipmentBarSeries"
- :tooltip="tooltip"
- :xAxis="equipmentXAxis"
- :yAxis="yAxis"
- style="height: 300px"
- ></Echarts>
- </div>
- <div class="main-panel">
- <div class="section-title">鎵垮寘鍟嗗鏍告椂鏁�</div>
- <Echarts
- ref="chart"
- :color="barColors"
- :chartStyle="chartStyle"
- :grid="grid"
- :series="handlingTimeSeries"
- :tooltip="tooltip"
- :xAxis="handlingTimeXAxis"
- :yAxis="yAxis"
- style="height: 300px"
- ></Echarts>
- </div>
- </div>
- </div>
+ <div class="dashboard">
+ <!-- 椤堕儴妯悜涓ゆ爮 -->
+ <div class="dashboard-top">
+ <!-- 宸︼細浼佷笟淇℃伅+涓夊ぇ鏁版嵁鍗$墖锛堜笂涓嬫帓鍒楋級 -->
+ <div class="top-left">
+ <div class="company-info">
+ <div class="section-title">鐧婚檰淇℃伅</div>
+ <div style="display: flex;align-items: center;gap: 20px">
+ <img :src="userStore.avatar" class="avatar" alt=""/>
+ <div class="company-card">
+ <div class="company-name">{{userStore.name}}</div>
+ <div class="company-meta">{{userStore.roleName}}</div>
+ </div>
+ <div style="display: flex;align-items: center;gap: 8px">
+ <el-icon color="#5053B5" size="22"><Clock /></el-icon>
+ <span>鐧婚檰鏃ユ湡锛歿{userStore.currentLoginTime}}</span>
+ </div>
+ </div>
+ </div>
+<!-- <div class="data-cards">-->
+<!-- <div class="data-card sales">-->
+<!-- <div class="data-title">閿�鍞暟鎹�</div>-->
+<!-- <div class="data-num">-->
+<!-- <div>-->
+<!-- <div class="data-desc">鏈湀閿�鍞/鍏�</div>-->
+<!-- <div class="data-value">{{businessInfo.monthSaleMoney}}</div>-->
+<!-- </div>-->
+<!-- <div>-->
+<!-- <div class="data-desc">鏈紑绁ㄩ噾棰�/鍏�</div>-->
+<!-- <div class="data-value">{{businessInfo.monthSaleHaveMoney}}</div>-->
+<!-- </div>-->
+<!-- </div>-->
+<!-- -->
+<!-- </div>-->
+<!-- <div class="data-card purchase">-->
+<!-- <div class="data-title">閲囪喘鏁版嵁</div>-->
+<!-- <div class="data-num">-->
+<!-- <div>-->
+<!-- <div class="data-desc">鏈湀閲囪喘棰�/鍏�</div>-->
+<!-- <div class="data-value">{{businessInfo.monthPurchaseMoney}}</div>-->
+<!-- </div>-->
+<!-- <div>-->
+<!-- <div class="data-desc">寰呬粯娆鹃噾棰�/鍏�</div>-->
+<!-- <div class="data-value">{{businessInfo.monthPurchaseHaveMoney}}</div>-->
+<!-- </div>-->
+<!-- </div>-->
+<!-- </div>-->
+<!-- <div class="data-card inventory">-->
+<!-- <div class="data-title">搴撳瓨鏁版嵁</div>-->
+<!-- <div class="data-num">-->
+<!-- <div>-->
+<!-- <div class="data-desc">褰撳墠搴撳瓨鎬婚噺/浠�</div>-->
+<!-- <div class="data-value">{{businessInfo.inventoryNum}}</div>-->
+<!-- </div>-->
+<!-- <div>-->
+<!-- <div class="data-desc">浠婃棩鍏ュ簱/浠�</div>-->
+<!-- <div class="data-value">{{businessInfo.todayInventoryNum}}</div>-->
+<!-- </div>-->
+<!-- </div>-->
+<!-- </div>-->
+<!-- </div>-->
+ </div>
+ <!-- 鍙筹細寰呭姙浜嬮」 -->
+<!-- <div class="todo-panel">-->
+<!-- <div class="section-title">寰呭姙浜嬮」</div>-->
+<!-- <ul class="todo-list" v-if="todoList.length > 0">-->
+<!-- <li v-for="item in todoList" :key="item.id">-->
+<!-- <div style="display: flex;flex-direction: column;justify-content: space-between;width: 100%;gap: 20px">-->
+<!-- <div style="display: flex;justify-content: space-between;align-items: center;">-->
+<!-- <div class="todo-title">娴佺▼缂栧彿锛歿{item.approveId}}</div>-->
+<!-- <div class="todo-division">鐢宠閮ㄩ棬锛歿{item.approveDeptName}}</div>-->
+<!-- <div class="todo-time">{{item.approveTime}}</div>-->
+<!-- </div>-->
+<!-- <div class="todo-division">瀹℃壒浜嬬敱锛歿{item.approveReason}}</div>-->
+<!-- </div>-->
+<!-- </li>-->
+<!-- </ul>-->
+<!-- <div v-else style="text-align: center">-->
+<!-- 鏆傛棤鏁版嵁-->
+<!-- </div>-->
+<!-- </div>-->
+<!-- </div>-->
+<!-- -->
+<!-- <!– 涓儴妯悜涓ゆ爮 –>-->
+<!-- <div class="dashboard-row">-->
+<!-- <div class="main-panel">-->
+<!-- <div class="section-title">瀹㈡埛鍚堝悓閲戦鍒嗘瀽</div>-->
+<!-- <div class="contract-summary">-->
+<!-- <div class="contract-info">-->
+<!-- <img src="../assets/images/khtitle.png" alt="" style="width: 42px"/>-->
+<!-- <div class="contract-card">-->
+<!-- <div class="contract-name">鎬诲悎鍚岄噾棰�(鍏�)</div>-->
+<!-- <div class="contract-meta">-->
+<!-- <div class="main-amount">{{sum}}</div>-->
+<!-- <div>鍛ㄥ悓姣�: <span class="up">{{yny}}% </span> 鏃ョ幆姣�: <span class="up">{{chain}}% </span></div>-->
+<!-- </div>-->
+<!-- </div>-->
+<!-- </div>-->
+<!-- </div>-->
+<!-- <div style="display: flex;align-items: center;gap: 20px;justify-content: space-evenly;height: 180px;margin-top: 20px">-->
+<!-- <div>-->
+<!-- <Echarts ref="chart" :legend="pieLegend" :chartStyle="chartStylePie"-->
+<!-- :series="materialPieSeries"-->
+<!-- :tooltip="pieTooltip"></Echarts>-->
+<!-- </div>-->
+<!-- <ul class="contract-list">-->
+<!-- <li v-for="item in materialPieSeries[0].data" :key="item.name">-->
+<!-- <div style="display: flex;align-items: center;justify-content: space-between;width: 100%">-->
+<!-- <div class="line" :style="{color: item.itemStyle.color}">鈼弡{item.name}}</div>-->
+<!-- <div style="width: 70px">{{item.rate}}%</div>-->
+<!-- <div>锟{item.value}}</div>-->
+<!-- </div>-->
+<!-- </li>-->
+<!-- </ul>-->
+<!-- </div>-->
+<!-- </div>-->
+<!-- <div class="main-panel">-->
+<!-- <div style="display: flex;justify-content: space-between;">-->
+<!-- <div class="section-title">搴旀敹搴斾粯缁熻</div>-->
+<!--<!– <el-radio-group v-model="radio1" size="large" @change="statisticsReceivable">–>-->
+<!--<!– <el-radio-button label="鎸夊懆" :value="1" />–>-->
+<!--<!– <el-radio-button label="鎸夋湀" :value="2" />–>-->
+<!--<!– <el-radio-button label="鎸夊搴�" :value="3" />–>-->
+<!--<!– </el-radio-group>–>-->
+<!-- </div>-->
+<!-- <Echarts ref="chart"-->
+<!-- :color="barColors2"-->
+<!-- :chartStyle="chartStyle"-->
+<!-- :grid="grid"-->
+<!-- :series="barSeries"-->
+<!-- :tooltip="tooltip"-->
+<!-- :xAxis="xAxis"-->
+<!-- :yAxis="yAxis"-->
+<!-- style="height: 260px"></Echarts>-->
+<!-- </div>-->
+<!-- </div>-->
+
+ <!-- 搴曢儴妯悜涓ゆ爮 -->
+<!-- <div class="dashboard-row">-->
+<!-- <div class="main-panel">-->
+<!-- <div class="section-title">璐ㄩ噺缁熻</div>-->
+<!-- <div class="quality-cards">-->
+<!-- <div class="quality-card one">鍘熸潗鏂欏凡妫�娴嬫暟 <span>{{qualityStatisticsObject.supplierNum}}浠�</span></div>-->
+<!-- <div class="quality-card two">杩囩▼妫�楠屾暟閲� <span>{{qualityStatisticsObject.processNum}}浠�</span></div>-->
+<!-- <div class="quality-card three">鍑哄巶宸叉鏁伴噺 <span>{{qualityStatisticsObject.factoryNum}}浠�</span></div>-->
+<!-- </div>-->
+<!-- <Echarts ref="chart"-->
+<!-- :chartStyle="chartStyle"-->
+<!-- :grid="grid"-->
+<!-- :legend="barLegend"-->
+<!-- :series="barSeries1"-->
+<!-- :tooltip="tooltip"-->
+<!-- :xAxis="xAxis1"-->
+<!-- :yAxis="yAxis1"-->
+<!-- style="height: 260px"></Echarts>-->
+<!-- </div>-->
+<!-- <div class="main-panel">-->
+<!-- <div class="section-title">鍥炴涓庡紑绁ㄥ垎鏋�</div>-->
+<!-- <Echarts ref="chart" :chartStyle="chartStyle" :grid="grid" :legend="lineLegend" :series="lineSeries"-->
+<!-- :tooltip="tooltipLine" :xAxis="xAxis2" :yAxis="yAxis2" style="height: 270px;"></Echarts>-->
+<!-- </div>-->
+ </div>
+ </div>
</template>
<script setup>
-import { ref, onMounted, reactive, computed } from "vue";
+import { ref, onMounted } from 'vue'
import Echarts from "@/components/Echarts/echarts.vue";
-import { Clock } from "@element-plus/icons-vue";
+import * as echarts from 'echarts';
+import useUserStore from "@/store/modules/user.js";
+import {
+ analysisCustomerContractAmounts, getAmountHalfYear,
+ getBusiness,
+ homeTodos,
+ qualityStatistics,
+ statisticsReceivablePayable
+} from "@/api/viewIndex.js";
-// 褰撳墠鏃堕棿
-const currentTime = computed(() => {
- const now = new Date();
- return now.toLocaleString("zh-CN");
-});
+const userStore = useUserStore()
-// 鎵垮寘鍟嗙粺璁℃暟鎹�
-const contractorStats = reactive({
- total: 156,
- approved: 132,
- pending: 24,
- aPending: 8,
- bPending: 12,
- cPending: 4,
- monthly: 28,
- monthly鍚屾瘮: "+18.5",
- monthly鐜瘮: "+5.2",
-});
+const businessInfo = ref({
+ inventoryNum: 0,
+ monthPurchaseHaveMoney: 0,
+ monthPurchaseMoney: 0,
+ monthSaleHaveMoney: 0,
+ monthSaleMoney: 0,
+ todayInventoryNum: 0,
+})
+const qualityStatisticsObject = ref({
+ supplierNum: 0,
+ processNum: 0,
+ factoryNum: 0,
+})
+const sum = ref(0)
+const yny = ref(0)
+const chain = ref(0)
-// 寰呭鏍告壙鍖呭晢鍒楄〃
-const pendingContractors = ref([
- {
- id: 1,
- name: "寤虹瓚宸ョ▼鏈夐檺鍏徃",
- type: "寤虹瓚鏂藉伐",
- contact: "寮犱笁",
- registerDate: "2025-01-15",
- applyDate: "2025-12-16 14:30:23",
- level: "A",
- },
- {
- id: 2,
- name: "鏈虹數瀹夎宸ョ▼鍏徃",
- type: "鏈虹數瀹夎",
- contact: "鏉庡洓",
- registerDate: "2025-03-20",
- applyDate: "2025-12-16 14:28:15",
- level: "B",
- },
- {
- id: 3,
- name: "瑁呴グ瑁呬慨宸ョ▼鍏徃",
- type: "瑁呴グ瑁呬慨",
- contact: "鐜嬩簲",
- registerDate: "2025-05-10",
- applyDate: "2025-12-16 14:22:18",
- level: "B",
- },
- {
- id: 4,
- name: "缁垮寲宸ョ▼鏈夐檺鍏徃",
- type: "鍥灄缁垮寲",
- contact: "璧靛叚",
- registerDate: "2025-08-05",
- applyDate: "2025-12-16 14:18:55",
- level: "C",
- },
-]);
-
-// 鍥捐〃鏍峰紡
+const pieLegend = reactive({
+ show: false,
+})
+const barSeries = ref([
+ {
+ type: 'bar',
+ data: [],
+ label: {
+ show: true,
+ }
+ },
+])
+const barSeries1 = ref([
+ {
+ name: '鍘熸潗鏂欎笉鍚堟牸鏁�',
+ type: 'bar',
+ barGap: 0,
+ emphasis: {
+ focus: 'series'
+ },
+ data: []
+ },
+ {
+ name: '杩囩▼涓嶅悎鏍兼暟',
+ type: 'bar',
+ emphasis: {
+ focus: 'series'
+ },
+ data: []
+ },
+ {
+ name: '鍑哄巶涓嶅悎鏍兼暟',
+ type: 'bar',
+ emphasis: {
+ focus: 'series'
+ },
+ data: []
+ },
+])
const chartStyle = {
- width: "100%",
- height: "100%",
-};
-
+ width: '100%',
+ height: '100%' // 璁剧疆鍥捐〃瀹瑰櫒鐨勯珮搴�
+}
const chartStylePie = {
- width: "100%",
- height: "100%",
-};
-
+ width: '140%',
+ height: '140%' // 璁剧疆鍥捐〃瀹瑰櫒鐨勯珮搴�
+}
const grid = {
- left: "3%",
- right: "4%",
- bottom: "3%",
- containLabel: true,
-};
-
-// 鎵垮寘鍟嗗闀胯秼鍔� - 鎶樼嚎鍥�
+ left: '3%',
+ right: '4%',
+ bottom: '3%',
+ containLabel: true
+}
+const barLegend = {
+ show: true,
+ data: ['鍘熸潗鏂欎笉鍚堟牸鏁�', '杩囩▼涓嶅悎鏍兼暟', '鍑哄巶涓嶅悎鏍兼暟']
+}
+const barLegend1 = {
+ show: true,
+ data: ['棰勪粯璐︽', '搴斾粯璐︽', '棰勬敹璐︽', '搴旀敹璐︽']
+}
const lineLegend = {
- show: true,
- data: ["A绾�", "B绾�", "C绾�"],
-};
-
-const tooltipLine = {
- trigger: "axis",
- axisPointer: {
- type: "cross",
- },
-};
-
-const xAxis = ref({
- type: "category",
- data: ["12-01", "12-02", "12-03", "12-04", "12-05", "12-06", "12-07"],
-});
-
-const yAxis = ref({
- type: "value",
- name: "鎵垮寘鍟嗘暟閲�",
-});
-
-const lineSeries = ref([
- {
- name: "A绾�",
- type: "line",
- data: [15, 18, 20, 22, 25, 28, 30],
- itemStyle: {
- color: "#f56c6c",
- },
- lineStyle: {
- width: 2,
- },
- showSymbol: true,
- },
- {
- name: "B绾�",
- type: "line",
- data: [45, 52, 58, 65, 70, 75, 80],
- itemStyle: {
- color: "#e6a23c",
- },
- lineStyle: {
- width: 2,
- },
- showSymbol: true,
- },
- {
- name: "C绾�",
- type: "line",
- data: [30, 35, 40, 45, 50, 55, 60],
- itemStyle: {
- color: "#67c23a",
- },
- lineStyle: {
- width: 2,
- },
- showSymbol: true,
- },
-]);
-
-// 鎵垮寘鍟嗚祫璐ㄥ垎甯� - 楗煎浘
-const pieLegend = {
- show: false,
-};
-
-const pieTooltip = {
- trigger: "item",
- formatter: "{b}: {c}瀹� ({d}%)",
-};
-
-const levelPieSeries = ref([
- {
- type: "pie",
- radius: ["60%", "80%"],
- avoidLabelOverlap: false,
- itemStyle: {
- borderColor: "#fff",
- borderWidth: 2,
- },
- label: {
- show: false,
- },
- data: [
- {
- name: "A绾�",
- value: 45,
- rate: 28.85,
- itemStyle: { color: "#f56c6c" },
- },
- {
- name: "B绾�",
- value: 70,
- rate: 44.87,
- itemStyle: { color: "#e6a23c" },
- },
- {
- name: "C绾�",
- value: 41,
- rate: 26.28,
- itemStyle: { color: "#67c23a" },
- },
- ],
- },
-]);
-
-// 鎵垮寘鍟嗙被鍨嬪垎甯� - 鏌辩姸鍥�
-const barColors = ["#409eff", "#67c23a", "#e6a23c", "#f56c6c", "#909399"];
-
+ show: true,
+ data: ['寮�绁�', '鍥炴']
+}
const tooltip = {
- trigger: "axis",
- axisPointer: {
- type: "shadow",
- },
-};
+ trigger: 'axis',
+ axisPointer: {
+ type: 'shadow'
+ }
+}
+const xAxis = [{
+ type: 'value',
+}]
+const xAxis1 = ref([{
+ type: 'category',
+ axisTick: { show: false },
+ data: []
+}])
+const yAxis = [{
+ type: 'category',
+ data: [ '搴斾粯璐︽', '搴旀敹璐︽',]
+}]
+const yAxis1 = [{
+ type: 'value'
+}]
+const pieTooltip = reactive({
+ trigger: 'item',
+ formatter: function (params) {
+ // 鍔ㄦ�佺敓鎴愭彁绀轰俊鎭紝鍩轰簬鏁版嵁椤圭殑 name 灞炴��
+ const description = params.name === '鏈湀鍥炴閲戦' ? '鏈湀鍥炴閲戦' : '搴旀敹娆鹃噾棰�';
+ return `${description} ${formatNumber(params.value)}鍏� ${params.percent}%`;
+ },
+ position: 'right'
+})
+const materialPieSeries = ref([
+ {
+ type: 'pie',
+ radius: ['66%', '90%'],
+ avoidLabelOverlap: false,
+ itemStyle: {
+ borderColor: '#fff',
+ borderWidth: 2
+ },
+ label: {
+ show: false
+ },
+ data: []
+ }
+])
+const lineSeries = ref([
+ {
+ type: 'line',
+ data: [],
+ label: {
+ show: true
+ },
+ showSymbol: true, // 鏄剧ず鍦嗙偣
+ },
+])
+const tooltipLine = {
+ trigger: 'axis',
+}
+const yAxis2 = ref([
+ {
+ type: 'value',
+ }
+])
+const xAxis2 = ref([
+ {
+ type: 'category',
+ data: [],
+ axisLabel: {
+ interval: 0,
+ formatter: function(value) {
+ return value.replace(/~/g, '\n');
+ },
+ }
+ }
+])
-const equipmentXAxis = ref({
- type: "category",
- data: ["寤虹瓚鏂藉伐", "鏈虹數瀹夎", "瑁呴グ瑁呬慨", "鍥灄缁垮寲", "鍏朵粬"],
-});
+// 寰呭姙浜嬮」
+const todoList = ref([])
+const radio1 = ref(1)
-const equipmentBarSeries = ref([
- {
- name: "鎵垮寘鍟嗘暟閲�",
- type: "bar",
- data: [45, 35, 25, 20, 31],
- itemStyle: {
- color: function (params) {
- return barColors[params.dataIndex % barColors.length];
- },
- },
- label: {
- show: true,
- position: "top",
- },
- },
-]);
+// 鍥捐〃寮曠敤
+const barChart = ref(null)
+const lineChart = ref(null)
+const barColors2 = ['#5181DB', '#D369E0', '#F2CA6D', '#60CCA8']
-// 鎵垮寘鍟嗗鏍告椂鏁堝垎鏋� - 鏌辩姸鍥�
-const handlingTimeXAxis = ref({
- type: "category",
- data: ["0-1澶�", "1-3澶�", "3-7澶�", "7澶╀互涓�"],
-});
-
-const handlingTimeSeries = ref([
- {
- name: "瀹℃牳鏁伴噺",
- type: "bar",
- data: [85, 45, 20, 10],
- itemStyle: {
- color: function (params) {
- return barColors[params.dataIndex % barColors.length];
- },
- },
- label: {
- show: true,
- position: "top",
- },
- },
-]);
-
-// 鑾峰彇鎵垮寘鍟嗙骇鍒牱寮�
-const getContractorLevelType = (level) => {
- switch (level) {
- case "A":
- return "danger";
- case "B":
- return "warning";
- case "C":
- return "info";
- default:
- return "info";
- }
-};
+// 闅忔満棰滆壊鐢熸垚鍑芥暟
+const getRandomColor = () => {
+ return '#' + Math.floor(Math.random() * 0xffffff).toString(16).padStart(6, '0');
+}
onMounted(() => {
- // 椤甸潰鍔犺浇瀹屾垚鍚庣殑鍒濆鍖栨搷浣�
-});
+ getBusinessData()
+ analysisCustomer()
+ todoInfoS()
+ statisticsReceivable()
+ qualityStatisticsInfo()
+ getAmountHalfYearNum()
+})
+// 鏁版嵁缁熻
+const getBusinessData = () => {
+ getBusiness().then((res) => {
+ businessInfo.value = {...res.data}
+ })
+}
+// 鍚堝悓閲戦
+const analysisCustomer = () => {
+ analysisCustomerContractAmounts().then((res) => {
+ sum.value = res.data.sum
+ yny.value = res.data.yny
+ chain.value = res.data.chain
+ // 涓烘瘡涓暟鎹」鍒嗛厤闅忔満棰滆壊
+ materialPieSeries.value[0].data = res.data.item.map(item => ({
+ ...item,
+ itemStyle: { color: getRandomColor() }
+ }))
+ })
+}
+// 寰呭姙浜嬮」
+const todoInfoS = () => {
+ homeTodos().then((res) => {
+ todoList.value = res.data
+ })
+}
+// 搴斾粯搴旀敹缁熻
+const statisticsReceivable = (type) => {
+ console.log(type)
+ statisticsReceivablePayable({type: radio1.value}).then((res) => {
+ barSeries.value[0].data = [
+ // { value: res.data.prepayMoney, itemStyle: { color: barColors2[0] } },
+ { value: res.data.payableMoney, itemStyle: { color: barColors2[0] } },
+ // { value: res.data.advanceMoney, itemStyle: { color: barColors2[2] } },
+ { value: res.data.receivableMoney, itemStyle: { color: barColors2[1] } }
+ ]
+ })
+}
+// 璐ㄦ缁熻
+const qualityStatisticsInfo = () => {
+ qualityStatistics().then((res) => {
+ res.data.item.forEach(item => {
+ xAxis1.value[0].data.push(item.date)
+ barSeries1.value[0].data.push(item.supplierNum)
+ barSeries1.value[1].data.push(item.processNum)
+ barSeries1.value[2].data.push(item.factoryNum)
+ })
+ qualityStatisticsObject.value.supplierNum = res.data.supplierNum
+ qualityStatisticsObject.value.processNum = res.data.processNum
+ qualityStatisticsObject.value.factoryNum = res.data.factoryNum
+ })
+}
+const getAmountHalfYearNum = async () => {
+ const res = await getAmountHalfYear()
+ console.log(res)
+ const monthName = []
+ const receiptAmount = []
+ const invoiceAmount = []
+ res.data.forEach(item => {
+ monthName.push(item.month)
+ receiptAmount.push(item.receiptAmount)
+ invoiceAmount.push(item.invoiceAmount)
+ })
+ // 姝g‘鍝嶅簲寮忚祴鍊硷細鍒涘缓鏂扮殑 xAxis 鍜� series 瀵硅薄
+ xAxis2.value[0].data = monthName
+ xAxis2.value[0].data = monthName.map(item => item.replace(/~/g, '\n~'));
+ lineSeries.value = [
+ {
+ name: '寮�绁�',
+ type: 'line',
+ data: receiptAmount,
+ stack: 'Total',
+ areaStyle: {
+ color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
+ {
+ offset: 0,
+ color: 'rgba(131, 207, 255, 1)'
+ },
+ {
+ offset: 1,
+ color: 'rgba(186, 228, 255, 1)'
+ }
+ ])
+ },
+ itemStyle: {
+ color: '#2D99FF',
+ borderColor: '#2D99FF'
+ },
+ emphasis: {
+ focus: 'series'
+ },
+ lineStyle: {
+ width: 0
+ },
+ showSymbol: true,
+ },
+ {
+ name: '鍥炴',
+ type: 'line',
+ data: invoiceAmount,
+ stack: 'Total',
+ lineStyle: {
+ width: 0
+ },
+ itemStyle: {
+ color: '#83CFFF',
+ borderColor: '#83CFFF'
+ },
+ showSymbol: true,
+ areaStyle: {
+ color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
+ {
+ offset: 0,
+ color: 'rgba(54, 153, 255, 1)'
+ },
+ {
+ offset: 1,
+ color: 'rgba(89, 169, 254, 1)'
+ }
+ ])
+ },
+ emphasis: {
+ focus: 'series'
+ },
+ }
+ ]
+}
</script>
<style scoped>
.dashboard {
- background: #f5f7fa;
- min-height: 100vh;
- padding: 20px;
- box-sizing: border-box;
+ background: #f5f7fa;
+ min-height: 100vh;
+ padding: 20px;
+ box-sizing: border-box;
}
-
.dashboard-top {
- display: flex;
- gap: 20px;
- margin-bottom: 20px;
+ display: flex;
+ gap: 20px;
+ margin-bottom: 20px;
+}
+.company-info {
+ display: flex;
+ flex-direction: column;
+ gap: 8px;
+ padding: 20px;
+ min-width: 0;
+ background-color: #EFF2FB; /* 浣跨敤鎸囧畾鐨勮儗鏅鑹� */
+ background-image: url("../assets/images/denglu.png");
+ background-size: 100% 260%;
+ background-position: center;
+ background-repeat: no-repeat;
+ border-radius: 12px;
+ height: 138px;
+}
+.avatar {
+ width: 60px;
+ height: 60px;
+ border-radius: 50%;
+ object-fit: contain;
+ background: #fff;
+ border: 1px solid #eee;
+}
+.company-card {
+ display: flex;
+ flex-direction: column;
+ gap: 10px;
+ position: relative;
+ padding-right: 15px;
}
-.system-info {
- display: flex;
- flex-direction: column;
- gap: 8px;
- padding: 20px;
- min-width: 0;
- background-color: #eff2fb;
- border-radius: 12px;
- height: 138px;
+.company-card::after {
+ content: '';
+ position: absolute;
+ right: 0;
+ top: 0;
+ bottom: 0;
+ width: 1px;
+ background-color: #C9C5C5;
+ border-radius: 2px;
}
-
-.system-card {
- display: flex;
- flex-direction: column;
- gap: 10px;
- position: relative;
- padding-right: 15px;
+.company-name {
+ font-weight: 400;
+ font-size: 16px;
+ color: #161A9A;
}
-
-.system-name {
- font-weight: 600;
- font-size: 18px;
- color: #161a9a;
+.company-meta {
+ font-weight: 400;
+ font-size: 12px;
+ color: #818185;
}
-
-.system-meta {
- font-weight: 400;
- font-size: 12px;
- color: #818185;
-}
-
.data-cards {
- display: flex;
- gap: 16px;
- justify-content: flex-start;
- background: #ffffff;
- border-radius: 12px;
- padding: 20px;
+ display: flex;
+ gap: 16px;
+ justify-content: flex-start;
+ background: #ffffff;
+ border-radius: 12px;
+ padding: 20px;
}
-
-.data-card {
- background: #fff;
- border-radius: 12px;
- padding: 16px;
- min-width: 160px;
- box-shadow: 0 2px 8px #eee;
- display: flex;
- flex-direction: column;
- align-items: center;
- justify-content: center;
- width: 32%;
- height: 140px;
- transition: all 0.3s ease;
-}
-
-.data-card:hover {
- transform: translateY(-2px);
- box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
-}
-
-.data-card.total {
- background: linear-gradient(135deg, #409eff 0%, #66b1ff 100%);
- color: #fff;
-}
-
-.data-card.pending {
- background: linear-gradient(135deg, #f56c6c 0%, #f78989 100%);
- color: #fff;
-}
-
-.data-card.today {
- background: linear-gradient(135deg, #67c23a 0%, #85ce61 100%);
- color: #fff;
-}
-
.data-title {
- font-weight: 600;
- font-size: 16px;
- margin-bottom: 12px;
+ font-weight: 700;
+ font-size: 26px;
+ color: #FFFFFF;
}
-
-.data-value {
- font-size: 32px;
- font-weight: bold;
- margin-bottom: 8px;
+.data-num {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ margin-top: 20px;
}
-
+.data-card {
+ background: #fff;
+ border-radius: 12px;
+ padding: 14px 10px 10px 10px;
+ min-width: 160px;
+ box-shadow: 0 2px 8px #eee;
+ display: flex;
+ flex-direction: column;
+ width: 32%;
+ height: 140px;
+}
+.data-card.sales {
+ background-image: url("../assets/images/xioashoushuju.png");
+ background-size: cover;
+ background-position: center;
+ background-repeat: no-repeat;
+}
+.data-card.purchase {
+ background-image: url("../assets/images/caigou.png");
+ background-size: cover;
+ background-position: center;
+ background-repeat: no-repeat;
+}
+.data-card.inventory {
+ background-image: url("../assets/images/kucun.png");
+ background-size: cover;
+ background-position: center;
+ background-repeat: no-repeat;
+}
.data-desc {
- font-size: 12px;
- opacity: 0.9;
+ font-weight: 500;
+ font-size: 13px;
+ color: #FFFFFF;
}
-
+.data-value {
+ font-size: 18px;
+ font-weight: 500;
+ margin: 10px 0;
+ color: #FFFFFF;
+}
.top-left {
- display: flex;
- flex-direction: column;
- gap: 20px;
- width: 60%;
+ display: flex;
+ flex-direction: column;
+ gap: 20px;
+ width: 50%;
}
-
-.alarm-panel {
- background: #fff;
- border-radius: 12px;
- padding: 20px;
- width: 40%;
+.todo-panel {
+ background: #fff;
+ border-radius: 12px;
+ padding: 20px;
+ width: 50%;
}
-
-.alarm-list {
- list-style: none;
- padding: 0;
- margin: 0;
- font-size: 14px;
- overflow-y: auto;
- height: 260px;
+.todo-list {
+ list-style: none;
+ padding: 0;
+ margin: 0;
+ font-size: 15px;
+ overflow-y: auto;
+ height: 260px;
}
-
-.alarm-list li {
- border-radius: 8px;
- margin-bottom: 12px;
- padding: 12px 20px;
- display: flex;
- justify-content: space-between;
- align-items: center;
- background: #f8f9fa;
- border-left: 4px solid #409eff;
- transition: all 0.3s ease;
+.todo-list li {
+ border-radius: 8px;
+ margin-bottom: 12px;
+ padding: 8px 20px;
+ height: 74px;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ background: rgba(225,227,250,0.62);
}
-
-.alarm-list li:hover {
- background: #e9ecef;
- transform: translateX(4px);
+.todo-title {
+ font-weight: 400;
+ font-size: 12px;
+ color: #000000;
+ position: relative;
}
-
-.alarm-title {
- font-weight: 600;
- font-size: 14px;
- color: #303133;
+.todo-title::before {
+ content: ''; /* 蹇呴渶锛岃〃绀鸿繖閲屾湁涓�涓唴瀹� */
+ position: absolute;
+ left: -10px; /* 瀹氫綅鍒板乏渚� */
+ top: 50%; /* 鍨傜洿灞呬腑 */
+ transform: translateY(-50%); /* 寰皟鍨傜洿灞呬腑 */
+ width: 6px; /* 鍦嗙殑鐩村緞 */
+ height: 6px; /* 鍦嗙殑鐩村緞 */
+ background: #498CEB;
+ border-radius: 50%; /* 璁╁叾鍙樻垚鍦嗗舰 */
}
-
-.alarm-value {
- font-size: 12px;
- color: #606266;
+.todo-division {
+ font-weight: 400;
+ font-size: 12px;
+ color: #000000;
}
-
-.alarm-time {
- font-size: 12px;
- color: #909399;
+.todo-time {
+ font-weight: 400;
+ font-size: 12px;
+ color: #000000;
}
-
+.todo-meta {
+ color: #888;
+ font-size: 13px;
+}
.dashboard-row {
- display: flex;
- gap: 20px;
- margin-bottom: 20px;
+ display: flex;
+ gap: 20px;
+ margin-bottom: 20px;
}
-
.main-panel {
- background: #fff;
- border-radius: 12px;
- padding: 20px;
- flex: 1;
- min-width: 0;
- display: flex;
- flex-direction: column;
+ background: #fff;
+ border-radius: 12px;
+ padding: 20px;
+ flex: 1;
+ min-width: 0;
+ display: flex;
+ flex-direction: column;
}
-
.section-title {
- position: relative;
- font-size: 18px;
- color: #333;
- padding-left: 10px;
- margin-bottom: 20px;
- font-weight: 700;
+ position: relative;
+ font-size: 18px;
+ color: #333;
+ padding-left: 10px;
+ margin-bottom: 10px;
+ font-weight: 700;
}
.section-title::before {
- position: absolute;
- left: 0;
- top: 4px;
- content: "";
- width: 4px;
- height: 18px;
- background-color: #409eff;
- border-radius: 2px;
+ position: absolute;
+ left: 0;
+ top: 4px;
+ content: '';
+ width: 4px;
+ height: 18px;
+ background-color: #002FA7;
+ border-radius: 2px;
}
-
-.level-list {
- margin: 0;
- padding: 0;
- list-style: none;
- height: 200px;
- overflow-y: auto;
- width: 100%;
+.contract-info {
+ display: flex;
+ align-items: center;
+ gap: 20px;
+ height: 90px;
+ background: rgba(245,245,245,0.59);
+ width: 100%;
+ border-radius: 10px;
+ padding: 10px 30px;
}
-
-.level-list li {
- margin-bottom: 15px;
- padding: 10px;
- background: #f8f9fa;
- border-radius: 6px;
- transition: all 0.3s ease;
+.contract-summary {
+ display: flex;
+ align-items: center;
+ gap: 30px;
}
-
-.level-list li:hover {
- background: #e9ecef;
+.contract-card {
+ display: flex;
+ flex-direction: column;
+ gap: 10px;
}
-
+.contract-name {
+ font-weight: 400;
+ font-size: 14px;
+ color: #050505;
+}
+.contract-meta {
+ display: flex;
+ align-items: center;
+ width: 100%;
+ gap: 80px;
+}
+.main-amount {
+ font-size: 24px;
+ color: rgba(51,50,50,0.85);
+}
+.up { color: #e57373; }
+.contract-list {
+ margin-top: 16px;
+ font-size: 14px;
+ color: #666;
+ list-style: none;
+ padding: 0;
+ height: 190px;
+ overflow-y: auto;
+ width: 460px;
+}
.line {
- position: relative;
- width: 80px;
- font-weight: 500;
+ position: relative;
+ width: 230px;
}
-</style>
+.line::after {
+ content: '';
+ position: absolute;
+ right: 2px;
+ top: 0;
+ bottom: 0;
+ width: 1px;
+ background-color: #C9C5C5;
+ border-radius: 2px;
+}
+.contract-list li {
+ margin-top: 10px;
+}
+.quality-cards {
+ display: flex;
+ gap: 12px;
+ margin-bottom: 12px;
+}
+.quality-card {
+ border-radius: 8px;
+ padding: 15px 10px 10px 50px;
+ font-weight: 400;
+ font-size: 12px;
+ color: rgba(0,0,0,0.67);
+ width: 236px;
+ height: 49px;
+ background-size: cover;
+ background-position: center;
+ background-repeat: no-repeat;
+}
+.quality-card.one {
+ background-image: url("../assets/images/yuancailiao.png");
+}
+.quality-card.two {
+ background-image: url("../assets/images/guocheng.png");
+}
+.quality-card.three {
+ background-image: url("../assets/images/chuchang.png");
+
+}
+.quality-card span {
+ color: #4fc3f7;
+ font-weight: bold;
+ margin-left: 6px;
+}
+.chart {
+ width: 100%;
+ height: 220px;
+ margin-top: 10px;
+}
+</style>
\ No newline at end of file
diff --git a/src/views/inventoryManagement/dispatchLog/index.vue b/src/views/inventoryManagement/dispatchLog/index.vue
new file mode 100644
index 0000000..c15e73f
--- /dev/null
+++ b/src/views/inventoryManagement/dispatchLog/index.vue
@@ -0,0 +1,371 @@
+<template>
+ <div class="app-container">
+ <div class="search_form">
+ <div>
+ <span class="search_title ml10">鍙戞枡鏃ユ湡锛�</span>
+ <el-date-picker
+ v-model="searchForm.timeStr"
+ type="date"
+ placeholder="璇烽�夋嫨鏃ユ湡"
+ value-format="YYYY-MM-DD"
+ format="YYYY-MM-DD"
+ clearable
+ @change="handleQuery"
+ />
+ <span class="search_title ml10">浜у搧澶х被锛�</span>
+ <el-input
+ v-model="searchForm.productCategory"
+ style="width: 240px"
+ placeholder="璇疯緭鍏�"
+ clearable
+ />
+ <el-button type="primary" @click="handleQuery" style="margin-left: 10px">鎼滅储</el-button>
+ </div>
+ <div>
+ <el-button @click="handleOut">瀵煎嚭</el-button>
+ <el-button type="danger" plain @click="handleDelete">鍒犻櫎</el-button>
+ </div>
+ </div>
+ <div class="table_list">
+ <el-table
+ :data="tableData"
+ border
+ v-loading="tableLoading"
+ @selection-change="handleSelectionChange"
+ :expand-row-keys="expandedRowKeys"
+ :row-key="(row) => row.id"
+ show-summary
+ style="width: 100%"
+ :summary-method="summarizeMainTable"
+ height="calc(100vh - 18.5em)"
+ >
+ <el-table-column align="center" type="selection" width="55" />
+ <el-table-column align="center" label="搴忓彿" type="index" width="60" />
+ <el-table-column label="鍙戞枡鏃ユ湡" prop="createTime" show-overflow-tooltip width="130"/>
+ <el-table-column label="鎵规鍙�" prop="code" width="130" show-overflow-tooltip />
+ <el-table-column label="浜у搧澶х被" prop="productCategory" show-overflow-tooltip />
+ <el-table-column label="瑙勬牸鍨嬪彿" prop="specificationModel" show-overflow-tooltip />
+ <el-table-column label="鍗曚綅" prop="unit" show-overflow-tooltip />
+ <el-table-column label="鐗╁搧绫诲瀷" prop="itemType" show-overflow-tooltip />
+ <el-table-column label="鍙戞枡鏁伴噺" prop="inboundNum" show-overflow-tooltip />
+ <el-table-column label="鍗曚环(鍏�)" prop="taxInclusiveUnitPrice" width="150"></el-table-column>
+ <el-table-column label="鎬讳环(鍏�)" prop="taxInclusiveTotalPrice" width="150"></el-table-column>
+ <el-table-column label="鍙戞枡浜�" prop="createBy" show-overflow-tooltip />
+ </el-table>
+ <pagination
+ v-show="total > 0"
+ :total="total"
+ layout="total, sizes, prev, pager, next, jumper"
+ :page="page.current"
+ :limit="page.size"
+ @pagination="paginationChange"
+ />
+ </div>
+ </div>
+</template>
+
+<script setup>
+import pagination from "@/components/PIMTable/Pagination.vue";
+import { ref, reactive, toRefs, onMounted, getCurrentInstance } from "vue";
+import { ElMessageBox } from "element-plus";
+import {
+ delStockOut,
+} from "@/api/inventoryManagement/stockOut.js";
+import {
+ getStockInPageByCustom,
+} from "@/api/inventoryManagement/stockIn.js";
+import { getCurrentDate } from "@/utils/index.js";
+const { proxy } = getCurrentInstance();
+const tableData = ref([]);
+const selectedRows = ref([]);
+const tableLoading = ref(false);
+const page = reactive({
+ current: 1,
+ size: 100,
+});
+const total = ref(0);
+
+// 鏌ヨ琛ㄥ崟鏁版嵁
+const data = reactive({
+ searchForm: {
+ supplierName: "",
+ customerName: "",
+ productCategory:'',
+ timeStr: getCurrentDate(),
+ },
+});
+const { searchForm } = toRefs(data);
+
+// 鏌ヨ鍒楄〃
+/** 鎼滅储鎸夐挳鎿嶄綔 */
+const handleQuery = () => {
+ page.current = 1;
+ getList();
+};
+const paginationChange = (obj) => {
+ page.current = obj.page;
+ page.size = obj.limit;
+ getList();
+};
+const getList = () => {
+ tableLoading.value = true;
+ const params = { ...page }
+ params.supplierName = searchForm.value.supplierName
+ params.timeStr = searchForm.value.timeStr
+ params.productCategory = searchForm.value.productCategory
+
+ // 鏉愭枡鍑哄簱锛氳皟鐢ㄨ嚜瀹氫箟鍑哄簱璁板綍鎺ュ彛
+ const apiCall = getStockInPageByCustom(params)
+
+ apiCall
+ .then((res) => {
+ tableLoading.value = false;
+ tableData.value = res.data.records;
+ tableData.value.map((item) => {
+ item.children = [];
+ // 鍓嶇璁$畻鎬讳环
+ const inboundNum = Number(item.inboundNum) || 0;
+ // 鏉愭枡鍑哄簱锛氭�讳环 = taxInclusiveUnitPrice 脳 inboundNum
+ const taxInclusiveUnitPrice = Number(item.taxInclusiveUnitPrice) || 0;
+ item.taxInclusiveTotalPrice = (taxInclusiveUnitPrice * inboundNum).toFixed(2);
+ });
+ total.value = res.data.total;
+ })
+ .catch(() => {
+ tableLoading.value = false;
+ });
+};
+
+// 琛ㄦ牸閫夋嫨鏁版嵁
+const handleSelectionChange = (selection) => {
+ // 杩囨护鎺夊瓙鏁版嵁
+ selectedRows.value = selection.filter((item) => item.id);
+ console.log("selection", selectedRows.value);
+};
+const expandedRowKeys = ref([]);
+
+// 涓昏〃鍚堣鏂规硶
+const summarizeMainTable = (param) => {
+ return proxy.summarizeTable(param, [
+ "contractAmount",
+ "taxInclusiveTotalPrice",
+ "taxExclusiveTotalPrice",
+ ]);
+};
+
+// 瀵煎嚭
+const handleOut = () => {
+ ElMessageBox.confirm("鏄惁纭瀵煎嚭锛�", "瀵煎嚭", {
+ confirmButtonText: "纭",
+ cancelButtonText: "鍙栨秷",
+ type: "warning",
+ })
+ .then(() => {
+ // 鏍规嵁涓嶅悓鐨� tab 绫诲瀷璋冪敤涓嶅悓鐨勫鍑烘帴鍙�
+ const exportUrl = "/stockmanagement/exportTwo"
+ proxy.download(exportUrl, {}, "鍑哄簱鍙拌处.xlsx");
+ })
+ .catch(() => {
+ proxy.$modal.msg("宸插彇娑�");
+ });
+};
+
+// 鍒犻櫎
+const handleDelete = () => {
+ let ids = [];
+ if (selectedRows.value.length > 0) {
+ ids = selectedRows.value.map((item) => item.id);
+ } else {
+ proxy.$modal.msgWarning("璇烽�夋嫨鏁版嵁");
+ return;
+ }
+ ElMessageBox.confirm("閫変腑鐨勫唴瀹瑰皢琚垹闄わ紝鏄惁纭鍒犻櫎锛�", "瀵煎嚭", {
+ confirmButtonText: "纭",
+ cancelButtonText: "鍙栨秷",
+ type: "warning",
+ })
+ .then(() => {
+ delStockOut({ids:ids}).then((res) => {
+ proxy.$modal.msgSuccess("鍒犻櫎鎴愬姛");
+ getList();
+ });
+ })
+ .catch(() => {
+ proxy.$modal.msg("宸插彇娑�");
+ });
+};
+onMounted(() => {
+ getList();
+});
+</script>
+
+<style scoped lang="scss">
+.print-preview-dialog {
+ .el-dialog__body {
+ padding: 0;
+ max-height: 80vh;
+ overflow-y: auto;
+ }
+}
+
+.print-preview-container {
+ .print-preview-header {
+ padding: 15px;
+ border-bottom: 1px solid #e4e7ed;
+ text-align: center;
+
+ .el-button {
+ margin: 0 10px;
+ }
+ }
+
+ .print-preview-content {
+ padding: 20px;
+ background-color: #f5f5f5;
+ min-height: 400px;
+ }
+}
+
+.print-page {
+ width: 220mm;
+ height: 90mm;
+ padding: 10mm;
+ margin: 0 auto;
+ background: white;
+ box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
+ margin-bottom: 10px;
+ box-sizing: border-box;
+}
+
+.delivery-note {
+ width: 100%;
+ height: 100%;
+ font-family: "SimSun", serif;
+ font-size: 10px;
+ line-height: 1.2;
+ display: flex;
+ flex-direction: column;
+}
+
+.header {
+ text-align: center;
+ margin-bottom: 8px;
+
+ .company-name {
+ font-size: 18px;
+ font-weight: bold;
+ margin-bottom: 4px;
+ }
+
+ .document-title {
+ font-size: 16px;
+ font-weight: bold;
+ }
+}
+
+.info-section {
+ margin-bottom: 8px;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+
+ .info-row {
+ line-height: 20px;
+
+ .label {
+ font-weight: bold;
+ width: 60px;
+ font-size: 14px;
+ }
+
+ .value {
+ margin-right: 20px;
+ min-width: 80px;
+ font-size: 14px;
+ }
+ }
+}
+
+.table-section {
+ margin-bottom: 4px;
+ flex: 1;
+
+ .product-table {
+ width: 100%;
+ border-collapse: collapse;
+ border: 1px solid #000;
+
+ th, td {
+ border: 1px solid #000;
+ padding: 6px;
+ text-align: center;
+ font-size: 14px;
+ line-height: 1.4;
+ }
+
+ th {
+ font-weight: bold;
+ }
+
+ .total-label {
+ text-align: right;
+ font-weight: bold;
+ }
+
+ .total-value {
+ font-weight: bold;
+ }
+ }
+}
+
+.footer-section {
+ .footer-row {
+ display: flex;
+ margin-bottom: 3px;
+ line-height: 20px;
+ justify-content: space-between;
+
+ .footer-item {
+ display: flex;
+ margin-right: 20px;
+
+ .label {
+ font-weight: bold;
+ width: 80px;
+ font-size: 14px;
+ }
+
+ .value {
+ min-width: 80px;
+ font-size: 14px;
+ }
+
+ &.address-item {
+ .address-value {
+ min-width: 200px;
+ }
+ }
+ }
+ }
+}
+
+@media print {
+ .app-container {
+ display: none;
+ }
+
+ .print-page {
+ box-shadow: none;
+ margin: 0;
+ padding: 10mm;
+ padding-left: 20mm;
+ page-break-inside: avoid;
+ page-break-after: always;
+ }
+ .print-page:last-child {
+ page-break-after: avoid;
+ }
+}
+</style>
+
+
diff --git a/src/views/inventoryManagement/index.vue b/src/views/inventoryManagement/index.vue
new file mode 100644
index 0000000..f371e26
--- /dev/null
+++ b/src/views/inventoryManagement/index.vue
@@ -0,0 +1,309 @@
+<template>
+ <div class="app-container">
+ <div class="search_form">
+ <div>
+ <span class="search_title">鍙戞斁瀛e害锛�</span>
+ <el-select
+ style="width: 200px;"
+ @change="handleQuery"
+ v-model="searchForm.season"
+ placeholder="璇烽�夋嫨"
+ :clearable="false"
+ >
+ <el-option :label="item.label" :value="item.value" v-for="(item,index) in jidu" :key="item.value" />
+ </el-select>
+ <span class="search_title ml10">鍛樺伐鍚嶇О锛�</span>
+ <el-input
+ v-model="searchForm.staffName"
+ style="width: 240px"
+ placeholder="璇疯緭鍏�"
+ @change="handleQuery"
+ clearable
+ prefix-icon="Search"
+ />
+ <el-button type="primary" @click="handleQuery" style="margin-left: 10px"
+ >鎼滅储</el-button
+ >
+ </div>
+ <div>
+ <el-button type="primary" @click="add" icon="Plus"> 鏂板 </el-button>
+ <el-button @click="handleOut" icon="download">瀵煎嚭</el-button>
+ <el-button
+ type="danger"
+ icon="Delete"
+ :disabled="multipleList.length <= 0"
+ @click="deleteRow(multipleList.map((item) => item.id))"
+ >
+ 鎵归噺鍒犻櫎
+ </el-button>
+ </div>
+ </div>
+ <div class="table_list">
+ <el-table
+ ref="tableRef"
+ v-loading="tableLoading"
+ :data="tableData"
+ border
+ height="calc(100vh - 21em)"
+ :header-cell-style="{ background: '#F0F1F5', color: '#333333' }"
+ style="width: 100%"
+ @selection-change="handleSelectionChange"
+ >
+ <!-- 閫夋嫨鍒� -->
+ <el-table-column
+ align="center"
+ type="selection"
+ width="55"
+ fixed="left"
+ />
+
+ <!-- 搴忓彿鍒� -->
+ <el-table-column
+ align="center"
+ label="搴忓彿"
+ type="index"
+ width="60"
+ fixed="left"
+ />
+
+ <!-- 鍥哄畾鍒楋細濮撳悕 -->
+ <el-table-column
+ label="濮撳悕"
+ prop="staffName"
+ width="100"
+ show-overflow-tooltip
+ align="center"
+ fixed="left"
+ />
+
+ <!-- 鍥哄畾鍒楋細宸ュ彿 -->
+ <el-table-column
+ label="宸ュ彿"
+ prop="staffNo"
+ width="100"
+ show-overflow-tooltip
+ align="center"
+ fixed="left"
+ />
+
+ <!-- 鍔ㄦ�佸垪锛氭牴鎹瓧鍏告覆鏌� -->
+ <el-table-column
+ v-for="(dictItem, index) in sys_lavor_issue"
+ :key="dictItem.value"
+ :label="dictItem.label"
+ :prop="dictItem.value"
+ show-overflow-tooltip
+ >
+ </el-table-column>
+
+ <!-- 鎿嶄綔鍒� -->
+ <el-table-column
+ label="鎿嶄綔"
+ width="150"
+ align="center"
+ fixed="right"
+ >
+ <template #default="scope">
+ <el-button
+ type="primary"
+ link
+ size="small"
+ @click="edit(scope.row)"
+ >
+ 缂栬緫
+ </el-button>
+ <el-button
+ type="danger"
+ link
+ size="small"
+ :disabled="!!scope.row.adoptedDate"
+ @click="adopted(scope.row)"
+ >
+ 棰嗙敤
+ </el-button>
+ </template>
+ </el-table-column>
+ </el-table>
+ <pagination :total="total" layout="total, sizes, prev, pager, next, jumper"
+ :page="page.current" :limit="page.size" @pagination="paginationChange" />
+ </div>
+ <!-- <Modal ref="modalRef" @success="handleQuery"></Modal> -->
+ <!-- <files-dia ref="filesDia"></files-dia> -->
+ </div>
+</template>
+
+<script setup>
+import { ref, onMounted, reactive, toRefs, nextTick, getCurrentInstance } from 'vue'
+import dayjs from "dayjs";
+// import Modal from "./Modal.vue";
+// import FilesDia from "./filesDia.vue";
+import Pagination from "@/components/Pagination/index.vue";
+import {listPage, deleteLedger, update} from "@/api/lavorissce/ledger.js";
+import {ElMessageBox, ElMessage} from "element-plus";
+const { proxy } = getCurrentInstance();
+import { getCurrentMonth } from "@/utils/util"
+
+const page = ref({
+ current: 1,
+ size: 100,
+})
+const total = ref(0)
+// 鍝嶅簲寮忔暟鎹�
+const tableRef = ref(null)
+const tableData = ref([])
+const tableLoading = ref(false)
+const { sys_lavor_issue } = proxy.useDict("sys_lavor_issue")
+const data = reactive({
+ searchForm: {
+ season: "",
+ staffName: "",
+ },
+});
+const { searchForm } = toRefs(data);
+
+const modalRef = ref();
+// const filesDia = ref();
+const multipleList = ref([]);
+const jidu = ref([
+ {
+ value: '1',
+ label: '绗竴瀛e害'
+ },
+ {
+ value: '2',
+ label: '绗簩瀛e害'
+ },
+ {
+ value: '3',
+ label: '绗笁瀛e害'
+ },
+ {
+ value: '4',
+ label: '绗洓瀛e害'
+ }
+])
+
+/** 鎼滅储鎸夐挳鎿嶄綔 */
+const handleQuery = () => {
+ page.value.current = 1;
+ getList();
+};
+// 鑾峰彇瀛楀吀鏁版嵁
+const getList = async () => {
+ tableLoading.value = true;
+ const params = { ...searchForm.value, ...page.value };
+ listPage(params).then(res => {
+ tableLoading.value = false;
+ tableData.value = res.data.records;
+ total.value = res.data.total;
+ }).catch(err => {
+ tableLoading.value = false;
+ })
+}
+const add = () => {
+ modalRef.value.openModal();
+};
+const edit = (row) => {
+ modalRef.value.loadForm(row);
+};
+const deleteRow = (id) => {
+ ElMessageBox.confirm("姝ゆ搷浣滃皢姘镐箙鍒犻櫎璇ユ暟鎹�, 鏄惁缁х画?", "鎻愮ず", {
+ confirmButtonText: "纭畾",
+ cancelButtonText: "鍙栨秷",
+ type: "warning",
+ }).then(async () => {
+ const { code } = await deleteLedger(id);
+ if (code == 200) {
+ ElMessage({
+ type: "success",
+ message: "鍒犻櫎鎴愬姛",
+ });
+ await getList();
+ }
+ });
+};
+const handleOut = () => {
+ ElMessageBox.confirm("閫変腑鐨勫唴瀹瑰皢琚鍑猴紝鏄惁纭瀵煎嚭锛�", "瀵煎嚭", {
+ confirmButtonText: "纭",
+ cancelButtonText: "鍙栨秷",
+ type: "warning",
+ })
+ .then(() => {
+ proxy.download(`/lavorIssue/exportCopy`, {season: searchForm.value.season}, "鍔充繚鍙拌处.xlsx");
+ })
+ .catch(() => {
+ ElMessage.info("宸插彇娑�");
+ });
+};
+const adopted = (row) => {
+ ElMessageBox.confirm("鏄惁纭棰嗙敤?", "鎻愮ず", {
+ confirmButtonText: "纭畾",
+ cancelButtonText: "鍙栨秷",
+ type: "warning",
+ }).then(async () => {
+ const params = {
+ id: row.id,
+ adoptedDate: dayjs().format("YYYY-MM-DD")
+ }
+ const { code } = await update(params);
+ if (code == 200) {
+ ElMessage({
+ type: "success",
+ message: "棰嗙敤鎴愬姛",
+ });
+ await getList();
+ }
+ })
+}
+// 鎵撳紑闄勪欢寮规
+// const openFilesFormDia = (row) => {
+// nextTick(() => {
+// filesDia.value?.openDialog( row,'鏀跺叆')
+// })
+// };
+// 浜嬩欢澶勭悊鍑芥暟
+const handleSelectionChange = (selection) => {
+ multipleList.value = selection;
+}
+
+const paginationChange = (pagination) => {
+ page.value.current = pagination.page;
+ page.value.size = pagination.limit;
+ getList();
+}
+
+// 缁勪欢鎸傝浇鏃跺姞杞藉瓧鍏告暟鎹�
+onMounted(() => {
+ handleQuery()
+})
+</script>
+
+<style scoped>
+.dynamic-table-container {
+ width: 100%;
+}
+
+.pagination-container {
+ margin-top: 20px;
+ display: flex;
+ justify-content: flex-end;
+}
+
+:deep(.el-table .el-table__header-wrapper th) {
+ background-color: #F0F1F5 !important;
+ color: #333333;
+ font-weight: 600;
+}
+
+:deep(.el-table .el-table__body-wrapper td) {
+ padding: 8px 0;
+}
+
+:deep(.el-select) {
+ width: 100%;
+}
+
+:deep(.el-input) {
+ width: 100%;
+}
+</style>
diff --git a/src/views/inventoryManagement/issueManagement/index.vue b/src/views/inventoryManagement/issueManagement/index.vue
new file mode 100644
index 0000000..0577fe1
--- /dev/null
+++ b/src/views/inventoryManagement/issueManagement/index.vue
@@ -0,0 +1,337 @@
+<template>
+ <div class="app-container">
+ <div class="search_form">
+ <div>
+ <span class="search_title ml10">浜у搧澶х被锛�</span>
+ <el-input
+ v-model="searchForm.productCategory"
+ style="width: 240px"
+ placeholder="璇疯緭鍏�"
+ clearable
+ />
+ <el-button type="primary" @click="handleQuery" style="margin-left: 10px">鎼滅储</el-button>
+ </div>
+ <div>
+ <el-button @click="handleOut">瀵煎嚭</el-button>
+ </div>
+ </div>
+ <div class="table_list">
+ <el-table
+ :data="tableData"
+ border
+ v-loading="tableLoading"
+ @selection-change="handleSelectionChange"
+ :expand-row-keys="expandedRowKeys"
+ :row-key="row => row.id"
+ show-summary
+ style="width: 100%"
+ :summary-method="summarizeMainTable"
+ height="calc(100vh - 18.5em)"
+ >
+ <el-table-column align="center" type="selection" width="55" />
+ <el-table-column align="center" label="搴忓彿" type="index" width="60" />
+ <el-table-column label="鎵规鍙�" prop="code" width="130" show-overflow-tooltip />
+ <el-table-column label="浜у搧澶х被" prop="productCategory" show-overflow-tooltip />
+ <el-table-column label="瑙勬牸鍨嬪彿" prop="specificationModel" show-overflow-tooltip />
+ <el-table-column label="鍗曚綅" prop="unit" width="70" show-overflow-tooltip />
+ <el-table-column label="鐗╁搧绫诲瀷" prop="itemType" show-overflow-tooltip />
+ <el-table-column label="鍓╀綑搴撳瓨" prop="inboundNum0" width="90" show-overflow-tooltip />
+ <el-table-column fixed="right" label="鎿嶄綔" width="100" align="center">
+ <template #default="scope">
+ <el-button link type="primary" size="small" @click="openForm(scope.row);">鍙戞枡</el-button>
+ </template>
+ </el-table-column>
+ </el-table>
+ <pagination
+ v-show="total > 0"
+ :total="total"
+ layout="total, sizes, prev, pager, next, jumper"
+ :page="page.current"
+ :limit="page.size"
+ @pagination="paginationChange"
+ />
+ </div>
+ <el-dialog v-model="dialogFormVisible" :title="getDialogTitle()" width="40%" @close="closeDia" draggable>
+ <el-form :model="form" label-width="140px" label-position="top" :rules="rules" ref="formRef">
+ <div>{{getAvailableQuantityText()}}:{{currentRowNum}}</div>
+ <el-form-item :label="getQuantityLabel()" prop="salesContractNo">
+ <el-input-number :step="0.01" :min="0" :max="currentRowNum" style="width: 100%" v-model="form.inboundQuantity" placeholder="璇疯緭鍏�" clearable />
+ </el-form-item>
+ <el-form-item :label="getDateLabel()" prop="projectName">
+ <el-date-picker style="width: 100%" v-model="form.inboundTime" value-format="YYYY-MM-DD" format="YYYY-MM-DD"
+ type="date" placeholder="璇烽�夋嫨" clearable />
+ </el-form-item>
+ <el-form-item :label="getPersonLabel()" prop="entryPerson">
+ <el-select v-model="form.nickName"
+ filterable
+ default-first-option
+ :reserve-keyword="false" placeholder="璇烽�夋嫨" clearable>
+ <el-option v-for="item in userList" :key="item.userId" :label="item.nickName" :value="item.userId" />
+ </el-select>
+ </el-form-item>
+ </el-form>
+ <template #footer>
+ <div class="dialog-footer">
+ <el-button type="primary" @click="submitForm">纭</el-button>
+ <el-button @click="closeDia">鍙栨秷</el-button>
+ </div>
+ </template>
+ </el-dialog>
+ </div>
+</template>
+
+<script setup>
+import pagination from '@/components/PIMTable/Pagination.vue'
+import { ref, reactive, toRefs, onMounted, getCurrentInstance } from 'vue'
+import { ElMessageBox } from "element-plus";
+import useUserStore from '@/store/modules/user'
+import { userListNoPageByTenantId } from "@/api/system/user.js";
+import {
+ getInPageByCustom
+} from "@/api/inventoryManagement/stockIn.js";
+import {
+ delStockManage,
+ stockOut,
+} from "@/api/inventoryManagement/stockManage.js";
+import { getCurrentDate } from "@/utils/index.js";
+
+const userStore = useUserStore()
+const { proxy } = getCurrentInstance()
+const tableData = ref([])
+const selectedRows = ref([])
+const userList = ref([])
+const tableLoading = ref(false)
+const page = reactive({
+ current: 1,
+ size: 100,
+})
+const total = ref(0)
+const fileList = ref([])
+
+// 鐢ㄦ埛淇℃伅琛ㄥ崟寮规鏁版嵁
+const dialogFormVisible = ref(false)
+const data = reactive({
+ searchForm: {
+ inboundQuantity:'',
+ inboundTime:'',
+ nickName: '',
+ userId: '',
+ productCategory:'',
+ // timeStr: getCurrentDate(),
+ },
+ form: {
+ productrecordId: '',
+ },
+ rules: {
+ inboundTime: [{ required: true, message: "璇烽�夋嫨", trigger: "change" }],
+ inboundQuantity: [{ required: true, message: "璇疯緭鍏�", trigger: "blur" }],
+ nickname: [{ required: true, message: "璇烽�夋嫨", trigger: "change" }]
+ }
+})
+const { searchForm, form, rules } = toRefs(data)
+
+// 鏌ヨ鍒楄〃
+/** 鎼滅储鎸夐挳鎿嶄綔 */
+const handleQuery = () => {
+ page.current = 1
+ getList()
+}
+const paginationChange = (obj) => {
+ page.current = obj.page;
+ page.size = obj.limit;
+ getList()
+}
+const getList = () => {
+ tableLoading.value = true
+ const params = { ...page }
+ params.productCategory = searchForm.value.productCategory
+ params.flag = true
+ const apiCall = getInPageByCustom(params)
+ apiCall.then(res => {
+ tableLoading.value = false
+ tableData.value = res.data.records
+ total.value = res.data.total
+ }).catch(() => {
+ tableLoading.value = false
+ })
+}
+
+const findNodeById = (nodes, productId) => {
+ for (let i = 0; i < nodes.length; i++) {
+ if (nodes[i].value === productId) {
+ return nodes[i].label; // 鎵惧埌鑺傜偣锛岃繑鍥炶鑺傜偣
+ }
+ if (nodes[i].children && nodes[i].children.length > 0) {
+ const foundNode = findNodeById(nodes[i].children, productId);
+ if (foundNode) {
+ return foundNode.label; // 鍦ㄥ瓙鑺傜偣涓壘鍒帮紝杩斿洖璇ヨ妭鐐�
+ }
+ }
+ }
+ return null; // 娌℃湁鎵惧埌鑺傜偣锛岃繑鍥瀗ull
+};
+// 琛ㄦ牸閫夋嫨鏁版嵁
+const handleSelectionChange = (selection) => {
+ // 杩囨护鎺夊瓙鏁版嵁
+ selectedRows.value = selection.filter(item => item.id);
+ console.log('selection', selectedRows.value)
+}
+const expandedRowKeys = ref([])
+
+// 涓昏〃鍚堣鏂规硶
+const summarizeMainTable = (param) => {
+ return proxy.summarizeTable(param, ['contractAmount', 'taxInclusiveTotalPrice', 'taxExclusiveTotalPrice']);
+};
+const currentRowId = ref(null) // 鏂板锛氬瓨鍌ㄥ綋鍓嶆搷浣滅殑琛孖D
+
+const currentRowNum = ref(0)
+const salesLedgerProductId = ref(null);
+
+// 鎵撳紑寮规
+const openForm = async (row) => {
+ dialogFormVisible.value = true
+ currentRowId.value = row.id
+ currentRowNum.value = row.inboundNum0
+ salesLedgerProductId.value = row.salesLedgerProductId
+ form.value = {}
+
+ // 鍔犺浇鐢ㄦ埛鍒楄〃
+ try {
+ const userLists = await userListNoPageByTenantId()
+ userList.value = userLists.data
+
+ // 鏉愭枡鍑哄簱榛樿鍙戞枡浜�
+ const defaultPersonName = '鍚寸帀姊�'
+ const defaultPerson = userList.value.find(user => user.nickName === defaultPersonName)
+ const defaultUserId = defaultPerson ? defaultPerson.userId : ''
+
+ // 鍒濆鍖栬〃鍗曟暟鎹�
+ form.value = {
+ productrecordId: '',
+ inboundQuantity: currentRowNum.value, // 浣跨敤currentRowNum浣滀负榛樿鍊�
+ inboundTime: getCurrentDate(), // 榛樿褰撳墠鏃ユ湡
+ nickName: defaultUserId, // 鏍规嵁tab绫诲瀷璁剧疆榛樿鍙戣揣浜�
+ }
+ console.log('form',form.value)
+ } catch (error) {
+ console.error('鍔犺浇鐢ㄦ埛鍒楄〃澶辫触:', error)
+ // 濡傛灉鍔犺浇澶辫触锛屼娇鐢ㄧ┖鍊煎垵濮嬪寲
+ form.value = {
+ productrecordId: '',
+ inboundQuantity: currentRowNum.value,
+ inboundTime: getCurrentDate(),
+ nickName: '',
+ }
+ }
+}
+
+// 鎻愪氦琛ㄥ崟
+const submitForm = () => {
+ let num = Number(form.value.inboundQuantity)
+ if(num <= 0 || num > currentRowNum.value){
+ return proxy.$modal.msgWarning("璇峰~鍏ユ湁鏁堟暟瀛�")
+ }
+ proxy.$refs["formRef"].validate(valid => {
+ if (valid && currentRowId.value) {
+ const outData = {
+ id: currentRowId.value, // 鍘熷璁板綍ID
+ salesLedgerProductId: 0,
+ quantity: form.value.inboundQuantity, // 鍑哄簱鏁伴噺
+ time: form.value.inboundTime, // 鍑哄簱鏃堕棿
+ userId: form.value.nickName, // 鎿嶄綔浜�
+ type: 3 // 鍑哄簱绫诲瀷锛氳嚜瀹氫箟/鏉愭枡
+ }
+ console.log(outData)
+
+ stockOut(outData).then(res => {
+ proxy.$modal.msgSuccess("鎻愪氦鎴愬姛")
+ closeDia()
+ getList()
+ }).catch(err => {
+ proxy.$modal.msgError("鍑哄簱澶辫触")
+ })
+ }
+ })
+}
+// 鍏抽棴寮规
+const closeDia = () => {
+ proxy.resetForm("formRef")
+ dialogFormVisible.value = false
+}
+
+// 瀵煎嚭
+const handleOut = () => {
+ ElMessageBox.confirm(
+ '鏄惁纭瀵煎嚭锛�',
+ '瀵煎嚭', {
+ confirmButtonText: '纭',
+ cancelButtonText: '鍙栨秷',
+ type: 'warning',
+ }
+ ).then(() => {
+ const exportUrl = "/stockin/exportTwo"
+ proxy.download(exportUrl, {}, '鍑哄簱鍙拌处.xlsx')
+ }).catch(() => {
+ proxy.$modal.msg("宸插彇娑�")
+ })
+}
+// 鍒犻櫎
+const handleDelete = () => {
+ let ids = []
+ if (selectedRows.value.length > 0) {
+ ids = selectedRows.value.map(item => item.id);
+ } else {
+ proxy.$modal.msgWarning('璇烽�夋嫨鏁版嵁')
+ return
+ }
+ ElMessageBox.confirm(
+ '閫変腑鐨勫唴瀹瑰皢琚垹闄わ紝鏄惁纭鍒犻櫎锛�',
+ '瀵煎嚭', {
+ confirmButtonText: '纭',
+ cancelButtonText: '鍙栨秷',
+ type: 'warning',
+ }
+ ).then(() => {
+ delStockManage(ids).then(res => {
+ proxy.$modal.msgSuccess("鍒犻櫎鎴愬姛")
+ getList()
+ })
+ }).catch(() => {
+ proxy.$modal.msg("宸插彇娑�")
+ })
+}
+
+// 鏍规嵁tab绫诲瀷鑾峰彇寮规鏍囬
+const getDialogTitle = () => {
+ return '鏂板鍙戞枡';
+};
+
+// 鏍规嵁tab绫诲瀷鑾峰彇鍙嚭搴撴暟閲忔枃鏈�
+const getAvailableQuantityText = () => {
+ return '鍙彂鏂欐暟閲�';
+};
+
+// 鏍规嵁tab绫诲瀷鑾峰彇鏁伴噺瀛楁鏍囩
+const getQuantityLabel = () => {
+ return '鍙戞枡鏁伴噺锛�';
+};
+
+// 鏍规嵁tab绫诲瀷鑾峰彇鏃ユ湡瀛楁鏍囩
+const getDateLabel = () => {
+ return '鍙戞枡鏃ユ湡锛�';
+};
+
+// 鏍规嵁tab绫诲瀷鑾峰彇浜哄憳瀛楁鏍囩
+const getPersonLabel = () => {
+ return '鍙戞枡浜猴細';
+};
+
+onMounted(() => {
+ getList()
+})
+</script>
+
+<style scoped lang="scss"></style>
+
+
+
diff --git a/src/views/inventoryManagement/receiptManagement/components/formDia.vue b/src/views/inventoryManagement/receiptManagement/components/formDia.vue
new file mode 100644
index 0000000..a736b52
--- /dev/null
+++ b/src/views/inventoryManagement/receiptManagement/components/formDia.vue
@@ -0,0 +1,401 @@
+<template>
+ <el-dialog v-model="dialogFormVisible" :title="getDialogTitle()" width="70%"
+ @close="closeDia">
+ <el-form :model="form" label-width="140px" label-position="top" :rules="rules" ref="formRef">
+ <el-form-item label="閲囪喘璁㈠崟鍙�" prop="purchaseContractNumber">
+ <el-select
+ v-model="form.purchaseContractNumber"
+ placeholder="璇烽�夋嫨閲囪喘璁㈠崟鍙�"
+ clearable
+ filterable
+ :loading="loadingPurchaseOptions"
+ @change="handlePurchaseChange"
+ :disabled="operationType === 'edit'"
+ style="width: 100%"
+ >
+ <el-option
+ v-for="item in purchaseOptions"
+ :key="item.purchaseContractNumber"
+ :label="formatPurchaseOption(item)"
+ :value="item.purchaseContractNumber"
+ />
+ </el-select>
+ </el-form-item>
+ <el-table
+ :data="productList"
+ border
+ v-loading="loadingProducts"
+ @selection-change="handleSelectionChange"
+ >
+ <el-table-column align="center" type="selection" width="55" />
+ <el-table-column
+ align="center"
+ label="搴忓彿"
+ type="index"
+ width="60"
+ />
+ <el-table-column label="浜у搧澶х被" prop="productCategory" />
+ <el-table-column label="瑙勬牸鍨嬪彿" prop="specificationModel" />
+ <el-table-column label="鍗曚綅" prop="unit" width="70" />
+ <!-- <el-table-column label="渚涘簲鍟�" prop="supplierName" width="100" /> -->
+ <el-table-column label="閲囪喘鏁伴噺" prop="quantity" width="100" />
+ <el-table-column label="寰呭叆搴撴暟閲�" prop="quantity0" width="100" />
+ <el-table-column label="鏈鍏ュ簱鏁伴噺" prop="quantityStock" width="150">
+ <template #default="scope">
+ <el-input-number :step="0.01" :min="0" style="width: 100%" v-model="scope.row.quantityStock" @change="() => calculateTotalPrice(scope.row)" />
+ </template>
+ </el-table-column>
+ <el-table-column label="绋庣巼(%)" prop="taxRate" width="120" />
+ <el-table-column label="鍗曚环(鍏�)" prop="taxInclusiveUnitPrice" width="150">
+ <template #default="scope">
+ <el-input-number :step="0.01" :min="0" style="width: 100%" v-model="scope.row.taxInclusiveUnitPrice" @change="() => calculateTotalPrice(scope.row)" :disabled="operationType === 'edit'"/>
+ </template>
+ </el-table-column>
+ <el-table-column
+ label="鎬讳环(鍏�)"
+ :formatter="formattedNumber"
+ prop="taxInclusiveTotalPrice"
+ width="150"
+ >
+ </el-table-column>
+ </el-table>
+ </el-form>
+ <template #footer>
+ <div class="dialog-footer">
+ <el-button type="primary" @click="submitForm">纭</el-button>
+ <el-button @click="closeDia">鍙栨秷</el-button>
+ </div>
+ </template>
+ </el-dialog>
+</template>
+
+<script setup>
+import { ref, reactive, toRefs, getCurrentInstance } from 'vue'
+import useUserStore from '@/store/modules/user'
+import {
+ updateStockIn,
+ addSutockIn,
+ selectProductRecordListByPuechaserId
+} from "@/api/inventoryManagement/stockIn.js";
+import { purchaseListPage } from "@/api/procurementManagement/procurementLedger.js";
+import { getCurrentDate } from "@/utils/index.js";
+
+const userStore = useUserStore()
+const { proxy } = getCurrentInstance()
+const emit = defineEmits(['close', 'success'])
+
+const operationType = ref('')// 鎿嶄綔绫诲瀷: 'add' 鎴� 'edit'
+const dialogFormVisible = ref(false)// 寮规鏄剧ず鐘舵��
+const productList = ref([]);// 浜у搧鍒楄〃鏁版嵁
+const loadingProducts = ref(false);// 浜у搧鍔犺浇鐘舵��
+const selectedRows = ref([]) // 浜у搧琛ㄦ牸閫変腑琛�
+const purchaseOptions = ref([])
+const loadingPurchaseOptions = ref(false)
+const loading = ref(false);
+
+const data = reactive({
+ form: {
+ id: null,
+ purchaseContractNumber: '', // 閲囪喘璁㈠崟鍙�
+ supplierId: null, // 渚涘簲鍟咺D
+ supplierName: '', // 渚涘簲鍟嗗悕绉�
+ inboundTime: '', // 鍏ュ簱鏃堕棿
+ inboundBatch: '', // 鍏ュ簱鎵规
+ recorderId: userStore.userId, // 褰曞叆浜篒D
+ recorderName: userStore.name, // 褰曞叆浜哄鍚�
+ entryDate: getCurrentDate(), // 褰曞叆鏃ユ湡
+ remark: '', // 澶囨敞
+ },
+ rules: {
+ purchaseContractNumber: [{ required: true, message: "璇疯緭鍏ラ噰璐悎鍚屽彿", trigger: "blur" }],
+ supplierId: [{ required: true, message: "璇烽�夋嫨渚涘簲鍟�", trigger: "change" }],
+ inboundTime: [{ required: true, message: "璇烽�夋嫨鍏ュ簱鏃堕棿", trigger: "change" }],
+ inboundBatch: [{ required: true, message: "璇疯緭鍏ュ叆搴撴壒娆�", trigger: "blur" }]
+ }
+})
+const { form, rules } = toRefs(data)
+
+// 鍔ㄦ�佽绠楀璇濇鏍囬
+const getDialogTitle = () => {
+ return operationType.value === 'add' ? '鏂板鍏ュ簱' : '缂栬緫鍏ュ簱'
+}
+
+const formatPurchaseOption = (item = {}) => {
+ const contract = item.purchaseContractNumber || '--';
+ const supplier = item.supplierName ? ` 路 ${item.supplierName}` : '';
+ return `${contract}${supplier}`;
+};
+
+const loadPurchaseOptions = async (keyword = '') => {
+ try {
+ loadingPurchaseOptions.value = true;
+ const res = await purchaseListPage({
+ current: -1,
+ size: -1,
+ purchaseContractNumber: keyword,
+ });
+ const records = res.data?.records || [];
+ purchaseOptions.value = records;
+ if (
+ form.value.purchaseContractNumber &&
+ !purchaseOptions.value.find(
+ (item) => item.purchaseContractNumber === form.value.purchaseContractNumber
+ )
+ ) {
+ purchaseOptions.value.push({
+ purchaseContractNumber: form.value.purchaseContractNumber,
+ supplierName: form.value.supplierName,
+ supplierId: form.value.supplierId,
+ });
+ }
+ } finally {
+ loadingPurchaseOptions.value = false;
+ }
+};
+
+const handlePurchaseChange = (value) => {
+ form.value.purchaseContractNumber = value || '';
+ const matched = purchaseOptions.value.find(
+ (item) => item.purchaseContractNumber === value
+ );
+ if (matched) {
+ form.value.supplierName = matched.supplierName || form.value.supplierName;
+ form.value.supplierId = matched.supplierId || form.value.supplierId;
+ }
+ if (!value) {
+ productList.value = [];
+ return;
+ }
+ fetchProductsByContract();
+};
+
+const exceedsAddLimit = (product) => {
+ const stock = Number(product?.quantityStock ?? 0);
+ const waiting = Number(product?.quantity0 ?? 0);
+ if (!Number.isFinite(stock) || !Number.isFinite(waiting)) {
+ return false;
+ }
+ return stock > waiting;
+};
+
+const exceedsEditLimit = (product) => {
+ const stock = Number(product?.quantityStock ?? 0);
+ const waiting = Number(product?.quantity0 ?? 0);
+ const original = Number(product?.originalQuantityStock ?? 0);
+ if (!Number.isFinite(stock) || !Number.isFinite(waiting) || !Number.isFinite(original)) {
+ return false;
+ }
+ return stock > waiting + original;
+};
+
+const formattedNumber = (row, column, cellValue) => {
+ return parseFloat(cellValue).toFixed(2);
+};
+
+// 璁$畻鎬讳环
+const calculateTotalPrice = (row) => {
+ const quantityStock = Number(row?.quantityStock ?? 0);
+ const taxInclusiveUnitPrice = Number(row?.taxInclusiveUnitPrice ?? 0);
+
+ if (Number.isFinite(quantityStock) && Number.isFinite(taxInclusiveUnitPrice)) {
+ row.taxInclusiveTotalPrice = quantityStock * taxInclusiveUnitPrice;
+ } else {
+ row.taxInclusiveTotalPrice = 0;
+ }
+};
+
+const fetchProductsByContract = async () => {
+ if (!form.value.purchaseContractNumber) {
+ proxy.$modal.msgWarning('璇烽�夋嫨鍚堝悓鍙�')
+ return
+ }
+ try {
+ loadingProducts.value = true
+ const productRes = await selectProductRecordListByPuechaserId({
+ purchaseContractNumber: form.value.purchaseContractNumber
+ });
+ if (!productRes.data || productRes.data.length === 0) {
+ proxy.$modal.msgWarning('璇ュ悎鍚屼笅娌℃湁浜у搧璁板綍')
+ productList.value = [];
+ return
+ }
+ productList.value = productRes.data.map(item => {
+ const quantityStock = Number(item?.quantity0 ?? 0);
+ const taxInclusiveUnitPrice = Number(item?.taxInclusiveUnitPrice ?? 0);
+ return {
+ ...item,
+ quantityStock,
+ taxInclusiveUnitPrice,
+ taxInclusiveTotalPrice: quantityStock * taxInclusiveUnitPrice,
+ originalQuantityStock: Number(item.quantityStock ?? item.inboundQuantity ?? 0),
+ };
+ })
+ } catch (error) {
+ console.error('鏌ヨ浜у搧璁板綍澶辫触:', error)
+ proxy.$modal.msgError('鏌ヨ浜у搧璁板綍澶辫触')
+ productList.value = [];
+ } finally {
+ loadingProducts.value = false
+ }
+}
+
+const updatePro = async () => {
+ if (selectedRows.value.length === 0) {
+ proxy.$modal.msgWarning('璇峰厛閫夋嫨浜у搧');
+ return;
+ }
+ const target = selectedRows.value[0];
+ const stock = Number(target?.quantityStock ?? 0);
+ if (!Number.isFinite(stock) || stock <= 0) {
+ proxy.$modal.msgWarning('璇峰~鍐欐湁鏁堢殑鍏ュ簱鏁伴噺');
+ return;
+ }
+ if (exceedsEditLimit(target)) {
+ proxy.$modal.msgError('鏈鍏ュ簱鏁伴噺涓嶈兘瓒呰繃鍘熷叆搴撴暟閲忎笌寰呭叆搴撴暟閲忎箣鍜�');
+ return;
+ }
+ const stockInData = {
+ id: selectedRows.value[0].recordId,
+ quantityStock: Number(selectedRows.value[0].quantityStock),
+ };
+ await updateStockIn(stockInData)
+ proxy.$modal.msgSuccess('淇敼鍏ュ簱鎴愬姛')
+ closeDia()
+ emit('success')
+}
+
+const submitForm = async () => {
+ if (selectedRows.value.length === 0) {
+ proxy.$modal.msgWarning('璇峰厛閫夋嫨閲囪喘鍚堝悓骞堕�夋嫨浜у搧')
+ return
+ }
+ if(operationType.value !== 'add'){
+ await updatePro()
+ return
+ }
+ try {
+ await proxy.$refs.formRef.validate()
+ const invalidProducts = selectedRows.value.filter((product) => {
+ const stock = Number(product?.quantityStock ?? 0);
+ if (!Number.isFinite(stock) || stock <= 0) {
+ return true;
+ }
+ return exceedsAddLimit(product);
+ })
+
+ if (invalidProducts.length > 0) {
+ proxy.$modal.msgError('鏈鍏ュ簱鏁伴噺闇�澶т簬0锛屼笖涓嶈兘瓒呰繃寰呭叆搴撴暟閲�')
+ return
+ }
+
+ const stockInData = {
+ ...form.value,
+ inboundTime: formatDateTime(form.value.inboundTime),
+ nickName: userStore.nickName,
+ details: selectedRows.value.map(product => ({
+ id: product.id,
+ inboundQuantity: Number(product.quantityStock),
+ taxInclusiveUnitPrice: Number(product.taxInclusiveUnitPrice),
+ taxInclusiveTotalPrice: Number(product.taxInclusiveTotalPrice)
+ })),
+ };
+ loading.value = true
+ await addSutockIn(stockInData)
+
+ proxy.$modal.msgSuccess('鏂板鍏ュ簱鎴愬姛')
+ closeDia()
+ emit('success')
+
+ } catch (error) {
+ console.error('鎻愪氦澶辫触:', error)
+ if (!error.errors) {
+ proxy.$modal.msgError('鎿嶄綔澶辫触锛岃閲嶈瘯')
+ }
+ } finally {
+ loading.value = false
+ }
+}
+
+const closeDia = () => {
+ proxy.$refs.formRef.resetFields()
+ dialogFormVisible.value = false
+ emit('close')
+}
+
+const handleSelectionChange = (selection) => {
+ selectedRows.value = selection.filter(item => item.id);
+}
+
+function formatDateTime(date = new Date(), includeTime = true) {
+ const d = new Date(date);
+ const year = d.getFullYear();
+ const month = String(d.getMonth() + 1).padStart(2, '0');
+ const day = String(d.getDate()).padStart(2, '0');
+
+ if (!includeTime) {
+ return `${year}-${month}-${day}`;
+ }
+
+ const hours = String(d.getHours()).padStart(2, '0');
+ const minutes = String(d.getMinutes()).padStart(2, '0');
+ const seconds = String(d.getSeconds()).padStart(2, '0');
+
+ return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
+}
+
+
+const openDialog = async (type, row) => {
+ operationType.value = type
+ dialogFormVisible.value = true
+ selectedRows.value = []
+ await loadPurchaseOptions();
+
+ if (type === 'add') {
+ form.value = {
+ id: null,
+ purchaseContractNumber: '',
+ supplierId: null,
+ supplierName: '',
+ inboundTime: '',
+ inboundBatch: '',
+ recorderId: userStore.userId,
+ recorderName: userStore.name,
+ entryDate: getCurrentDate(),
+ remark: ''
+ }
+ productList.value = []
+ } else {
+ form.value = JSON.parse(JSON.stringify(row))
+ try {
+ loadingProducts.value = true
+ const res = await selectProductRecordListByPuechaserId({
+ purchaseContractNumber: form.value.purchaseContractNumber,
+ id: row.id
+ });
+ productList.value = res.data.map(item => ({
+ ...item,
+ quantityStock: Number(item.quantityStock ?? item.inboundQuantity ?? row.inboundNum ?? 0),
+ taxInclusiveUnitPrice: Number(item?.taxInclusiveUnitPrice ?? 0),
+ taxInclusiveTotalPrice: Number(item?.quantityStock ?? 0) * Number(item?.taxInclusiveUnitPrice ?? 0),
+ originalQuantityStock: Number(item.quantityStock ?? item.inboundQuantity ?? row.inboundNum ?? 0),
+ }))
+ selectedRows.value = productList.value
+ } catch (error) {
+ console.error('鍔犺浇浜у搧澶辫触:', error)
+ proxy.$modal.msgError('鍔犺浇浜у搧澶辫触')
+ productList.value = []
+ } finally {
+ loadingProducts.value = false
+ }
+ }
+}
+
+defineExpose({
+ openDialog,
+})
+</script>
+
+<style scoped lang="scss"></style>
+
+
+
diff --git a/src/views/inventoryManagement/receiptManagement/components/formDiaManual.vue b/src/views/inventoryManagement/receiptManagement/components/formDiaManual.vue
new file mode 100644
index 0000000..f0f9d75
--- /dev/null
+++ b/src/views/inventoryManagement/receiptManagement/components/formDiaManual.vue
@@ -0,0 +1,310 @@
+<template>
+ <el-dialog v-model="dialogFormVisible" :title="operationType === 'add' ? '鏂板鑷畾涔夊叆搴�' : '缂栬緫鑷畾涔夊叆搴�'" width="70%"
+ @close="closeDia">
+ <el-form :model="form" label-width="140px" label-position="top" :rules="rules" ref="formRef">
+ <div style="margin-bottom: 10px;" v-if="operationType === 'add'">
+ <el-button type="primary" @click="addProductRow">鏂板</el-button>
+ </div>
+ <el-table
+ :data="productList"
+ border
+ v-loading="loadingProducts"
+ >
+ <el-table-column
+ align="center"
+ label="搴忓彿"
+ type="index"
+ width="60"
+ />
+ <el-table-column label="浜у搧澶х被" prop="productCategory" width="200">
+ <template #default="scope">
+ <el-input v-model="scope.row.productCategory" placeholder="璇疯緭鍏ヤ骇鍝佸ぇ绫�" />
+ </template>
+ </el-table-column>
+ <el-table-column label="瑙勬牸鍨嬪彿" prop="specificationModel" width="200">
+ <template #default="scope">
+ <el-input v-model="scope.row.specificationModel" placeholder="璇疯緭鍏ヨ鏍煎瀷鍙�" />
+ </template>
+ </el-table-column>
+ <el-table-column label="鍗曚綅" prop="unit" width="100">
+ <template #default="scope">
+ <el-input v-model="scope.row.unit" placeholder="璇疯緭鍏ュ崟浣�" />
+ </template>
+ </el-table-column>
+ <el-table-column label="渚涘簲鍟�" prop="supplierName" width="200">
+ <template #default="scope">
+ <el-input v-model="scope.row.supplierName" placeholder="璇疯緭鍏ヤ緵搴斿晢" />
+ </template>
+ </el-table-column>
+ <el-table-column label="鐗╁搧绫诲瀷" prop="itemType" width="150">
+ <template #default="scope">
+ <el-input v-model="scope.row.itemType" placeholder="璇疯緭鍏ョ墿鍝佺被鍨�" />
+ </template>
+ </el-table-column>
+ <el-table-column label="鍏ュ簱鏁伴噺" prop="inboundNum" width="150">
+ <template #default="scope">
+ <el-input-number :step="0.01" :min="0" style="width: 100%" v-model="scope.row.inboundNum" @change="() => calculateTotalPrice(scope.row)" />
+ </template>
+ </el-table-column>
+ <el-table-column label="鍏ュ簱鏃ユ湡" prop="inboundDate" width="180">
+ <template #default="scope">
+ <el-date-picker
+ v-model="scope.row.inboundDate"
+ type="date"
+ placeholder="璇烽�夋嫨鍏ュ簱鏃ユ湡"
+ value-format="YYYY-MM-DD"
+ format="YYYY-MM-DD"
+ style="width: 100%"
+ />
+ </template>
+ </el-table-column>
+ <el-table-column label="鍗曚环(鍏�)" prop="taxInclusiveUnitPrice" width="150">
+ <template #default="scope">
+ <el-input-number :step="0.01" :min="0" style="width: 100%" v-model="scope.row.taxInclusiveUnitPrice" @change="() => calculateTotalPrice(scope.row)" />
+ </template>
+ </el-table-column>
+ <el-table-column
+ label="鎬讳环(鍏�)"
+ prop="taxInclusiveTotalPrice"
+ width="150"
+ >
+ </el-table-column>
+ <el-table-column label="鎿嶄綔" width="80" v-if="operationType === 'add'">
+ <template #default="scope">
+ <el-button type="danger" size="small" @click="removeProductRow(scope.$index)">鍒犻櫎</el-button>
+ </template>
+ </el-table-column>
+ </el-table>
+ </el-form>
+ <template #footer>
+ <div class="dialog-footer">
+ <el-button type="primary" @click="submitForm">纭</el-button>
+ <el-button @click="closeDia">鍙栨秷</el-button>
+ </div>
+ </template>
+ </el-dialog>
+</template>
+
+<script setup>
+import { ref, reactive, toRefs, getCurrentInstance } from 'vue'
+import useUserStore from '@/store/modules/user'
+import {
+ addStockInCustom,
+ updateStockInCustom,
+} from "@/api/inventoryManagement/stockIn.js";
+import { getCurrentDate } from "@/utils/index.js";
+
+const userStore = useUserStore()
+const { proxy } = getCurrentInstance()
+const emit = defineEmits(['close', 'success'])
+
+const operationType = ref('')// 鎿嶄綔绫诲瀷: 'add' 鎴� 'edit'
+const dialogFormVisible = ref(false)// 寮规鏄剧ず鐘舵��
+const productList = ref([]);// 浜у搧鍒楄〃鏁版嵁
+const loadingProducts = ref(false);// 浜у搧鍔犺浇鐘舵��
+const loading = ref(false);
+
+function formatDateTime(date = new Date(), includeTime = true) {
+ const d = new Date(date);
+ const year = d.getFullYear();
+ const month = String(d.getMonth() + 1).padStart(2, '0');
+ const day = String(d.getDate()).padStart(2, '0');
+
+ if (!includeTime) {
+ return `${year}-${month}-${day}`;
+ }
+
+ const hours = String(d.getHours()).padStart(2, '0');
+ const minutes = String(d.getMinutes()).padStart(2, '0');
+ const seconds = String(d.getSeconds()).padStart(2, '0');
+
+ return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
+}
+
+
+const taxRateOptions = [
+ { label: '1', value: 1 },
+ { label: '6', value: 6 },
+ { label: '13', value: 13 },
+]
+
+const data = reactive({
+ form: {
+ id: null,
+ supplierId: null, // 渚涘簲鍟咺D
+ supplierName: '', // 渚涘簲鍟嗗悕绉�
+ recorderId: userStore.userId, // 褰曞叆浜篒D
+ recorderName: userStore.name, // 褰曞叆浜哄鍚�
+ entryDate: getCurrentDate(), // 褰曞叆鏃ユ湡
+ remark: '', // 澶囨敞
+ },
+ rules: {
+ supplierName: [{ required: true, message: "璇疯緭鍏ヤ緵搴斿晢鍚嶇О", trigger: "blur" }]
+ }
+})
+const { form, rules } = toRefs(data)
+
+// 鏂板浜у搧琛�
+const addProductRow = () => {
+ productList.value.push({
+ id: null,
+ productCategory: '',
+ specificationModel: '',
+ unit: '',
+ supplierName: form.value.supplierName || '',
+ itemType: '',
+ inboundNum: 0,
+ inboundDate: getCurrentDate(), // 榛樿褰撳ぉ鏃ユ湡
+ // quantityStock: 0,
+ taxInclusiveUnitPrice: 0,
+ taxInclusiveTotalPrice: 0,
+ taxRate: null,
+ taxExclusiveTotalPrice: 0,
+ });
+};
+
+// 鍒犻櫎浜у搧琛�
+const removeProductRow = (index) => {
+ productList.value.splice(index, 1);
+};
+
+// 璁$畻鎬讳环锛堟牴鎹暟閲忋�佸崟浠峰拰鍚◣鍗曚环锛�
+const calculateTotalPrice = (row) => {
+ // 璁$畻鏅�氭�讳环锛歩nboundNum * taxInclusiveUnitPrice
+ const quantity = Number(row.inboundNum || 0);
+ const taxInclusiveUnitPrice = Number(row.taxInclusiveUnitPrice || 0);
+ row.taxInclusiveTotalPrice = quantity * taxInclusiveUnitPrice;
+ calculateExclusivePrice(row);
+};
+
+// 璁$畻涓嶅惈绋庢�讳环锛堟牴鎹惈绋庢�讳环鍜岀◣鐜囷級
+const calculateExclusivePrice = (row) => {
+ const taxInclusiveTotalPrice = Number(row.taxInclusiveTotalPrice || 0);
+ const taxRate = Number(row.taxRate || 0);
+ row.taxExclusiveTotalPrice = taxInclusiveTotalPrice / (1 + taxRate / 100);
+};
+
+const submitForm = async () => {
+ try {
+ await proxy.$refs.formRef.validate()
+
+ if (!productList.value.length) {
+ proxy.$modal.msgError('璇疯嚦灏戞坊鍔犱竴鏉′骇鍝佹暟鎹�')
+ return
+ }
+
+ // 楠岃瘉鑷畾涔夋坊鍔犵殑鏁版嵁蹇呭~瀛楁
+ for (let i = 0; i < productList.value.length; i++) {
+ const product = productList.value[i];
+ if (!product.productCategory || !product.specificationModel || !product.unit) {
+ proxy.$modal.msgError(`绗�${i + 1}琛屼骇鍝佹暟鎹湭濉啓瀹屾暣锛堜骇鍝佸ぇ绫汇�佽鏍煎瀷鍙枫�佸崟浣嶄负蹇呭~锛塦)
+ return
+ }
+ if (!product.itemType) {
+ proxy.$modal.msgError(`绗�${i + 1}琛岃閫夋嫨鐗╁搧绫诲瀷`)
+ return
+ }
+ if (!product.inboundDate) {
+ proxy.$modal.msgError(`绗�${i + 1}琛岃閫夋嫨鍏ュ簱鏃ユ湡`)
+ return
+ }
+ const stock = Number(product?.inboundNum ?? 0);
+ if (!Number.isFinite(stock) || stock <= 0) {
+ proxy.$modal.msgError(`绗�${i + 1}琛屾湰娆″叆搴撴暟閲忛渶澶т簬0`)
+ return
+ }
+ }
+
+ const payloadList = productList.value.map(product => ({
+ id: product.id ?? null,
+ inboundNum: Number(product.inboundNum),
+ productCategory: product.productCategory,
+ specificationModel: product.specificationModel,
+ unit: product.unit,
+ supplierName: product.supplierName || form.value.supplierName,
+ itemType: product.itemType,
+ inboundDate: formatDateTime(product.inboundDate, false),
+ taxRate: Number(product.taxRate || 0),
+ taxExclusiveTotalPrice: Number(product.taxExclusiveTotalPrice || 0),
+ taxInclusiveUnitPrice: Number(product.taxInclusiveUnitPrice || 0),
+ taxInclusiveTotalPrice: Number(product.taxInclusiveTotalPrice || 0),
+ }));
+ loading.value = true
+ if (operationType.value === 'edit') {
+ const editPayload = payloadList[0]
+ await updateStockInCustom(editPayload)
+ } else {
+ await addStockInCustom(payloadList)
+ }
+
+ proxy.$modal.msgSuccess(operationType.value === 'edit' ? '缂栬緫鑷畾涔夊叆搴撴垚鍔�' : '鏂板鑷畾涔夊叆搴撴垚鍔�')
+ closeDia()
+ emit('success')
+
+ } catch (error) {
+ console.error('鎻愪氦澶辫触:', error)
+ if (!error.errors) {
+ proxy.$modal.msgError('鎿嶄綔澶辫触锛岃閲嶈瘯')
+ }
+ } finally {
+ loading.value = false
+ }
+}
+
+const closeDia = () => {
+ proxy.$refs.formRef.resetFields()
+ dialogFormVisible.value = false
+ productList.value = []
+ emit('close')
+}
+
+const openDialog = async (type, row) => {
+ operationType.value = type
+ dialogFormVisible.value = true
+
+ if (type === 'add') {
+ form.value = {
+ id: null,
+ supplierId: null,
+ supplierName: '',
+ recorderId: userStore.userId,
+ recorderName: userStore.name,
+ entryDate: getCurrentDate(),
+ remark: ''
+ }
+ productList.value = []
+ } else {
+ // 缂栬緫妯″紡锛氬皢琛屾暟鎹~鍏呭埌琛ㄦ牸涓互鏀寔淇敼
+ form.value = {
+ id: row?.id ?? null,
+ supplierId: row?.supplierId ?? null,
+ supplierName: row?.supplierName ?? '',
+ recorderId: userStore.userId,
+ recorderName: userStore.name,
+ entryDate: getCurrentDate(),
+ remark: row?.remark ?? ''
+ }
+ productList.value = [{
+ id: row?.id ?? null,
+ productCategory: row?.productCategory ?? '',
+ specificationModel: row?.specificationModel ?? '',
+ unit: row?.unit ?? '',
+ supplierName: row?.supplierName ?? '',
+ itemType: row?.itemType ?? '',
+ inboundNum: Number(row?.inboundNum ?? row?.inboundQuantity ?? 0),
+ inboundDate: row?.inboundDate ?? row?.createTime ?? '',
+ taxRate: Number(row?.taxRate ?? 0),
+ taxInclusiveUnitPrice: Number(row?.taxInclusiveUnitPrice ?? 0),
+ taxInclusiveTotalPrice: Number(row?.taxInclusiveTotalPrice ?? 0),
+ taxExclusiveTotalPrice: Number(row?.taxExclusiveTotalPrice ?? 0),
+ }]
+ }
+}
+
+defineExpose({
+ openDialog,
+})
+</script>
+
+<style scoped lang="scss"></style>
+
diff --git a/src/views/inventoryManagement/receiptManagement/components/formDiaProduct.vue b/src/views/inventoryManagement/receiptManagement/components/formDiaProduct.vue
new file mode 100644
index 0000000..8ac355d
--- /dev/null
+++ b/src/views/inventoryManagement/receiptManagement/components/formDiaProduct.vue
@@ -0,0 +1,300 @@
+<template>
+ <el-dialog v-model="dialogFormVisible" :title="operationType === 'add' ? '鏂板鑷畾涔夊叆搴�' : '缂栬緫鑷畾涔夊叆搴�'" width="70%"
+ @close="closeDia">
+ <el-form :model="form" label-width="140px" label-position="top" :rules="rules" ref="formRef">
+ <div style="margin-bottom: 10px;" v-if="operationType === 'add'">
+ <el-button type="primary" @click="addProductRow">鏂板</el-button>
+ </div>
+ <el-table
+ :data="productList"
+ border
+ v-loading="loadingProducts"
+ >
+ <el-table-column
+ align="center"
+ label="搴忓彿"
+ type="index"
+ width="60"
+ />
+ <el-table-column label="浜у搧澶х被" prop="productCategory" width="200">
+ <template #default="scope">
+ <el-input v-model="scope.row.productCategory" placeholder="璇疯緭鍏ヤ骇鍝佸ぇ绫�" />
+ </template>
+ </el-table-column>
+ <el-table-column label="瑙勬牸鍨嬪彿" prop="specificationModel" width="200">
+ <template #default="scope">
+ <el-input v-model="scope.row.specificationModel" placeholder="璇疯緭鍏ヨ鏍煎瀷鍙�" />
+ </template>
+ </el-table-column>
+ <el-table-column label="鍗曚綅" prop="unit" width="100">
+ <template #default="scope">
+ <el-input v-model="scope.row.unit" placeholder="璇疯緭鍏ュ崟浣�" />
+ </template>
+ </el-table-column>
+ <el-table-column label="鍏ュ簱鏁伴噺" prop="inboundNum" width="150">
+ <template #default="scope">
+ <el-input-number :step="0.01" :min="0" style="width: 100%" v-model="scope.row.inboundNum" @change="() => calculateTotalPrice(scope.row)" />
+ </template>
+ </el-table-column>
+ <el-table-column label="鍏ュ簱鏃ユ湡" prop="inboundDate" width="180">
+ <template #default="scope">
+ <el-date-picker
+ v-model="scope.row.inboundDate"
+ type="date"
+ placeholder="璇烽�夋嫨鍏ュ簱鏃ユ湡"
+ value-format="YYYY-MM-DD"
+ format="YYYY-MM-DD"
+ style="width: 100%"
+ />
+ </template>
+ </el-table-column>
+ <el-table-column label="鍗曚环(鍏�)" prop="unitPrice" width="150">
+ <template #default="scope">
+ <el-input-number :step="0.01" :min="0" style="width: 100%" v-model="scope.row.unitPrice" @change="() => calculateTotalPrice(scope.row)" />
+ </template>
+ </el-table-column>
+ <el-table-column
+ label="鎬讳环(鍏�)"
+ prop="totalPrice"
+ width="150"
+ >
+ </el-table-column>
+ <el-table-column label="鎿嶄綔" width="80" v-if="operationType === 'add'">
+ <template #default="scope">
+ <el-button type="danger" size="small" @click="removeProductRow(scope.$index)">鍒犻櫎</el-button>
+ </template>
+ </el-table-column>
+ </el-table>
+ </el-form>
+ <template #footer>
+ <div class="dialog-footer">
+ <el-button type="primary" @click="submitForm">纭</el-button>
+ <el-button @click="closeDia">鍙栨秷</el-button>
+ </div>
+ </template>
+ </el-dialog>
+</template>
+
+<script setup>
+import { ref, reactive, toRefs, getCurrentInstance } from 'vue'
+import useUserStore from '@/store/modules/user'
+import {
+ addStockInCustom, updateProduct
+} from "@/api/inventoryManagement/stockIn.js";
+import { getCurrentDate } from "@/utils/index.js";
+
+const userStore = useUserStore()
+const { proxy } = getCurrentInstance()
+const emit = defineEmits(['close', 'success'])
+
+const operationType = ref('')// 鎿嶄綔绫诲瀷: 'add' 鎴� 'edit'
+const dialogFormVisible = ref(false)// 寮规鏄剧ず鐘舵��
+const productList = ref([]);// 浜у搧鍒楄〃鏁版嵁
+const loadingProducts = ref(false);// 浜у搧鍔犺浇鐘舵��
+const loading = ref(false);
+
+function formatDateTime(date = new Date(), includeTime = true) {
+ const d = new Date(date);
+ const year = d.getFullYear();
+ const month = String(d.getMonth() + 1).padStart(2, '0');
+ const day = String(d.getDate()).padStart(2, '0');
+
+ if (!includeTime) {
+ return `${year}-${month}-${day}`;
+ }
+
+ const hours = String(d.getHours()).padStart(2, '0');
+ const minutes = String(d.getMinutes()).padStart(2, '0');
+ const seconds = String(d.getSeconds()).padStart(2, '0');
+
+ return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
+}
+
+
+const itemTypeOptions = [
+ { label: '鐗╂枡', value: '鐗╂枡' },
+ { label: '鍘熸枡', value: '鍘熸枡' },
+ { label: '鎴愬搧', value: '鎴愬搧' },
+ { label: '鍏朵粬', value: '鍏朵粬' },
+]
+
+const taxRateOptions = [
+ { label: '1', value: 1 },
+ { label: '6', value: 6 },
+ { label: '13', value: 13 },
+]
+
+const data = reactive({
+ form: {
+ id: null,
+ supplierId: null, // 渚涘簲鍟咺D
+ supplierName: '', // 渚涘簲鍟嗗悕绉�
+ recorderId: userStore.userId, // 褰曞叆浜篒D
+ recorderName: userStore.name, // 褰曞叆浜哄鍚�
+ entryDate: getCurrentDate(), // 褰曞叆鏃ユ湡
+ remark: '', // 澶囨敞
+ },
+ rules: {
+ supplierName: [{ required: true, message: "璇疯緭鍏ヤ緵搴斿晢鍚嶇О", trigger: "blur" }]
+ }
+})
+const { form, rules } = toRefs(data)
+
+// 鏂板浜у搧琛�
+const addProductRow = () => {
+ productList.value.push({
+ id: null,
+ productCategory: '',
+ specificationModel: '',
+ unit: '',
+ supplierName: form.value.supplierName || '',
+ itemType: '',
+ inboundNum: 0,
+ inboundDate: '',
+ quantityStock: 0,
+ unitPrice: 0,
+ totalPrice: 0,
+ taxRate: null,
+ taxExclusiveTotalPrice: 0,
+ });
+};
+
+// 鍒犻櫎浜у搧琛�
+const removeProductRow = (index) => {
+ productList.value.splice(index, 1);
+};
+
+// 璁$畻鎬讳环锛堟牴鎹暟閲忋�佸崟浠峰拰鍚◣鍗曚环锛�
+const calculateTotalPrice = (row) => {
+ // 璁$畻鏅�氭�讳环锛歩nboundNum * unitPrice
+ const quantity = Number(row.inboundNum || 0);
+ const unitPrice = Number(row.unitPrice || 0);
+ row.totalPrice = quantity * unitPrice;
+ calculateExclusivePrice(row);
+};
+
+// 璁$畻涓嶅惈绋庢�讳环锛堟牴鎹惈绋庢�讳环鍜岀◣鐜囷級
+const calculateExclusivePrice = (row) => {
+ const totalPrice = Number(row.totalPrice || 0);
+ const taxRate = Number(row.taxRate || 0);
+ row.taxExclusiveTotalPrice = totalPrice / (1 + taxRate / 100);
+};
+
+const submitForm = async () => {
+ try {
+ await proxy.$refs.formRef.validate()
+
+ if (!productList.value.length) {
+ proxy.$modal.msgError('璇疯嚦灏戞坊鍔犱竴鏉′骇鍝佹暟鎹�')
+ return
+ }
+
+ // 楠岃瘉鑷畾涔夋坊鍔犵殑鏁版嵁蹇呭~瀛楁
+ for (let i = 0; i < productList.value.length; i++) {
+ const product = productList.value[i];
+ if (!product.productCategory || !product.specificationModel || !product.unit) {
+ proxy.$modal.msgError(`绗�${i + 1}琛屼骇鍝佹暟鎹湭濉啓瀹屾暣锛堜骇鍝佸ぇ绫汇�佽鏍煎瀷鍙枫�佸崟浣嶄负蹇呭~锛塦)
+ return
+ }
+ if (!product.inboundDate) {
+ proxy.$modal.msgError(`绗�${i + 1}琛岃閫夋嫨鍏ュ簱鏃ユ湡`)
+ return
+ }
+ const stock = Number(product?.inboundNum ?? 0);
+ if (!Number.isFinite(stock) || stock <= 0) {
+ proxy.$modal.msgError(`绗�${i + 1}琛屾湰娆″叆搴撴暟閲忛渶澶т簬0`)
+ return
+ }
+ }
+
+ const payloadList = productList.value.map(product => ({
+ id: product.id ?? null,
+ inboundNum: Number(product.inboundNum),
+ productCategory: product.productCategory,
+ specificationModel: product.specificationModel,
+ unit: product.unit,
+ supplierName: product.supplierName || form.value.supplierName,
+ itemType: product.itemType,
+ inboundDate: formatDateTime(product.inboundDate, false),
+ taxRate: Number(product.taxRate || 0),
+ taxExclusiveTotalPrice: Number(product.taxExclusiveTotalPrice || 0),
+ unitPrice: Number(product.unitPrice || 0),
+ }));
+ loading.value = true
+ if (operationType.value === 'edit') {
+ const editPayload = payloadList[0]
+ await updateProduct(editPayload)
+ } else {
+ await addStockInCustom(payloadList)
+ }
+
+ proxy.$modal.msgSuccess(operationType.value === 'edit' ? '缂栬緫鑷畾涔夊叆搴撴垚鍔�' : '鏂板鑷畾涔夊叆搴撴垚鍔�')
+ closeDia()
+ emit('success')
+
+ } catch (error) {
+ console.error('鎻愪氦澶辫触:', error)
+ if (!error.errors) {
+ proxy.$modal.msgError('鎿嶄綔澶辫触锛岃閲嶈瘯')
+ }
+ } finally {
+ loading.value = false
+ }
+}
+
+const closeDia = () => {
+ proxy.$refs.formRef.resetFields()
+ dialogFormVisible.value = false
+ productList.value = []
+ emit('close')
+}
+
+const openDialog = async (type, row) => {
+ operationType.value = type
+ dialogFormVisible.value = true
+
+ if (type === 'add') {
+ form.value = {
+ id: null,
+ supplierId: null,
+ supplierName: '',
+ recorderId: userStore.userId,
+ recorderName: userStore.name,
+ entryDate: getCurrentDate(),
+ remark: ''
+ }
+ productList.value = []
+ } else {
+ // 缂栬緫妯″紡锛氬皢琛屾暟鎹~鍏呭埌琛ㄦ牸涓互鏀寔淇敼
+ form.value = {
+ id: row?.id ?? null,
+ supplierId: row?.supplierId ?? null,
+ supplierName: row?.supplierName ?? '',
+ recorderId: userStore.userId,
+ recorderName: userStore.name,
+ entryDate: getCurrentDate(),
+ remark: row?.remark ?? ''
+ }
+ productList.value = [{
+ id: row?.id ?? null,
+ productCategory: row?.productCategory ?? '',
+ specificationModel: row?.specificationModel ?? '',
+ unit: row?.unit ?? '',
+ supplierName: row?.supplierName ?? '',
+ itemType: row?.itemType ?? '',
+ inboundNum: Number(row?.inboundNum ?? row?.inboundQuantity ?? 0),
+ inboundDate: row?.inboundDate ?? row?.createTime ?? '',
+ taxRate: Number(row?.taxRate ?? 0),
+ unitPrice: Number(row?.unitPrice ?? 0),
+ taxExclusiveTotalPrice: Number(row?.taxExclusiveTotalPrice ?? 0),
+ }]
+ }
+}
+
+defineExpose({
+ openDialog,
+})
+</script>
+
+<style scoped lang="scss"></style>
+
diff --git a/src/views/inventoryManagement/receiptManagement/index.vue b/src/views/inventoryManagement/receiptManagement/index.vue
new file mode 100644
index 0000000..7f1f83c
--- /dev/null
+++ b/src/views/inventoryManagement/receiptManagement/index.vue
@@ -0,0 +1,212 @@
+<template>
+ <div class="app-container">
+ <div class="search_form">
+ <div>
+ <span class="search_title ml10">鍏ュ簱鏃ユ湡锛�</span>
+ <el-date-picker
+ v-model="searchForm.timeStr"
+ type="date"
+ placeholder="璇烽�夋嫨鏃ユ湡"
+ value-format="YYYY-MM-DD"
+ format="YYYY-MM-DD"
+ clearable
+ @change="handleQuery"
+ />
+ <span class="search_title ml10">浜у搧澶х被锛�</span>
+ <el-input
+ v-model="searchForm.productCategory"
+ style="width: 240px"
+ placeholder="璇疯緭鍏�"
+ clearable
+ />
+ <el-button type="primary" @click="handleQuery" style="margin-left: 10px">鎼滅储</el-button>
+ </div>
+ <div>
+ <el-button type="primary" @click="openForm('add')">鏂板鍏ュ簱</el-button>
+ <el-button @click="handleOut">瀵煎嚭</el-button>
+ <el-button type="danger" plain @click="handleDelete">鍒犻櫎</el-button>
+ </div>
+ </div>
+ <div class="table_list">
+ <el-table
+ :data="tableData"
+ border
+ v-loading="tableLoading"
+ @selection-change="handleSelectionChange"
+ :expand-row-keys="expandedRowKeys"
+ :row-key="row => row.id"
+ show-summary
+ style="width: 100%"
+ :summary-method="summarizeMainTable"
+ height="calc(100vh - 18.5em)"
+ >
+ <el-table-column align="center" type="selection" width="55" />
+ <el-table-column align="center" label="搴忓彿" type="index" width="60" />
+ <el-table-column label="鍏ュ簱鏃堕棿" prop="inboundDate" width="100" show-overflow-tooltip />
+ <el-table-column label="鎵规鍙�" prop="code" width="130" show-overflow-tooltip />
+ <el-table-column label="浜у搧澶х被" prop="productCategory" show-overflow-tooltip />
+ <el-table-column label="瑙勬牸鍨嬪彿" prop="specificationModel" show-overflow-tooltip />
+ <el-table-column label="鍗曚綅" prop="unit" width="70" show-overflow-tooltip />
+ <el-table-column label="鐗╁搧绫诲瀷" prop="itemType" show-overflow-tooltip />
+ <el-table-column label="鍏ュ簱鏁伴噺" prop="inboundNum" width="100" show-overflow-tooltip />
+ <el-table-column label="鍓╀綑搴撳瓨" prop="inboundNum0" show-overflow-tooltip />
+ <el-table-column label="鍗曚环(鍏�)" prop="taxInclusiveUnitPrice" width="150" />
+ <el-table-column label="鎬讳环(鍏�)" prop="taxInclusiveTotalPrice" width="150" />
+ <el-table-column label="鍏ュ簱浜�" prop="createBy" width="80" show-overflow-tooltip />
+ <el-table-column fixed="right" label="鎿嶄綔" width="100" align="center">
+ <template #default="scope">
+ <el-button link type="primary" size="small" @click="openForm('edit', scope.row)">缂栬緫</el-button>
+ </template>
+ </el-table-column>
+ </el-table>
+ <pagination
+ v-show="total > 0"
+ :total="total"
+ layout="total, sizes, prev, pager, next, jumper"
+ :page="page.current"
+ :limit="page.size"
+ @pagination="paginationChange"
+ />
+ </div>
+
+ <form-dia-manual ref="formDiaManual" @close="handleQuery" @success="handleQuery"></form-dia-manual>
+ </div>
+</template>
+
+<script setup>
+import pagination from '@/components/PIMTable/Pagination.vue'
+import { ref, reactive, toRefs, onMounted, getCurrentInstance, nextTick } from 'vue'
+import { ElMessageBox } from "element-plus";
+import useUserStore from '@/store/modules/user'
+import dayjs from 'dayjs'
+import {
+ delStockInCustom,
+ getInPageByCustom,
+} from "@/api/inventoryManagement/stockIn.js";
+import FormDiaManual from './components/formDiaManual.vue'
+import { getCurrentDate } from "@/utils/index.js";
+
+const { proxy } = getCurrentInstance()
+
+const tableData = ref([])
+const selectedRows = ref([])
+const tableLoading = ref(false)
+const formDiaManual = ref()
+
+const page = reactive({
+ current: 1,
+ size: 100,
+})
+const total = ref(0)
+
+const data = reactive({
+ searchForm: {
+ productCategory:'',
+ timeStr: getCurrentDate(),
+ },
+})
+const { searchForm } = toRefs(data)
+// 鏌ヨ鍒楄〃
+/** 鎼滅储鎸夐挳鎿嶄綔 */
+const handleQuery = () => {
+ page.current = 1
+ getList()
+}
+const paginationChange = (obj) => {
+ page.current = obj.page;
+ page.size = obj.limit;
+ getList()
+}
+const getList = () => {
+ tableLoading.value = true
+ const params = { ...page }
+ params.timeStr = searchForm.value.timeStr
+ params.productCategory = searchForm.value.productCategory
+ params.flag = false
+ const apiCall = getInPageByCustom(params)
+
+ apiCall.then(res => {
+ tableLoading.value = false
+ tableData.value = res.data.records
+
+ // 鍓嶇璁$畻鍚◣鎬讳环锛氬惈绋庢�讳环 = taxInclusiveUnitPrice * 鍏ュ簱鏁伴噺
+ tableData.value = tableData.value.map(item => {
+ const inboundNum = Number(item.inboundNum) || 0
+ const taxInclusiveUnitPrice = Number(item.taxInclusiveUnitPrice) || 0
+ item.taxInclusiveTotalPrice = (taxInclusiveUnitPrice * inboundNum).toFixed(2)
+ return item
+ })
+
+ total.value = res.data.total
+ }).catch(() => {
+ tableLoading.value = false
+ })
+}
+
+// 鎵撳紑寮规
+const openForm = async (type, row) => {
+ await nextTick(() => {
+ formDiaManual.value?.openDialog(type, row)
+ })
+}
+
+// 琛ㄦ牸閫夋嫨鏁版嵁
+const handleSelectionChange = (selection) => {
+ selectedRows.value = selection.filter(item => item.id)
+}
+
+const expandedRowKeys = ref([])
+
+// 涓昏〃鍚堣鏂规硶
+const summarizeMainTable = (param) => {
+ return proxy.summarizeTable(param, ['contractAmount', 'taxInclusiveTotalPrice', 'taxExclusiveTotalPrice'])
+}
+
+// 瀵煎嚭
+const handleOut = () => {
+ ElMessageBox.confirm('鏄惁纭瀵煎嚭锛�', '瀵煎嚭', {
+ confirmButtonText: '纭',
+ cancelButtonText: '鍙栨秷',
+ type: 'warning',
+ }).then(() => {
+ const exportUrl = "/stockin/exportTwo"
+ proxy.download(exportUrl, {}, '鍏ュ簱鍙拌处.xlsx')
+ }).catch(() => {
+ proxy.$modal.msg("宸插彇娑�")
+ })
+}
+
+// 鍒犻櫎
+const handleDelete = () => {
+ if (selectedRows.value.length === 0) {
+ proxy.$modal.msgWarning('璇烽�夋嫨鏁版嵁')
+ return
+ }
+ const ids = selectedRows.value.map(item => item.id)
+
+ ElMessageBox.confirm('閫変腑鐨勫唴瀹瑰皢琚垹闄わ紝鏄惁纭鍒犻櫎锛�', '鍒犻櫎', {
+ confirmButtonText: '纭',
+ cancelButtonText: '鍙栨秷',
+ type: 'warning',
+ }).then(() => {
+ // 鏉愭枡鍏ュ簱鍒犻櫎
+ delStockInCustom(ids).then(() => {
+ proxy.$modal.msgSuccess("鍒犻櫎鎴愬姛")
+ getList()
+ }).catch(() => {
+ proxy.$modal.msgError("鍒犻櫎澶辫触")
+ })
+ }).catch(() => {
+ proxy.$modal.msg("宸插彇娑�")
+ })
+}
+
+onMounted(() => {
+ getList()
+})
+</script>
+
+<style scoped lang="scss"></style>
+
+
+
diff --git a/src/views/inventoryManagement/stockManagement/components/FormDiaManual.vue b/src/views/inventoryManagement/stockManagement/components/FormDiaManual.vue
new file mode 100644
index 0000000..d66b0e6
--- /dev/null
+++ b/src/views/inventoryManagement/stockManagement/components/FormDiaManual.vue
@@ -0,0 +1,154 @@
+<template>
+ <el-dialog :model-value="dialogFormVisible" :title="operationType === 'add' ? '鏂板鏉愭枡搴撳瓨' : '缂栬緫鏉愭枡搴撳瓨'" width="70%"
+ @update:model-value="$emit('update:dialogFormVisible', $event)" @close="closeDia">
+ <el-form :model="form" label-width="140px" label-position="top" :rules="rules" ref="formRef">
+ <el-row :gutter="30">
+ <el-col :span="12">
+ <el-form-item label="浜у搧澶х被锛�" prop="productCategory">
+ <el-input disabled v-model="form.productCategory" placeholder="璇疯緭鍏�" clearable />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="瑙勬牸鍨嬪彿锛�" prop="specificationModel">
+ <el-input disabled v-model="form.specificationModel" placeholder="璇疯緭鍏�" clearable />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="30">
+ <el-col :span="12">
+ <el-form-item label="鍗曚綅锛�" prop="unit">
+ <el-input disabled v-model="form.unit" placeholder="璇疯緭鍏�" clearable />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="鐗╁搧绫诲瀷锛�" prop="itemType">
+ <el-input disabled v-model="form.itemType" placeholder="璇疯緭鍏�" clearable />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="30">
+ <el-col :span="12">
+ <el-form-item label="鍏ュ簱鏃堕棿锛�" prop="createTime">
+ <el-date-picker style="width: 100%" v-model="form.createTime" value-format="YYYY-MM-DD" format="YYYY-MM-DD"
+ type="date" placeholder="璇烽�夋嫨" clearable />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="搴撳瓨鏁伴噺锛�" prop="inboundNum">
+ <el-input v-model="form.inboundNum" placeholder="璇疯緭鍏�" clearable @input="calculateTotalPrice" />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="30">
+ <el-col :span="12">
+ <el-form-item label="宸插嚭搴撴暟閲忥細" prop="totalInboundNum">
+ <el-input disabled v-model="form.totalInboundNum" placeholder="璇疯緭鍏�" clearable />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="寰呭嚭搴撴暟閲忥細" prop="inboundNum0">
+ <el-input disabled v-model="form.inboundNum0" placeholder="璇疯緭鍏�" clearable />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="30">
+ <el-col :span="12">
+ <el-form-item label="鍗曚环(鍏�)锛�" prop="taxInclusiveUnitPrice">
+ <el-input v-model="form.taxInclusiveUnitPrice" placeholder="璇疯緭鍏�" clearable @input="calculateTotalPrice" />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="鎬讳环(鍏�)锛�" prop="taxInclusiveTotalPrice">
+ <el-input disabled v-model="form.taxInclusiveTotalPrice" placeholder="鑷姩璁$畻" clearable />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ </el-form>
+ <template #footer>
+ <div class="dialog-footer">
+ <el-button type="primary" @click="submitForm">纭</el-button>
+ <el-button @click="closeDia">鍙栨秷</el-button>
+ </div>
+ </template>
+ </el-dialog>
+</template>
+
+<script setup>
+import { ref, reactive, toRefs, watch } from 'vue'
+
+const props = defineProps({
+ dialogFormVisible: Boolean,
+ operationType: String,
+ formData: Object
+})
+
+const emit = defineEmits(['update:dialogFormVisible', 'submit', 'close'])
+
+const formRef = ref()
+
+const data = reactive({
+ form: {
+ productCategory: '',
+ specificationModel: '',
+ unit: '',
+ itemType: '',
+ createTime: '',
+ inboundNum: '',
+ totalInboundNum: '',
+ inboundNum0: '',
+ taxInclusiveUnitPrice: '',
+ taxInclusiveTotalPrice: ''
+ },
+ rules: {
+ productCategory: [{ required: true, message: '璇疯緭鍏ヤ骇鍝佸ぇ绫�', trigger: 'blur' }],
+ specificationModel: [{ required: true, message: '璇疯緭鍏ヨ鏍煎瀷鍙�', trigger: 'blur' }],
+ unit: [{ required: true, message: '璇疯緭鍏ュ崟浣�', trigger: 'blur' }],
+ itemType: [{ required: true, message: '璇疯緭鍏ョ墿鍝佺被鍨�', trigger: 'blur' }],
+ createTime: [{ required: true, message: '璇烽�夋嫨鍏ュ簱鏃堕棿', trigger: 'change' }],
+ inboundNum: [{ required: true, message: '璇疯緭鍏ュ簱瀛樻暟閲�', trigger: 'blur' }],
+ taxInclusiveUnitPrice: [{ required: true, message: '璇疯緭鍏ュ崟浠�', trigger: 'blur' }]
+ }
+})
+
+const { form, rules } = toRefs(data)
+
+// 璁$畻鎬讳环锛氭�讳环 = 鍗曚环 脳 鍓╀綑搴撳瓨
+const calculateTotalPrice = () => {
+ const unitPrice = parseFloat(form.value.taxInclusiveUnitPrice) || 0
+ const stockQuantity = parseFloat(form.value.inboundNum) || 0 // 搴撳瓨鏁伴噺
+ const outboundQuantity = parseFloat(form.value.totalInboundNum) || 0 // 宸插嚭搴撴暟閲�
+ const remainingStock = stockQuantity - outboundQuantity // 鍓╀綑搴撳瓨
+ form.value.taxInclusiveTotalPrice = (unitPrice * remainingStock).toFixed(2)
+}
+
+// 鐩戝惉formData鍙樺寲
+watch(() => props.formData, (newVal) => {
+ if (newVal) {
+ form.value = { ...newVal }
+ // 鏁版嵁鍙樺寲鍚庨噸鏂拌绠楁�讳环
+ calculateTotalPrice()
+ }
+}, { immediate: true })
+
+// 鎻愪氦琛ㄥ崟
+const submitForm = () => {
+ formRef.value.validate(valid => {
+ if (valid) {
+ emit('submit', form.value)
+ }
+ })
+}
+
+// 鍏抽棴寮规
+const closeDia = () => {
+ emit('close')
+ emit('update:dialogFormVisible', false)
+}
+
+</script>
+
+<style scoped lang="scss">
+.dialog-footer {
+ text-align: center;
+}
+</style>
\ No newline at end of file
diff --git a/src/views/inventoryManagement/stockManagement/components/FormDiaProduction.vue b/src/views/inventoryManagement/stockManagement/components/FormDiaProduction.vue
new file mode 100644
index 0000000..1653307
--- /dev/null
+++ b/src/views/inventoryManagement/stockManagement/components/FormDiaProduction.vue
@@ -0,0 +1,147 @@
+<template>
+ <el-dialog :model-value="dialogFormVisible" :title="operationType === 'add' ? '鏂板鎴愬搧搴撳瓨' : '缂栬緫鎴愬搧搴撳瓨'" width="70%"
+ @update:model-value="$emit('update:dialogFormVisible', $event)" @close="closeDia">
+ <el-form :model="form" label-width="140px" label-position="top" :rules="rules" ref="formRef">
+ <el-row :gutter="30">
+ <el-col :span="12">
+ <el-form-item label="浜у搧澶х被锛�" prop="productCategory">
+ <el-input disabled v-model="form.productCategory" placeholder="璇疯緭鍏�" clearable />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="瑙勬牸鍨嬪彿锛�" prop="specificationModel">
+ <el-input disabled v-model="form.specificationModel" placeholder="璇疯緭鍏�" clearable />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="30">
+ <el-col :span="12">
+ <el-form-item label="鍗曚綅锛�" prop="unit">
+ <el-input disabled v-model="form.unit" placeholder="璇疯緭鍏�" clearable />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="鍏ュ簱鏃堕棿锛�" prop="createTime">
+ <el-date-picker style="width: 100%" v-model="form.createTime" value-format="YYYY-MM-DD" format="YYYY-MM-DD"
+ type="date" placeholder="璇烽�夋嫨" clearable />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="30">
+ <el-col :span="12">
+ <el-form-item label="搴撳瓨鏁伴噺锛�" prop="inboundNum">
+ <el-input v-model="form.inboundNum" placeholder="璇疯緭鍏�" clearable @input="calculateTotalPrice" />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="宸插嚭搴撴暟閲忥細" prop="totalInboundNum">
+ <el-input disabled v-model="form.totalInboundNum" placeholder="璇疯緭鍏�" clearable />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="30">
+ <el-col :span="12">
+ <el-form-item label="寰呭嚭搴撴暟閲忥細" prop="inboundNum0">
+ <el-input disabled v-model="form.inboundNum0" placeholder="璇疯緭鍏�" clearable />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="鍗曚环(鍏�)锛�" prop="unitPrice">
+ <el-input v-model="form.unitPrice" placeholder="璇疯緭鍏�" clearable @input="calculateTotalPrice" />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="30">
+ <el-col :span="12">
+ <el-form-item label="鎬讳环(鍏�)锛�" prop="totalPrice">
+ <el-input disabled v-model="form.totalPrice" placeholder="鑷姩璁$畻" clearable />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ </el-form>
+ <template #footer>
+ <div class="dialog-footer">
+ <el-button type="primary" @click="submitForm">纭</el-button>
+ <el-button @click="closeDia">鍙栨秷</el-button>
+ </div>
+ </template>
+ </el-dialog>
+</template>
+
+<script setup>
+import { ref, reactive, toRefs, watch } from 'vue'
+
+const props = defineProps({
+ dialogFormVisible: Boolean,
+ operationType: String,
+ formData: Object
+})
+
+const emit = defineEmits(['update:dialogFormVisible', 'submit', 'close'])
+
+const formRef = ref()
+
+const data = reactive({
+ form: {
+ productCategory: '',
+ specificationModel: '',
+ unit: '',
+ createTime: '',
+ inboundNum: '',
+ totalInboundNum: '',
+ inboundNum0: '',
+ unitPrice: '',
+ totalPrice: ''
+ },
+ rules: {
+ productCategory: [{ required: true, message: '璇疯緭鍏ヤ骇鍝佸ぇ绫�', trigger: 'blur' }],
+ specificationModel: [{ required: true, message: '璇疯緭鍏ヨ鏍煎瀷鍙�', trigger: 'blur' }],
+ unit: [{ required: true, message: '璇疯緭鍏ュ崟浣�', trigger: 'blur' }],
+ createTime: [{ required: true, message: '璇烽�夋嫨鍏ュ簱鏃堕棿', trigger: 'change' }],
+ inboundNum: [{ required: true, message: '璇疯緭鍏ュ簱瀛樻暟閲�', trigger: 'blur' }],
+ unitPrice: [{ required: true, message: '璇疯緭鍏ュ崟浠�', trigger: 'blur' }]
+ }
+})
+
+const { form, rules } = toRefs(data)
+
+// 璁$畻鎬讳环锛氭�讳环 = 鍗曚环 脳 鍓╀綑搴撳瓨
+const calculateTotalPrice = () => {
+ const unitPrice = parseFloat(form.value.unitPrice) || 0
+ const stockQuantity = parseFloat(form.value.inboundNum) || 0 // 搴撳瓨鏁伴噺
+ const outboundQuantity = parseFloat(form.value.totalInboundNum) || 0 // 宸插嚭搴撴暟閲�
+ const remainingStock = stockQuantity - outboundQuantity // 鍓╀綑搴撳瓨
+ form.value.totalPrice = (unitPrice * remainingStock).toFixed(2)
+}
+
+// 鐩戝惉formData鍙樺寲
+watch(() => props.formData, (newVal) => {
+ if (newVal) {
+ form.value = { ...newVal }
+ // 鏁版嵁鍙樺寲鍚庨噸鏂拌绠楁�讳环
+ calculateTotalPrice()
+ }
+}, { immediate: true })
+
+// 鎻愪氦琛ㄥ崟
+const submitForm = () => {
+ formRef.value.validate(valid => {
+ if (valid) {
+ emit('submit', form.value)
+ }
+ })
+}
+
+// 鍏抽棴寮规
+const closeDia = () => {
+ emit('close')
+ emit('update:dialogFormVisible', false)
+}
+
+</script>
+
+<style scoped lang="scss">
+.dialog-footer {
+ text-align: center;
+}
+</style>
\ No newline at end of file
diff --git a/src/views/inventoryManagement/stockManagement/components/FormDiaPurchase.vue b/src/views/inventoryManagement/stockManagement/components/FormDiaPurchase.vue
new file mode 100644
index 0000000..5da2eee
--- /dev/null
+++ b/src/views/inventoryManagement/stockManagement/components/FormDiaPurchase.vue
@@ -0,0 +1,147 @@
+<template>
+ <el-dialog :model-value="dialogFormVisible" :title="operationType === 'add' ? '鏂板鍘熸枡搴撳瓨' : '缂栬緫鍘熸枡搴撳瓨'" width="70%"
+ @update:model-value="$emit('update:dialogFormVisible', $event)" @close="closeDia">
+ <el-form :model="form" label-width="140px" label-position="top" :rules="rules" ref="formRef">
+ <el-row :gutter="30">
+ <el-col :span="12">
+ <el-form-item label="浜у搧澶х被锛�" prop="productCategory">
+ <el-input disabled v-model="form.productCategory" placeholder="璇疯緭鍏�" clearable />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="瑙勬牸鍨嬪彿锛�" prop="specificationModel">
+ <el-input disabled v-model="form.specificationModel" placeholder="璇疯緭鍏�" clearable />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="30">
+ <el-col :span="12">
+ <el-form-item label="鍗曚綅锛�" prop="unit">
+ <el-input disabled v-model="form.unit" placeholder="璇疯緭鍏�" clearable />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="鍏ュ簱鏃堕棿锛�" prop="createTime">
+ <el-date-picker style="width: 100%" v-model="form.createTime" value-format="YYYY-MM-DD" format="YYYY-MM-DD"
+ type="date" placeholder="璇烽�夋嫨" clearable />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="30">
+ <el-col :span="12">
+ <el-form-item label="搴撳瓨鏁伴噺锛�" prop="inboundNum">
+ <el-input v-model="form.inboundNum" placeholder="璇疯緭鍏�" clearable @input="calculateTotalPrice" />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="宸插嚭搴撴暟閲忥細" prop="totalInboundNum">
+ <el-input disabled v-model="form.totalInboundNum" placeholder="璇疯緭鍏�" clearable />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="30">
+ <el-col :span="12">
+ <el-form-item label="寰呭嚭搴撴暟閲忥細" prop="inboundNum0">
+ <el-input disabled v-model="form.inboundNum0" placeholder="璇疯緭鍏�" clearable />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="鍚◣鍗曚环(鍏�)锛�" prop="taxInclusiveUnitPrice">
+ <el-input v-model="form.taxInclusiveUnitPrice" placeholder="璇疯緭鍏�" clearable @input="calculateTotalPrice" />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="30">
+ <el-col :span="12">
+ <el-form-item label="鍚◣鎬讳环(鍏�)锛�" prop="taxInclusiveTotalPrice">
+ <el-input disabled v-model="form.taxInclusiveTotalPrice" placeholder="鑷姩璁$畻" clearable />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ </el-form>
+ <template #footer>
+ <div class="dialog-footer">
+ <el-button type="primary" @click="submitForm">纭</el-button>
+ <el-button @click="closeDia">鍙栨秷</el-button>
+ </div>
+ </template>
+ </el-dialog>
+</template>
+
+<script setup>
+import { ref, reactive, toRefs, watch } from 'vue'
+
+const props = defineProps({
+ dialogFormVisible: Boolean,
+ operationType: String,
+ formData: Object
+})
+
+const emit = defineEmits(['update:dialogFormVisible', 'submit', 'close'])
+
+const formRef = ref()
+
+const data = reactive({
+ form: {
+ productCategory: '',
+ specificationModel: '',
+ unit: '',
+ createTime: '',
+ inboundNum: '',
+ totalInboundNum: '',
+ inboundNum0: '',
+ taxInclusiveUnitPrice: '',
+ taxInclusiveTotalPrice: ''
+ },
+ rules: {
+ productCategory: [{ required: true, message: '璇疯緭鍏ヤ骇鍝佸ぇ绫�', trigger: 'blur' }],
+ specificationModel: [{ required: true, message: '璇疯緭鍏ヨ鏍煎瀷鍙�', trigger: 'blur' }],
+ unit: [{ required: true, message: '璇疯緭鍏ュ崟浣�', trigger: 'blur' }],
+ createTime: [{ required: true, message: '璇烽�夋嫨鍏ュ簱鏃堕棿', trigger: 'change' }],
+ inboundNum: [{ required: true, message: '璇疯緭鍏ュ簱瀛樻暟閲�', trigger: 'blur' }],
+ taxInclusiveUnitPrice: [{ required: true, message: '璇疯緭鍏ュ惈绋庡崟浠�', trigger: 'blur' }]
+ }
+})
+
+const { form, rules } = toRefs(data)
+
+// 璁$畻鎬讳环锛氬惈绋庢�讳环 = 鍚◣鍗曚环 脳 鍓╀綑搴撳瓨
+const calculateTotalPrice = () => {
+ const unitPrice = parseFloat(form.value.taxInclusiveUnitPrice) || 0
+ const stockQuantity = parseFloat(form.value.inboundNum) || 0 // 搴撳瓨鏁伴噺
+ const outboundQuantity = parseFloat(form.value.totalInboundNum) || 0 // 宸插嚭搴撴暟閲�
+ const remainingStock = stockQuantity - outboundQuantity // 鍓╀綑搴撳瓨
+ form.value.taxInclusiveTotalPrice = (unitPrice * remainingStock).toFixed(2)
+}
+
+// 鐩戝惉formData鍙樺寲
+watch(() => props.formData, (newVal) => {
+ if (newVal) {
+ form.value = { ...newVal }
+ // 鏁版嵁鍙樺寲鍚庨噸鏂拌绠楁�讳环
+ calculateTotalPrice()
+ }
+}, { immediate: true })
+
+// 鎻愪氦琛ㄥ崟
+const submitForm = () => {
+ formRef.value.validate(valid => {
+ if (valid) {
+ emit('submit', form.value)
+ }
+ })
+}
+
+// 鍏抽棴寮规
+const closeDia = () => {
+ emit('close')
+ emit('update:dialogFormVisible', false)
+}
+
+</script>
+
+<style scoped lang="scss">
+.dialog-footer {
+ text-align: center;
+}
+</style>
\ No newline at end of file
diff --git a/src/views/inventoryManagement/stockManagement/index.vue b/src/views/inventoryManagement/stockManagement/index.vue
new file mode 100644
index 0000000..508395e
--- /dev/null
+++ b/src/views/inventoryManagement/stockManagement/index.vue
@@ -0,0 +1,387 @@
+<template>
+ <div class="app-container">
+ <div class="search_form">
+ <div>
+ <span class="search_title ml10">鍏ュ簱鏃ユ湡锛�</span>
+ <el-date-picker
+ v-model="searchForm.timeStr"
+ type="date"
+ placeholder="璇烽�夋嫨鏃ユ湡"
+ value-format="YYYY-MM-DD"
+ format="YYYY-MM-DD"
+ clearable
+ @change="handleQuery"
+ />
+ <span class="search_title ml10">浜у搧澶х被锛�</span>
+ <el-input
+ v-model="searchForm.productCategory"
+ style="width: 240px"
+ placeholder="璇疯緭鍏�"
+ clearable
+ />
+ <el-button type="primary" @click="handleQuery" style="margin-left: 10px">鎼滅储</el-button>
+ </div>
+ <div>
+ <el-button @click="handleOut">瀵煎嚭</el-button>
+ </div>
+ </div>
+ <div class="table_list">
+ <el-table
+ :data="tableData"
+ border
+ v-loading="tableLoading"
+ @selection-change="handleSelectionChange"
+ :expand-row-keys="expandedRowKeys"
+ :row-key="row => row.id"
+ show-summary
+ style="width: 100%"
+ :row-class-name="tableRowClassName"
+ :summary-method="summarizeMainTable"
+ height="calc(100vh - 18.5em)"
+ >
+ <el-table-column align="center" type="selection" width="55" />
+ <el-table-column align="center" label="搴忓彿" type="index" width="60" />
+ <el-table-column label="鍏ュ簱鏃ユ湡" prop="inboundDate" width="100" show-overflow-tooltip />
+ <el-table-column label="鎵规鍙�" prop="code" width="130" show-overflow-tooltip />
+ <el-table-column label="浜у搧澶х被" prop="productCategory" show-overflow-tooltip />
+ <el-table-column label="瑙勬牸鍨嬪彿" prop="specificationModel" show-overflow-tooltip />
+ <el-table-column label="鍗曚綅" prop="unit" width="80" show-overflow-tooltip />
+ <el-table-column label="鐗╁搧绫诲瀷" prop="itemType" width="120" show-overflow-tooltip />
+ <el-table-column label="宸插彂鏂欐暟閲�" prop="totalInboundNum" width="100" show-overflow-tooltip />
+ <el-table-column label="鍓╀綑搴撳瓨" prop="inboundNum0" width="100" show-overflow-tooltip />
+ <el-table-column label="鍗曚环(鍏�)" prop="taxInclusiveUnitPrice" width="150" />
+ <el-table-column label="鎬讳环(鍏�)" prop="taxInclusiveTotalPrice" width="150" />
+ <el-table-column fixed="right" label="鎿嶄綔" width="100" align="center">
+ <template #default="scope">
+ <el-button link type="primary" size="small" @click="openForm('edit', scope.row);">缂栬緫</el-button>
+ </template>
+ </el-table-column>
+ </el-table>
+ <pagination
+ v-show="total > 0"
+ :total="total"
+ layout="total, sizes, prev, pager, next, jumper"
+ :page="page.current"
+ :limit="page.size"
+ @pagination="paginationChange"
+ />
+ </div>
+
+ <!-- 鏉愭枡搴撳瓨寮规 -->
+ <FormDiaManual
+ v-model:dialogFormVisible="manualDialogVisible"
+ :operationType="operationType"
+ :formData="form"
+ @submit="submitForm"
+ @close="closeDia"
+ />
+ </div>
+</template>
+
+<script setup>
+import pagination from '@/components/PIMTable/Pagination.vue'
+import { ref, reactive, toRefs, onMounted, getCurrentInstance } from 'vue'
+import { ElMessageBox } from "element-plus";
+import useUserStore from '@/store/modules/user'
+import { userListNoPageByTenantId } from "@/api/system/user.js";
+import { productTreeList,modelList } from "@/api/basicData/product.js"
+import {
+ getStockManagePageByCustom,
+ delStockManage,
+} from "@/api/inventoryManagement/stockManage.js";
+import {
+ updateManagement, updateManagementByCustom, updateStockIn
+} from "@/api/inventoryManagement/stockIn.js";
+import { getCurrentDate } from "@/utils/index.js";
+
+// 瀵煎叆鏉愭枡搴撳瓨寮规缁勪欢
+import FormDiaManual from './components/FormDiaManual.vue'
+
+const userStore = useUserStore()
+const { proxy } = getCurrentInstance()
+const tableData = ref([])
+const productData = ref([])
+const selectedRows = ref([])
+const userList = ref([])
+const productList = ref([])
+const productModelList = ref([])
+// const customerOption = ref([])
+const tableLoading = ref(false)
+const page = reactive({
+ current: 1,
+ size: 100,
+})
+const total = ref(0)
+const fileList = ref([])
+const loading = ref(false);
+// 鐢ㄦ埛淇℃伅琛ㄥ崟寮规鏁版嵁
+const operationType = ref('')
+
+// 鏉愭枡搴撳瓨寮规鏄剧ず鐘舵��
+const manualDialogVisible = ref(false)
+
+const data = reactive({
+ searchForm: {
+ // supplierName: '',
+ productCategory:'',
+ customerName: '',
+ timeStr: getCurrentDate(),
+ },
+ form: {
+ supplierId: null,
+ productId: null,
+ productName: '',
+ userId: userStore.userId,
+ nickName: '',
+ productModelId: null,
+ model: '',
+ unit: '',
+ productrecordId: null,
+ taxInclusiveUnitPrice: '',
+ taxInclusiveTotalPrice: '',
+ taxRate: '',
+ taxExclusiveTotalPrice: '',
+ inboundTime: '',
+ inboundBatch: '',
+ stockQuantity: '',
+ boundTime: '',
+ warnNum: '', // 鏂板鏈�浣庡簱瀛樺瓧娈�
+ salesLedgerProductId: null,
+ },
+ rules: {
+ // supplierName: [{ required: true, message: '璇疯緭鍏ヤ緵搴斿晢鍚嶇О', trigger: 'blur' }],
+ productCategory: [{ required: true, message: '璇烽�夋嫨浜у搧澶х被', trigger: 'change' }],
+ specificationModel: [{ required: true, message: '璇疯緭鍏ヨ鏍煎瀷鍙�', trigger: 'blur' }],
+ unit: [{ required: true, message: '璇疯緭鍏ュ崟浣�', trigger: 'blur' }],
+ stockQuantity: [{ required: true, message: '璇疯緭鍏ュ嚭搴撴暟閲�', trigger: 'blur' }],
+ unitPrice: [{ required: true, message: '璇疯緭鍏ュ崟浠�', trigger: 'blur' }], // 娣诲姞鎴愬搧搴撳瓨鍗曚环鐨勯獙璇佽鍒�
+ taxInclusiveUnitPrice: [{ required: true, message: '璇疯緭鍏ュ惈绋庡崟浠�', trigger: 'blur' }],
+ taxInclusiveTotalPrice: [{ required: true, message: '璇疯緭鍏ュ惈绋庢�讳环', trigger: 'blur' }],
+ taxRate: [{ required: true, message: '璇疯緭鍏ョ◣鐜�', trigger: 'blur' }],
+ taxExclusiveTotalPrice: [{ required: true, message: '璇疯緭鍏ヤ笉鍚◣鎬讳环', trigger: 'blur' }],
+ boundTime: [{ required: true, message: '璇烽�夋嫨搴撳瓨鏃堕棿', trigger: 'change' }],
+ inboundTime: [{ required: true, message: '璇烽�夋嫨鍏ュ簱鏃堕棿', trigger: 'change' }],
+ inboundPerson: [{ required: true, message: '璇烽�夋嫨鍑哄簱浜�', trigger: 'change' }],
+ warnNum: [{ required: true, message: '璇疯緭鍏ユ渶浣庡簱瀛�', trigger: 'blur' }],
+ }
+})
+const { searchForm, form, rules } = toRefs(data)
+
+// 鏌ヨ鍒楄〃
+/** 鎼滅储鎸夐挳鎿嶄綔 */
+const handleQuery = () => {
+ page.current = 1
+ getList()
+}
+const paginationChange = (obj) => {
+ page.current = obj.page;
+ page.size = obj.limit;
+ getList()
+}
+const buildQueryParams = () => {
+ const params = {
+ ...page,
+ timeStr: searchForm.value.timeStr,
+ }
+ params.productCategory = searchForm.value.productCategory
+ return params
+}
+
+const getList = () => {
+ tableLoading.value = true
+ const params = buildQueryParams()
+ getStockManagePageByCustom(params).then(res => {
+ tableLoading.value = false
+ tableData.value = res.data.records
+
+ // 涓鸿〃鏍兼暟鎹嚜鍔ㄨ绠楁�讳环
+ tableData.value = tableData.value.map(item => {
+ // 璁$畻鍓╀綑搴撳瓨
+ const stockQuantity = parseFloat(item.inboundNum) || 0
+ const outboundQuantity = parseFloat(item.totalInboundNum) || 0
+ const remainingStock = Math.max(stockQuantity - outboundQuantity, 0)
+
+ // 鏉愭枡搴撳瓨锛氬惈绋庢�讳环 = 鍚◣鍗曚环 脳 鍓╀綑搴撳瓨
+ const taxInclusiveUnitPrice = parseFloat(item.taxInclusiveUnitPrice) || 0
+ item.taxInclusiveTotalPrice = (taxInclusiveUnitPrice * remainingStock).toFixed(2)
+
+ return item
+ })
+
+ total.value = res.data.total
+ // 鏁版嵁鍔犺浇瀹屾垚鍚庢鏌ュ簱瀛�
+ // checkStockAndCreatePurchase();
+ }).catch(() => {
+ tableLoading.value = false
+ })
+}
+
+// 琛ㄦ牸閫夋嫨鏁版嵁
+const handleSelectionChange = (selection) => {
+
+ // 杩囨护鎺夊瓙鏁版嵁
+ selectedRows.value = selection.filter(item => item.id);
+ console.log('selection', selectedRows.value)
+}
+const expandedRowKeys = ref([])
+
+// 涓昏〃鍚堣鏂规硶
+const summarizeMainTable = (param) => {
+ return proxy.summarizeTable(param, ['contractAmount', 'taxInclusiveTotalPrice', 'taxExclusiveTotalPrice']);
+};
+
+// 琛ㄦ牸琛岀被鍚�
+const tableRowClassName = ({ row }) => {
+ const stock = Number(row?.inboundNum0 ?? 0);
+ const warn = Number(row?.warnNum ?? 0);
+ if (!Number.isFinite(stock) || !Number.isFinite(warn)) {
+ return '';
+ }
+ return stock < warn ? 'row-low-stock' : '';
+};
+
+// 鎵撳紑寮规
+const openForm = async (type, row) => {
+ operationType.value = type
+ form.value = {}
+ productData.value = []
+ let userLists = await userListNoPageByTenantId()
+ userList.value = userLists.data
+ if (type === 'edit') {
+ form.value = { ...row }
+ productTreeList().then(res =>{
+ productList.value = res
+ productList.value.forEach(i =>{
+ if (i.label === row.productCategory) {
+ modelList({ id: i.id }).then((res) => {
+ productModelList.value = res;
+ });
+ }
+ })
+ })
+ }
+ form.value.entryDate = getCurrentDate() // 璁剧疆榛樿褰曞叆鏃ユ湡涓哄綋鍓嶆棩鏈�
+
+ // 浠呮潗鏂欏簱瀛樺脊妗�
+ manualDialogVisible.value = true
+}
+
+// 鎻愪氦琛ㄥ崟
+const submitForm = (submittedData) => {
+ console.log('瀛愮粍浠舵彁浜ょ殑鏁版嵁:', submittedData)
+
+ // 浣跨敤瀛愮粍浠舵彁浜ょ殑鏁版嵁锛岃�屼笉鏄埗缁勪欢鐨刦orm瀵硅薄
+ const submitData = { ...submittedData }
+
+ // 鏉愭枡搴撳瓨锛氱Щ闄ゅ惈绋庢�讳环瀛楁
+ delete submitData.taxInclusiveTotalPrice
+ // 绉婚櫎鍏朵粬鍙兘鐨勬�讳环瀛楁
+ delete submitData.taxExclusiveTotalPrice
+
+ console.log('鎻愪氦缁欏悗绔殑鏁版嵁锛堝凡绉婚櫎鎬讳环瀛楁锛�:', submitData)
+
+ // 鏉愭枡搴撳瓨浣跨敤 updateManagementByCustom 鎺ュ彛
+ updateManagementByCustom(submitData).then(res => {
+ proxy.$modal.msgSuccess("鎻愪氦鎴愬姛")
+ closeDia()
+ getList()
+ // 鎻愪氦鍚庢鏌ュ簱瀛樺苟灏濊瘯鍒涘缓璇疯喘鍗�
+ // checkStockAndCreatePurchase();
+ }).catch(error => {
+ console.error('鎻愪氦澶辫触:', error)
+ proxy.$modal.msgError("鎻愪氦澶辫触锛岃閲嶈瘯")
+ })
+}
+// 妫�鏌ュ簱瀛樺苟鍒涘缓璇疯喘鍗�
+// const checkStockAndCreatePurchase = async () => {
+// const stockList = tableData.value;
+// // handList()
+// for (const item of stockList) {
+// if (item.inboundNum0 < item.warnNum) {
+// try {
+// const stockInData = {
+// id: item.id,
+// quantityStock: item.warnNum + item.totalInboundNum,// 浣跨敤鏂版牸寮忓寲鍑芥暟
+// };
+// loading.value = true
+// await updateStockIn(stockInData)
+// proxy.$modal.msgSuccess(`浜у搧 ${item.productCategory} 淇敼鍏ュ簱鎴愬姛`)
+// loading.value = false
+// } catch (error) {
+// proxy.$modal.msgError(`浜у搧 ${item.productCategory} 鐢熸垚璇疯喘鍗曞け璐ワ紝璇锋墜鍔ㄥ鐞哷);
+//
+// }
+// }
+// }
+// };
+// 鍏抽棴寮规
+const closeDia = () => {
+ proxy.resetForm("formRef")
+ manualDialogVisible.value = false
+}
+
+// 瀵煎嚭
+const handleOut = () => {
+ ElMessageBox.confirm(
+ '鏄惁纭瀵煎嚭锛�',
+ '瀵煎嚭', {
+ confirmButtonText: '纭',
+ cancelButtonText: '鍙栨秷',
+ type: 'warning',
+ }
+ ).then(() => {
+ const exportParams = buildQueryParams()
+ const exportUrl = "/stockin/exportCopyTwo"
+ proxy.download(exportUrl, exportParams, '搴撳瓨淇℃伅.xlsx')
+ }).catch(() => {
+ proxy.$modal.msg("宸插彇娑�")
+ })
+}
+// 鍒犻櫎
+const handleDelete = () => {
+ let ids = []
+ if (selectedRows.value.length > 0) {
+ ids = selectedRows.value.map(item => item.id);
+ } else {
+ proxy.$modal.msgWarning('璇烽�夋嫨鏁版嵁')
+ return
+ }
+ ElMessageBox.confirm(
+ '閫変腑鐨勫唴瀹瑰皢琚垹闄わ紝鏄惁纭鍒犻櫎锛�',
+ '瀵煎嚭', {
+ confirmButtonText: '纭',
+ cancelButtonText: '鍙栨秷',
+ type: 'warning',
+ }
+ ).then(() => {
+ delStockManage({ids:ids}).then(res => {
+ proxy.$modal.msgSuccess("鍒犻櫎鎴愬姛")
+ getList()
+ })
+ }).catch(() => {
+ proxy.$modal.msg("宸插彇娑�")
+ })
+}
+onMounted(() => {
+ getList()
+ // checkStockAndCreatePurchase();
+ // 姣忓皬鏃舵鏌ヤ竴娆″簱瀛�
+ // const intervalId = setInterval(checkStockAndCreatePurchase, 60 * 60 * 1000);
+
+// onUnmounted(() => {
+// // 缁勪欢鍗歌浇鏃舵竻闄ゅ畾鏃跺櫒
+// clearInterval(intervalId);
+// });
+})
+</script>
+
+<style scoped lang="scss">
+:deep(.row-low-stock td) {
+ background-color: #fde2e2;
+ color: #c45656;
+}
+
+:deep(.row-low-stock:hover > td) {
+ background-color: #fcd4d4;
+}
+</style>
\ No newline at end of file
diff --git a/src/views/inventoryManagement/stockReport/index.vue b/src/views/inventoryManagement/stockReport/index.vue
new file mode 100644
index 0000000..728c1ab
--- /dev/null
+++ b/src/views/inventoryManagement/stockReport/index.vue
@@ -0,0 +1,713 @@
+<template>
+ <div class="app-container">
+ <!-- 鎼滅储琛ㄥ崟 -->
+ <div class="search_form">
+ <div class="search_left">
+ <span class="search_title">鎶ヨ〃绫诲瀷锛�</span>
+ <el-select
+ v-model="searchForm.reportType"
+ style="width: 150px;"
+ placeholder="璇烽�夋嫨"
+ @change="handleReportTypeChange"
+ >
+ <el-option label="鏃ユ姤" value="daily" />
+ <el-option label="鏈堟姤" value="monthly" />
+ <el-option label="浣滀笟鎶ヨ〃" value="work" />
+ <el-option label="杩涘嚭瀛樻姤琛�" value="inout" />
+ </el-select>
+
+ <span class="search_title ml10">鏃堕棿鑼冨洿锛�</span>
+ <el-date-picker
+ v-if="searchForm.reportType === 'daily'"
+ v-model="searchForm.singleDate"
+ type="date"
+ placeholder="璇烽�夋嫨鏃ユ湡"
+ format="YYYY-MM-DD"
+ value-format="YYYY-MM-DD"
+ style="width: 200px;"
+ />
+ <el-date-picker
+ v-else-if="searchForm.reportType === 'monthly'"
+ v-model="searchForm.monthRange"
+ type="monthrange"
+ range-separator="鑷�"
+ start-placeholder="寮�濮嬫湀浠�"
+ end-placeholder="缁撴潫鏈堜唤"
+ format="YYYY-MM-DD"
+ value-format="YYYY-MM-DD"
+ style="width: 240px;"
+ />
+ <el-date-picker
+ v-else
+ v-model="searchForm.dateRange"
+ type="daterange"
+ range-separator="鑷�"
+ start-placeholder="寮�濮嬫棩鏈�"
+ end-placeholder="缁撴潫鏃ユ湡"
+ format="YYYY-MM-DD"
+ value-format="YYYY-MM-DD"
+ style="width: 240px;"
+ />
+
+ <el-button type="primary" @click="handleQuery" style="margin-left: 10px">
+ 鏌ヨ
+ </el-button>
+ <el-button @click="handleReset">閲嶇疆</el-button>
+ </div>
+
+ <div class="search_right">
+ <el-button type="success" @click="handleExport" icon="Download">
+ 瀵煎嚭鎶ヨ〃
+ </el-button>
+ </div>
+ </div>
+
+ <!-- 缁熻鍗$墖 -->
+ <div class="stats_cards" v-if="reportData.summary">
+ <el-row :gutter="20">
+ <el-col :span="6">
+ <el-card class="stats_card">
+ <div class="stats_content">
+ <div class="stats_icon in">
+ <el-icon><TrendCharts /></el-icon>
+ </div>
+ <div class="stats_info">
+ <div class="stats_value">{{ reportData.summary.totalIn || 0 }}</div>
+ <div class="stats_label">鎬诲叆搴撻噺</div>
+ </div>
+ </div>
+ </el-card>
+ </el-col>
+ <el-col :span="6">
+ <el-card class="stats_card">
+ <div class="stats_content">
+ <div class="stats_icon out">
+ <el-icon><TrendCharts /></el-icon>
+ </div>
+ <div class="stats_info">
+ <div class="stats_value">{{ reportData.summary.totalOut || 0 }}</div>
+ <div class="stats_label">鎬诲嚭搴撻噺</div>
+ </div>
+ </div>
+ </el-card>
+ </el-col>
+ <el-col :span="6">
+ <el-card class="stats_card">
+ <div class="stats_content">
+ <div class="stats_icon stock">
+ <el-icon><Box /></el-icon>
+ </div>
+ <div class="stats_info">
+ <div class="stats_value">{{ reportData.summary.currentStock || 0 }}</div>
+ <div class="stats_label">褰撳墠搴撳瓨</div>
+ </div>
+ </div>
+ </el-card>
+ </el-col>
+ <el-col :span="6">
+ <el-card class="stats_card">
+ <div class="stats_content">
+ <div class="stats_icon turnover">
+ <el-icon><Refresh /></el-icon>
+ </div>
+ <div class="stats_info">
+ <div class="stats_value">{{ reportData.summary.turnoverRate || 0 }}%</div>
+ <div class="stats_label">鍛ㄨ浆鐜�</div>
+ </div>
+ </div>
+ </el-card>
+ </el-col>
+ </el-row>
+ </div>
+
+ <!-- 鍥捐〃鍖哄煙 -->
+ <div class="chart_section" v-if="reportData.chartData">
+ <el-row :gutter="20">
+ <el-col :span="12">
+ <el-card>
+ <template #header>
+ <span>搴撳瓨瓒嬪娍鍥�</span>
+ </template>
+ <div ref="trendChart" style="height: 300px;"></div>
+ </el-card>
+ </el-col>
+ <el-col :span="12">
+ <el-card>
+ <template #header>
+ <span>杩涘嚭搴撳姣�</span>
+ </template>
+ <div ref="comparisonChart" style="height: 300px;"></div>
+ </el-card>
+ </el-col>
+ </el-row>
+ </div>
+
+ <!-- 璇︾粏鏁版嵁琛ㄦ牸 -->
+ <div class="table_section">
+ <el-card>
+ <template #header>
+ <span>{{ getTableTitle() }}</span>
+ </template>
+ <el-table
+ v-loading="tableLoading"
+ :data="reportData.tableData"
+ border
+ height="400"
+ style="width: 100%"
+ :header-cell-style="{ background: '#F0F1F5', color: '#333333' }"
+ >
+ <el-table-column
+ align="center"
+ label="搴忓彿"
+ type="index"
+ width="60"
+ />
+ <el-table-column
+ v-if="searchForm.reportType === 'daily'"
+ label="鏃ユ湡"
+ prop="createTime"
+ width="100"
+ align="center"
+ />
+ <el-table-column
+ v-if="searchForm.reportType === 'monthly'"
+ label="鏈堜唤"
+ prop="createTime"
+ width="100"
+ align="center"
+ />
+ <el-table-column
+ label="鍏ュ簱鏃堕棿"
+ prop="createTime"
+ width="100"
+ show-overflow-tooltip
+ />
+ <el-table-column
+ label="鍏ュ簱鎵规"
+ prop="inboundBatches"
+ width="160"
+ show-overflow-tooltip
+ />
+ <el-table-column
+ label="浜у搧澶х被"
+ prop="productCategory"
+ width="100"
+ show-overflow-tooltip
+ />
+ <el-table-column
+ label="瑙勬牸鍨嬪彿"
+ prop="specificationModel"
+ min-width="200"
+ show-overflow-tooltip
+ />
+ <el-table-column
+ label="鍗曚綅"
+ prop="unit"
+ width="70"
+ show-overflow-tooltip
+ />
+ <!-- <el-table-column
+ label="鏈熷垵搴撳瓨"
+ prop="beginStock"
+ width="100"
+ align="center"
+ /> -->
+ <el-table-column
+ label="鍏ュ簱鏁伴噺"
+ prop="inboundNum"
+ width="100"
+ align="center"
+ />
+ <!-- <el-table-column
+ label="鍑哄簱鏁伴噺"
+ prop=""
+ width="100"
+ align="center"
+ /> -->
+ <el-table-column
+ label="鐜板湪搴撳瓨"
+ prop="inboundNum0"
+ width="100"
+ align="center"
+ />
+ <el-table-column
+ label="鍚◣鍗曚环"
+ prop="taxInclusiveUnitPrice"
+ width="100"
+ show-overflow-tooltip
+ />
+ <el-table-column
+ label="鍚◣鎬讳环"
+ prop="taxInclusiveTotalPrice"
+ width="100"
+ show-overflow-tooltip
+ />
+ <el-table-column
+ label="绋庣巼(%)"
+ prop="taxRate"
+ width="80"
+ show-overflow-tooltip
+ />
+ <el-table-column
+ label="涓嶅惈绋庢�讳环"
+ prop="taxExclusiveTotalPrice"
+ width="100"
+ show-overflow-tooltip
+ />
+ <el-table-column
+ label="鍏ュ簱浜�"
+ prop="createBy"
+ width="80"
+ show-overflow-tooltip
+ />
+ <el-table-column
+ v-if="searchForm.reportType === 'work'"
+ label="鎿嶄綔浜哄憳"
+ prop="operator"
+ width="80"
+ align="center"
+ />
+ <el-table-column
+ v-if="searchForm.reportType === 'work'"
+ label="鎿嶄綔鏃堕棿"
+ prop="operateTime"
+ width="150"
+ align="center"
+ />
+ </el-table>
+ </el-card>
+ </div>
+ </div>
+</template>
+
+<script setup>
+import { ref, reactive, onMounted, nextTick } from 'vue'
+import { ElMessage } from 'element-plus'
+import * as echarts from 'echarts'
+import {
+ getStockDailyReport,
+ getStockMonthlyReport,
+ getWorkReport,
+ getStockInOutReport,
+ exportStockReport
+} from '@/api/inventoryManagement/stockReport'
+
+
+const { proxy } = getCurrentInstance()
+// 鍝嶅簲寮忔暟鎹�
+const tableLoading = ref(false)
+const trendChart = ref(null)
+const comparisonChart = ref(null)
+
+const searchForm = reactive({
+ reportType: 'daily',
+ singleDate: '',
+ dateRange: [],
+ monthRange: []
+})
+
+const reportData = ref({
+ summary: null,
+ chartData: null,
+ tableData: []
+})
+
+// 鑾峰彇琛ㄦ牸鏍囬
+const getTableTitle = () => {
+ const typeMap = {
+ daily: '鏃ユ姤璇︾粏鏁版嵁',
+ monthly: '鏈堟姤璇︾粏鏁版嵁',
+ work: '浣滀笟鎶ヨ〃璇︾粏鏁版嵁',
+ inout: '杩涘嚭瀛樻姤琛ㄨ缁嗘暟鎹�'
+ }
+ return typeMap[searchForm.reportType] || '鎶ヨ〃璇︾粏鏁版嵁'
+}
+
+// 鎶ヨ〃绫诲瀷鏀瑰彉
+const handleReportTypeChange = () => {
+ reportData.value = {
+ summary: null,
+ chartData: null,
+ tableData: []
+ }
+}
+
+// 鏌ヨ鏁版嵁
+const handleQuery = async () => {
+ if (!validateSearchForm()) {
+ return
+ }
+
+ tableLoading.value = true
+ try {
+ const params = getQueryParams()
+ let response
+
+ switch (searchForm.reportType) {
+ case 'daily':
+ response = await getStockDailyReport(params)
+ break
+ case 'monthly':
+ response = await getStockMonthlyReport(params)
+ break
+ case 'work':
+ response = await getWorkReport(params)
+ break
+ case 'inout':
+ response = await getStockInOutReport(params)
+ break
+ default:
+ throw new Error('鏈煡鐨勬姤琛ㄧ被鍨�')
+ }
+
+ if (response.code === 200) {
+ // generateMockData()
+ reportData.value.tableData = response.data.tableData
+ reportData.value.summary = response.data.summary
+ reportData.value.chartData = response.data.chartData
+ nextTick(() => {
+ initCharts()
+ })
+
+ }
+ } catch (error) {
+ ElMessage.error('鏌ヨ澶辫触锛�' + error.message)
+ } finally {
+ tableLoading.value = false
+ }
+}
+// // 鐢熸垚鍋囨暟鎹�
+// const generateMockData = () => {
+// // 鐢熸垚缁熻鍗$墖鍋囨暟鎹�
+// const summary = {
+// totalIn: 1000,
+// totalOut: 600,
+// currentStock: 400,
+// turnoverRate: 30
+// }
+
+// // 鐢熸垚鍥捐〃鍋囨暟鎹�
+// const trendDates = ['2025-09-15', '2025-09-16', '2025-09-17', '2025-09-18', '2025-09-19']
+// const trendValues = [300, 350, 400, 380, 420]
+// const comparisonDates = ['2025-09-15', '2025-09-16', '2025-09-17']
+// const inValues = [100, 150, 200]
+// const outValues = [80, 120, 100]
+
+// const chartData = {
+// trendDates,
+// trendValues,
+// comparisonDates,
+// inValues,
+// outValues
+// }
+
+// reportData.value = {
+// summary,
+// chartData,
+// tableData: []
+// }
+// }
+// 楠岃瘉鎼滅储琛ㄥ崟
+const validateSearchForm = () => {
+ if (searchForm.reportType === 'daily') {
+ if (!searchForm.singleDate) {
+ ElMessage.warning('璇烽�夋嫨鏃ユ湡')
+ return false
+ }
+ } else if (searchForm.reportType === 'work' || searchForm.reportType === 'inout') {
+ if (!searchForm.dateRange || searchForm.dateRange.length !== 2) {
+ ElMessage.warning('璇烽�夋嫨鏃ユ湡鑼冨洿')
+ return false
+ }
+ } else if (searchForm.reportType === 'monthly') {
+ if (!searchForm.monthRange || searchForm.monthRange.length !== 2) {
+ ElMessage.warning('璇烽�夋嫨鏈堜唤鑼冨洿')
+ return false
+ }
+ }
+ return true
+}
+
+// 鑾峰彇鏌ヨ鍙傛暟
+const getQueryParams = () => {
+ const params = {
+ reportType: searchForm.reportType,
+ reportDate: "",
+ startMonth: "",
+ endMonth: "",
+ startDate: "",
+ endDate: ""
+ }
+
+ if (searchForm.reportType === 'daily') {
+ params.reportDate = searchForm.singleDate
+ } else if (searchForm.reportType === 'monthly') {
+ params.startMonth = searchForm.monthRange[0]
+ params.endMonth = searchForm.monthRange[1]
+ } else {
+ params.startDate = searchForm.dateRange[0]
+ params.endDate = searchForm.dateRange[1]
+ }
+
+ return params
+}
+
+// 閲嶇疆鎼滅储
+const handleReset = () => {
+ searchForm.reportType = 'daily'
+ searchForm.singleDate = ''
+ searchForm.dateRange = []
+ searchForm.monthRange = []
+ reportData.value = {
+ summary: null,
+ chartData: null,
+ tableData: []
+ }
+}
+
+// 瀵煎嚭鎶ヨ〃
+const handleExport = async () => {
+ if (!validateSearchForm()) {
+ return
+ }
+
+ try {
+ const params = getQueryParams()
+ // const response = await exportStockReport(params)
+ proxy.download("/stockin/exportCopy", params, '搴撳瓨鎶ヨ〃.xlsx')
+ // 鍒涘缓涓嬭浇閾炬帴
+ // const blob = new Blob([response], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' })
+ // const url = window.URL.createObjectURL(blob)
+ // const link = document.createElement('a')
+ // link.href = url
+ // link.download = `${getTableTitle()}_${new Date().getTime()}.xlsx`
+ // document.body.appendChild(link)
+ // link.click()
+ // document.body.removeChild(link)
+ // window.URL.revokeObjectURL(url)
+
+ // ElMessage.success('瀵煎嚭鎴愬姛')
+ } catch (error) {
+ ElMessage.error('瀵煎嚭澶辫触锛�' + error.message)
+ }
+}
+
+// 鍒濆鍖栧浘琛�
+const initCharts = () => {
+ if (!reportData.value.chartData) return
+
+ initTrendChart()
+ initComparisonChart()
+}
+
+// 鍒濆鍖栬秼鍔垮浘
+const initTrendChart = () => {
+ if (!trendChart.value) return
+
+ const chart = echarts.init(trendChart.value)
+ const option = {
+ title: {
+ text: '搴撳瓨鍙樺寲瓒嬪娍',
+ left: 'center'
+ },
+ tooltip: {
+ trigger: 'axis'
+ },
+ legend: {
+ data: ['搴撳瓨閲�'],
+ top: 30
+ },
+ xAxis: {
+ type: 'category',
+ data: reportData.value.chartData.trendDates || []
+ },
+ yAxis: {
+ type: 'value'
+ },
+ series: [{
+ name: '搴撳瓨閲�',
+ type: 'line',
+ data: reportData.value.chartData.trendValues || [],
+ smooth: true,
+ itemStyle: {
+ color: '#409EFF'
+ }
+ }]
+ }
+ chart.setOption(option)
+}
+
+// 鍒濆鍖栧姣斿浘
+const initComparisonChart = () => {
+ if (!comparisonChart.value) return
+
+ const chart = echarts.init(comparisonChart.value)
+ const option = {
+ title: {
+ text: '杩涘嚭搴撳姣�',
+ left: 'center'
+ },
+ tooltip: {
+ trigger: 'axis'
+ },
+ legend: {
+ data: ['鍏ュ簱', '鍑哄簱'],
+ top: 30
+ },
+ xAxis: {
+ type: 'category',
+ data: reportData.value.chartData.comparisonDates || []
+ },
+ yAxis: {
+ type: 'value'
+ },
+ series: [
+ {
+ name: '鍏ュ簱',
+ type: 'bar',
+ data: reportData.value.chartData.inValues || [],
+ itemStyle: {
+ color: '#67C23A'
+ }
+ },
+ {
+ name: '鍑哄簱',
+ type: 'bar',
+ data: reportData.value.chartData.outValues || [],
+ itemStyle: {
+ color: '#F56C6C'
+ }
+ }
+ ]
+ }
+ chart.setOption(option)
+}
+
+// 缁勪欢鎸傝浇鏃惰缃粯璁ゆ椂闂�
+onMounted(() => {
+ const today = new Date()
+ searchForm.singleDate = today.toISOString().split('T')[0]
+
+ const yesterday = new Date(today.getTime() - 24 * 60 * 60 * 1000)
+ searchForm.dateRange = [
+ yesterday.toISOString().split('T')[0],
+ today.toISOString().split('T')[0]
+ ]
+})
+</script>
+
+<style scoped>
+.app-container {
+ padding: 20px;
+}
+
+.search_form {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 20px;
+ padding: 20px;
+ background: #fff;
+ border-radius: 4px;
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
+}
+
+.search_left {
+ display: flex;
+ align-items: center;
+}
+
+.search_title {
+ font-weight: 500;
+ color: #333;
+ margin-right: 8px;
+}
+
+.ml10 {
+ margin-left: 10px;
+}
+
+.stats_cards {
+ margin-bottom: 20px;
+}
+
+.stats_card {
+ border-radius: 8px;
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
+}
+
+.stats_content {
+ display: flex;
+ align-items: center;
+ padding: 10px 0;
+}
+
+.stats_icon {
+ width: 50px;
+ height: 50px;
+ border-radius: 50%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ margin-right: 15px;
+ font-size: 24px;
+ color: #fff;
+}
+
+.stats_icon.in {
+ background: linear-gradient(135deg, #67C23A, #85CE61);
+}
+
+.stats_icon.out {
+ background: linear-gradient(135deg, #F56C6C, #F78989);
+}
+
+.stats_icon.stock {
+ background: linear-gradient(135deg, #409EFF, #66B1FF);
+}
+
+.stats_icon.turnover {
+ background: linear-gradient(135deg, #E6A23C, #EEBE77);
+}
+
+.stats_info {
+ flex: 1;
+}
+
+.stats_value {
+ font-size: 24px;
+ font-weight: bold;
+ color: #333;
+ line-height: 1;
+ margin-bottom: 5px;
+}
+
+.stats_label {
+ font-size: 14px;
+ color: #666;
+}
+
+.chart_section {
+ margin-bottom: 20px;
+}
+
+.table_section {
+ margin-bottom: 20px;
+}
+
+:deep(.el-card__header) {
+ background: #f8f9fa;
+ border-bottom: 1px solid #e9ecef;
+ font-weight: 500;
+}
+
+:deep(.el-table .el-table__header-wrapper th) {
+ background-color: #F0F1F5 !important;
+ color: #333333;
+ font-weight: 600;
+}
+
+:deep(.el-table .el-table__body-wrapper td) {
+ padding: 8px 0;
+}
+</style>
diff --git a/src/views/inventoryManagement/stockWarning/index.vue b/src/views/inventoryManagement/stockWarning/index.vue
new file mode 100644
index 0000000..d0d5834
--- /dev/null
+++ b/src/views/inventoryManagement/stockWarning/index.vue
@@ -0,0 +1,943 @@
+<template>
+ <div class="app-container">
+ <!-- 鎼滅储琛ㄥ崟 -->
+ <div class="search_form">
+ <el-form :model="searchForm" :inline="true">
+ <el-form-item label="鍌ㄦ皵缃愬悕绉帮細">
+ <el-input v-model="searchForm.tankName" placeholder="璇疯緭鍏ュ偍姘旂綈鍚嶇О" clearable style="width: 200px" />
+ </el-form-item>
+ <el-form-item label="鍌ㄦ皵缃愮被鍨嬶細">
+ <el-select v-model="searchForm.tankType" placeholder="璇烽�夋嫨鍌ㄦ皵缃愮被鍨�" clearable style="width: 200px">
+ <el-option label="娑插寲姘斿偍缃�" value="娑插寲姘斿偍缃�" />
+ <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="searchForm.warningType" placeholder="璇烽�夋嫨棰勮绫诲瀷" clearable style="width: 200px">
+ <el-option label="姘斾綋涓嶈冻" value="姘斾綋涓嶈冻" />
+ <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="searchForm.warningLevel" placeholder="璇烽�夋嫨棰勮绾у埆" clearable style="width: 200px">
+ <el-option label="绱ф��" value="绱ф��" />
+ <el-option label="閲嶈" value="閲嶈" />
+ <el-option label="涓�鑸�" value="涓�鑸�" />
+ </el-select>
+ </el-form-item>
+ <el-form-item>
+ <el-button type="primary" @click="handleQuery">鎼滅储</el-button>
+ <el-button @click="resetQuery">閲嶇疆</el-button>
+ </el-form-item>
+ </el-form>
+ </div>
+
+ <!-- 鏁版嵁琛ㄦ牸 -->
+ <div class="table_list">
+ <!-- 鎿嶄綔鎸夐挳 -->
+ <div class="table-operations">
+ <el-button type="primary" @click="handleAdd">鏂板棰勮瑙勫垯</el-button>
+ <!-- <el-button type="success" @click="handleBatchProcess">鎵归噺澶勭悊@</el-button> -->
+ <el-button @click="handleExport">瀵煎嚭</el-button>
+ </div>
+ <el-table
+ :data="tableData"
+ border
+ v-loading="tableLoading"
+ @selection-change="handleSelectionChange"
+ style="width: 100%"
+ height="calc(100vh - 280px)"
+ >
+ <el-table-column align="center" type="selection" width="55" />
+ <el-table-column align="center" label="搴忓彿" type="index" width="60" />
+
+ <!-- 鍩虹淇℃伅瀛楁 -->
+ <el-table-column label="鍌ㄦ皵缃愮紪鐮�" prop="tankCode" width="120" show-overflow-tooltip />
+ <el-table-column label="鍌ㄦ皵缃愬悕绉�" prop="tankName" width="200" show-overflow-tooltip />
+ <el-table-column label="鍌ㄦ皵缃愮被鍨�" prop="tankType" width="120" show-overflow-tooltip />
+ <el-table-column label="瑙勬牸鍨嬪彿" prop="specificationModel" width="150" show-overflow-tooltip />
+ <el-table-column label="瀹圭Н(m鲁)" prop="volume" width="100" show-overflow-tooltip />
+
+ <!-- 搴撳瓨鐩稿叧瀛楁 -->
+ <el-table-column label="褰撳墠姘斾綋閲�" prop="currentGasLevel" width="120" show-overflow-tooltip>
+ <template #default="scope">
+ <span :class="getGasLevelClass(scope.row)">{{ scope.row.currentGasLevel }}%</span>
+ </template>
+ </el-table-column>
+ <el-table-column label="瀹夊叏姘斾綋閲�" prop="safetyGasLevel" width="120" show-overflow-tooltip />
+ <el-table-column label="鏈�浣庢皵浣撻噺" prop="minGasLevel" width="120" show-overflow-tooltip />
+ <el-table-column label="鏈�楂樻皵浣撻噺" prop="maxGasLevel" width="120" show-overflow-tooltip />
+ <el-table-column label="褰撳墠鍘嬪姏(MPa)" prop="currentPressure" width="140" show-overflow-tooltip />
+
+ <!-- 棰勮瑙勫垯瀛楁 -->
+ <el-table-column label="棰勮绫诲瀷" prop="warningType" width="100" show-overflow-tooltip>
+ <template #default="scope">
+ <el-tag :type="getWarningTypeTag(scope.row.warningType)">
+ {{ scope.row.warningType }}
+ </el-tag>
+ </template>
+ </el-table-column>
+ <el-table-column label="棰勮绾у埆" prop="warningLevel" width="100" show-overflow-tooltip>
+ <template #default="scope">
+ <el-tag :type="getWarningLevelTag(scope.row.warningLevel)">
+ {{ scope.row.warningLevel }}
+ </el-tag>
+ </template>
+ </el-table-column>
+ <el-table-column label="棰勮闃堝��" prop="warningThreshold" width="100" show-overflow-tooltip />
+ <el-table-column label="鏄惁鍚敤" prop="isEnabled" width="100" show-overflow-tooltip>
+ <template #default="scope">
+ <el-switch v-model="scope.row.isEnabled" @change="handleEnableChange(scope.row)" />
+ </template>
+ </el-table-column>
+
+ <!-- 鏃堕棿鐩稿叧瀛楁 -->
+ <el-table-column label="棰勮鏃堕棿" prop="warningTime" width="150" show-overflow-tooltip />
+ <el-table-column label="棰勮鎸佺画澶╂暟" prop="warningDuration" width="120" show-overflow-tooltip />
+ <el-table-column label="鏈�鍚庢洿鏂版椂闂�" prop="lastUpdateTime" width="150" show-overflow-tooltip />
+ <el-table-column label="棰勮鍏呰鏃堕棿" prop="expectedRefillTime" width="150" show-overflow-tooltip />
+ <el-table-column label="棰勮缂烘皵鏃堕棿" prop="expectedShortageTime" width="150" show-overflow-tooltip>
+ <template #default="scope">
+ <div v-if="scope.row.expectedShortageTime">
+ <div v-if="getCountdown(scope.row.expectedShortageTime).isExpired" class="countdown-expired">
+ <el-tag type="danger">宸茬己姘�</el-tag>
+ </div>
+ <div v-else class="countdown-timer">
+ <span :class="getCountdownClass(scope.row.expectedShortageTime)">
+ {{ getCountdown(scope.row.expectedShortageTime).text }}
+ </span>
+ </div>
+ </div>
+ <span v-else>-</span>
+ </template>
+ </el-table-column>
+
+ <!-- 鎿嶄綔鍒� -->
+ <el-table-column fixed="right" label="鎿嶄綔" width="200" align="center">
+ <template #default="scope">
+ <el-button link type="primary" size="small" @click="handleEdit(scope.row)">缂栬緫</el-button>
+<!-- <el-button link type="success" size="small" @click="handleProcess(scope.row)">澶勭悊@</el-button>-->
+ <el-button link type="danger" size="small" @click="handleDelete(scope.row)">鍒犻櫎</el-button>
+ </template>
+ </el-table-column>
+ </el-table>
+
+ <!-- 鍒嗛〉 -->
+ <pagination
+ v-show="total > 0"
+ :total="total"
+ layout="total, sizes, prev, pager, next, jumper"
+ :page="page.current"
+ :limit="page.size"
+ @pagination="paginationChange"
+ />
+ </div>
+
+ <!-- 鏂板/缂栬緫棰勮瑙勫垯寮圭獥 -->
+ <el-dialog
+ v-model="dialogFormVisible"
+ :title="operationType === 'add' ? '鏂板棰勮瑙勫垯' : '缂栬緫棰勮瑙勫垯'"
+ width="50%"
+ @close="closeDialog"
+ >
+ <el-form :model="form" :rules="rules" ref="formRef" label-width="140px">
+ <el-row :gutter="20">
+ <!-- 鍩虹淇℃伅 -->
+ <el-col :span="12">
+ <el-form-item label="鍌ㄦ皵缃愮紪鐮侊細" prop="tankCode">
+ <el-input v-model="form.tankCode" placeholder="璇疯緭鍏ュ偍姘旂綈缂栫爜" />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="鍌ㄦ皵缃愬悕绉帮細" prop="tankName">
+ <el-input v-model="form.tankName" placeholder="璇疯緭鍏ュ偍姘旂綈鍚嶇О" />
+ </el-form-item>
+ </el-col>
+ </el-row>
+
+ <el-row :gutter="20">
+ <el-col :span="12">
+ <el-form-item label="鍌ㄦ皵缃愮被鍨嬶細" prop="tankType">
+ <el-select v-model="form.tankType" placeholder="璇烽�夋嫨鍌ㄦ皵缃愮被鍨�" style="width: 100%">
+ <el-option label="娑插寲姘斿偍缃�" value="娑插寲姘斿偍缃�" />
+ <el-option label="鍘嬬缉姘斿偍缃�" value="鍘嬬缉姘斿偍缃�" />
+ <el-option label="澶╃劧姘斿偍缃�" value="澶╃劧姘斿偍缃�" />
+ <el-option label="姘ф皵鍌ㄧ綈" value="姘ф皵鍌ㄧ綈" />
+ </el-select>
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="瑙勬牸鍨嬪彿锛�" prop="specificationModel">
+ <el-input v-model="form.specificationModel" placeholder="璇疯緭鍏ヨ鏍煎瀷鍙�" />
+ </el-form-item>
+ </el-col>
+ </el-row>
+
+ <el-row :gutter="20">
+ <el-col :span="12">
+ <el-form-item label="瀹圭Н(m鲁)锛�" prop="volume">
+ <el-input-number v-model="form.volume" :min="0" :precision="2" style="width: 100%" />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="褰撳墠姘斾綋閲�(%)锛�" prop="currentGasLevel">
+ <el-input-number v-model="form.currentGasLevel" :min="0" :max="100" :precision="1" style="width: 100%" />
+ </el-form-item>
+ </el-col>
+ </el-row>
+
+ <!-- 搴撳瓨鐩稿叧 -->
+ <el-row :gutter="20">
+ <el-col :span="12">
+ <el-form-item label="瀹夊叏姘斾綋閲�(%)锛�" prop="safetyGasLevel">
+ <el-input-number v-model="form.safetyGasLevel" :min="0" :max="100" :precision="1" style="width: 100%" />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="鏈�浣庢皵浣撻噺(%)锛�" prop="minGasLevel">
+ <el-input-number v-model="form.minGasLevel" :min="0" :max="100" :precision="1" style="width: 100%" />
+ </el-form-item>
+ </el-col>
+ </el-row>
+
+ <el-row :gutter="20">
+ <el-col :span="12">
+ <el-form-item label="鏈�楂樻皵浣撻噺(%)锛�" prop="maxGasLevel">
+ <el-input-number v-model="form.maxGasLevel" :min="0" :max="100" :precision="1" style="width: 100%" />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="褰撳墠鍘嬪姏(MPa)锛�" prop="currentPressure">
+ <el-input-number v-model="form.currentPressure" :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="warningType">
+ <el-select v-model="form.warningType" placeholder="璇烽�夋嫨棰勮绫诲瀷" style="width: 100%">
+ <el-option label="姘斾綋涓嶈冻" value="姘斾綋涓嶈冻" />
+ <el-option label="鍘嬪姏寮傚父" value="鍘嬪姏寮傚父" />
+ <el-option label="娓╁害寮傚父" value="娓╁害寮傚父" />
+ <el-option label="娉勬紡棰勮" value="娉勬紡棰勮" />
+ </el-select>
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="棰勮绾у埆锛�" prop="warningLevel">
+ <el-select v-model="form.warningLevel" placeholder="璇烽�夋嫨棰勮绾у埆" style="width: 100%">
+ <el-option label="绱ф��" value="绱ф��" />
+ <el-option label="閲嶈" value="閲嶈" />
+ <el-option label="涓�鑸�" value="涓�鑸�" />
+ </el-select>
+ </el-form-item>
+ </el-col>
+ </el-row>
+
+ <el-row :gutter="20">
+ <el-col :span="12">
+ <el-form-item label="棰勮闃堝�硷細" prop="warningThreshold">
+ <el-input-number v-model="form.warningThreshold" :min="0" :precision="2" style="width: 100%" />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="鏄惁鍚敤锛�" prop="isEnabled">
+ <el-switch v-model="form.isEnabled" />
+ </el-form-item>
+ </el-col>
+ </el-row>
+
+ <!-- 鏃堕棿鐩稿叧 -->
+ <el-row :gutter="20">
+ <el-col :span="12">
+ <el-form-item label="棰勮鏃堕棿锛�" prop="warningTime">
+ <el-date-picker
+ v-model="form.warningTime"
+ type="datetime"
+ placeholder="璇烽�夋嫨棰勮鏃堕棿"
+ style="width: 100%"
+ value-format="YYYY-MM-DD HH:mm:ss"
+ />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="棰勮鍏呰鏃堕棿锛�" prop="expectedRefillTime">
+ <el-date-picker
+ v-model="form.expectedRefillTime"
+ type="datetime"
+ placeholder="璇烽�夋嫨棰勮鍏呰鏃堕棿"
+ style="width: 100%"
+ value-format="YYYY-MM-DD HH:mm:ss"
+ />
+ </el-form-item>
+ </el-col>
+ </el-row>
+
+ <el-row :gutter="20">
+ <el-col :span="12">
+ <el-form-item label="棰勮缂烘皵鏃堕棿锛�" prop="expectedShortageTime">
+ <el-date-picker
+ v-model="form.expectedShortageTime"
+ type="datetime"
+ placeholder="璇烽�夋嫨棰勮缂烘皵鏃堕棿"
+ style="width: 100%"
+ value-format="YYYY-MM-DD HH:mm:ss"
+ />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="棰勮瑙勫垯鎻忚堪锛�" prop="warningRule">
+ <el-input
+ v-model="form.warningRule"
+ type="textarea"
+ :rows="3"
+ placeholder="璇疯緭鍏ラ璀﹁鍒欐弿杩�"
+ />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ </el-form>
+
+ <template #footer>
+ <div class="dialog-footer">
+ <el-button @click="closeDialog">鍙栨秷</el-button>
+ <el-button type="primary" @click="submitForm">纭</el-button>
+ </div>
+ </template>
+ </el-dialog>
+
+ <!-- 缂烘皵棰勮寮规 -->
+ <el-dialog
+ v-model="shortageWarningVisible"
+ title="鈿狅笍 缂烘皵棰勮"
+ width="400px"
+ :close-on-click-modal="false"
+ :close-on-press-escape="false"
+ :show-close="false"
+ >
+ <div class="shortage-warning-content">
+ <div class="warning-icon">
+ <el-icon size="48" color="#f56c6c"><WarningFilled /></el-icon>
+ </div>
+ <div class="warning-message">
+ <h3>{{ currentWarningTank.tankName }}</h3>
+ <p>鍌ㄦ皵缃愬凡缂烘皵锛岃鍙婃椂澶勭悊锛�</p>
+ <p class="warning-details">
+ 鍌ㄦ皵缃愮紪鐮侊細{{ currentWarningTank.tankCode }}<br>
+ 鍌ㄦ皵缃愮被鍨嬶細{{ currentWarningTank.tankType }}<br>
+ 褰撳墠姘斾綋閲忥細{{ currentWarningTank.currentGasLevel }}%
+ </p>
+ </div>
+ </div>
+ <template #footer>
+ <div class="dialog-footer">
+ <el-button type="primary" @click="handleShortageWarning">绔嬪嵆澶勭悊</el-button>
+ <el-button @click="closeShortageWarning">绋嶅悗澶勭悊</el-button>
+ </div>
+ </template>
+ </el-dialog>
+
+ <!-- 缂烘皵棰勮寮规 -->
+ <el-dialog
+ v-model="shortageWarningVisible"
+ title="鈿狅笍 缂烘皵棰勮"
+ width="400px"
+ :close-on-click-modal="false"
+ :close-on-press-escape="false"
+ :show-close="false"
+ >
+ <div class="shortage-warning-content">
+ <div class="warning-icon">
+ <el-icon size="48" color="#f56c6c"><WarningFilled /></el-icon>
+ </div>
+ <div class="warning-message">
+ <h3>{{ currentWarningTank.tankName }}</h3>
+ <p>鍌ㄦ皵缃愬凡缂烘皵锛岃鍙婃椂澶勭悊锛�</p>
+ <p class="warning-details">
+ 鍌ㄦ皵缃愮紪鐮侊細{{ currentWarningTank.tankCode }}<br>
+ 鍌ㄦ皵缃愮被鍨嬶細{{ currentWarningTank.tankType }}<br>
+ 褰撳墠姘斾綋閲忥細{{ currentWarningTank.currentGasLevel }}%
+ </p>
+ </div>
+ </div>
+ <template #footer>
+ <div class="dialog-footer">
+ <el-button type="primary" @click="handleShortageWarning">绔嬪嵆澶勭悊</el-button>
+ <el-button @click="closeShortageWarning">绋嶅悗澶勭悊</el-button>
+ </div>
+ </template>
+ </el-dialog>
+ </div>
+</template>
+
+<script setup>
+import { ref, reactive, onMounted, onUnmounted } from 'vue'
+import { ElMessage, ElMessageBox } from 'element-plus'
+import { WarningFilled } from '@element-plus/icons-vue'
+import pagination from '@/components/PIMTable/Pagination.vue'
+// 娉ㄩ噴鎺堿PI瀵煎叆锛屼娇鐢ㄥ亣鏁版嵁
+import {
+ getStockWarningPage,
+ addStockWarning,
+ updateStockWarning,
+ deleteStockWarning,
+ batchProcessStockWarning,
+ exportStockWarning,
+ toggleStockWarningStatus
+} from '@/api/inventoryManagement/stockWarning.js'
+
+const { proxy } = getCurrentInstance()
+
+// 鍝嶅簲寮忔暟鎹�
+const tableData = ref([])
+const tableLoading = ref(false)
+const selectedRows = ref([])
+const dialogFormVisible = ref(false)
+const operationType = ref('add')
+const total = ref(0)
+
+// 缂烘皵棰勮鐩稿叧
+const shortageWarningVisible = ref(false)
+const currentWarningTank = ref({})
+const countdownTimer = ref(null)
+
+// 鍒嗛〉鍙傛暟
+const page = reactive({
+ current: 1,
+ size: 10,
+ total: 0
+})
+
+// 鎼滅储琛ㄥ崟
+const searchForm = reactive({
+ tankName: '',
+ tankType: '',
+ warningType: '',
+ warningLevel: ''
+})
+
+// 琛ㄥ崟鏁版嵁
+const form = reactive({
+ id: null,
+ tankCode: '',
+ tankName: '',
+ tankType: '',
+ specificationModel: '',
+ volume: 0,
+ currentGasLevel: 0,
+ safetyGasLevel: 0,
+ minGasLevel: 0,
+ maxGasLevel: 0,
+ currentPressure: 0,
+ warningType: '',
+ warningLevel: '',
+ warningThreshold: 0,
+ isEnabled: true,
+ warningTime: '',
+ warningDuration: 0,
+ lastUpdateTime: '',
+ expectedRefillTime: '',
+ expectedShortageTime: '',
+ warningRule: ''
+})
+
+// 琛ㄥ崟楠岃瘉瑙勫垯
+const rules = {
+ tankCode: [{ required: true, message: '璇疯緭鍏ュ偍姘旂綈缂栫爜', trigger: 'blur' }],
+ tankName: [{ required: true, message: '璇疯緭鍏ュ偍姘旂綈鍚嶇О', trigger: 'blur' }],
+ tankType: [{ required: true, message: '璇烽�夋嫨鍌ㄦ皵缃愮被鍨�', trigger: 'change' }],
+ warningType: [{ required: true, message: '璇烽�夋嫨棰勮绫诲瀷', trigger: 'change' }],
+ warningLevel: [{ required: true, message: '璇烽�夋嫨棰勮绾у埆', trigger: 'change' }],
+ warningThreshold: [{ required: true, message: '璇疯緭鍏ラ璀﹂槇鍊�', trigger: 'blur' }]
+}
+
+// 鑾峰彇鍊掕鏃朵俊鎭�
+const getCountdown = (expectedTime) => {
+ if (!expectedTime) return { text: '-', isExpired: false }
+
+ const now = new Date().getTime()
+ const expected = new Date(expectedTime).getTime()
+ const diff = expected - now
+
+ if (diff <= 0) {
+ return { text: '宸茬己姘�', isExpired: true }
+ }
+
+ const days = Math.floor(diff / (1000 * 60 * 60 * 24))
+ const hours = Math.floor((diff % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60))
+ const minutes = Math.floor((diff % (1000 * 60 * 60)) / (1000 * 60))
+
+ if (days > 0) {
+ return { text: `${days}澶�${hours}灏忔椂`, isExpired: false }
+ } else if (hours > 0) {
+ return { text: `${hours}灏忔椂${minutes}鍒嗛挓`, isExpired: false }
+ } else {
+ return { text: `${minutes}鍒嗛挓`, isExpired: false }
+ }
+}
+
+// 鑾峰彇鍊掕鏃舵牱寮忕被
+const getCountdownClass = (expectedTime) => {
+ if (!expectedTime) return ''
+
+ const now = new Date().getTime()
+ const expected = new Date(expectedTime).getTime()
+ const diff = expected - now
+
+ if (diff <= 0) {
+ return 'countdown-expired'
+ } else if (diff <= 24 * 60 * 60 * 1000) { // 24灏忔椂鍐�
+ return 'countdown-urgent'
+ } else if (diff <= 7 * 24 * 60 * 60 * 1000) { // 7澶╁唴
+ return 'countdown-warning'
+ } else {
+ return 'countdown-normal'
+ }
+}
+
+// 妫�鏌ョ己姘旈璀�
+const checkShortageWarnings = () => {
+ tableData.value.forEach(tank => {
+ if (tank.expectedShortageTime) {
+ const countdown = getCountdown(tank.expectedShortageTime)
+ if (countdown.isExpired && !tank.warningShown) {
+ // 鏍囪宸叉樉绀洪璀︼紝閬垮厤閲嶅寮规
+ tank.warningShown = true
+ showShortageWarning(tank)
+ }
+ }
+ })
+}
+
+// 鏄剧ず缂烘皵棰勮寮规
+const showShortageWarning = (tank) => {
+ currentWarningTank.value = tank
+ shortageWarningVisible.value = true
+
+ // 鎾斁鎻愮ず闊筹紙鍙�夛級
+ // const audio = new Audio('/path/to/warning-sound.mp3')
+ // audio.play()
+}
+
+// 澶勭悊缂烘皵棰勮
+const handleShortageWarning = () => {
+ ElMessage.success(`姝e湪澶勭悊鍌ㄦ皵缃� ${currentWarningTank.value.tankName} 鐨勭己姘旈棶棰榒)
+ shortageWarningVisible.value = false
+ // 杩欓噷鍙互璋冪敤澶勭悊API
+}
+// 澶勭悊缂烘皵棰勮
+const closeShortageWarning = () => {
+ // ElMessage.success(`姝e湪澶勭悊鍌ㄦ皵缃� ${currentWarningTank.value.tankName} 鐨勭己姘旈棶棰榒)
+ shortageWarningVisible.value = false
+ // 杩欓噷鍙互璋冪敤澶勭悊API
+}
+
+// 鑾峰彇鍒楄〃鏁版嵁
+const getList = async () => {
+ tableLoading.value = true
+ getStockWarningPage(page, searchForm)
+ .then(res => {
+
+ tableData.value = res.data.records
+ page.value.total = res.data.total;
+ tableLoading.value = false;
+ // 妫�鏌ョ己姘旈璀�
+ checkShortageWarnings()
+ }).catch(err => {
+ tableLoading.value = false;
+ })
+}
+
+// 鎼滅储
+const handleQuery = () => {
+ page.current = 1
+ getList()
+}
+
+// 閲嶇疆鎼滅储
+const resetQuery = () => {
+ Object.keys(searchForm).forEach(key => {
+ searchForm[key] = ''
+ })
+ handleQuery()
+}
+
+// 鍒嗛〉鍙樺寲
+const paginationChange = (obj) => {
+ page.current = obj.page
+ page.size = obj.limit
+ getList()
+}
+
+// 琛ㄦ牸閫夋嫨鍙樺寲
+const handleSelectionChange = (selection) => {
+ selectedRows.value = selection
+}
+
+// 鏂板
+const handleAdd = () => {
+ operationType.value = 'add'
+ // resetForm()
+ dialogFormVisible.value = true
+}
+
+// 缂栬緫
+const handleEdit = (row) => {
+ operationType.value = 'edit'
+ Object.assign(form, row)
+ dialogFormVisible.value = true
+}
+
+// 澶勭悊棰勮
+const handleProcess = async (row) => {
+ try {
+ // 妯℃嫙API璋冪敤寤惰繜
+ await new Promise(resolve => setTimeout(resolve, 300))
+ ElMessage.success(`姝e湪澶勭悊棰勮锛�${row.tankName}`)
+ // getList()
+ } catch (error) {
+ ElMessage.error('澶勭悊棰勮澶辫触')
+ }
+}
+
+// 鍒犻櫎
+const handleDelete = async (row) => {
+ try {
+ await ElMessageBox.confirm(`纭畾瑕佸垹闄ら璀﹁鍒欙細${row.tankName}鍚楋紵`, '鎻愮ず', {
+ confirmButtonText: '纭畾',
+ cancelButtonText: '鍙栨秷',
+ type: 'warning'
+ })
+ let ids = [];
+ ids.push(row.id);
+ deleteStockWarning(ids).then(res => {
+ if(res.code == 200){
+ ElMessage.success("鍒犻櫎鎴愬姛");
+ ids.value = [];
+ getList();
+ }
+ }).catch(err => {
+ ElMessage.error(err.msg);
+ })
+ // // 妯℃嫙API璋冪敤寤惰繜
+ // await new Promise(resolve => setTimeout(resolve, 300))
+ // ElMessage.success('鍒犻櫎鎴愬姛')
+ // getList()
+ } catch (error) {
+ if (error !== 'cancel') {
+ ElMessage.error('鍒犻櫎澶辫触')
+ }
+ }
+}
+
+// 鎵归噺澶勭悊
+const handleBatchProcess = async () => {
+ if (selectedRows.value.length === 0) {
+ ElMessage.warning('璇烽�夋嫨瑕佸鐞嗙殑棰勮')
+ return
+ }
+
+ try {
+ // 妯℃嫙API璋冪敤寤惰繜
+ await new Promise(resolve => setTimeout(resolve, 500))
+ ElMessage.success(`鎵归噺澶勭悊浜� ${selectedRows.value.length} 鏉¢璀)
+ getList()
+ } catch (error) {
+ ElMessage.error('鎵归噺澶勭悊澶辫触')
+ }
+}
+
+// 瀵煎嚭
+const handleExport = async () => {
+ // if (selectedRows.value.length === 0) {
+ // exportStockWarning().then(res => {
+ // // // 鍒涘缓涓嬭浇閾炬帴
+ // // const blob = new Blob([res.data], { type: 'text/csv;charset=utf-8;' })
+ // // const url = window.URL.createObjectURL(blob)
+ // // const link = document.createElement('a')
+ // // link.href = url
+ // // link.download = `鍌ㄦ皵缃愰璀︽暟鎹甠${new Date().getTime()}.csv`
+ // // link.click()
+ // // window.URL.revokeObjectURL(url)
+ // }).catch(err => {
+ // ElMessage.error(err.msg);
+ // })
+ // }else{
+ // let ids = [];
+ // selectedRows.value.forEach(item => {
+ // ids.push(item.id);
+ // })
+ // exportStockWarning(ids).then(res => {
+ // // // 鍒涘缓涓嬭浇閾炬帴
+ // // const blob = new Blob([res.data], { type: 'text/csv;charset=utf-8;' })
+ // // const url = window.URL.createObjectURL(blob)
+ // // const link = document.createElement('a')
+ // // link.href = url
+ // // link.download = `鍌ㄦ皵缃愰璀︽暟鎹甠${new Date().getTime()}.csv`
+ // // link.click()
+ // // window.URL.revokeObjectURL(url)
+ // }).catch(err => {
+ // ElMessage.error(err.msg);
+ // })
+ // }
+
+ ElMessageBox.confirm("閫変腑鐨勫唴瀹瑰皢琚鍑猴紝鏄惁纭瀵煎嚭锛�", "瀵煎嚭", {
+ confirmButtonText: "纭",
+ cancelButtonText: "鍙栨秷",
+ type: "warning",
+ })
+ .then(() => {
+ proxy.download("/gasTankWarning/export", {ids: selectedRows.value.map(item => item.id)}, "鍌ㄦ皵缃愰璀�.xlsx");
+ })
+ .catch(() => {
+ proxy.$modal.msg("宸插彇娑�");
+ });
+}
+
+
+
+// // 鍚敤鐘舵�佸彉鍖�
+const handleEnableChange = async (row) => {
+
+ try {
+ updateStockWarning(row).then(res => {
+ if(res.code == 200){
+ ElMessage.success(`${row.tankName} 鐨勫惎鐢ㄧ姸鎬佸凡鏇存柊`);
+ getList();
+ }
+ }).catch(err => {
+ ElMessage.error(err.msg);
+ })
+ } catch (error) {
+ ElMessage.error('鐘舵�佹洿鏂板け璐�')
+ // 鎭㈠鍘熺姸鎬�
+ row.isEnabled = !row.isEnabled
+ }
+}
+
+// 鎻愪氦琛ㄥ崟
+const submitForm = async () => {
+ try {
+ await proxy.$refs.formRef.validate()
+
+ // 妯℃嫙API璋冪敤寤惰繜
+ // await new Promise(resolve => setTimeout(resolve, 500))
+
+ if (operationType.value === 'add') {
+ addStockWarning(form).then(res => {
+ if(res.code == 200){
+ ElMessage.success("娣诲姞鎴愬姛");
+ dialogFormVisible.value = false
+ getList()
+ resetForm()
+ }
+ }).catch(err => {
+ ElMessage.error(err.msg);
+ })
+
+ // ElMessage.success('鏂板鎴愬姛')
+ } else {
+ updateStockWarning(form).then(res => {
+ if(res.code == 200){
+ ElMessage.success("鏇存柊鎴愬姛");
+ dialogFormVisible.value = false
+ getList()
+ resetForm()
+ }
+ }).catch(err => {
+ ElMessage.error(err.msg);
+ })
+ // ElMessage.success('缂栬緫鎴愬姛')
+ }
+
+ // closeDialog()
+ // getList()
+ } catch (error) {
+ if (!error.errors) {
+ ElMessage.error(operationType.value === 'add' ? '鏂板澶辫触' : '缂栬緫澶辫触')
+ }
+ }
+}
+
+// 鍏抽棴寮圭獥
+const closeDialog = () => {
+ dialogFormVisible.value = false
+ // resetForm()
+}
+
+// 閲嶇疆琛ㄥ崟
+const resetForm = () => {
+ Object.keys(form).forEach(key => {
+ if (key === 'isEnabled') {
+ form[key] = true
+ } else if (typeof form[key] === 'number') {
+ form[key] = 0
+ } else {
+ form[key] = ''
+ }
+ })
+ proxy.$refs.formRef?.resetFields()
+}
+
+// 鑾峰彇姘斾綋閲忔牱寮忕被
+const getGasLevelClass = (row) => {
+ if (row.currentGasLevel < row.minGasLevel) {
+ return 'text-danger'
+ } else if (row.currentGasLevel > row.maxGasLevel) {
+ return 'text-warning'
+ }
+ return 'text-success'
+}
+
+// 鑾峰彇棰勮绫诲瀷鏍囩鏍峰紡
+const getWarningTypeTag = (type) => {
+ const typeMap = {
+ '姘斾綋涓嶈冻': 'danger',
+ '鍘嬪姏寮傚父': 'warning',
+ '娓╁害寮傚父': 'info',
+ '娉勬紡棰勮': 'danger'
+ }
+ return typeMap[type] || 'info'
+}
+
+// 鑾峰彇棰勮绾у埆鏍囩鏍峰紡
+const getWarningLevelTag = (level) => {
+ const levelMap = {
+ '绱ф��': 'danger',
+ '閲嶈': 'warning',
+ '涓�鑸�': 'info'
+ }
+ return levelMap[level] || 'info'
+}
+
+// 鍚姩鍊掕鏃跺畾鏃跺櫒
+const startCountdownTimer = () => {
+ countdownTimer.value = setInterval(() => {
+ checkShortageWarnings()
+ }, 60000) // 姣忓垎閽熸鏌ヤ竴娆�
+}
+
+// 鍋滄鍊掕鏃跺畾鏃跺櫒
+const stopCountdownTimer = () => {
+ if (countdownTimer.value) {
+ clearInterval(countdownTimer.value)
+ countdownTimer.value = null
+ }
+}
+
+// 椤甸潰鍔犺浇
+onMounted(() => {
+ getList()
+ startCountdownTimer()
+})
+
+// 椤甸潰鍗歌浇
+onUnmounted(() => {
+ stopCountdownTimer()
+})
+</script>
+
+<style scoped lang="scss">
+.app-container {
+ padding: 20px;
+
+ .table-operations {
+ text-align: right;
+ margin-bottom: 20px;
+
+ .el-button {
+ margin-right: 10px;
+ }
+ }
+
+ .table_list {
+ background: #fff;
+ border-radius: 4px;
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
+ }
+
+ .text-danger {
+ color: #f56c6c;
+ font-weight: bold;
+ }
+
+ .text-warning {
+ color: #e6a23c;
+ font-weight: bold;
+ }
+
+ .text-success {
+ color: #67c23a;
+ font-weight: bold;
+ }
+
+ .dialog-footer {
+ text-align: right;
+ }
+
+ // 鍊掕鏃舵牱寮�
+ .countdown-timer {
+ font-weight: bold;
+ }
+
+ .countdown-normal {
+ color: #67c23a;
+ }
+
+ .countdown-warning {
+ color: #e6a23c;
+ }
+
+ .countdown-urgent {
+ color: #f56c6c;
+ animation: blink 1s infinite;
+ }
+
+ .countdown-expired {
+ color: #f56c6c;
+ font-weight: bold;
+ }
+
+ @keyframes blink {
+ 0%, 50% { opacity: 1; }
+ 51%, 100% { opacity: 0.5; }
+ }
+
+ // 缂烘皵棰勮寮规鏍峰紡
+ .shortage-warning-content {
+ text-align: center;
+ padding: 20px 0;
+
+ .warning-icon {
+ margin-bottom: 20px;
+ }
+
+ .warning-message {
+ h3 {
+ color: #f56c6c;
+ margin-bottom: 10px;
+ }
+
+ p {
+ margin-bottom: 10px;
+ color: #606266;
+ }
+
+ .warning-details {
+ background: #f5f7fa;
+ padding: 15px;
+ border-radius: 4px;
+ text-align: left;
+ font-size: 14px;
+ line-height: 1.6;
+ }
+ }
+ }
+}
+</style>
diff --git a/src/views/procurementManagement/paymentEntry/index.vue b/src/views/procurementManagement/paymentEntry/index.vue
new file mode 100644
index 0000000..02fda1a
--- /dev/null
+++ b/src/views/procurementManagement/paymentEntry/index.vue
@@ -0,0 +1,575 @@
+<template>
+ <div class="app-container">
+ <div class="search_form">
+ <el-form :inline="true" :model="searchForm" style="width: 100%">
+ <el-row justify="space-between">
+ <el-col :span="20">
+ <el-form-item label="渚涘簲鍟嗗悕绉�/鍚堝悓鍙�">
+ <el-input
+ v-model="searchForm.supplierNameOrContractNo"
+ style="width: 240px"
+ placeholder="杈撳叆渚涘簲鍟嗗悕绉�/鍚堝悓鍙锋悳绱�"
+ clearable
+ prefix-icon="Search"
+ @change="handleQuery"
+ />
+ </el-form-item>
+ <el-form-item>
+ <el-checkbox
+ v-model="searchForm.status"
+ label="涓嶆樉绀哄緟浠樻涓�0"
+ @change="handleQuery"
+ />
+ </el-form-item>
+ <el-form-item>
+ <el-button type="primary" @click="handleQuery"> 鎼滅储 </el-button>
+ </el-form-item>
+ </el-col>
+ <el-col :span="4">
+ <el-form-item style="float: right; margin-right: unset">
+ <el-button type="primary" @click="openForm('add')">
+ 鏂板浠樻
+ </el-button>
+<!-- <el-button type="danger" plain @click="handleDelete">-->
+<!-- 鍒犻櫎-->
+<!-- </el-button>-->
+ </el-form-item>
+ </el-col>
+ </el-row>
+ </el-form>
+ </div>
+ <div class="table_list">
+ <PIMTable
+ rowKey="id"
+ :column="tableColumn"
+ :tableData="tableData"
+ :page="page"
+ :expandRowKeys="expandedRowKeys"
+ :isSelection="true"
+ :isShowSummary="isShowSummarySon"
+ :summaryMethod="summarizeMainTable1"
+ @selection-change="handleSelectionChange"
+ @expand-change="expandChange"
+ :tableLoading="tableLoading"
+ @pagination="pagination"
+ :total="page.total"
+ >
+ <template #expand="{ row }">
+ <el-table
+ :data="expandData"
+ border
+ show-summary
+ v-loading="childrenLoading"
+ :summary-method="summarizeMainTable2"
+ stripe
+ >
+ <el-table-column
+ align="center"
+ label="搴忓彿"
+ type="index"
+ width="60"
+ />
+ <el-table-column label="浠樻鏃ユ湡" prop="paymentDate" />
+ <el-table-column label="浠樻閲戦" prop="currentPaymentAmount">
+ <template #default="scope">
+ <el-input-number :step="0.01" :min="0" style="width: 100%"
+ v-model="scope.row.currentPaymentAmount"
+ :disabled="!scope.row.editType"
+ :precision="2"
+ placeholder="璇疯緭鍏�"
+ clearable
+ />
+ </template>
+ </el-table-column>
+ <el-table-column label="浠樻鏂瑰紡" prop="paymentMethod">
+ <template #default="scope">
+ <el-input
+ :disabled="!scope.row.editType"
+ v-model="scope.row.paymentMethod"
+ placeholder="璇疯緭鍏�"
+ clearable
+ />
+ </template>
+ </el-table-column>
+ <el-table-column label="鐧昏浜�" prop="registrant" />
+ <el-table-column label="鐧昏鏃ユ湡" prop="registrationtDate" />
+ <el-table-column label="鎿嶄綔" width="150">
+ <template #default="scope">
+ <el-button
+ link
+ type="primary"
+ size="small"
+ @click="changeEditType(scope.row)"
+ v-if="!scope.row.editType"
+ :disabled="scope.row.registrant !== userStore.nickName"
+ >缂栬緫</el-button
+ >
+ <el-button
+ link
+ type="primary"
+ size="small"
+ @click="saveReceiptPayment(scope.row)"
+ v-if="scope.row.editType"
+ :disabled="scope.row.registrant !== userStore.nickName"
+ >淇濆瓨</el-button
+ >
+ <el-button
+ link
+ type="primary"
+ size="small"
+ @click="handleDelete(scope.row)"
+ :disabled="scope.row.registrant !== userStore.nickName"
+ >鍒犻櫎</el-button
+ >
+ </template>
+ </el-table-column>
+ </el-table>
+ </template>
+ </PIMTable>
+ </div>
+ <el-dialog
+ v-model="dialogFormVisible"
+ :title="operationType === 'add' ? '鏂板浠樻鐧昏' : '缂栬緫浠樻鐧昏'"
+ width="60%"
+ @close="closeDia"
+ >
+ <el-form
+ :model="form"
+ label-width="140px"
+ label-position="top"
+ :rules="rules"
+ ref="formRef"
+ >
+ <el-row :gutter="30">
+ <el-col :span="12">
+ <el-form-item label="閲囪喘鍚堝悓鍙凤細" prop="purchaseContractNumber">
+ <el-input
+ v-model="form.purchaseContractNumber"
+ placeholder="鑷姩濉厖"
+ clearable
+ disabled
+ />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="閿�鍞悎鍚屽彿锛�" prop="salesContractNo">
+ <el-input
+ v-model="form.salesContractNo"
+ placeholder="鑷姩濉厖"
+ clearable
+ disabled
+ />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="30">
+ <el-col :span="12">
+ <el-form-item label="渚涘簲鍟嗗悕绉帮細" prop="supplierName">
+ <el-input
+ v-model="form.supplierName"
+ placeholder="鑷姩濉厖"
+ clearable
+ disabled
+ />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="鍙戠エ鍙凤細" prop="invoiceNumber">
+ <el-input
+ v-model="form.invoiceNumber"
+ placeholder="鑷姩濉厖"
+ clearable
+ disabled
+ />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="30">
+ <el-col :span="12">
+ <el-form-item label="鍙戠エ閲戦(鍏�)锛�" prop="invoiceAmount">
+ <el-input
+ v-model="form.invoiceAmount"
+ placeholder="鑷姩濉厖"
+ clearable
+ disabled
+ />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="鏈浠樻閲戦锛�" prop="currentPaymentAmount">
+ <el-input-number :step="0.01" :min="0" style="width: 100%"
+ :precision="2"
+ v-model="form.currentPaymentAmount"
+ placeholder="璇疯緭鍏�"
+ clearable
+ />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="30">
+ <el-col :span="12">
+ <el-form-item label="浠樻鏂瑰紡锛�" prop="paymentMethod">
+ <el-input
+ v-model="form.paymentMethod"
+ placeholder="璇疯緭鍏�"
+ clearable
+ />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="浠樻鏃ユ湡锛�" prop="paymentDate">
+ <el-date-picker
+ style="width: 100%"
+ v-model="form.paymentDate"
+ value-format="YYYY-MM-DD"
+ format="YYYY-MM-DD"
+ type="date"
+ placeholder="璇烽�夋嫨"
+ clearable
+ />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="30">
+ <el-col :span="12">
+ <el-form-item label="鐧昏浜猴細" prop="registrant">
+ <el-input
+ v-model="form.registrant"
+ placeholder="璇疯緭鍏�"
+ clearable
+ disabled
+ />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="鐧昏鏃ユ湡锛�" prop="registrationtDate">
+ <el-input
+ v-model="form.registrationtDate"
+ placeholder="璇疯緭鍏�"
+ clearable
+ disabled
+ />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ </el-form>
+ <template #footer>
+ <div class="dialog-footer">
+ <el-button type="primary" @click="submitForm">纭</el-button>
+ <el-button @click="closeDia">鍙栨秷</el-button>
+ </div>
+ </template>
+ </el-dialog>
+ </div>
+</template>
+
+<script setup>
+import { ref } from "vue";
+import { Search } from "@element-plus/icons-vue";
+import { ElMessageBox } from "element-plus";
+import useUserStore from "@/store/modules/user.js";
+import {
+ byPurchaseId,
+ paymentRegistrationAdd,
+ paymentRegistrationDel,
+ paymentRegistrationEdit,
+ getTicketNo,
+} from "@/api/procurementManagement/paymentEntry.js";
+import {
+ delPaymentRegistration,
+ invoiceListPage,
+ registrationListPageGetById,
+ updatePaymentRegistration
+} from "@/api/procurementManagement/procurementInvoiceLedger.js";
+import useFormData from "@/hooks/useFormData";
+
+const { proxy } = getCurrentInstance();
+const tableColumn = ref([
+ {
+ type: "expand",
+ dataType: "slot",
+ slot: "expand",
+ },
+ {
+ label: "閲囪喘鍚堝悓鍙�",
+ prop: "purchaseContractNumber",
+ },
+ {
+ label: "閿�鍞悎鍚屽彿",
+ prop: "salesContractNo",
+ },
+ {
+ label: "渚涘簲鍟嗗悕绉�",
+ prop: "supplierName",
+ width:240
+ },
+ {
+ label: "鍙戠エ鍙�",
+ prop: "invoiceNumber",
+ width:200
+ },
+ {
+ label: "鍙戠エ閲戦(鍏�)",
+ prop: "invoiceAmount",
+ formatData: (params) => {
+ return params ? parseFloat(params).toFixed(2) : 0;
+ },
+ },
+ {
+ label: "宸蹭粯娆鹃噾棰�(鍏�)",
+ prop: "paymentAmountTotal",
+ formatData: (params) => {
+ return params ? parseFloat(params).toFixed(2) : 0;
+ },
+ },
+ {
+ label: "寰呬粯娆鹃噾棰�(鍏�)",
+ prop: "unPaymentAmountTotal",
+ formatData: (params) => {
+ return params ? parseFloat(params).toFixed(2) : 0;
+ },
+ },
+ {
+ label: "褰曞叆浜�",
+ prop: "issUer",
+ },
+]);
+const tableData = ref([]);
+const expandData = ref([]);
+const selectedRows = ref([]);
+const tableLoading = ref(false);
+const childrenLoading = ref(false);
+const userStore = useUserStore();
+const page = reactive({
+ current: 1,
+ size: 100,
+ total: 0,
+});
+
+// 鐢ㄦ埛淇℃伅琛ㄥ崟寮规鏁版嵁
+const operationType = ref("");
+const dialogFormVisible = ref(false);
+const data = reactive({
+ searchForm: {
+ supplierNameOrContractNo: "",
+ status: false,
+ },
+ form: {
+ purchaseContractNumber: "",
+ purchaseLedgerId: "",
+ salesContractNo: "",
+ supplierName: "",
+ invoiceNumber: "",
+ invoiceAmount: "",
+ taxRate: "",
+ currentPaymentAmount: "",
+ paymentMethod: "",
+ registrant: "",
+ registrantId: "",
+ paymentDate: "",
+ registrationtDate: "",
+ },
+ rules: {
+ purchaseLedgerId: [
+ { required: true, message: "璇烽�夋嫨", trigger: "change" },
+ ],
+ currentPaymentAmount: [
+ { required: true, message: "璇疯緭鍏�", trigger: "blur" },
+ ],
+ paymentMethod: [{ required: true, message: "璇烽�夋嫨", trigger: "change" }],
+ invoiceNumber: [
+ { required: true, message: "璇烽�夋嫨閲囪喘鍚堝悓鍙�", trigger: "change" },
+ ],
+ },
+});
+const { form, rules } = toRefs(data);
+const { form: searchForm, resetForm } = useFormData(data.searchForm);
+const isShowSummarySon = ref(true);
+const expandedRowKeys = ref([]);
+
+
+// 瀛愯〃鍚堣鏂规硶
+const summarizeMainTable1 = (param) => {
+ return proxy.summarizeTable(
+ param,
+ ["invoiceAmount", "paymentAmountTotal", "unPaymentAmountTotal"],
+ {
+ ticketsNum: { noDecimal: true }, // 涓嶄繚鐣欏皬鏁�
+ futureTickets: { noDecimal: true }, // 涓嶄繚鐣欏皬鏁�
+ }
+ );
+};
+// 瀛愯〃鍚堣鏂规硶
+const summarizeMainTable2 = (param) => {
+ return proxy.summarizeTable(param, ["currentPaymentAmount"], {
+ ticketsNum: { noDecimal: true }, // 涓嶄繚鐣欏皬鏁�
+ futureTickets: { noDecimal: true }, // 涓嶄繚鐣欏皬鏁�
+ });
+};
+// 鏌ヨ鍒楄〃
+/** 鎼滅储鎸夐挳鎿嶄綔 */
+const handleQuery = () => {
+ page.current = 1;
+ getList();
+};
+const pagination = (obj) => {
+ page.current = obj.page;
+ page.size = obj.limit;
+ getList();
+};
+const getList = () => {
+ tableLoading.value = true;
+ invoiceListPage({ ...searchForm, ...page }).then((res) => {
+ tableLoading.value = false;
+ tableData.value = res.records;
+ page.total = res.total;
+ if (expandedRowKeys.value.length > 0) {
+ const arr = []
+ const index = tableData.value.findIndex(item => item.id === expandedRowKeys.value[0]);
+ if (index > -1) {
+ arr.push(tableData.value[index]);
+ expandChange(tableData.value[index], arr)
+ }
+ }
+ });
+};
+// 灞曞紑琛�
+const expandChange = (row, expandedRows) => {
+ if (expandedRows.length > 0) {
+ nextTick(() => {
+ expandedRowKeys.value = [];
+ try {
+ childrenLoading.value = true;
+ registrationListPageGetById({ id: row.id }).then((res) => {
+ childrenLoading.value = false;
+ const index = tableData.value.findIndex((item) => item.id === row.id);
+ if (index > -1) {
+ expandData.value = res;
+ }
+ expandedRowKeys.value.push(row.id);
+ });
+ } catch (error) {
+ childrenLoading.value = false;
+ console.log(error);
+ }
+ })
+ } else {
+ expandedRowKeys.value = [];
+ }
+};
+// 缂栬緫淇敼鐘舵��
+const changeEditType = (row) => {
+ row.editType = !row.editType;
+};
+// 淇濆瓨鍥炴璁板綍
+const saveReceiptPayment = (row) => {
+ let updateData = {
+ id: row.id,
+ currentPaymentAmount: row.currentPaymentAmount,
+ paymentMethod: row.paymentMethod,
+ };
+ updatePaymentRegistration(updateData).then((res) => {
+ row.editType = !row.editType;
+ getList();
+ proxy.$modal.msgSuccess("鎻愪氦鎴愬姛");
+ });
+};
+// 琛ㄦ牸閫夋嫨鏁版嵁
+const handleSelectionChange = (selection) => {
+ selectedRows.value = selection;
+};
+// 鎵撳紑寮规
+const openForm = (type, row) => {
+ if (selectedRows.value.length !== 1) {
+ proxy.$message.error("璇烽�夋嫨涓�鏉″彂绁ㄦ暟鎹�");
+ return;
+ }
+ if (selectedRows.value[0].unPaymentAmountTotal == 0) {
+ proxy.$message.warning("鏃犻渶鍐嶄粯娆�");
+ return;
+ }
+ operationType.value = type;
+ form.value = {};
+ form.value = { ...selectedRows.value[0] };
+ form.value.ticketRegistrationId = selectedRows.value[0].id;
+ form.value.id = null;
+ // 鏌ヨ閲囪喘鍚堝悓鍙�
+ form.value.registrationtDate = getCurrentDate();
+ form.value.paymentDate = getCurrentDate();
+ form.value.registrant = userStore.name;
+ dialogFormVisible.value = true;
+};
+// 鎻愪氦琛ㄥ崟
+const submitForm = () => {
+ proxy.$refs["formRef"].validate((valid) => {
+ if (valid) {
+ if (operationType.value === "edit") {
+ submitEdit();
+ } else {
+ submitAdd();
+ }
+ }
+ });
+};
+// 鎻愪氦鏂板
+const submitAdd = () => {
+ paymentRegistrationAdd(form.value).then((res) => {
+ proxy.$modal.msgSuccess("鎻愪氦鎴愬姛");
+ closeDia();
+ getList();
+ });
+};
+// 鎻愪氦淇敼
+const submitEdit = () => {
+ paymentRegistrationEdit(form.value).then((res) => {
+ proxy.$modal.msgSuccess("鎻愪氦鎴愬姛");
+ closeDia();
+ getList();
+ });
+};
+// 鍏抽棴寮规
+const closeDia = () => {
+ proxy.resetForm("formRef");
+ dialogFormVisible.value = false;
+};
+// 鍒犻櫎
+const handleDelete = (row) => {
+ ElMessageBox.confirm("閫変腑鐨勫唴瀹瑰皢琚垹闄わ紝鏄惁纭鍒犻櫎锛�", "鍒犻櫎鎻愮ず", {
+ confirmButtonText: "纭",
+ cancelButtonText: "鍙栨秷",
+ type: "warning",
+ })
+ .then(() => {
+ tableLoading.value = true;
+ delPaymentRegistration(row.id)
+ .then((res) => {
+ proxy.$modal.msgSuccess("鍒犻櫎鎴愬姛");
+ getList();
+ })
+ .finally(() => {
+ tableLoading.value = false;
+ });
+ })
+ .catch(() => {
+ proxy.$modal.msg("宸插彇娑�");
+ });
+};
+// 鑾峰彇褰撳墠鏃ユ湡骞舵牸寮忓寲涓� YYYY-MM-DD
+function getCurrentDate() {
+ const today = new Date();
+ const year = today.getFullYear();
+ const month = String(today.getMonth() + 1).padStart(2, "0"); // 鏈堜唤浠�0寮�濮�
+ const day = String(today.getDate()).padStart(2, "0");
+ return `${year}-${month}-${day}`;
+}
+getList();
+</script>
+
+<style scoped lang="scss">
+.table_list {
+ margin-top: unset;
+}
+::v-deep(.el-checkbox__label) {
+ font-weight: bold;
+}
+</style>
diff --git a/src/views/procurementManagement/procurementInvoiceLedger/Form/EditForm.vue b/src/views/procurementManagement/procurementInvoiceLedger/Form/EditForm.vue
new file mode 100644
index 0000000..6290804
--- /dev/null
+++ b/src/views/procurementManagement/procurementInvoiceLedger/Form/EditForm.vue
@@ -0,0 +1,196 @@
+<template>
+ <el-form :model="form">
+ <el-row :gutter="20">
+ <el-col :span="12">
+ <el-form-item label="閲囪喘鍚堝悓鍙凤細">
+ <el-tag size="large">{{ form.purchaseContractNumber }}</el-tag>
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="閿�鍞悎鍚屽彿锛�">
+ <el-text>{{ form.salesContractNo }}</el-text>
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="鍚◣鍗曚环(鍏�)锛�">
+ <el-text type="primary">{{ form.taxInclusiveUnitPrice }}</el-text>
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="鍒涘缓鏃堕棿锛�">
+ <el-text>{{ form.createdAt }}</el-text>
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="鍙戠エ鍙凤細">
+ <el-input v-model="form.invoiceNumber" />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="鏉ョエ鏁帮細">
+ <el-input-number :step="0.1" :min="0" style="width: 100%" v-model="form.ticketsNum" @change="inputTicketsNum" :precision="2"/>
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="鏈鏉ョエ閲戦(鍏�)锛�">
+ <el-input-number :step="0.1" :min="0" style="width: 100%" v-model="form.ticketsAmount" @change="inputTicketsAmount" :precision="2"/>
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="鏈潵绁ㄦ暟锛�">
+ <el-text type="success">{{ form.futureTickets }}</el-text>
+ </el-form-item>
+ </el-col>
+ </el-row>
+ </el-form>
+</template>
+
+<script setup>
+import useFormData from "@/hooks/useFormData";
+import { getProductRecordById } from "@/api/procurementManagement/procurementInvoiceLedger";
+const { proxy } = getCurrentInstance()
+
+defineOptions({
+ name: "鏉ョエ鍙拌处琛ㄥ崟",
+});
+const temFutureTickets = ref(0) // 鍒濆鏈潵绁ㄦ暟
+const initialTicketsNum = ref(0) // 鍒濆鏉ョエ鏁�
+const initialTicketsAmount = ref(0) // 鍒濆鏉ョエ閲戦
+const quantity = ref(0) // 鎬绘暟閲�
+const { form, resetForm } = useFormData({
+ id: undefined,
+ purchaseContractNumber: undefined, // 閲囪喘鍚堝悓鍙�
+ salesContractNo: undefined, // 閿�鍞悎鍚屽彿
+ createdAt: undefined, // 鍒涘缓鏃堕棿
+ invoiceNumber: undefined, // 鍙戠エ鍙�
+ ticketsNum: undefined, // 鏉ョエ鏁�
+ ticketsAmount: undefined, // 鏉ョエ閲戦
+ taxInclusiveUnitPrice: undefined, // 鍚◣鍗曚环
+ ticketRegistrationId: undefined, // 鍚◣鍗曚环
+});
+
+const load = async (id) => {
+ const { code, data } = await getProductRecordById({ id });
+ if (code === 200) {
+ form.id = data.id;
+ form.purchaseContractNumber = data.purchaseContractNumber;
+ form.salesContractNo = data.salesContractNo;
+ form.createdAt = data.createdAt;
+ form.invoiceNumber = data.invoiceNumber;
+ form.ticketsNum = data.ticketsNum;
+ form.ticketsAmount = data.ticketsAmount ? Number(data.ticketsAmount).toFixed(2) : 0;
+ form.taxInclusiveUnitPrice = data.taxInclusiveUnitPrice;
+ form.futureTickets = data.futureTickets;
+ temFutureTickets.value = data.futureTickets;
+ initialTicketsNum.value = data.ticketsNum || 0;
+ initialTicketsAmount.value = data.ticketsAmount || 0;
+ form.ticketRegistrationId = data.ticketRegistrationId;
+ // 鑾峰彇鎬绘暟閲忥紝濡傛灉鏁版嵁涓湁 quantity 瀛楁鍒欎娇鐢紝鍚﹀垯浣跨敤鏉ョエ鏁�+鏈潵绁ㄦ暟
+ quantity.value = data.quantity || (Number(data.ticketsNum || 0) + Number(data.futureTickets || 0));
+ }
+};
+
+const inputTicketsNum = (val) => {
+ // 纭繚鍚◣鍗曚环瀛樺湪涓斾笉涓洪浂
+ if (!form.taxInclusiveUnitPrice || Number(form.taxInclusiveUnitPrice) === 0) {
+ proxy.$modal.msgWarning("鍚◣鍗曚环涓嶈兘涓洪浂鎴栨湭瀹氫箟");
+ return;
+ }
+
+ const newTicketsNum = Number(form.ticketsNum) || 0;
+ const currentTicketsNum = Number(initialTicketsNum.value) || 0;
+
+ // 璁$畻鏂板鐨勬潵绁ㄦ暟
+ const addedTicketsNum = newTicketsNum - currentTicketsNum;
+
+ // 璁$畻鏂扮殑鏈潵绁ㄦ暟 = 鍒濆鏈潵绁ㄦ暟 - 鏂板鐨勬潵绁ㄦ暟
+ const newFutureTickets = Number(temFutureTickets.value) - addedTicketsNum;
+
+ // 楠岃瘉锛氭柊鐨勬潵绁ㄦ暟 + 鏂扮殑鏈潵绁ㄦ暟 鈮� quantity
+ if (newTicketsNum + newFutureTickets > Number(quantity.value)) {
+ proxy.$modal.msgWarning(`鏉ョエ鏁�+鏈潵绁ㄦ暟涓嶈兘澶т簬鎬绘暟閲�(${quantity.value})`);
+ // 闄愬埗鏉ョエ鏁帮紝浣垮叾婊¤冻锛氭潵绁ㄦ暟 + 鏈潵绁ㄦ暟 鈮� quantity
+ // 鏈�澶ф潵绁ㄦ暟 = quantity - 鍒濆鏈潵绁ㄦ暟 + 鍒濆鏉ョエ鏁�
+ const maxTicketsNum = Number(quantity.value) - Number(temFutureTickets.value) + Number(initialTicketsNum.value);
+ form.ticketsNum = Math.max(0, Math.min(maxTicketsNum, newTicketsNum));
+ // 閲嶆柊璁$畻
+ const recalculatedAddedTicketsNum = Number(form.ticketsNum) - Number(initialTicketsNum.value);
+ const recalculatedFutureTickets = Number(temFutureTickets.value) - recalculatedAddedTicketsNum;
+ form.futureTickets = Number(recalculatedFutureTickets.toFixed(2));
+ const ticketsAmount = Number(form.ticketsNum) * Number(form.taxInclusiveUnitPrice);
+ form.ticketsAmount = Number(ticketsAmount.toFixed(2));
+ return;
+ }
+
+ // 妫�鏌ユ柊澧炵殑鏉ョエ鏁版槸鍚﹀ぇ浜庡垵濮嬫湭鏉ョエ鏁�
+ if (addedTicketsNum > Number(temFutureTickets.value)) {
+ proxy.$modal.msgWarning("鏂板寮�绁ㄦ暟涓嶅緱澶т簬鏈紑绁ㄦ暟");
+ form.ticketsNum = Number(initialTicketsNum.value) + Number(temFutureTickets.value);
+ }
+
+ // 纭繚鎵�鏈夋暟鍊奸兘杞崲涓烘暟瀛楃被鍨嬭繘琛岃绠�
+ const finalTicketsNum = Number(form.ticketsNum) || 0;
+ const finalAddedTicketsNum = finalTicketsNum - Number(initialTicketsNum.value);
+ const finalFutureTickets = Number(temFutureTickets.value) - finalAddedTicketsNum;
+ const ticketsAmount = finalTicketsNum * Number(form.taxInclusiveUnitPrice);
+ form.futureTickets = Number(finalFutureTickets.toFixed(2));
+ form.ticketsAmount = Number(ticketsAmount.toFixed(2));
+};
+const inputTicketsAmount = (val) => {
+ // 纭繚鍚◣鍗曚环瀛樺湪涓斾笉涓洪浂
+ if (!form.taxInclusiveUnitPrice || Number(form.taxInclusiveUnitPrice) === 0) {
+ proxy.$modal.msgWarning("鍚◣鍗曚环涓嶈兘涓洪浂鎴栨湭瀹氫箟");
+ return;
+ }
+
+ const newTicketsAmount = Number(val) || 0;
+
+ // 璁$畻鏂扮殑鏉ョエ鏁�
+ const newTicketsNum = newTicketsAmount / Number(form.taxInclusiveUnitPrice);
+ const currentTicketsNum = Number(initialTicketsNum.value) || 0;
+
+ // 璁$畻鏂板鐨勬潵绁ㄦ暟
+ const addedTicketsNum = newTicketsNum - currentTicketsNum;
+
+ // 璁$畻鏂扮殑鏈潵绁ㄦ暟 = 鍒濆鏈潵绁ㄦ暟 - 鏂板鐨勬潵绁ㄦ暟
+ const newFutureTickets = Number(temFutureTickets.value) - addedTicketsNum;
+
+ // 楠岃瘉锛氭柊鐨勬潵绁ㄦ暟 + 鏂扮殑鏈潵绁ㄦ暟 鈮� quantity
+ if (newTicketsNum + newFutureTickets > Number(quantity.value)) {
+ proxy.$modal.msgWarning(`鏉ョエ鏁�+鏈潵绁ㄦ暟涓嶈兘澶т簬鎬绘暟閲�(${quantity.value})`);
+ // 闄愬埗鏉ョエ鏁帮紝浣垮叾婊¤冻锛氭潵绁ㄦ暟 + 鏈潵绁ㄦ暟 鈮� quantity
+ const maxTicketsNum = Number(quantity.value) - Number(temFutureTickets.value) + Number(initialTicketsNum.value);
+ form.ticketsNum = Math.max(0, Math.min(maxTicketsNum, newTicketsNum));
+ form.ticketsAmount = Number((form.ticketsNum * Number(form.taxInclusiveUnitPrice)).toFixed(2));
+ const recalculatedAddedTicketsNum = Number(form.ticketsNum) - Number(initialTicketsNum.value);
+ const recalculatedFutureTickets = Number(temFutureTickets.value) - recalculatedAddedTicketsNum;
+ form.futureTickets = Number(recalculatedFutureTickets.toFixed(2));
+ return;
+ }
+
+ // 妫�鏌ユ柊澧炵殑鏉ョエ閲戦鏄惁澶т簬鍒濆鏈潵绁ㄦ暟瀵瑰簲鐨勯噾棰�
+ const maxAddedAmount = Number(temFutureTickets.value * form.taxInclusiveUnitPrice);
+ if (addedTicketsNum > 0 && addedTicketsNum * Number(form.taxInclusiveUnitPrice) > maxAddedAmount) {
+ proxy.$modal.msgWarning("鏂板鏉ョエ閲戦涓嶅緱澶т簬鏈紑绁ㄩ噾棰�");
+ form.ticketsAmount = Number((initialTicketsAmount.value + maxAddedAmount).toFixed(2));
+ form.ticketsNum = Number((currentTicketsNum + Number(temFutureTickets.value)).toFixed(2));
+ form.futureTickets = 0;
+ return;
+ }
+
+ // 纭繚鎵�鏈夋暟鍊奸兘杞崲涓烘暟瀛楃被鍨嬭繘琛岃绠�
+ const finalTicketsNum = Number(newTicketsNum.toFixed(2));
+ const finalAddedTicketsNum = finalTicketsNum - Number(initialTicketsNum.value);
+ const finalFutureTickets = Number(temFutureTickets.value) - finalAddedTicketsNum;
+ form.ticketsNum = finalTicketsNum;
+ form.futureTickets = Number(finalFutureTickets.toFixed(2));
+};
+
+defineExpose({
+ load,
+ form,
+ resetForm,
+});
+</script>
+
+<style lang="scss" scoped></style>
diff --git a/src/views/procurementManagement/procurementLedger/index.vue b/src/views/procurementManagement/procurementLedger/index.vue
new file mode 100644
index 0000000..30dacdb
--- /dev/null
+++ b/src/views/procurementManagement/procurementLedger/index.vue
@@ -0,0 +1,1131 @@
+<template>
+ <div class="app-container">
+ <div class="search_form">
+ <div>
+ <el-form :model="searchForm" :inline="true">
+ <el-form-item label="渚涘簲鍟嗗悕绉帮細">
+ <el-input v-model="searchForm.supplierName" placeholder="璇疯緭鍏�" clearable prefix-icon="Search"
+ @change="handleQuery" />
+ </el-form-item>
+ <el-form-item label="閲囪喘鍚堝悓鍙凤細">
+ <el-input
+ v-model="searchForm.purchaseContractNumber"
+ style="width: 240px"
+ placeholder="璇疯緭鍏�"
+ @change="handleQuery"
+ clearable
+ :prefix-icon="Search"
+ />
+ </el-form-item>
+ <el-form-item label="閿�鍞悎鍚屽彿锛�">
+ <el-input v-model="searchForm.salesContractNo" placeholder="璇疯緭鍏�" clearable prefix-icon="Search"
+ @change="handleQuery" />
+ </el-form-item>
+ <el-form-item label="椤圭洰鍚嶇О锛�">
+ <el-input v-model="searchForm.projectName" placeholder="璇疯緭鍏�" clearable prefix-icon="Search"
+ @change="handleQuery" />
+ </el-form-item>
+ <el-form-item label="褰曞叆鏃ユ湡锛�">
+ <el-date-picker v-model="searchForm.entryDate" value-format="YYYY-MM-DD" format="YYYY-MM-DD" type="daterange"
+ placeholder="璇烽�夋嫨" clearable @change="changeDaterange" />
+ </el-form-item>
+ <el-form-item>
+ <el-button type="primary" @click="handleQuery"> 鎼滅储 </el-button>
+ </el-form-item>
+ </el-form>
+ </div>
+
+ </div>
+ <div class="table_list">
+ <div style="display: flex;justify-content: flex-end;margin-bottom: 20px;">
+ <el-button type="primary" @click="openForm('add')">鏂板鍙拌处</el-button>
+ <el-button @click="handleOut">瀵煎嚭</el-button>
+ <el-button type="danger" plain @click="handleDelete">鍒犻櫎</el-button>
+ </div>
+ <el-table
+ :data="tableData"
+ border
+ v-loading="tableLoading"
+ @selection-change="handleSelectionChange"
+ :expand-row-keys="expandedRowKeys"
+ :row-key="(row) => row.id"
+ show-summary
+ :summary-method="summarizeMainTable"
+ @expand-change="expandChange"
+ height="calc(100vh - 18.5em)"
+ stripe
+ >
+ <el-table-column align="center" type="selection" width="55" />
+ <el-table-column type="expand">
+ <template #default="props">
+ <el-table
+ :data="props.row.children"
+ border
+ show-summary
+ :summary-method="summarizeChildrenTable"
+ stripe
+ >
+ <el-table-column
+ align="center"
+ label="搴忓彿"
+ type="index"
+ width="60"
+ />
+ <el-table-column label="浜у搧澶х被" prop="productCategory" />
+ <el-table-column label="瑙勬牸鍨嬪彿" prop="specificationModel" />
+ <el-table-column label="鍗曚綅" prop="unit" />
+ <el-table-column label="鏁伴噺" prop="quantity" />
+ <el-table-column label="绋庣巼(%)" prop="taxRate" />
+ <el-table-column
+ label="鍚◣鍗曚环(鍏�)"
+ prop="taxInclusiveUnitPrice"
+ :formatter="formattedNumber"
+ />
+ <el-table-column
+ label="鍚◣鎬讳环(鍏�)"
+ prop="taxInclusiveTotalPrice"
+ :formatter="formattedNumber"
+ />
+ <el-table-column
+ label="涓嶅惈绋庢�讳环(鍏�)"
+ prop="taxExclusiveTotalPrice"
+ :formatter="formattedNumber"
+ />
+ </el-table>
+ </template>
+ </el-table-column>
+ <el-table-column align="center" label="搴忓彿" type="index" width="60" />
+ <el-table-column
+ label="閲囪喘鍚堝悓鍙�"
+ prop="purchaseContractNumber"
+ width="200"
+ show-overflow-tooltip
+ />
+ <el-table-column
+ label="閿�鍞悎鍚屽彿"
+ prop="salesContractNo"
+ width="200"
+ show-overflow-tooltip
+ />
+ <el-table-column
+ label="渚涘簲鍟嗗悕绉�"
+ width="240"
+ prop="supplierName"
+ show-overflow-tooltip
+ />
+ <el-table-column
+ label="椤圭洰鍚嶇О"
+ prop="projectName"
+ width="420"
+ show-overflow-tooltip
+ />
+ <el-table-column
+ label="浠樻鏂瑰紡"
+ width="100"
+ prop="paymentMethod"
+ show-overflow-tooltip
+ />
+ <el-table-column
+ label="鍚堝悓閲戦(鍏�)"
+ prop="contractAmount"
+ width="200"
+ show-overflow-tooltip
+ :formatter="formattedNumber"
+ />
+ <el-table-column
+ label="褰曞叆浜�"
+ prop="recorderName"
+ width="100"
+ show-overflow-tooltip
+ />
+ <el-table-column
+ label="褰曞叆鏃ユ湡"
+ prop="entryDate"
+ width="100"
+ show-overflow-tooltip
+ />
+ <el-table-column
+ fixed="right"
+ label="鎿嶄綔"
+ min-width="100"
+ align="center"
+ >
+ <template #default="scope">
+ <el-button
+ link
+ type="primary"
+ size="small"
+ @click="openForm('edit', scope.row)"
+ :disabled="scope.row.receiptPaymentAmount>0 || scope.row.recorderName !== userStore.nickName"
+ >缂栬緫</el-button
+ >
+ </template>
+ </el-table-column>
+ </el-table>
+ <pagination
+ v-show="total > 0"
+ :total="total"
+ layout="total, sizes, prev, pager, next, jumper"
+ :page="page.current"
+ :limit="page.size"
+ @pagination="paginationChange"
+ />
+ </div>
+ <el-dialog
+ v-model="dialogFormVisible"
+ :title="operationType === 'add' ? '鏂板閲囪喘鍙拌处椤甸潰' : '缂栬緫閲囪喘鍙拌处椤甸潰'"
+ width="70%"
+ @close="closeDia"
+ >
+ <el-form
+ :model="form"
+ label-width="140px"
+ label-position="top"
+ :rules="rules"
+ ref="formRef"
+ >
+ <el-row :gutter="30">
+ <el-col :span="12">
+ <el-form-item label="閲囪喘鍚堝悓鍙凤細" prop="purchaseContractNumber">
+ <el-input
+ v-model="form.purchaseContractNumber"
+ placeholder="璇疯緭鍏�"
+ clearable
+ />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="閿�鍞悎鍚屽彿锛�" prop="salesContractNo">
+ <el-input
+ v-model="form.salesContractNo"
+ placeholder="璇疯緭鍏�"
+ clearable
+ />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="30">
+ <el-col :span="12">
+ <el-form-item label="渚涘簲鍟嗗悕绉帮細" prop="supplierId">
+ <el-select
+ v-model="form.supplierId"
+ placeholder="璇烽�夋嫨"
+ clearable
+ >
+ <el-option
+ v-for="item in supplierList"
+ :key="item.id"
+ :label="item.supplierName"
+ :value="item.id"
+ />
+ </el-select>
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="椤圭洰鍚嶇О锛�" prop="projectName">
+ <el-input
+ v-model="form.projectName"
+ placeholder="璇疯緭鍏�"
+ clearable
+ />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="30">
+ <el-col :span="12">
+ <el-form-item label="褰曞叆浜猴細" prop="recorderId">
+ <el-select
+ v-model="form.recorderId"
+ placeholder="璇烽�夋嫨"
+ clearable
+ disabled
+ >
+ <el-option
+ v-for="item in userList"
+ :key="item.userId"
+ :label="item.nickName"
+ :value="item.userId"
+ />
+ </el-select>
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="褰曞叆鏃ユ湡锛�" prop="entryDate">
+ <el-date-picker
+ disabled
+ style="width: 100%"
+ v-model="form.entryDate"
+ value-format="YYYY-MM-DD"
+ format="YYYY-MM-DD"
+ type="date"
+ placeholder="璇烽�夋嫨"
+ clearable
+ />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="30">
+ <el-col :span="12">
+ <el-form-item label="浠樻鏂瑰紡">
+ <el-input
+ v-model="form.paymentMethod"
+ placeholder="璇疯緭鍏�"
+ clearable
+ />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row>
+ <el-form-item label="浜у搧淇℃伅锛�" prop="entryDate">
+ <el-button type="primary" @click="openProductForm('add')"
+ >娣诲姞</el-button
+ >
+ <el-button plain type="danger" @click="deleteProduct"
+ >鍒犻櫎</el-button
+ >
+ </el-form-item>
+ </el-row>
+ <el-table
+ :data="productData"
+ border
+ @selection-change="productSelected"
+ show-summary
+ stripe
+ :summary-method="summarizeProTable"
+ >
+ <el-table-column align="center" type="selection" width="55" />
+ <el-table-column
+ align="center"
+ label="搴忓彿"
+ type="index"
+ width="60"
+ />
+ <el-table-column label="浜у搧澶х被" prop="productCategory" />
+ <el-table-column label="瑙勬牸鍨嬪彿" prop="specificationModel" />
+ <el-table-column label="鍗曚綅" prop="unit" width="70" />
+ <el-table-column label="鏁伴噺" prop="quantity" width="70" />
+ <el-table-column label="绋庣巼(%)" prop="taxRate" width="80" />
+ <el-table-column
+ label="鍚◣鍗曚环(鍏�)"
+ prop="taxInclusiveUnitPrice"
+ :formatter="formattedNumber"
+ width="150"
+ />
+ <el-table-column
+ label="鍚◣鎬讳环(鍏�)"
+ prop="taxInclusiveTotalPrice"
+ :formatter="formattedNumber"
+ width="150"
+ />
+ <el-table-column
+ label="涓嶅惈绋庢�讳环(鍏�)"
+ prop="taxExclusiveTotalPrice"
+ :formatter="formattedNumber"
+ width="150"
+ />
+ <el-table-column
+ fixed="right"
+ label="鎿嶄綔"
+ min-width="60"
+ align="center"
+ >
+ <template #default="scope">
+ <el-button
+ link
+ type="primary"
+ size="small"
+ @click="openProductForm('edit', scope.row, scope.$index)"
+ >缂栬緫</el-button
+ >
+ </template>
+ </el-table-column>
+ </el-table>
+ <el-row :gutter="30">
+ <el-col :span="24">
+ <el-form-item label="澶囨敞路锛�" prop="remark">
+ <el-input
+ v-model="form.remark"
+ placeholder="璇疯緭鍏�"
+ clearable
+ type="textarea"
+ :rows="2"
+ />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="30">
+ <el-col :span="24">
+ <el-form-item label="闄勪欢鏉愭枡锛�" prop="remark">
+ <el-upload
+ v-model:file-list="fileList"
+ :action="upload.url"
+ multiple
+ ref="fileUpload"
+ auto-upload
+ :headers="upload.headers"
+ :before-upload="handleBeforeUpload"
+ :on-error="handleUploadError"
+ :on-success="handleUploadSuccess"
+ :on-remove="handleRemove"
+ >
+ <el-button type="primary">涓婁紶</el-button>
+ <template #tip>
+ <div class="el-upload__tip">
+ 鏂囦欢鏍煎紡鏀寔
+ doc锛宒ocx锛寈ls锛寈lsx锛宲pt锛宲ptx锛宲df锛宼xt锛寈ml锛宩pg锛宩peg锛宲ng锛実if锛宐mp锛宺ar锛寊ip锛�7z
+ </div>
+ </template>
+ </el-upload>
+ </el-form-item>
+ </el-col>
+ </el-row>
+ </el-form>
+ <template #footer>
+ <div class="dialog-footer">
+ <el-button type="primary" @click="submitForm">纭</el-button>
+ <el-button @click="closeDia">鍙栨秷</el-button>
+ </div>
+ </template>
+ </el-dialog>
+ <el-dialog
+ v-model="productFormVisible"
+ :title="productOperationType === 'add' ? '鏂板浜у搧' : '缂栬緫浜у搧'"
+ width="40%"
+ @close="closeProductDia"
+ >
+ <el-form
+ :model="productForm"
+ label-width="140px"
+ label-position="top"
+ :rules="productRules"
+ ref="productFormRef"
+ >
+ <el-row :gutter="30">
+ <el-col :span="24">
+ <el-form-item label="浜у搧澶х被锛�" prop="productId">
+ <el-tree-select
+ v-model="productForm.productId"
+ placeholder="璇烽�夋嫨"
+ clearable
+ check-strictly
+ @change="getModels"
+ :data="productOptions"
+ :render-after-expand="false"
+ style="width: 100%"
+ />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="30">
+ <el-col :span="24">
+ <el-form-item label="瑙勬牸鍨嬪彿锛�" prop="productModelId">
+ <el-select
+ v-model="productForm.productModelId"
+ placeholder="璇烽�夋嫨"
+ clearable
+ @change="getProductModel"
+ >
+ <el-option
+ v-for="item in modelOptions"
+ :key="item.id"
+ :label="item.model"
+ :value="item.id"
+ />
+ </el-select>
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="30">
+ <el-col :span="12">
+ <el-form-item label="鍗曚綅锛�" prop="unit">
+ <el-input
+ v-model="productForm.unit"
+ placeholder="璇疯緭鍏�"
+ clearable
+ />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="绋庣巼(%)锛�" prop="taxRate">
+ <el-select
+ v-model="productForm.taxRate"
+ placeholder="璇烽�夋嫨"
+ clearable
+ @change="mathNum"
+ >
+ <el-option label="1" value="1" />
+ <el-option label="6" value="6" />
+ <el-option label="13" value="13" />
+ </el-select>
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="30">
+ <el-col :span="12">
+ <el-form-item label="鍚◣鍗曚环(鍏�)锛�" prop="taxInclusiveUnitPrice">
+ <el-input-number
+ v-model="productForm.taxInclusiveUnitPrice"
+ :precision="2"
+ :step="0.1"
+ clearable
+ style="width: 100%"
+ @change="mathNum"
+ />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="鏁伴噺锛�" prop="quantity">
+ <el-input-number
+ :step="0.1"
+ clearable
+ :precision="2"
+ style="width: 100%"
+ v-model="productForm.quantity"
+ placeholder="璇疯緭鍏�"
+ @change="mathNum"
+ />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="30">
+ <el-col :span="12">
+ <el-form-item label="鍚◣鎬讳环(鍏�)锛�" prop="taxInclusiveTotalPrice">
+ <el-input-number
+ v-model="productForm.taxInclusiveTotalPrice"
+ :precision="2"
+ :step="0.1"
+ clearable
+ style="width: 100%"
+ @change="reverseMathNum('taxInclusiveTotalPrice')"
+ />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item
+ label="涓嶅惈绋庢�讳环(鍏�)锛�"
+ prop="taxExclusiveTotalPrice"
+ >
+ <el-input
+ v-model="productForm.taxExclusiveTotalPrice"
+ @change="reverseMathNum('taxExclusiveTotalPrice')"
+ />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="30">
+ <el-col :span="12">
+ <el-form-item label="鍙戠エ绫诲瀷锛�" prop="invoiceType">
+ <el-select
+ v-model="productForm.invoiceType"
+ placeholder="璇烽�夋嫨"
+ clearable
+ >
+ <el-option label="澧炴櫘绁�" value="澧炴櫘绁�" />
+ <el-option label="澧炰笓绁�" value="澧炰笓绁�" />
+ </el-select>
+ </el-form-item>
+ </el-col>
+ </el-row>
+ </el-form>
+ <template #footer>
+ <div class="dialog-footer">
+ <el-button type="primary" @click="submitProduct">纭</el-button>
+ <el-button @click="closeProductDia">鍙栨秷</el-button>
+ </div>
+ </template>
+ </el-dialog>
+ </div>
+</template>
+
+<script setup>
+import { getToken } from "@/utils/auth";
+import pagination from "@/components/PIMTable/Pagination.vue";
+import { ref, onMounted, reactive, toRefs, getCurrentInstance, nextTick } from "vue";
+import { Search } from "@element-plus/icons-vue";
+import { ElMessageBox } from "element-plus";
+import { userListNoPage } from "@/api/system/user.js";
+import {
+ getSalesLedgerWithProducts,
+ addOrUpdateSalesLedgerProduct,
+ delProduct,
+ delLedgerFile,
+} from "@/api/salesManagement/salesLedger.js";
+import {
+ addOrEditPurchase,
+ delPurchase,
+ purchaseListPage,
+ productList,
+ getPurchaseById,
+ getOptions,
+ createPurchaseNo,
+} from "@/api/procurementManagement/procurementLedger.js";
+import useFormData from "@/hooks/useFormData.js";
+const { proxy } = getCurrentInstance();
+const tableData = ref([]);
+const productData = ref([]);
+const selectedRows = ref([]);
+const productSelectedRows = ref([]);
+const modelOptions = ref([]);
+const userList = ref([]);
+const productOptions = ref([]);
+const supplierList = ref([]);
+const tableLoading = ref(false);
+const page = reactive({
+ current: 1,
+ size: 100,
+});
+const total = ref(0);
+const fileList = ref([]);
+import useUserStore from "@/store/modules/user";
+import { modelList, productTreeList } from "@/api/basicData/product.js";
+import dayjs from "dayjs";
+
+const userStore = useUserStore();
+
+// 鐢ㄦ埛淇℃伅琛ㄥ崟寮规鏁版嵁
+const operationType = ref("");
+const dialogFormVisible = ref(false);
+const data = reactive({
+ searchForm: {
+ supplierName: "", // 渚涘簲鍟嗗悕绉�
+ purchaseContractNumber: "", // 閲囪喘鍚堝悓缂栧彿
+ salesContractNo: "", // 閿�鍞悎鍚岀紪鍙�
+ projectName: "", // 椤圭洰鍚嶇О
+ entryDate: [
+ dayjs().format("YYYY-MM-DD"),
+ dayjs().add(1, "day").format("YYYY-MM-DD"),
+ ], // 褰曞叆鏃ユ湡
+ entryDateStart: dayjs().format("YYYY-MM-DD"),
+ entryDateEnd: dayjs().add(1, "day").format("YYYY-MM-DD"),
+ },
+ form: {
+ purchaseContractNumber: "",
+ salesContractNo: "",
+ projectName: "",
+ recorderId: "",
+ entryDate: "",
+ productData: [],
+ supplierName: "",
+ supplierId: "",
+ paymentMethod: "",
+ },
+ rules: {
+ purchaseContractNumber: [
+ { required: true, message: "璇疯緭鍏�", trigger: "blur" },
+ ],
+ projectName: [{ required: true, message: "璇疯緭鍏�", trigger: "blur" }],
+ supplierId: [{ required: true, message: "璇疯緭鍏�", trigger: "blur" }],
+ },
+});
+const { form, rules } = toRefs(data);
+const { form: searchForm } = useFormData(data.searchForm);
+
+// 浜у搧琛ㄥ崟寮规鏁版嵁
+const productFormVisible = ref(false);
+const productOperationType = ref("");
+const productOperationIndex = ref("");
+const currentId = ref("");
+const productFormData = reactive({
+ productForm: {
+ productId: "",
+ productCategory: "",
+ productModelId: "",
+ specificationModel: "",
+ unit: "",
+ quantity: "",
+ taxInclusiveUnitPrice: "",
+ taxRate: "",
+ taxInclusiveTotalPrice: "",
+ taxExclusiveTotalPrice: "",
+ invoiceType: "",
+ },
+ productRules: {
+ productId: [{ required: true, message: "璇烽�夋嫨", trigger: "change" }],
+ productModelId: [{ required: true, message: "璇烽�夋嫨", trigger: "change" }],
+ unit: [{ required: true, message: "璇疯緭鍏�", trigger: "blur" }],
+ quantity: [{ required: true, message: "璇疯緭鍏�", trigger: "blur" }],
+ taxInclusiveUnitPrice: [
+ { required: true, message: "璇疯緭鍏�", trigger: "blur" },
+ ],
+ taxRate: [{ required: true, message: "璇烽�夋嫨", trigger: "change" }],
+ taxInclusiveTotalPrice: [
+ { required: true, message: "璇疯緭鍏�", trigger: "blur" },
+ ],
+ taxExclusiveTotalPrice: [
+ { required: true, message: "璇疯緭鍏�", trigger: "blur" },
+ ],
+ invoiceType: [{ required: true, message: "璇烽�夋嫨", trigger: "change" }],
+ },
+});
+const { productForm, productRules } = toRefs(productFormData);
+const upload = reactive({
+ // 涓婁紶鐨勫湴鍧�
+ url: import.meta.env.VITE_APP_BASE_API + "/file/upload",
+ // 璁剧疆涓婁紶鐨勮姹傚ご閮�
+ headers: { Authorization: "Bearer " + getToken() },
+});
+
+const changeDaterange = (value) => {
+ if (value) {
+ searchForm.entryDateStart = dayjs(value[0]).format("YYYY-MM-DD");
+ searchForm.entryDateEnd = dayjs(value[1]).format("YYYY-MM-DD");
+ } else {
+ searchForm.entryDateStart = undefined;
+ searchForm.entryDateEnd = undefined;
+ }
+ handleQuery();
+};
+
+const formattedNumber = (row, column, cellValue) => {
+ return parseFloat(cellValue).toFixed(2);
+};
+// 鏌ヨ鍒楄〃
+/** 鎼滅储鎸夐挳鎿嶄綔 */
+const handleQuery = () => {
+ page.current = 1;
+ getList();
+};
+// 瀛愯〃鍚堣鏂规硶
+const summarizeChildrenTable = (param) => {
+ return proxy.summarizeTable(
+ param,
+ [
+ "taxInclusiveUnitPrice",
+ "taxInclusiveTotalPrice",
+ "taxExclusiveTotalPrice",
+ "ticketsNum",
+ "ticketsAmount",
+ "futureTickets",
+ "futureTicketsAmount",
+ ],
+ {
+ ticketsNum: { noDecimal: true }, // 涓嶄繚鐣欏皬鏁�
+ futureTickets: { noDecimal: true }, // 涓嶄繚鐣欏皬鏁�
+ }
+ );
+};
+const paginationChange = (obj) => {
+ page.current = obj.page;
+ page.size = obj.limit;
+ getList();
+};
+const getList = () => {
+ tableLoading.value = true;
+ const { entryDate, ...rest } = searchForm;
+ purchaseListPage({ ...rest, ...page })
+ .then((res) => {
+ tableLoading.value = false;
+ tableData.value = res.data.records;
+ tableData.value.map((item) => {
+ item.children = [];
+ });
+ total.value = res.data.total;
+ expandedRowKeys.value = [];
+ })
+ .catch(() => {
+ tableLoading.value = false;
+ });
+};
+// 琛ㄦ牸閫夋嫨鏁版嵁
+const handleSelectionChange = (selection) => {
+ selectedRows.value = selection;
+};
+const productSelected = (selectedRows) => {
+ productSelectedRows.value = selectedRows;
+};
+const expandedRowKeys = ref([]);
+// 灞曞紑琛�
+const expandChange = (row, expandedRows) => {
+ if (expandedRows.length > 0) {
+ expandedRowKeys.value = [];
+ try {
+ productList({ salesLedgerId: row.id, type: 2 }).then((res) => {
+ const index = tableData.value.findIndex((item) => item.id === row.id);
+ if (index > -1) {
+ tableData.value[index].children = res.data;
+ }
+ expandedRowKeys.value.push(row.id);
+ });
+ } catch (error) {
+ console.log(error);
+ }
+ } else {
+ expandedRowKeys.value = [];
+ }
+};
+// 涓昏〃鍚堣鏂规硶
+const summarizeMainTable = (param) => {
+ return proxy.summarizeTable(param, ["contractAmount"]);
+};
+// 瀛愯〃鍚堣鏂规硶
+const summarizeProTable = (param) => {
+ return proxy.summarizeTable(param, [
+ "taxInclusiveUnitPrice",
+ "taxInclusiveTotalPrice",
+ "taxExclusiveTotalPrice",
+ ]);
+};
+// 鎵撳紑寮规
+const openForm = (type, row) => {
+ operationType.value = type;
+ form.value = {};
+ productData.value = [];
+ fileList.value = [];
+ if (operationType.value == "add") {
+ createPurchaseNo().then((res) => {
+ form.value.purchaseContractNumber = res.data;
+ });
+ }
+ userListNoPage().then((res) => {
+ userList.value = res.data;
+ });
+ getOptions().then((res) => {
+ supplierList.value = res.data;
+ });
+ form.value.recorderId = userStore.id;
+ form.value.entryDate = getCurrentDate();
+ if (type === "edit") {
+ currentId.value = row.id;
+ getPurchaseById({ id: row.id, type: 2 }).then((res) => {
+ form.value = { ...res };
+ productData.value = form.value.productData;
+ if (form.value.salesLedgerFiles) {
+ fileList.value = form.value.salesLedgerFiles;
+ } else {
+ fileList.value = [];
+ }
+ });
+ }
+ dialogFormVisible.value = true;
+};
+// 涓婁紶鍓嶆牎妫�
+function handleBeforeUpload(file) {
+ // 鏍℃鏂囦欢澶у皬
+ if (file.size > 1024 * 1024 * 50) {
+ proxy.$modal.msgError("涓婁紶鏂囦欢澶у皬涓嶈兘瓒呰繃50MB!");
+ return false;
+ }
+ proxy.$modal.loading("姝e湪涓婁紶鏂囦欢锛岃绋嶅��...");
+ return true;
+}
+// 涓婁紶澶辫触
+function handleUploadError(err) {
+ proxy.$modal.msgError("涓婁紶鏂囦欢澶辫触");
+ proxy.$modal.closeLoading();
+}
+// 涓婁紶鎴愬姛鍥炶皟
+function handleUploadSuccess(res, file, uploadFiles) {
+ proxy.$modal.closeLoading();
+ if (res.code === 200) {
+ file.tempId = res.data.tempId;
+ proxy.$modal.msgSuccess("涓婁紶鎴愬姛");
+ } else {
+ proxy.$modal.msgError(res.msg);
+ proxy.$refs.fileUpload.handleRemove(file);
+ }
+}
+// 绉婚櫎鏂囦欢
+function handleRemove(file) {
+ console.log("handleRemove", file.id);
+ if (file.size > 1024 * 1024 * 10) {
+ // 浠呭墠绔竻鐞嗭紝涓嶈皟鐢ㄥ垹闄ゆ帴鍙e拰鎻愮ず
+ return;
+ }
+ if (operationType.value === "edit") {
+ let ids = [];
+ ids.push(file.id);
+ delLedgerFile(ids).then((res) => {
+ proxy.$modal.msgSuccess("鍒犻櫎鎴愬姛");
+ });
+ }
+}
+// 鎻愪氦琛ㄥ崟
+const submitForm = () => {
+ proxy.$refs["formRef"].validate((valid) => {
+ if (valid) {
+ if (productData.value.length > 0) {
+ form.value.productData = proxy.HaveJson(productData.value);
+ } else {
+ proxy.$modal.msgWarning("璇锋坊鍔犱骇鍝佷俊鎭�");
+ return;
+ }
+ let tempFileIds = [];
+ if (fileList.value.length > 0) {
+ tempFileIds = fileList.value.map((item) => item.tempId);
+ }
+ form.value.tempFileIds = tempFileIds;
+ form.value.type = 2;
+ addOrEditPurchase(form.value).then((res) => {
+ proxy.$modal.msgSuccess("鎻愪氦鎴愬姛");
+ closeDia();
+ getList();
+ });
+ }
+ });
+};
+// 鍏抽棴寮规
+const closeDia = () => {
+ proxy.resetForm("formRef");
+ dialogFormVisible.value = false;
+};
+// 鎵撳紑浜у搧寮规
+const openProductForm = (type, row, index) => {
+ productOperationType.value = type;
+ productOperationIndex.value = index;
+ productForm.value = {};
+ proxy.resetForm("productFormRef");
+ if (type === "edit") {
+ productForm.value = { ...row };
+ }
+ productFormVisible.value = true;
+ getProductOptions();
+};
+const getProductOptions = () => {
+ productTreeList().then((res) => {
+ productOptions.value = convertIdToValue(res);
+ });
+};
+const getModels = (value) => {
+ productForm.value.productCategory = findNodeById(productOptions.value, value);
+ modelList({ id: value }).then((res) => {
+ modelOptions.value = res;
+ });
+};
+const getProductModel = (value) => {
+ const index = modelOptions.value.findIndex((item) => item.id === value);
+ if (index !== -1) {
+ productForm.value.specificationModel = modelOptions.value[index].model;
+ productForm.value.unit = modelOptions.value[index].unit;
+ } else {
+ productForm.value.specificationModel = null;
+ productForm.value.unit = null;
+ }
+};
+const findNodeById = (nodes, productId) => {
+ for (let i = 0; i < nodes.length; i++) {
+ if (nodes[i].value === productId) {
+ return nodes[i].label; // 鎵惧埌鑺傜偣锛岃繑鍥炶鑺傜偣
+ }
+ if (nodes[i].children && nodes[i].children.length > 0) {
+ const foundNode = findNodeById(nodes[i].children, productId);
+ if (foundNode) {
+ return foundNode.label; // 鍦ㄥ瓙鑺傜偣涓壘鍒帮紝杩斿洖璇ヨ妭鐐�
+ }
+ }
+ }
+ return null; // 娌℃湁鎵惧埌鑺傜偣锛岃繑鍥瀗ull
+};
+function convertIdToValue(data) {
+ return data.map((item) => {
+ const { id, children, ...rest } = item;
+ const newItem = {
+ ...rest,
+ value: id, // 灏� id 鏀逛负 value
+ };
+ if (children && children.length > 0) {
+ newItem.children = convertIdToValue(children);
+ }
+
+ return newItem;
+ });
+}
+// 鎻愪氦浜у搧琛ㄥ崟
+const submitProduct = () => {
+ proxy.$refs["productFormRef"].validate((valid) => {
+ if (valid) {
+ if (operationType.value === "edit") {
+ submitProductEdit();
+ } else {
+ if (productOperationType.value === "add") {
+ productData.value.push({ ...productForm.value });
+ console.log("productData.value---", productData.value);
+ } else {
+ productData.value[productOperationIndex.value] = {
+ ...productForm.value,
+ };
+ }
+ closeProductDia();
+ }
+ }
+ });
+};
+const submitProductEdit = () => {
+ productForm.value.salesLedgerId = currentId.value;
+ productForm.value.type = 2;
+ addOrUpdateSalesLedgerProduct(productForm.value).then((res) => {
+ proxy.$modal.msgSuccess("鎻愪氦鎴愬姛");
+ closeProductDia();
+ getPurchaseById({ id: currentId.value, type: 2 }).then((res) => {
+ productData.value = res.productData;
+ });
+ });
+};
+// 鍒犻櫎浜у搧
+const deleteProduct = () => {
+ if (productSelectedRows.value.length === 0) {
+ proxy.$modal.msgWarning("璇烽�夋嫨鏁版嵁");
+ return;
+ }
+ if (operationType.value === "add") {
+ productSelectedRows.value.forEach((selectedRow) => {
+ const index = productData.value.findIndex(
+ (product) => product.id === selectedRow.id
+ );
+ if (index !== -1) {
+ productData.value.splice(index, 1);
+ }
+ });
+ } else {
+ let ids = [];
+ if (productSelectedRows.value.length > 0) {
+ ids = productSelectedRows.value.map((item) => item.id);
+ }
+ ElMessageBox.confirm("閫変腑鐨勫唴瀹瑰皢琚垹闄わ紝鏄惁纭鍒犻櫎锛�", "瀵煎嚭", {
+ confirmButtonText: "纭",
+ cancelButtonText: "鍙栨秷",
+ type: "warning",
+ })
+ .then(() => {
+ delProduct(ids).then((res) => {
+ proxy.$modal.msgSuccess("鍒犻櫎鎴愬姛");
+ closeProductDia();
+ getSalesLedgerWithProducts({ id: currentId.value, type: 2 }).then(
+ (res) => {
+ productData.value = res.productData;
+ }
+ );
+ });
+ })
+ .catch(() => {
+ proxy.$modal.msg("宸插彇娑�");
+ });
+ }
+};
+// 鍏抽棴浜у搧寮规
+const closeProductDia = () => {
+ proxy.resetForm("productFormRef");
+ productFormVisible.value = false;
+};
+// 瀵煎嚭
+const handleOut = () => {
+ ElMessageBox.confirm("閫変腑鐨勫唴瀹瑰皢琚鍑猴紝鏄惁纭瀵煎嚭锛�", "瀵煎嚭", {
+ confirmButtonText: "纭",
+ cancelButtonText: "鍙栨秷",
+ type: "warning",
+ })
+ .then(() => {
+ proxy.download("/purchase/ledger/export", {}, "閲囪喘鍙拌处.xlsx");
+ })
+ .catch(() => {
+ proxy.$modal.msg("宸插彇娑�");
+ });
+};
+// 鍒犻櫎
+const handleDelete = () => {
+ let ids = [];
+ if (selectedRows.value.length > 0) {
+ // 妫�鏌ユ槸鍚︽湁浠栦汉缁存姢鐨勬暟鎹�
+ const unauthorizedData = selectedRows.value.filter(item => item.recorderName !== userStore.nickName);
+ if (unauthorizedData.length > 0) {
+ proxy.$modal.msgWarning("涓嶅彲鍒犻櫎浠栦汉缁存姢鐨勬暟鎹�");
+ return;
+ }
+ ids = selectedRows.value.map((item) => item.id);
+ } else {
+ proxy.$modal.msgWarning("璇烽�夋嫨鏁版嵁");
+ return;
+ }
+ ElMessageBox.confirm("閫変腑鐨勫唴瀹瑰皢琚垹闄わ紝鏄惁纭鍒犻櫎锛�", "瀵煎嚭", {
+ confirmButtonText: "纭",
+ cancelButtonText: "鍙栨秷",
+ type: "warning",
+ })
+ .then(() => {
+ delPurchase(ids).then((res) => {
+ proxy.$modal.msgSuccess("鍒犻櫎鎴愬姛");
+ getList();
+ });
+ })
+ .catch(() => {
+ proxy.$modal.msg("宸插彇娑�");
+ });
+};
+// 鑾峰彇褰撳墠鏃ユ湡骞舵牸寮忓寲涓� YYYY-MM-DD
+function getCurrentDate() {
+ const today = new Date();
+ const year = today.getFullYear();
+ const month = String(today.getMonth() + 1).padStart(2, "0"); // 鏈堜唤浠�0寮�濮�
+ const day = String(today.getDate()).padStart(2, "0");
+ return `${year}-${month}-${day}`;
+}
+const mathNum = () => {
+ if (!productForm.value.taxRate) {
+ proxy.$modal.msgWarning("璇峰厛閫夋嫨绋庣巼");
+ return;
+ }
+ if (!productForm.value.taxInclusiveUnitPrice) {
+ return;
+ }
+ if (!productForm.value.quantity) {
+ return;
+ }
+ // 鍚◣鎬讳环璁$畻
+ productForm.value.taxInclusiveTotalPrice =
+ proxy.calculateTaxIncludeTotalPrice(
+ productForm.value.taxInclusiveUnitPrice,
+ productForm.value.quantity
+ );
+ if (productForm.value.taxRate) {
+ // 涓嶅惈绋庢�讳环璁$畻
+ productForm.value.taxExclusiveTotalPrice =
+ proxy.calculateTaxExclusiveTotalPrice(
+ productForm.value.taxInclusiveTotalPrice,
+ productForm.value.taxRate
+ );
+ }
+};
+const reverseMathNum = (field) => {
+ if (!productForm.value.taxRate) {
+ proxy.$modal.msgWarning("璇峰厛閫夋嫨绋庣巼");
+ return;
+ }
+ const taxRate = Number(productForm.value.taxRate);
+ if (!taxRate) return;
+ if (field === 'taxInclusiveTotalPrice') {
+ // 宸茬煡鍚◣鎬讳环鍜屾暟閲忥紝鍙嶇畻鍚◣鍗曚环
+ if (productForm.value.quantity) {
+ productForm.value.taxInclusiveUnitPrice =
+ (Number(productForm.value.taxInclusiveTotalPrice) / Number(productForm.value.quantity)).toFixed(2);
+ }
+ // 宸茬煡鍚◣鎬讳环鍜屽惈绋庡崟浠凤紝鍙嶇畻鏁伴噺
+ else if (productForm.value.taxInclusiveUnitPrice) {
+ productForm.value.quantity =
+ (Number(productForm.value.taxInclusiveTotalPrice) / Number(productForm.value.taxInclusiveUnitPrice)).toFixed(2);
+ }
+ // 鍙嶇畻涓嶅惈绋庢�讳环
+ productForm.value.taxExclusiveTotalPrice =
+ (Number(productForm.value.taxInclusiveTotalPrice) / (1 + taxRate / 100)).toFixed(2);
+ } else if (field === 'taxExclusiveTotalPrice') {
+ // 鍙嶇畻鍚◣鎬讳环
+ productForm.value.taxInclusiveTotalPrice =
+ (Number(productForm.value.taxExclusiveTotalPrice) * (1 + taxRate / 100)).toFixed(2);
+ // 宸茬煡鏁伴噺锛屽弽绠楀惈绋庡崟浠�
+ if (productForm.value.quantity) {
+ productForm.value.taxInclusiveUnitPrice =
+ (Number(productForm.value.taxInclusiveTotalPrice) / Number(productForm.value.quantity)).toFixed(2);
+ }
+ // 宸茬煡鍚◣鍗曚环锛屽弽绠楁暟閲�
+ else if (productForm.value.taxInclusiveUnitPrice) {
+ productForm.value.quantity =
+ (Number(productForm.value.taxInclusiveTotalPrice) / Number(productForm.value.taxInclusiveUnitPrice)).toFixed(2);
+ }
+ }
+};
+
+
+onMounted(() => {
+ getList();
+});
+</script>
+
+<style scoped lang="scss"></style>
diff --git a/src/views/productionManagement/operationScheduling/components/formDia.vue b/src/views/productionManagement/operationScheduling/components/formDia.vue
new file mode 100644
index 0000000..5d8fe3a
--- /dev/null
+++ b/src/views/productionManagement/operationScheduling/components/formDia.vue
@@ -0,0 +1,181 @@
+<template>
+ <div>
+ <el-dialog
+ v-model="dialogFormVisible"
+ title="宸ュ簭鎺掍骇"
+ width="70%"
+ @close="closeDia"
+ >
+ <el-button type="primary" @click="addRow" style="margin-bottom: 10px;">鏂板</el-button>
+ <span style="font-size: 18px;margin-left: 10px">寰呮帓浜ф暟閲忥細{{pendingNum}}</span>
+ <el-table :data="tableData" border style="width: 100%" :summary-method="summarizeMainTable" show-summary :row-key="row => row.id" stripe>
+ <el-table-column label="搴忓彿" width="60">
+ <template #default="scope">
+ {{ scope.$index + 1 }}
+ </template>
+ </el-table-column>
+ <el-table-column label="宸ュ簭" prop="process">
+ <template #default="scope">
+ <el-input
+ v-model="scope.row.process"
+ placeholder="璇疯緭鍏ュ伐搴�"
+ clearable
+ />
+ </template>
+ </el-table-column>
+ <el-table-column label="鍗曚綅" prop="unit">
+ <template #default="scope">
+ <el-input v-model="scope.row.unit" placeholder="璇疯緭鍏ュ崟浣�" />
+ </template>
+ </el-table-column>
+ <el-table-column label="鎺掍骇鏁伴噺" width="200" prop="schedulingNum">
+ <template #default="scope">
+ <el-input-number
+ v-model="scope.row.schedulingNum"
+ placeholder="璇疯緭鍏�"
+ :min="0"
+ :step="0.1"
+ :precision="2"
+ clearable
+ style="width: 100%"
+ />
+ </template>
+ </el-table-column>
+ <el-table-column label="宸ユ椂瀹氶" width="200" prop="workHours">
+ <template #default="scope">
+ <el-input-number
+ v-model="scope.row.workHours"
+ placeholder="璇疯緭鍏�"
+ :min="0"
+ :step="0.1"
+ :precision="2"
+ clearable
+ style="width: 100%"
+ />
+ </template>
+ </el-table-column>
+ <el-table-column label="鎺掍骇鏃ユ湡" prop="schedulingDate">
+ <template #default="scope">
+ <el-date-picker v-model="scope.row.schedulingDate" type="date" placeholder="閫夋嫨鏃ユ湡" style="width: 100%;" value-format="YYYY-MM-DD" format="YYYY-MM-DD"/>
+ </template>
+ </el-table-column>
+ <el-table-column label="鎺掍骇浜�" prop="schedulingUserId">
+ <template #default="scope">
+ <el-select
+ v-model="scope.row.schedulingUserId"
+ placeholder="閫夋嫨浜哄憳"
+ style="width: 100%;"
+ >
+ <el-option
+ v-for="user in userList"
+ :key="user.userId"
+ :label="user.nickName"
+ :value="user.userId"
+ />
+ </el-select>
+ </template>
+ </el-table-column>
+ <el-table-column label="鎿嶄綔" width="80">
+ <template #default="scope">
+ <el-button type="danger" size="small" @click="removeRow(scope.$index)">鍒犻櫎</el-button>
+ </template>
+ </el-table-column>
+ </el-table>
+ <template #footer>
+ <div class="dialog-footer">
+ <el-button type="primary" @click="submitForm">纭</el-button>
+ <el-button @click="closeDia">鍙栨秷</el-button>
+ </div>
+ </template>
+ </el-dialog>
+ </div>
+</template>
+
+<script setup>
+import {ref} from "vue";
+import {userListNoPageByTenantId} from "@/api/system/user.js";
+import {processScheduling} from "@/api/productionManagement/operationScheduling.js";
+const { proxy } = getCurrentInstance()
+const emit = defineEmits(['close'])
+
+const dialogFormVisible = ref(false);
+const operationType = ref('')
+const tableData = ref([
+ { process: '', schedulingDate: '', schedulingNum: '', schedulingUserId: '', workHours: '', unit: '' }
+]);
+const unitFromRow = ref('');
+const idFromRow = ref('');
+const pendingNum = ref('');
+const userList = ref([])
+
+// 鎵撳紑寮规
+const openDialog = (type, row) => {
+ operationType.value = type;
+ dialogFormVisible.value = true;
+ userListNoPageByTenantId().then((res) => {
+ userList.value = res.data;
+ });
+ pendingNum.value = row.pendingNum
+ if (row && row.unit !== undefined) {
+ unitFromRow.value = row.unit;
+ idFromRow.value = row.id;
+ tableData.value.forEach(item => {
+ item.unit = row.unit;
+ item.id = row.id;
+ });
+ } else {
+ unitFromRow.value = '';
+ }
+}
+const submitForm = () => {
+ // 1. 妫�鏌ユ瘡涓�琛屾槸鍚﹀~鍐欏畬鏁�
+ for (let i = 0; i < tableData.value.length; i++) {
+ const row = tableData.value[i];
+ if (
+ !row.process ||
+ !row.schedulingDate ||
+ row.schedulingNum === '' || row.schedulingNum === null ||
+ !row.schedulingUserId ||
+ row.workHours === '' || row.workHours === null ||
+ !row.unit
+ ) {
+ proxy.$modal.msgError(`绗�${i + 1}琛屾暟鎹湭濉啓瀹屾暣`);
+ return;
+ }
+ }
+ // 2. 鍚堣鎺掍骇鏁伴噺
+ const totalSchedulingNum = tableData.value.reduce((sum, row) => {
+ return sum + Number(row.schedulingNum || 0);
+ }, 0);
+ if (totalSchedulingNum > Number(pendingNum.value)) {
+ proxy.$modal.msgError('鎺掍骇鏁伴噺鍚堣涓嶈兘瓒呰繃寰呮帓浜ф暟閲�');
+ return;
+ }
+ processScheduling(tableData.value).then((res) => {
+ proxy.$modal.msgSuccess("鎻愪氦鎴愬姛");
+ closeDia();
+ })
+}
+const summarizeMainTable = (param) => {
+ return proxy.summarizeTable(param, ['schedulingNum']);
+};
+// 鍏抽棴寮规
+const closeDia = () => {
+ dialogFormVisible.value = false;
+ emit('close')
+};
+defineExpose({
+ openDialog,
+});
+
+const addRow = () => {
+ tableData.value.push({ id: idFromRow.value, process: '', unit: unitFromRow.value, schedulingNum: '', workHours: '', schedulingDate: '', schedulingUserId: '' });
+};
+const removeRow = (index) => {
+ tableData.value.splice(index, 1);
+};
+</script>
+
+<style scoped>
+
+</style>
\ No newline at end of file
diff --git a/src/views/productionManagement/operationScheduling/index.vue b/src/views/productionManagement/operationScheduling/index.vue
new file mode 100644
index 0000000..1427faf
--- /dev/null
+++ b/src/views/productionManagement/operationScheduling/index.vue
@@ -0,0 +1,228 @@
+<template>
+ <div class="app-container">
+ <div class="search_form">
+ <el-form :model="searchForm" :inline="true">
+ <el-form-item label="娲惧伐鏃ユ湡:">
+ <el-date-picker v-model="searchForm.entryDate" value-format="YYYY-MM-DD" format="YYYY-MM-DD" type="daterange"
+ placeholder="璇烽�夋嫨" clearable @change="changeDaterange" />
+ </el-form-item>
+ <el-form-item label="鐘舵��:">
+ <el-select v-model="searchForm.status" placeholder="璇烽�夋嫨鐘舵��" @change="handleQuery" style="width: 140px" clearable>
+ <el-option label="寰呮帓浜�" :value="1"></el-option>
+ <el-option label="宸叉帓浜�" :value="3"></el-option>
+ <el-option label="鎺掍骇涓�" :value="2"></el-option>
+ </el-select>
+ </el-form-item>
+ <el-form-item>
+ <el-button type="primary" @click="handleQuery">鎼滅储</el-button>
+ </el-form-item>
+ </el-form>
+ </div>
+ <div class="table_list">
+ <div style="text-align: right" class="mb10">
+ <el-button type="primary" @click="openForm">宸ュ簭鎺掍骇</el-button>
+ <el-button type="danger" @click="handleDelete" plain>鍙栨秷鎺掍骇</el-button>
+ </div>
+ <PIMTable
+ rowKey="id"
+ :column="tableColumn"
+ :tableData="tableData"
+ :page="page"
+ :isSelection="true"
+ @selection-change="handleSelectionChange"
+ :tableLoading="tableLoading"
+ @pagination="pagination"
+ :total="page.total"
+ ></PIMTable>
+ </div>
+ <form-dia ref="formDia" @close="handleQuery"></form-dia>
+ </div>
+</template>
+
+<script setup>
+import {onMounted, ref} from "vue";
+import FormDia from "@/views/productionManagement/operationScheduling/components/formDia.vue";
+import {ElMessageBox} from "element-plus";
+import dayjs from "dayjs";
+import {listPageProcess, productionDispatchDelete} from "@/api/productionManagement/operationScheduling.js";
+
+const data = reactive({
+ searchForm: {
+ staffName: "",
+ status: 1,
+ entryDate: [
+ dayjs().format("YYYY-MM-DD"),
+ dayjs().add(1, "day").format("YYYY-MM-DD"),
+ ], // 褰曞叆鏃ユ湡
+ entryDateStart: dayjs().format("YYYY-MM-DD"),
+ entryDateEnd: dayjs().add(1, "day").format("YYYY-MM-DD"),
+ },
+});
+const { searchForm } = toRefs(data);
+const tableColumn = ref([
+ {
+ label: "鐘舵��",
+ prop: "status",
+ dataType: "tag",
+ formatData: (params) => {
+ if (params == 3) {
+ return "宸叉帓浜�";
+ } else if (params == 1) {
+ return "寰呮帓浜�";
+ } else {
+ return '鎺掍骇涓�';
+ }
+ },
+ formatType: (params) => {
+ if (params == 3) {
+ return "success";
+ } else if (params == 1) {
+ return "primary";
+ } else {
+ return 'warning';
+ }
+ },
+ },
+ {
+ label: "娲惧伐鏃ユ湡",
+ prop: "schedulingDate",
+ width: 120,
+ },
+ {
+ label: "娲惧伐浜�",
+ prop: "schedulingUserName",
+ },
+ {
+ label: "浜у搧澶х被",
+ prop: "productCategory",
+ width: 150,
+ },
+ {
+ label: "瑙勬牸鍨嬪彿",
+ prop: "specificationModel",
+ width: 150,
+ },
+ {
+ label: "鍗曚綅",
+ prop: "unit",
+ },
+ {
+ label: "鎺掍骇鎬绘暟",
+ prop: "schedulingNum",
+ },
+ {
+ label: "宸叉帓浜ф暟閲�",
+ prop: "successNum",
+ width: 100,
+ },
+ {
+ label: "寰呮帓浜ф暟閲�",
+ prop: "pendingNum",
+ width: 100,
+ },
+]);
+const tableData = ref([]);
+const selectedRows = ref([]);
+const tableLoading = ref(false);
+const page = reactive({
+ current: 1,
+ size: 100,
+ total: 0,
+});
+const formDia = ref()
+const { proxy } = getCurrentInstance()
+
+// 鏌ヨ鍒楄〃
+/** 鎼滅储鎸夐挳鎿嶄綔 */
+const handleQuery = () => {
+ page.current = 1;
+ getList();
+};
+const changeDaterange = (value) => {
+ if (value) {
+ searchForm.value.entryDateStart = dayjs(value[0]).format("YYYY-MM-DD");
+ searchForm.value.entryDateEnd = dayjs(value[1]).format("YYYY-MM-DD");
+ } else {
+ searchForm.value.entryDateStart = undefined;
+ searchForm.value.entryDateEnd = undefined;
+ }
+ handleQuery();
+};
+const pagination = (obj) => {
+ page.current = obj.page;
+ page.size = obj.limit;
+ getList();
+};
+const getList = () => {
+ tableLoading.value = true;
+ const params = { ...searchForm.value, ...page };
+ params.entryDate = undefined
+ listPageProcess(params).then(res => {
+ tableLoading.value = false;
+ tableData.value = res.data.records.map(item => ({
+ ...item,
+ pendingNum: (Number(item.schedulingNum) || 0) - (Number(item.successNum) || 0)
+ }));
+ page.total = res.data.total;
+ }).catch(err => {
+ tableLoading.value = false;
+ })
+};
+// 琛ㄦ牸閫夋嫨鏁版嵁
+const handleSelectionChange = (selection) => {
+ selectedRows.value = selection;
+};
+
+// 鎵撳紑寮规
+const openForm = (type, row) => {
+ if (selectedRows.value.length !== 1) {
+ proxy.$message.error("璇烽�夋嫨涓�鏉℃暟鎹�");
+ return;
+ }
+ if (selectedRows.value[0].pendingNum == 0) {
+ proxy.$message.warning("鏃犻渶鍐嶆帓浜�");
+ return;
+ }
+ nextTick(() => {
+ formDia.value?.openDialog(type, selectedRows.value[0])
+ })
+};
+
+// 鍒犻櫎
+const handleDelete = () => {
+ let ids = [];
+ if (selectedRows.value.length > 0) {
+ // 鏂板锛氬垽鏂槸鍚︽湁宸叉帓浜х殑鏁版嵁
+ const hasScheduled = selectedRows.value.some(item => item.status == 3);
+ if (hasScheduled) {
+ proxy.$modal.msgWarning("宸叉帓浜ф暟鎹笉鑳藉彇娑堟帓浜�");
+ return;
+ }
+ ids = selectedRows.value.map((item) => item.id);
+ } else {
+ proxy.$modal.msgWarning("璇烽�夋嫨鏁版嵁");
+ return;
+ }
+ ElMessageBox.confirm("鏄惁纭鍙栨秷鎺掍骇锛�", "鍒犻櫎鎻愮ず", {
+ confirmButtonText: "纭",
+ cancelButtonText: "鍙栨秷",
+ type: "warning",
+ })
+ .then(() => {
+ tableLoading.value = true;
+ productionDispatchDelete(ids)
+ .then((res) => {
+ proxy.$modal.msgSuccess("鍙栨秷鎺掍骇鎴愬姛");
+ getList();
+ })
+ })
+ .catch(() => {
+ proxy.$modal.msg("宸插彇娑�");
+ });
+};
+onMounted(() => {
+ getList();
+});
+</script>
+
+<style scoped></style>
diff --git a/src/views/productionManagement/productionCosting/index.vue b/src/views/productionManagement/productionCosting/index.vue
new file mode 100644
index 0000000..6014d00
--- /dev/null
+++ b/src/views/productionManagement/productionCosting/index.vue
@@ -0,0 +1,166 @@
+<template>
+ <div class="app-container">
+ <div class="search_form">
+ <div>
+ <span class="search_title">鐢熶骇鏃ユ湡锛�</span>
+ <el-date-picker v-model="searchForm.entryDate" value-format="YYYY-MM-DD" format="YYYY-MM-DD" type="daterange"
+ placeholder="璇烽�夋嫨" clearable @change="changeDaterange" />
+ <span class="search_title ml10">鐢熶骇浜猴細</span>
+ <el-input
+ v-model="searchForm.schedulingUserName"
+ style="width: 240px"
+ placeholder="璇疯緭鍏�"
+ @change="handleQuery"
+ clearable
+ prefix-icon="Search"
+ />
+ <el-button type="primary" @click="handleQuery" style="margin-left: 10px"
+ >鎼滅储</el-button
+ >
+ </div>
+ <div>
+ <el-button @click="handleOut">瀵煎嚭</el-button>
+ </div>
+ </div>
+ <div class="table_list">
+ <PIMTable
+ rowKey="id"
+ :column="tableColumn"
+ :tableData="tableData"
+ :page="page"
+ :tableLoading="tableLoading"
+ @pagination="pagination"
+ ></PIMTable>
+ </div>
+ </div>
+</template>
+
+<script setup>
+import {onMounted, ref} from "vue";
+import {
+ listCustomer,
+} from "@/api/basicData/customerFile.js";
+import { ElMessageBox } from "element-plus";
+import dayjs from "dayjs";
+import {productionAccountingListPage} from "@/api/productionManagement/productionCosting.js";
+const { proxy } = getCurrentInstance();
+
+const tableColumn = ref([
+ {
+ label: "鐢熶骇鏃ユ湡",
+ prop: "schedulingDate",
+ width: 120,
+ },
+ {
+ label: "鐢熶骇浜�",
+ prop: "schedulingUserName",
+ width: 90,
+ },
+ {
+ label: "浜у搧澶х被",
+ prop: "productCategory",
+ width: 160,
+ },
+ {
+ label: "瑙勬牸鍨嬪彿",
+ prop: "specificationModel",
+ width: 160,
+ },
+ {
+ label: "鍗曚綅",
+ prop: "unit",
+ },
+ {
+ label: "宸ュ簭",
+ prop: "process",
+ },
+ {
+ label: "鐢熶骇鏁伴噺",
+ prop: "finishedNum",
+ width: 100,
+ },
+ {
+ label: "宸ユ椂瀹氶",
+ prop: "workHours",
+ width: 100,
+ },
+ {
+ label: "宸ヨ祫",
+ prop: "wages",
+ width: 100,
+ },
+]);
+const tableData = ref([]);
+const tableLoading = ref(false);
+const page = reactive({
+ current: 1,
+ size: 100,
+ total: 0,
+});
+
+const data = reactive({
+ searchForm: {
+ schedulingUserName: "",
+ entryDate: [
+ dayjs().format("YYYY-MM-DD"),
+ dayjs().add(1, "day").format("YYYY-MM-DD"),
+ ], // 褰曞叆鏃ユ湡
+ entryDateStart: dayjs().format("YYYY-MM-DD"),
+ entryDateEnd: dayjs().add(1, "day").format("YYYY-MM-DD"),
+ },
+});
+const { searchForm } = toRefs(data);
+
+// 鏌ヨ鍒楄〃
+/** 鎼滅储鎸夐挳鎿嶄綔 */
+const handleQuery = () => {
+ page.current = 1;
+ getList();
+};
+const pagination = (obj) => {
+ page.current = obj.page;
+ page.size = obj.limit;
+ getList();
+};
+const changeDaterange = (value) => {
+ if (value) {
+ searchForm.value.entryDateStart = dayjs(value[0]).format("YYYY-MM-DD");
+ searchForm.value.entryDateEnd = dayjs(value[1]).format("YYYY-MM-DD");
+ } else {
+ searchForm.value.entryDateStart = undefined;
+ searchForm.value.entryDateEnd = undefined;
+ }
+ handleQuery();
+};
+const getList = () => {
+ tableLoading.value = true;
+ const params = { ...searchForm.value, ...page };
+ params.entryDate = undefined
+ productionAccountingListPage(params).then((res) => {
+ tableLoading.value = false;
+ tableData.value = res.data.records;
+ page.total = res.data.total;
+ });
+};
+
+// 瀵煎嚭
+const handleOut = () => {
+ ElMessageBox.confirm("閫変腑鐨勫唴瀹瑰皢琚鍑猴紝鏄惁纭瀵煎嚭锛�", "瀵煎嚭", {
+ confirmButtonText: "纭",
+ cancelButtonText: "鍙栨秷",
+ type: "warning",
+ })
+ .then(() => {
+ proxy.download("/basic/customer/export", {}, "鐢熶骇鏍哥畻.xlsx");
+ })
+ .catch(() => {
+ proxy.$modal.msg("宸插彇娑�");
+ });
+};
+
+onMounted(() => {
+ getList();
+});
+</script>
+
+<style scoped lang="scss"></style>
diff --git a/src/views/productionManagement/productionDispatching/components/formDia.vue b/src/views/productionManagement/productionDispatching/components/formDia.vue
new file mode 100644
index 0000000..de04e07
--- /dev/null
+++ b/src/views/productionManagement/productionDispatching/components/formDia.vue
@@ -0,0 +1,167 @@
+<template>
+ <div>
+ <el-dialog
+ v-model="dialogFormVisible"
+ title="鐢熶骇娲惧伐"
+ width="50%"
+ @close="closeDia"
+ >
+ <el-form :model="form" label-width="140px" label-position="top" :rules="rules" ref="formRef">
+ <el-row :gutter="30">
+ <el-col :span="12">
+ <el-form-item label="浜у搧澶х被锛�" prop="productCategory">
+ <el-input v-model="form.productCategory" placeholder="璇疯緭鍏�" clearable disabled/>
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="鎬绘暟閲忥細" prop="quantity">
+ <el-input v-model="form.quantity" placeholder="璇疯緭鍏�" clearable disabled/>
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="30">
+
+ <el-col :span="12">
+ <el-form-item label="寰呮帓浜ф暟閲忥細" prop="pendingQuantity">
+ <el-input v-model="form.pendingQuantity" placeholder="璇疯緭鍏�" clearable disabled/>
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="鏈鎺掍骇鏁伴噺锛�" prop="schedulingNum">
+ <el-input-number
+ v-model="form.schedulingNum"
+ placeholder="璇疯緭鍏�"
+ :min="0"
+ :step="0.1"
+ :precision="2"
+ clearable
+ @change="changeNum"
+ style="width: 100%"
+ />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="30">
+ <el-col :span="12">
+ <el-form-item label="娲惧伐浜猴細" prop="schedulingUserId">
+ <el-select
+ v-model="form.schedulingUserId"
+ placeholder="閫夋嫨浜哄憳"
+ style="width: 100%;"
+ >
+ <el-option
+ v-for="user in userList"
+ :key="user.userId"
+ :label="user.nickName"
+ :value="user.userId"
+ />
+ </el-select>
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="娲惧伐鏃ユ湡锛�" prop="schedulingDate">
+ <el-date-picker
+ v-model="form.schedulingDate"
+ type="date"
+ placeholder="璇烽�夋嫨鏃ユ湡"
+ value-format="YYYY-MM-DD"
+ format="YYYY-MM-DD"
+ clearable
+ style="width: 100%"
+ />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ </el-form>
+ <template #footer>
+ <div class="dialog-footer">
+ <el-button type="primary" @click="submitForm">纭</el-button>
+ <el-button @click="closeDia">鍙栨秷</el-button>
+ </div>
+ </template>
+ </el-dialog>
+ </div>
+</template>
+
+<script setup>
+import {ref} from "vue";
+import {getStaffJoinInfo, staffJoinAdd, staffJoinUpdate} from "@/api/personnelManagement/onboarding.js";
+import {userListNoPageByTenantId} from "@/api/system/user.js";
+import {productionDispatch} from "@/api/productionManagement/productionOrder.js";
+import useUserStore from "@/store/modules/user.js";
+import dayjs from "dayjs";
+const { proxy } = getCurrentInstance()
+const emit = defineEmits(['close'])
+
+const dialogFormVisible = ref(false);
+const operationType = ref('')
+const data = reactive({
+ form: {
+ productCategory: "",
+ quantity: "",
+ schedulingNum: "",
+ schedulingUserId: "",
+ schedulingDate: "",
+ pendingQuantity: "",
+ salesLedgerProductId: "",
+ },
+ rules: {
+ schedulingNum: [{ required: true, message: "璇疯緭鍏�", trigger: "blur" },],
+ schedulingUserId: [{ required: true, message: "璇烽�夋嫨", trigger: "change" },],
+ schedulingDate: [{ required: true, message: "璇烽�夋嫨", trigger: "change" },],
+ },
+});
+const { form, rules } = toRefs(data);
+const userList = ref([])
+const userStore = useUserStore()
+
+// 鎵撳紑寮规
+const openDialog = (type, row) => {
+ operationType.value = type;
+ dialogFormVisible.value = true;
+ userListNoPageByTenantId().then((res) => {
+ userList.value = res.data;
+ });
+ form.value = {...row}
+ // 缁戝畾澶栧眰浼犲叆鐨勪骇鍝両D鍒板悗绔渶瑕佺殑 salesLedgerProductId 瀛楁
+ form.value.salesLedgerProductId = row.id;
+ // 纭繚涓嶄細鎶婂師濮� id 褰撲綔鎺掍骇璁板綍涓婚敭浼犵粰鍚庣
+ delete form.value.id;
+ form.value.schedulingNum = 0
+ form.value.schedulingUserId = userStore.id
+ form.value.schedulingDate = dayjs().format("YYYY-MM-DD");
+}
+
+//
+const changeNum = (value) => {
+ if (value > form.value.pendingQuantity) {
+ form.value.schedulingNum = form.value.pendingQuantity;
+ proxy.$modal.msgWarning('鎺掍骇鏁伴噺涓嶅彲澶т簬寰呮帓浜ф暟閲�')
+ }
+}
+// 鎻愪氦浜у搧琛ㄥ崟
+const submitForm = () => {
+ proxy.$refs.formRef.validate(valid => {
+ if (valid) {
+ productionDispatch(form.value).then(res => {
+ proxy.$modal.msgSuccess("鎻愪氦鎴愬姛");
+ closeDia();
+ })
+ }
+ })
+}
+
+// 鍏抽棴寮规
+const closeDia = () => {
+ proxy.resetForm("formRef");
+ dialogFormVisible.value = false;
+ emit('close')
+};
+defineExpose({
+ openDialog,
+});
+</script>
+
+<style scoped>
+
+</style>
\ No newline at end of file
diff --git a/src/views/productionManagement/productionDispatching/index.vue b/src/views/productionManagement/productionDispatching/index.vue
new file mode 100644
index 0000000..cb6accd
--- /dev/null
+++ b/src/views/productionManagement/productionDispatching/index.vue
@@ -0,0 +1,164 @@
+<template>
+ <div class="app-container">
+ <div class="search_form">
+ <div>
+ <span class="search_title">褰曞叆鏃ユ湡锛�</span>
+ <el-date-picker v-model="searchForm.entryDate" value-format="YYYY-MM-DD" format="YYYY-MM-DD" type="daterange"
+ placeholder="璇烽�夋嫨" clearable @change="changeDaterange" />
+ <el-button type="primary" @click="handleQuery" style="margin-left: 10px">鎼滅储</el-button>
+ </div>
+ <div>
+ <el-button type="primary" @click="openForm('add')">鐢熶骇娲惧伐</el-button>
+ </div>
+ </div>
+ <div class="table_list">
+ <PIMTable
+ rowKey="id"
+ :column="tableColumn"
+ :tableData="tableData"
+ :page="page"
+ :isSelection="true"
+ @selection-change="handleSelectionChange"
+ :tableLoading="tableLoading"
+ @pagination="pagination"
+ :total="page.total"
+ ></PIMTable>
+ </div>
+ <form-dia ref="formDia" @close="handleQuery"></form-dia>
+ </div>
+</template>
+
+<script setup>
+import {onMounted, ref} from "vue";
+import FormDia from "@/views/productionManagement/productionDispatching/components/formDia.vue";
+import dayjs from "dayjs";
+import {schedulingListPage} from "@/api/productionManagement/productionOrder.js";
+
+const data = reactive({
+ searchForm: {
+ entryDate: [
+ dayjs().format("YYYY-MM-DD"),
+ dayjs().add(1, "day").format("YYYY-MM-DD"),
+ ], // 褰曞叆鏃ユ湡
+ entryDateStart: dayjs().format("YYYY-MM-DD"),
+ entryDateEnd: dayjs().add(1, "day").format("YYYY-MM-DD"),
+ },
+});
+const { searchForm } = toRefs(data);
+const tableColumn = ref([
+ {
+ label: "褰曞叆鏃ユ湡",
+ prop: "registerDate",
+ width: 120,
+ },
+ {
+ label: "浜у搧澶х被",
+ prop: "productCategory",
+ width: 160,
+ },
+ {
+ label: "瑙勬牸鍨嬪彿",
+ prop: "specificationModel",
+ width: 220,
+ },
+ {
+ label: "鍗曚綅",
+ prop: "unit",
+ width:90
+ },
+ {
+ label: "鏁伴噺",
+ prop: "quantity",
+ },
+ {
+ label: "鎺掍骇鏁伴噺",
+ prop: "schedulingNum",
+ width: 100,
+ },
+ {
+ label: "寰呮帓鏁伴噺",
+ prop: "pendingQuantity",
+ width: 100,
+ },
+]);
+const tableData = ref([]);
+const selectedRows = ref([]);
+const tableLoading = ref(false);
+const page = reactive({
+ current: 1,
+ size: 100,
+ total: 0,
+});
+const formDia = ref()
+const { proxy } = getCurrentInstance()
+
+// 鏌ヨ鍒楄〃
+/** 鎼滅储鎸夐挳鎿嶄綔 */
+const handleQuery = () => {
+ page.current = 1;
+ getList();
+};
+const changeDaterange = (value) => {
+ if (value) {
+ searchForm.value.entryDateStart = value[0];
+ searchForm.value.entryDateEnd = value[1];
+ } else {
+ searchForm.value.entryDateStart = undefined;
+ searchForm.value.entryDateEnd = undefined;
+ }
+ handleQuery();
+};
+const pagination = (obj) => {
+ page.current = obj.page;
+ page.size = obj.limit;
+ getList();
+};
+const getList = () => {
+ tableLoading.value = true;
+ // 鏋勯�犱竴涓柊鐨勫璞★紝涓嶅寘鍚玡ntryDate瀛楁
+ const params = { ...searchForm.value, ...page };
+ params.entryDate = undefined
+ schedulingListPage(params).then((res) => {
+ tableLoading.value = false;
+ // 澶勭悊姣忔潯鏁版嵁锛屽鍔爌endingQuantity瀛楁
+ tableData.value = res.data.records.map(item => ({
+ ...item,
+ pendingQuantity: (Number(item.quantity) || 0) - (Number(item.schedulingNum) || 0)
+ }));
+ page.total = res.data.total;
+ }).catch(() => {
+ tableLoading.value = false;
+ })
+};
+// 琛ㄦ牸閫夋嫨鏁版嵁
+const handleSelectionChange = (selection) => {
+ selectedRows.value = selection;
+};
+
+// 鎵撳紑寮规
+const openForm = (type) => {
+ if (selectedRows.value.length !== 1) {
+ proxy.$message.error("璇烽�夋嫨涓�鏉℃暟鎹�");
+ return;
+ }
+ if (selectedRows.value[0].pendingQuantity == 0) {
+ proxy.$message.warning("鏃犻渶鍐嶆淳宸�");
+ return;
+ }
+ nextTick(() => {
+ formDia.value?.openDialog(type, selectedRows.value[0])
+ })
+};
+
+onMounted(() => {
+ searchForm.value.entryDate = [
+ dayjs().format("YYYY-MM-DD"),
+ dayjs().add(1, "day").format("YYYY-MM-DD"),
+ ]
+ searchForm.value.entryDateStart = dayjs().format("YYYY-MM-DD")
+ searchForm.value.entryDateEnd = dayjs().add(1, "day").format("YYYY-MM-DD")
+ getList();
+});
+</script>
+
+<style scoped></style>
diff --git a/src/views/productionManagement/productionOrder/index.vue b/src/views/productionManagement/productionOrder/index.vue
new file mode 100644
index 0000000..f14ec66
--- /dev/null
+++ b/src/views/productionManagement/productionOrder/index.vue
@@ -0,0 +1,374 @@
+<template>
+ <div class="app-container">
+ <div class="search_form">
+ <div>
+ <span class="search_title">浜у搧澶х被锛�</span>
+ <el-tree-select
+ v-model="searchForm.productCategory"
+ :data="productOptions"
+ placeholder="璇烽�夋嫨"
+ clearable
+ check-strictly
+ :render-after-expand="false"
+ style="width: 240px"
+ @change="handleQuery"
+ />
+ <span class="search_title ml10">褰曞叆鏃ユ湡锛�</span>
+ <el-date-picker v-model="searchForm.registerDate" value-format="YYYY-MM-DD" format="YYYY-MM-DD" type="daterange"
+ placeholder="璇烽�夋嫨" clearable @change="changeDaterange" />
+ <el-button type="primary" @click="handleQuery" style="margin-left: 10px"
+ >鎼滅储</el-button
+ >
+ </div>
+ <div>
+ <el-button type="primary" @click="openDialog('create')">鏂板璁㈠崟</el-button>
+ <el-button @click="handleOut">瀵煎嚭</el-button>
+ </div>
+ </div>
+ <div class="table_list">
+ <PIMTable
+ rowKey="id"
+ :column="tableColumn"
+ :tableData="tableData"
+ :page="page"
+ :tableLoading="tableLoading"
+ @pagination="pagination"
+ >
+ <template #action="{ row }">
+ <el-button type="primary" link @click="handleEdit(row)">缂栬緫</el-button>
+ <el-button type="danger" link @click="handleDelete(row)">鍒犻櫎</el-button>
+ </template>
+ </PIMTable>
+ </div>
+ <el-dialog v-model="dialogVisible" :title="dialogTitle" width="40%" @close="closeDialog">
+ <el-form ref="formRef" :model="form" :rules="formRules" label-width="100px">
+ <el-form-item label="褰曞叆鏃ユ湡" prop="registerDate">
+ <el-date-picker v-model="form.registerDate" type="date" value-format="YYYY-MM-DD" format="YYYY-MM-DD" placeholder="璇烽�夋嫨褰曞叆鏃ユ湡" style="width: 100%"/>
+ </el-form-item>
+ <el-form-item label="浜у搧澶х被" prop="productCategory">
+ <el-input
+ v-model="form.productCategory"
+ placeholder="璇疯緭鍏ヤ骇鍝佸ぇ绫�"
+ clearable
+ />
+ </el-form-item>
+ <el-form-item label="瑙勬牸鍨嬪彿" prop="specificationModel">
+ <el-input
+ v-model="form.specificationModel"
+ placeholder="璇疯緭鍏ヨ鏍煎瀷鍙�"
+ clearable
+ />
+ </el-form-item>
+ <el-form-item label="鍗曚綅" prop="unit">
+ <el-input
+ v-model="form.unit"
+ placeholder="璇疯緭鍏ュ崟浣�"
+ clearable
+ />
+ </el-form-item>
+ <el-form-item label="鏁伴噺" prop="quantity">
+ <el-input-number v-model="form.quantity" :min="0" :step="0.1" style="width: 100%"/>
+ </el-form-item>
+ </el-form>
+ <template #footer>
+ <div class="dialog-footer">
+ <el-button type="primary" @click="submitForm">纭</el-button>
+ <el-button @click="closeDialog">鍙栨秷</el-button>
+ </div>
+ </template>
+ </el-dialog>
+ </div>
+</template>
+
+<script setup>
+import {onMounted, ref, reactive, toRefs, getCurrentInstance} from "vue";
+import { ElMessageBox } from "element-plus";
+import dayjs from "dayjs";
+import {schedulingListPage, addProductionOrder, updateProductionOrder, deleteProductionOrder} from "@/api/productionManagement/productionOrder.js";
+import {productTreeList} from "@/api/basicData/product.js";
+const { proxy } = getCurrentInstance();
+
+const tableColumn = ref([
+ {
+ label: "褰曞叆鏃ユ湡",
+ prop: "registerDate",
+ width: 120,
+ },
+ {
+ label: "鐢熶骇璁㈠崟鍙�",
+ prop: "orderNo",
+ },
+ {
+ label: "浜у搧澶х被",
+ prop: "productCategory",
+ },
+ {
+ label: "瑙勬牸鍨嬪彿",
+ prop: "specificationModel",
+ },
+ {
+ label: "鍗曚綅",
+ prop: "unit",
+ },
+ {
+ label: "鏁伴噺",
+ prop: "quantity",
+ },
+ // {
+ // label: "鎺掍骇鏁伴噺",
+ // prop: "schedulingNum",
+ // width: 100,
+ // },
+ // {
+ // label: "瀹屽伐鏁伴噺",
+ // prop: "successNum",
+ // width: 100,
+ // },
+ {
+ label: "鎿嶄綔",
+ prop: "action",
+ width: 120,
+ fixed: "right",
+ dataType: "slot",
+ align: "center",
+ slot: "action"
+ }
+]);
+const tableData = ref([]);
+const tableLoading = ref(false);
+const page = ref({
+ current: 1,
+ size: 100,
+ total: 0,
+});
+const dialogVisible = ref(false);
+const dialogTitle = ref("");
+const dialogMode = ref(""); // 'create' 鎴� 'edit'
+const formRef = ref();
+const productOptions = ref([]);
+const form = reactive({
+ id: null,
+ registerDate: dayjs().format("YYYY-MM-DD"),
+ productCategory: "",
+ specificationModel: "",
+ unit: "",
+ quantity: null,
+});
+const formRules = {
+ registerDate: [{ required: true, message: "璇烽�夋嫨褰曞叆鏃ユ湡", trigger: "change" }],
+ productCategory: [{ required: true, message: "璇疯緭鍏ヤ骇鍝佸ぇ绫�", trigger: "blur" }],
+ specificationModel: [{ required: true, message: "璇疯緭鍏ヨ鏍煎瀷鍙�", trigger: "blur" }],
+ unit: [{ required: true, message: "璇疯緭鍏ュ崟浣�", trigger: "blur" }],
+ quantity: [{ required: true, message: "璇疯緭鍏ユ暟閲�", trigger: "blur" }],
+};
+
+const data = reactive({
+ searchForm: {
+ productCategory: "",
+ registerDate: null, // 褰曞叆鏃ユ湡
+ entryDateStart: undefined,
+ entryDateEnd: undefined,
+ },
+});
+const { searchForm } = toRefs(data);
+
+const openDialog = (mode, row = null) => {
+ dialogMode.value = mode;
+ if (mode === 'create') {
+ dialogTitle.value = "鏂板鐢熶骇璁㈠崟";
+ resetForm();
+ } else if (mode === 'edit') {
+ dialogTitle.value = "缂栬緫鐢熶骇璁㈠崟";
+ resetForm();
+
+ console.log('缂栬緫鏁版嵁:', row);
+
+ // 濉厖缂栬緫鏁版嵁
+ form.id = row.id;
+ form.registerDate = row.registerDate;
+ form.productCategory = row.productCategory;
+ form.specificationModel = row.specificationModel;
+ form.unit = row.unit;
+ form.quantity = row.quantity;
+ }
+
+ dialogVisible.value = true;
+};
+
+const closeDialog = () => {
+ dialogVisible.value = false;
+};
+
+const resetForm = () => {
+ form.id = null;
+ form.registerDate = dayjs().format("YYYY-MM-DD");
+ form.productCategory = "";
+ form.specificationModel = "";
+ form.unit = "";
+ form.quantity = null;
+};
+
+const submitForm = () => {
+ formRef.value?.validate(async (valid) => {
+ if (!valid) return;
+ try {
+ const payload = {
+ registerDate: form.registerDate,
+ productCategory: form.productCategory,
+ specificationModel: form.specificationModel,
+ unit: form.unit,
+ quantity: form.quantity,
+ };
+
+ if (dialogMode.value === 'create') {
+ await addProductionOrder(payload);
+ proxy.$modal.msgSuccess("鏂板鎴愬姛");
+ } else if (dialogMode.value === 'edit') {
+ payload.id = form.id;
+ await updateProductionOrder(payload);
+ proxy.$modal.msgSuccess("缂栬緫鎴愬姛");
+ }
+
+ closeDialog();
+ getList();
+ } catch (err) {
+ console.error(`${dialogMode.value === 'create' ? '鏂板' : '缂栬緫'}澶辫触`, err);
+ proxy.$modal.msgError(`${dialogMode.value === 'create' ? '鏂板' : '缂栬緫'}澶辫触锛岃閲嶈瘯`);
+ }
+ });
+};
+
+// 缂栬緫鏂规硶
+const handleEdit = (row) => {
+ openDialog('edit', row);
+};
+
+// 鍒犻櫎鏂规硶
+const handleDelete = (row) => {
+ proxy.$modal.confirm(`纭畾瑕佸垹闄ょ敓浜ц鍗�"${row.orderNo}"鍚楋紵`, "鍒犻櫎纭", {
+ confirmButtonText: "纭畾",
+ cancelButtonText: "鍙栨秷",
+ type: "warning",
+ }).then(async () => {
+ try {
+ await deleteProductionOrder([row.id]);
+ proxy.$modal.msgSuccess("鍒犻櫎鎴愬姛");
+ getList(); // 鍒锋柊鍒楄〃
+ } catch (err) {
+ console.error("鍒犻櫎澶辫触", err);
+ proxy.$modal.msgError("鍒犻櫎澶辫触锛岃閲嶈瘯");
+ }
+ }).catch(() => {
+ proxy.$modal.msg("宸插彇娑堝垹闄�");
+ });
+};
+
+const getProductOptions = () => {
+ return productTreeList().then((res) => {
+ productOptions.value = convertIdToValue(res || []);
+ });
+};
+
+const convertIdToValue = (data) => {
+ return data.map((item) => {
+ const { id, children, ...rest } = item;
+ const newItem = {
+ ...rest,
+ value: id,
+ };
+ if (children && children.length > 0) {
+ newItem.children = convertIdToValue(children);
+ }
+ return newItem;
+ });
+};
+
+const findNodeById = (nodes, value) => {
+ for (let i = 0; i < nodes.length; i++) {
+ if (nodes[i].value === value) {
+ return nodes[i].label;
+ }
+ if (nodes[i].children && nodes[i].children.length > 0) {
+ const label = findNodeById(nodes[i].children, value);
+ if (label) return label;
+ }
+ }
+ return null;
+};
+
+
+// 鏌ヨ鍒楄〃
+/** 鎼滅储鎸夐挳鎿嶄綔 */
+const handleQuery = () => {
+ page.value.current = 1;
+ getList();
+};
+const pagination = (obj) => {
+ page.value.current = obj.page;
+ page.value.size = obj.limit;
+ getList();
+};
+const changeDaterange = (value) => {
+ if (value) {
+ searchForm.value.entryDateStart = value[0];
+ searchForm.value.entryDateEnd = value[1];
+ } else {
+ searchForm.value.entryDateStart = undefined;
+ searchForm.value.entryDateEnd = undefined;
+ }
+ handleQuery();
+};
+const getList = () => {
+ tableLoading.value = true;
+ // 鏋勯�犱竴涓柊鐨勫璞★紝涓嶅寘鍚玡ntryDate瀛楁鍜� total 瀛楁
+ const { total, ...pageParams } = page.value;
+ const params = { ...searchForm.value, ...pageParams };
+ params.registerDate = undefined;
+ if (params.productCategory) {
+ // 濡傛灉鏄璞$被鍨嬶紝鑾峰彇鍏秎abel锛堝悕绉帮級鑰屼笉鏄痸alue锛圛D锛�
+ if (typeof params.productCategory === "object") {
+ params.productCategory = findNodeById(productOptions.value, params.productCategory) || params.productCategory;
+ }
+ // 濡傛灉鏄疘D锛岃浆鎹负鍚嶇О
+ else if (typeof params.productCategory === "string" || typeof params.productCategory === "number") {
+ const categoryName = findNodeById(productOptions.value, params.productCategory);
+ if (categoryName) {
+ params.productCategory = categoryName;
+ }
+ }
+ }
+ schedulingListPage(params).then((res) => {
+ tableLoading.value = false;
+ tableData.value = res.data.records;
+ page.value.total = res.data.total;
+ }).catch(() => {
+ tableLoading.value = false;
+ })
+};
+
+// 瀵煎嚭
+const handleOut = () => {
+ ElMessageBox.confirm("閫変腑鐨勫唴瀹瑰皢琚鍑猴紝鏄惁纭瀵煎嚭锛�", "瀵煎嚭", {
+ confirmButtonText: "纭",
+ cancelButtonText: "鍙栨秷",
+ type: "warning",
+ })
+ .then(() => {
+ proxy.download("/salesLedger/scheduling/export", {}, "鐢熶骇璁㈠崟.xlsx");
+ })
+ .catch(() => {
+ proxy.$modal.msg("宸插彇娑�");
+ });
+};
+
+onMounted(() => {
+ getProductOptions();
+ // 涓嶈缃粯璁ゆ棩鏈燂紝鍏ㄩ儴鏉′欢涓虹┖鍔犺浇
+ searchForm.value.registerDate = null;
+ searchForm.value.entryDateStart = undefined;
+ searchForm.value.entryDateEnd = undefined;
+ getList();
+});
+</script>
+
+<style scoped lang="scss"></style>
diff --git a/src/views/productionManagement/productionReporting/index.vue b/src/views/productionManagement/productionReporting/index.vue
new file mode 100644
index 0000000..1e45199
--- /dev/null
+++ b/src/views/productionManagement/productionReporting/index.vue
@@ -0,0 +1,401 @@
+<template>
+ <div class="app-container">
+ <div class="search_form">
+ <el-form :model="searchForm" :inline="true">
+ <el-form-item label="鎺掍骇鏃ユ湡:">
+ <el-date-picker v-model="searchForm.entryDate" value-format="YYYY-MM-DD" format="YYYY-MM-DD" type="daterange"
+ placeholder="璇烽�夋嫨" clearable @change="changeDaterange" />
+ </el-form-item>
+ <el-form-item label="鐘舵��:">
+ <el-select v-model="searchForm.status" placeholder="璇烽�夋嫨鐘舵��" style="width: 140px" clearable>
+ <el-option label="寰呯敓浜�" :value="1"></el-option>
+ <el-option label="宸叉姤宸�" :value="3"></el-option>
+ <el-option label="鐢熶骇涓�" :value="2"></el-option>
+ </el-select>
+ </el-form-item>
+ <el-form-item>
+ <el-button type="primary" @click="handleQuery">鎼滅储</el-button>
+ </el-form-item>
+ </el-form>
+ </div>
+ <div class="table_list">
+ <div style="text-align: right" class="mb10">
+ <el-button type="primary" @click="openForm('add')">鐢熶骇鎶ュ伐</el-button>
+ </div>
+ <PIMTable
+ rowKey="id"
+ :column="tableColumn"
+ :tableData="tableData"
+ :page="page"
+ :isSelection="true"
+ :expandRowKeys="expandedRowKeys"
+ @expand-change="expandChange"
+ @selection-change="handleSelectionChange"
+ :tableLoading="tableLoading"
+ @pagination="pagination"
+ :total="page.total"
+ >
+ <template #expand="{ row }">
+ <el-table
+ :data="expandData"
+ border
+ show-summary
+ stripe
+ :summary-method="summarizeMainTable"
+ v-loading="childrenLoading"
+ >
+ <el-table-column
+ align="center"
+ label="搴忓彿"
+ type="index"
+ width="60"
+ />
+ <el-table-column label="鏈鐢熶骇鏁伴噺" prop="finishedNum" align="center" width="400">
+ <template #default="scope">
+ <el-input-number :step="0.01" :min="0" style="width: 100%"
+ v-model="scope.row.finishedNum"
+ :disabled="!scope.row.editType"
+ :precision="2"
+ placeholder="璇疯緭鍏�"
+ clearable
+ @change="changeNum(scope.row)"
+ />
+ </template>
+ </el-table-column>
+<!-- <el-table-column label="寰呯敓浜ф暟閲�" prop="pendingNum" width="240" align="center"></el-table-column>-->
+ <el-table-column label="鐢熶骇浜�" prop="schedulingUserId" width="400">
+ <template #default="scope">
+ <el-select
+ v-model="scope.row.schedulingUserId"
+ placeholder="閫夋嫨浜哄憳"
+ :disabled="!scope.row.editType"
+ style="width: 100%;"
+ >
+ <el-option
+ v-for="user in userList"
+ :key="user.userId"
+ :label="user.nickName"
+ :value="user.userId"
+ />
+ </el-select>
+ </template>
+ </el-table-column>
+ <el-table-column label="鐢熶骇鏃ユ湡" prop="schedulingDate" width="400">
+ <template #default="scope">
+ <el-date-picker
+ v-model="scope.row.schedulingDate"
+ type="date"
+ :disabled="!scope.row.editType"
+ placeholder="璇烽�夋嫨鏃ユ湡"
+ value-format="YYYY-MM-DD"
+ format="YYYY-MM-DD"
+ clearable
+ style="width: 100%"
+ />
+ </template>
+ </el-table-column>
+ <el-table-column label="鎿嶄綔" width="60">
+ <template #default="scope">
+ <el-button
+ link
+ type="primary"
+ size="small"
+ @click="changeEditType(scope.row)"
+ v-if="!scope.row.editType"
+ :disabled="scope.row.parentStatus === 3"
+ >缂栬緫</el-button
+ >
+ <el-button
+ link
+ type="primary"
+ size="small"
+ @click="saveReceiptPayment(scope.row)"
+ v-if="scope.row.editType"
+ >淇濆瓨</el-button
+ >
+ </template>
+ </el-table-column>
+ </el-table>
+ </template>
+ </PIMTable>
+ </div>
+ <form-dia ref="formDia" @close="handleQuery"></form-dia>
+ </div>
+</template>
+
+<script setup>
+import {onMounted, ref} from "vue";
+import FormDia from "@/views/productionManagement/productionReporting/components/formDia.vue";
+import {staffJoinDel, staffJoinListPage} from "@/api/personnelManagement/onboarding.js";
+import {ElMessageBox} from "element-plus";
+import dayjs from "dayjs";
+import {
+ productionReportUpdate,
+ workListPage,
+ workListPageById
+} from "@/api/productionManagement/productionReporting.js";
+import {userListNoPageByTenantId} from "@/api/system/user.js";
+
+const data = reactive({
+ searchForm: {
+ staffName: "",
+ entryDate: [
+ dayjs().format("YYYY-MM-DD"),
+ dayjs().add(1, "day").format("YYYY-MM-DD"),
+ ], // 褰曞叆鏃ユ湡
+ entryDateStart: dayjs().format("YYYY-MM-DD"),
+ entryDateEnd: dayjs().add(1, "day").format("YYYY-MM-DD"),
+ },
+});
+const { searchForm } = toRefs(data);
+const expandedRowKeys = ref([]);
+const expandData = ref([]);
+const userList = ref([])
+const tableColumn = ref([
+ {
+ type: "expand",
+ dataType: "slot",
+ slot: "expand",
+ },
+ {
+ label: "鐘舵��",
+ prop: "status",
+ dataType: "tag",
+ formatData: (params) => {
+ if (params == 3) {
+ return "宸叉姤宸�";
+ } else if (params == 1) {
+ return "寰呯敓浜�";
+ } else {
+ return '鐢熶骇涓�';
+ }
+ },
+ formatType: (params) => {
+ if (params == 3) {
+ return "success";
+ } else if (params == 1) {
+ return "primary";
+ } else {
+ return 'warning';
+ }
+ },
+ },
+ {
+ label: "鎺掍骇鏃ユ湡",
+ prop: "schedulingDate",
+ width: 120,
+ },
+ {
+ label: "鎺掍骇浜�",
+ prop: "schedulingUserName",
+ },
+ {
+ label: "浜у搧澶х被",
+ prop: "productCategory",
+ width: 150,
+ },
+ {
+ label: "瑙勬牸鍨嬪彿",
+ prop: "specificationModel",
+ width: 150,
+ },
+ {
+ label: "鍗曚綅",
+ prop: "unit",
+ },
+ {
+ label: "宸ュ簭",
+ prop: "process",
+ },
+ {
+ label: "鎺掍骇鏁伴噺",
+ prop: "schedulingNum",
+ width: 100,
+ },
+ {
+ label: "鐢熶骇鏁伴噺",
+ prop: "finishedNum",
+ width: 100,
+ },
+ {
+ label: "寰呯敓浜ф暟閲�",
+ prop: "pendingFinishNum",
+ width: 100,
+ },
+]);
+const tableData = ref([]);
+const selectedRows = ref([]);
+const tableLoading = ref(false);
+const childrenLoading = ref(false);
+const page = reactive({
+ current: 1,
+ size: 100,
+ total: 0,
+});
+const formDia = ref()
+const { proxy } = getCurrentInstance()
+
+// 鏌ヨ鍒楄〃
+/** 鎼滅储鎸夐挳鎿嶄綔 */
+const handleQuery = () => {
+ page.current = 1;
+ getList();
+};
+const changeDaterange = (value) => {
+ if (value) {
+ searchForm.value.entryDateStart = value[0];
+ searchForm.value.entryDateEnd = value[1];
+ } else {
+ searchForm.value.entryDateStart = undefined;
+ searchForm.value.entryDateEnd = undefined;
+ }
+ handleQuery();
+};
+const pagination = (obj) => {
+ page.current = obj.page;
+ page.size = obj.limit;
+ getList();
+};
+const getList = () => {
+ tableLoading.value = true;
+ const params = { ...searchForm.value, ...page };
+ params.entryDate = undefined
+ expandedRowKeys.value = []
+ workListPage(params).then(res => {
+ tableLoading.value = false;
+ tableData.value = res.data.records.map(item => ({
+ ...item,
+ pendingFinishNum: (Number(item.schedulingNum) || 0) - (Number(item.finishedNum) || 0)
+ }));
+ page.total = res.data.total;
+ }).catch(err => {
+ tableLoading.value = false;
+ })
+};
+// 灞曞紑琛�
+const expandChange = (row, expandedRows) => {
+ userListNoPageByTenantId().then((res) => {
+ userList.value = res.data;
+ });
+ if (expandedRows.length > 0) {
+ nextTick(() => {
+ expandedRowKeys.value = [];
+ try {
+ childrenLoading.value = true;
+ workListPageById({ id: row.id }).then((res) => {
+ childrenLoading.value = false;
+ const index = tableData.value.findIndex((item) => item.id === row.id);
+ if (index > -1) {
+ expandData.value = res.data.map(item => ({
+ ...item,
+ pendingNum: (Number(item.schedulingNum) || 0) - (Number(item.finishedNum) || 0),
+ parentStatus: row.status // 鏂板鐖惰〃鐘舵��
+ }));
+ }
+ expandedRowKeys.value.push(row.id);
+ });
+ } catch (error) {
+ childrenLoading.value = false;
+ console.log(error);
+ }
+ })
+ } else {
+ expandedRowKeys.value = [];
+ }
+};
+const changeNum = (row) => {
+ // 鎵惧埌鐖惰〃鏍兼暟鎹�
+ const parentRow = tableData.value.find(item => item.id === expandedRowKeys.value[0]);
+ // 璁$畻鎵�鏈夊瓙琛ㄦ牸 finishedNum 鐨勬�诲拰
+ const totalFinishedNum = expandData.value.reduce((sum, item) => sum + (Number(item.finishedNum) || 0), 0);
+ // 鐖惰〃鏍肩殑鎺掍骇鏁伴噺
+ const schedulingNum = parentRow ? Number(parentRow.schedulingNum) : 0;
+
+ if (totalFinishedNum > schedulingNum) {
+ // 鍥為��鏈杈撳叆
+ row.finishedNum = schedulingNum - (totalFinishedNum - Number(row.finishedNum));
+ proxy.$modal.msgWarning('鎵�鏈夋湰娆$敓浜ф暟閲忎箣鍜屼笉鍙ぇ浜庢帓浜ф暟閲�');
+ }
+ row.pendingNum = row.schedulingNum - row.finishedNum;
+}
+// 缂栬緫淇敼鐘舵��
+const changeEditType = (row) => {
+ row.editType = !row.editType;
+};
+// 淇濆瓨璁板綍
+const saveReceiptPayment = (row) => {
+ productionReportUpdate(row).then((res) => {
+ row.editType = !row.editType;
+ getList();
+ proxy.$modal.msgSuccess("鎻愪氦鎴愬姛");
+ });
+};
+// 琛ㄦ牸閫夋嫨鏁版嵁
+const handleSelectionChange = (selection) => {
+ selectedRows.value = selection;
+};
+const summarizeMainTable = (param) => {
+ return proxy.summarizeTable(param, [
+ "finishedNum"
+ ]);
+};
+// 鎵撳紑寮规
+const openForm = (type, row) => {
+ if (selectedRows.value.length !== 1) {
+ proxy.$message.error("璇烽�夋嫨涓�鏉℃暟鎹�");
+ return;
+ }
+ if (selectedRows.value[0].pendingFinishNum == 0) {
+ proxy.$message.warning("鏃犻渶鍐嶆姤宸�");
+ return;
+ }
+ nextTick(() => {
+ const rowInfo = type === 'add' ? selectedRows.value[0] : row
+ formDia.value?.openDialog(type, rowInfo)
+ })
+};
+
+// 鍒犻櫎
+const handleDelete = () => {
+ let ids = [];
+ if (selectedRows.value.length > 0) {
+ ids = selectedRows.value.map((item) => item.id);
+ } else {
+ proxy.$modal.msgWarning("璇烽�夋嫨鏁版嵁");
+ return;
+ }
+ ElMessageBox.confirm("閫変腑鐨勫唴瀹瑰皢琚垹闄わ紝鏄惁纭鍒犻櫎锛�", "瀵煎嚭", {
+ confirmButtonText: "纭",
+ cancelButtonText: "鍙栨秷",
+ type: "warning",
+ })
+ .then(() => {
+ staffJoinDel(ids).then((res) => {
+ proxy.$modal.msgSuccess("鍒犻櫎鎴愬姛");
+ getList();
+ });
+ })
+ .catch(() => {
+ proxy.$modal.msg("宸插彇娑�");
+ });
+};
+// 瀵煎嚭
+const handleOut = () => {
+ ElMessageBox.confirm("閫変腑鐨勫唴瀹瑰皢琚鍑猴紝鏄惁纭瀵煎嚭锛�", "瀵煎嚭", {
+ confirmButtonText: "纭",
+ cancelButtonText: "鍙栨秷",
+ type: "warning",
+ })
+ .then(() => {
+ proxy.download("/staff/staffJoinLeaveRecord/export", {staffState: 1}, "浜哄憳鍏ヨ亴.xlsx");
+ })
+ .catch(() => {
+ proxy.$modal.msg("宸插彇娑�");
+ });
+};
+onMounted(() => {
+ getList();
+});
+</script>
+
+<style scoped></style>
diff --git a/src/views/salesManagement/receiptPayment/index.vue b/src/views/salesManagement/receiptPayment/index.vue
new file mode 100644
index 0000000..fe285ba
--- /dev/null
+++ b/src/views/salesManagement/receiptPayment/index.vue
@@ -0,0 +1,640 @@
+<template>
+ <div class="app-container">
+ <div class="search_form">
+ <el-form :inline="true" :model="searchForm" style="width: 100%">
+ <el-row justify="space-between">
+ <el-col :span="24">
+ <el-form-item label="瀹㈡埛鍚嶇О">
+ <el-input
+ v-model="searchForm.customerName"
+ placeholder="璇疯緭鍏�"
+ @change="handleQuery"
+ clearable
+ prefix-icon="Search"
+ />
+ </el-form-item>
+ <el-form-item label="瀹㈡埛鍚堝悓鍙�">
+ <el-input
+ v-model="searchForm.customerContractNo"
+ placeholder="璇疯緭鍏�"
+ @change="handleQuery"
+ clearable
+ prefix-icon="Search"
+ />
+ </el-form-item>
+ <el-form-item label="椤圭洰鍚嶇О">
+ <el-input
+ v-model="searchForm.projectName"
+ placeholder="璇疯緭鍏�"
+ @change="handleQuery"
+ clearable
+ prefix-icon="Search"
+ />
+ </el-form-item>
+ <el-form-item>
+ <el-checkbox
+ v-model="searchForm.status"
+ label="涓嶆樉绀哄緟鍥炴涓�0"
+ @change="handleQuery"
+ />
+ </el-form-item>
+ <br/>
+ <el-form-item label="寮�绁ㄦ棩鏈�">
+ <el-date-picker style="width: 240px" v-model="searchForm.commonDate" value-format="YYYY-MM-DD"
+ format="YYYY-MM-DD" type="daterange" start-placeholder="寮�濮嬫椂闂�" end-placeholder="缁撴潫鏃堕棿" clearable
+ @change="changeDateRange" @clear="clearRange" />
+ </el-form-item>
+ <el-form-item>
+ <el-button type="primary" @click="handleQuery"> 鎼滅储 </el-button>
+ </el-form-item>
+ </el-col>
+ </el-row>
+ </el-form>
+ </div>
+ <div class="table_list">
+ <div class="actions">
+ <div></div>
+ <div>
+ <el-button type="primary" @click="openForm('add')">
+ 鏂板鍥炴
+ </el-button>
+ <el-button icon="Download" @click="handleOut"> 瀵煎嚭 </el-button>
+ </div>
+ </div>
+ <el-table
+ :data="tableData"
+ border
+ v-loading="tableLoading"
+ @selection-change="handleSelectionChange"
+ :row-key="(row) => row.id"
+ show-summary
+ :summary-method="summarizeMainTable"
+ :expand-row-keys="expandedRowKeys"
+ @expand-change="expandChange"
+ stripe
+ height="calc(100vh - 21.5em)"
+ >
+ <el-table-column align="center" type="selection" width="55" />
+ <el-table-column type="expand">
+ <template #default="props">
+ <el-table
+ :data="props.row.children"
+ border
+ show-summary
+ :summary-method="summarizeChildrenTable"
+ stripe
+ >
+ <el-table-column
+ align="center"
+ label="搴忓彿"
+ type="index"
+ width="60"
+ />
+ <el-table-column label="鍥炴鏃ユ湡" prop="receiptPaymentDate" width="130"/>
+ <el-table-column label="鍥炴閲戦" prop="receiptPaymentAmount">
+ <template #default="scope">
+ <el-input-number :step="0.01" :min="0" style="width: 100%"
+ v-model="scope.row.receiptPaymentAmount"
+ :disabled="!scope.row.editType"
+ :precision="2"
+ placeholder="璇疯緭鍏�"
+ clearable
+ />
+ </template>
+ </el-table-column>
+ <el-table-column label="鍥炴鏂瑰紡" prop="receiptPaymentType">
+ <template #default="scope">
+ <el-input
+ v-model="scope.row.receiptPaymentType"
+ placeholder="璇疯緭鍏�"
+ clearable
+ :disabled="!scope.row.editType"
+ />
+ </template>
+ </el-table-column>
+ <el-table-column label="鐧昏浜�" prop="registrant" width="90"/>
+ <el-table-column label="鐧昏鏃ユ湡" prop="createTime" width="130"/>
+ <el-table-column label="鎿嶄綔" width="150" align="center">
+ <template #default="scope">
+ <el-button
+ link
+ type="primary"
+ size="small"
+ @click="changeEditType(scope.row)"
+ v-if="!scope.row.editType"
+ :disabled="scope.row.registrant !== userStore.nickName"
+ >缂栬緫</el-button
+ >
+ <el-button
+ link
+ type="primary"
+ size="small"
+ @click="saveReceiptPayment(scope.row)"
+ v-if="scope.row.editType"
+ :disabled="scope.row.registrant !== userStore.nickName"
+ >淇濆瓨</el-button
+ >
+ <el-button
+ link
+ type="primary"
+ size="small"
+ @click="delReceiptRecord(scope.row)"
+ :disabled="scope.row.registrant !== userStore.nickName"
+ >鍒犻櫎</el-button
+ >
+ </template>
+ </el-table-column>
+ </el-table>
+ </template>
+ </el-table-column>
+ <el-table-column align="center" label="搴忓彿" type="index" width="60" />
+ <el-table-column
+ label="寮�绁ㄦ棩鏈�"
+ prop="invoiceDate"
+ show-overflow-tooltip
+ width="240"
+ />
+ <el-table-column
+ label="閿�鍞悎鍚屽彿"
+ prop="salesContractNo"
+ show-overflow-tooltip
+ width="240"
+ />
+ <el-table-column
+ label="瀹㈡埛鍚堝悓鍙�"
+ prop="customerContractNo"
+ show-overflow-tooltip
+ width="240"
+
+ />
+ <el-table-column
+ label="瀹㈡埛鍚嶇О"
+ prop="customerName"
+ show-overflow-tooltip
+ width="240"
+ />
+ <el-table-column
+ label="椤圭洰鍚嶇О"
+ prop="projectName"
+ show-overflow-tooltip
+ width="340"
+ />
+ <el-table-column
+ label="浜у搧澶х被"
+ prop="productCategory"
+ show-overflow-tooltip
+ width="100"
+ />
+ <el-table-column
+ label="鍙戠エ鍙�"
+ prop="invoiceNo"
+ show-overflow-tooltip
+ width="200"
+ />
+ <el-table-column
+ label="鍙戠エ閲戦(鍏�)"
+ prop="invoiceTotal"
+ show-overflow-tooltip
+ :formatter="formattedNumber"
+ width="200"
+ />
+ <el-table-column label="绋庣巼(%)" prop="taxRate" show-overflow-tooltip />
+ <el-table-column
+ label="鍥炴閲戦(鍏�)"
+ prop="receiptPaymentAmountTotal"
+ show-overflow-tooltip
+ :formatter="formattedNumber"
+ width="200"
+ />
+ <el-table-column
+ label="寰呭洖娆鹃噾棰�(鍏�)"
+ prop="noReceiptAmount"
+ show-overflow-tooltip
+ width="200"
+ >
+ <template #default="{ row, column }">
+ <el-text type="danger">
+ {{ formattedNumber(row, column, row.noReceiptAmount) }}
+ </el-text>
+ </template>
+ </el-table-column>
+ </el-table>
+ <pagination
+ v-show="total > 0"
+ :total="total"
+ layout="total, sizes, prev, pager, next, jumper"
+ :page="page.current"
+ :limit="page.size"
+ @pagination="paginationChange"
+ />
+ </div>
+ <el-dialog
+ v-model="dialogFormVisible"
+ title="鏂板鍙戠エ鍙烽〉闈�"
+ width="70%"
+ @close="closeDia"
+ >
+ <el-form
+ :model="form"
+ label-width="140px"
+ label-position="top"
+ :rules="rules"
+ ref="formRef"
+ >
+ <el-row :gutter="30">
+ <el-col :span="12">
+ <el-form-item label="閿�鍞悎鍚屽彿锛�" prop="salesContractNo">
+ <el-input
+ v-model="form.salesContractNo"
+ placeholder="鑷姩濉厖"
+ disabled
+ />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="瀹㈡埛鍚嶇О锛�" prop="customerName">
+ <el-input
+ v-model="form.customerName"
+ placeholder="鑷姩濉厖"
+ disabled
+ />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="30">
+ <el-col :span="12">
+ <el-form-item label="鍙戠エ鍙凤細" prop="invoiceNo">
+ <el-input
+ v-model="form.invoiceNo"
+ placeholder="鑷姩濉厖"
+ disabled
+ />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="鍙戠エ閲戦(鍏�)锛�" prop="invoiceTotal">
+ <el-input
+ type="number"
+ v-model="form.invoiceTotal"
+ placeholder="鑷姩濉厖"
+ :step="0.01"
+ disabled
+ />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="30">
+ <el-col :span="12">
+ <el-form-item label="绋庣巼锛�" prop="taxRate">
+ <el-input
+ type="number"
+ v-model="form.taxRate"
+ placeholder="鑷姩濉厖"
+ :step="0.01"
+ disabled
+ />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="鏈鍥炴閲戦锛�" prop="receiptPaymentAmount">
+ <el-input-number :step="0.01" :min="0" style="width: 100%"
+ :precision="2"
+ v-model="form.receiptPaymentAmount"
+ placeholder="璇疯緭鍏�"
+ clearable
+ />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="30">
+ <el-col :span="12">
+ <el-form-item label="鍥炴褰㈠紡锛�" prop="receiptPaymentType">
+ <el-input
+ v-model="form.receiptPaymentType"
+ placeholder="璇疯緭鍏�"
+ clearable
+ />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="鐧昏浜猴細" prop="registrant">
+ <el-input
+ v-model="form.registrant"
+ placeholder="璇疯緭鍏�"
+ clearable
+ disabled
+ />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="30">
+ <el-col :span="12">
+ <el-form-item label="鏉ユ鏃ユ湡锛�" prop="receiptPaymentDate">
+ <el-date-picker
+ style="width: 100%"
+ v-model="form.receiptPaymentDate"
+ value-format="YYYY-MM-DD"
+ format="YYYY-MM-DD"
+ type="date"
+ placeholder="璇烽�夋嫨"
+ clearable
+ />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ </el-form>
+ <template #footer>
+ <div class="dialog-footer">
+ <el-button type="primary" @click="submitForm">纭</el-button>
+ <el-button @click="closeDia">鍙栨秷</el-button>
+ </div>
+ </template>
+ </el-dialog>
+ </div>
+</template>
+
+<script setup>
+import pagination from "@/components/PIMTable/Pagination.vue";
+import { onMounted, ref } from "vue";
+import {
+ receiptPaymentSaveOrUpdate,
+ bindInvoiceNoRegPage,
+ invoiceInfo,
+ receiptPaymentHistoryListNoPage,
+ receiptPaymentDel,
+} from "../../../api/salesManagement/receiptPayment.js";
+import useUserStore from "@/store/modules/user";
+import { ElMessage, ElMessageBox } from "element-plus";
+import useFormData from "@/hooks/useFormData";
+
+const userStore = useUserStore();
+const { proxy } = getCurrentInstance();
+const tableData = ref([]);
+const selectedRows = ref([]);
+const tableLoading = ref(false);
+const page = reactive({
+ current: 1,
+ size: 100,
+});
+const total = ref(0);
+const expandedRowKeys = ref([]);
+
+// 鐢ㄦ埛淇℃伅琛ㄥ崟寮规鏁版嵁
+const dialogFormVisible = ref(false);
+const data = reactive({
+ searchForm: {
+ searchText: "",
+ status: true,
+ customerName: "",
+ customerContractNo: "",
+ projectName: "",
+ },
+ form: {
+ salesContractNo: "",
+ customerName: "",
+ invoiceNo: "",
+ invoiceTotal: "",
+ taxRate: "",
+ receiptPaymentAmount: "",
+ receiptPaymentType: "",
+ registrant: "",
+ receiptPaymentDate: "",
+ },
+ rules: {
+ salesContractNo: [{ required: true, message: "璇烽�夋嫨", trigger: "change" }],
+ customerName: [{ required: true, message: "璇疯緭鍏�", trigger: "blur" }],
+ invoiceNo: [{ required: true, message: "璇烽�夋嫨", trigger: "change" }],
+ invoiceTotal: [{ required: true, message: "璇疯緭鍏�", trigger: "blur" }],
+ taxRate: [{ required: true, message: "璇烽�夋嫨", trigger: "change" }],
+ receiptPaymentAmount: [
+ { required: true, message: "璇烽�夋嫨", trigger: "change" },
+ ],
+ receiptPaymentType: [
+ { required: true, message: "璇烽�夋嫨", trigger: "change" },
+ ],
+ registrant: [{ required: true, message: "璇烽�夋嫨", trigger: "change" }],
+ receiptPaymentDate: [
+ { required: true, message: "璇烽�夋嫨", trigger: "change" },
+ ],
+ },
+});
+const { form, rules } = toRefs(data);
+const { form: searchForm, resetForm } = useFormData(data.searchForm);
+
+const formattedNumber = (row, column, cellValue) => {
+ return parseFloat(cellValue).toFixed(2);
+};
+
+const changeDateRange = (date) => {
+ if (date) {
+ searchForm.invoiceDateStart = date[0];
+ searchForm.invoiceDateEnd = date[1];
+ getList();
+ }
+};
+
+const clearRange = () => {
+ searchForm.commonDate = [];
+ searchForm.invoiceDateStart = undefined;
+ searchForm.invoiceDateEnd = undefined;
+ getList();
+};
+// 鏌ヨ鍒楄〃
+/** 鎼滅储鎸夐挳鎿嶄綔 */
+const handleQuery = () => {
+ page.current = 1;
+ getList();
+};
+const paginationChange = (obj) => {
+ page.current = obj.page;
+ page.size = obj.limit;
+ getList();
+};
+const getList = () => {
+ tableLoading.value = true;
+ bindInvoiceNoRegPage({ ...searchForm, ...page })
+ .then((res) => {
+ tableLoading.value = false;
+ tableData.value = res.data.records;
+ total.value = res.data.total;
+ if (expandedRowKeys.value.length > 0) {
+ const arr = []
+ const index = tableData.value.findIndex(item => item.id === expandedRowKeys.value[0]);
+ if (index > -1) {
+ arr.push(tableData.value[index]);
+ expandChange(tableData.value[index], arr)
+ }
+ }
+ })
+ .catch(() => {
+ tableLoading.value = false;
+ });
+};
+// 灞曞紑琛�
+const expandChange = (row, expandedRows) => {
+ if (expandedRows.length > 0) {
+ expandedRowKeys.value = [];
+ try {
+ receiptPaymentHistoryListNoPage({
+ invoiceLedgerId: row.id,
+ type: 1,
+ }).then((res) => {
+ const index = tableData.value.findIndex((item) => item.id === row.id);
+ if (index > -1) {
+ if (res?.length > 0) {
+ res.forEach((item) => {
+ item.editType = false;
+ });
+ }
+ tableData.value[index].children = res;
+ }
+ expandedRowKeys.value.push(row.id);
+ });
+ } catch (error) {
+ console.log(error);
+ }
+ } else {
+ expandedRowKeys.value = [];
+ }
+};
+// 琛ㄦ牸閫夋嫨鏁版嵁
+const handleSelectionChange = (selection) => {
+ console.log("selection", selection);
+ selectedRows.value = selection.filter(
+ (item) => item.customerContractNo !== null
+ );
+};
+// 涓昏〃鍚堣鏂规硶
+const summarizeMainTable = (param) => {
+ return proxy.summarizeTable(
+ param,
+ ["invoiceTotal", "receiptPaymentAmountTotal", "noReceiptAmount"],
+ {
+ ticketsNum: { noDecimal: true }, // 涓嶄繚鐣欏皬鏁�
+ futureTickets: { noDecimal: true }, // 涓嶄繚鐣欏皬鏁�
+ }
+ );
+};
+// 瀛愯〃鍚堣鏂规硶
+const summarizeChildrenTable = (param) => {
+ return proxy.summarizeTable(param, ["receiptPaymentAmount"]);
+};
+// 鎵撳紑寮规
+const openForm = () => {
+ form.value = {};
+ if (selectedRows.value.length !== 1) {
+ proxy.$modal.msgError("璇烽�夋嫨涓�鏉℃暟鎹�");
+ return;
+ }
+ if (selectedRows.value[0].noReceiptAmount == 0) {
+ proxy.$modal.msgWarning("鏃犻渶鍐嶅洖娆�");
+ return;
+ }
+ invoiceInfo({ id: selectedRows.value[0].id }).then((res) => {
+ form.value = { ...res.data };
+ form.value.invoiceLedgerId = form.value.id;
+ form.value.id = "";
+ form.value.registrant = userStore.nickName;
+ });
+ dialogFormVisible.value = true;
+};
+// 鎻愪氦琛ㄥ崟
+const submitForm = () => {
+ proxy.$refs["formRef"].validate((valid) => {
+ if (valid) {
+ receiptPaymentSaveOrUpdate(form.value).then((res) => {
+ proxy.$modal.msgSuccess("鎻愪氦鎴愬姛");
+ closeDia();
+ getList();
+ });
+ }
+ });
+};
+// 鍏抽棴寮规
+const closeDia = () => {
+ proxy.resetForm("formRef");
+ dialogFormVisible.value = false;
+};
+
+// 鍒犻櫎鍥炴璁板綍
+const delReceiptRecord = (row) => {
+ console.log("row", row);
+ ElMessageBox.confirm("纭鍒犻櫎璇ヨ褰曞悧锛�", "鎻愮ず", {
+ confirmButtonText: "纭畾",
+ cancelButtonText: "鍙栨秷",
+ type: "warning",
+ })
+ .then(async () => {
+ try {
+ let ids = [];
+ ids.push(row.id);
+ await receiptPaymentDel(ids);
+ ElMessage.success("鍒犻櫎鎴愬姛");
+ getList();
+ } catch (error) {
+ console.error("鍒犻櫎澶辫触:", error);
+ ElMessage.error("鍒犻櫎澶辫触");
+ }
+ })
+ .catch(() => {
+ ElMessage.info("宸插彇娑堝垹闄�");
+ });
+};
+
+// 缂栬緫淇敼鐘舵��
+const changeEditType = (row) => {
+ row.editType = !row.editType;
+};
+
+// 淇濆瓨鍥炴璁板綍
+const saveReceiptPayment = (row) => {
+ let updateData = {
+ id: row.id,
+ receiptPaymentType: row.receiptPaymentType,
+ receiptPaymentAmount: row.receiptPaymentAmount,
+ };
+ receiptPaymentSaveOrUpdate(updateData).then((res) => {
+ row.editType = !row.editType;
+ getList();
+ proxy.$modal.msgSuccess("鎻愪氦鎴愬姛");
+ });
+};
+
+// 瀵煎嚭
+const handleOut = () => {
+ ElMessageBox.confirm("閫変腑鐨勫唴瀹瑰皢琚鍑猴紝鏄惁纭瀵煎嚭锛�", "瀵煎嚭", {
+ confirmButtonText: "纭",
+ cancelButtonText: "鍙栨秷",
+ type: "warning",
+ })
+ .then(() => {
+ const ids = selectedRows.value.map((item) => item.id);
+ proxy.download(
+ `/receiptPayment/export`,
+ { ids: `${ids}` },
+ "鍥炴鐧昏妗f.xlsx"
+ );
+ })
+ .catch(() => {
+ proxy.$modal.msg("宸插彇娑�");
+ });
+};
+
+onMounted(() => {
+ getList();
+});
+</script>
+
+<style scoped lang="scss">
+.table_list {
+ margin-top: unset;
+}
+::v-deep(.el-checkbox__label) {
+ font-weight: bold;
+}
+.actions {
+ display: flex;
+ justify-content: space-between;
+ margin-bottom: 10px;
+}
+</style>
diff --git a/src/views/salesManagement/receiptPaymentHistory/index.vue b/src/views/salesManagement/receiptPaymentHistory/index.vue
new file mode 100644
index 0000000..d7ef8b2
--- /dev/null
+++ b/src/views/salesManagement/receiptPaymentHistory/index.vue
@@ -0,0 +1,208 @@
+<template>
+ <div class="app-container">
+ <el-form :model="searchForm" :inline="true">
+ <el-form-item label="瀹㈡埛鍚嶇О">
+ <el-input
+ v-model="searchForm.searchText"
+ placeholder="杈撳叆瀹㈡埛鍚嶇О鎼滅储"
+ @change="handleQuery"
+ clearable
+ :prefix-icon="Search"
+ />
+ </el-form-item>
+ <el-form-item label="瀹㈡埛鍚堝悓鍙�">
+ <el-input
+ v-model="searchForm.customerContractNo"
+ placeholder="杈撳叆瀹㈡埛鍚堝悓鍙�"
+ @change="handleQuery"
+ clearable
+ :prefix-icon="Search"
+ />
+ </el-form-item>
+ <el-form-item label="椤圭洰鍚嶇О">
+ <el-input
+ v-model="searchForm.projectName"
+ placeholder="杈撳叆椤圭洰鍚嶇О"
+ @change="handleQuery"
+ clearable
+ :prefix-icon="Search"
+ />
+ </el-form-item>
+ <el-form-item label="鍥炴鏃ユ湡">
+ <el-date-picker
+ v-model="searchForm.receiptPaymentDate"
+ value-format="YYYY-MM-DD"
+ format="YYYY-MM-DD"
+ type="daterange"
+ start-placeholder="寮�濮嬫椂闂�"
+ end-placeholder="缁撴潫鏃堕棿"
+ clearable
+ style="width: 300px"
+ @change="changeDateRange"
+ @clear="clearRange"
+ />
+ </el-form-item>
+ <el-form-item>
+ <el-button type="primary" @click="handleQuery"> 鎼滅储 </el-button>
+ </el-form-item>
+ </el-form>
+ <div class="table_list">
+ <PIMTable
+ rowKey="id"
+ :column="tableColumn"
+ :tableData="tableData"
+ :page="page"
+ :isSelection="true"
+ :isShowSummary="isShowSummarySon"
+ :summaryMethod="summarizeMainTable1"
+ :tableLoading="tableLoading"
+ :total="page.total"
+ @pagination="pagination"
+ @selection-change="handleSelectionChange"
+ ></PIMTable>
+ </div>
+ </div>
+</template>
+
+<script setup>
+import { ref } from "vue";
+import { Search } from "@element-plus/icons-vue";
+import { receiptPaymentHistoryListPage } from "@/api/salesManagement/receiptPayment.js";
+import useFormData from "@/hooks/useFormData";
+import dayjs from "dayjs";
+
+const { proxy } = getCurrentInstance();
+const tableColumn = ref([
+ {
+ label: "閿�鍞悎鍚屽彿",
+ prop: "salesContractNo",
+ width:240
+ },
+ {
+ label: "瀹㈡埛鍚堝悓鍙�",
+ prop: "customerContractNo",
+ width:240
+ },
+ {
+ label: "鍥炴鏃ユ湡",
+ prop: "receiptPaymentDate",
+ width:100
+ },
+ {
+ label: "瀹㈡埛鍚嶇О",
+ prop: "customerName",
+ width:240
+ },
+ {
+ label: "椤圭洰鍚嶇О",
+ prop: "projectName",
+ width:200
+ },
+ {
+ label: "鍥炴閲戦锛堝厓锛�",
+ prop: "receiptPaymentAmount",
+ width:200,
+ formatData: (params) => {
+ return params ? parseFloat(params).toFixed(2) : 0;
+ },
+ },
+ {
+ label: "鍥炴鏂瑰紡",
+ prop: "receiptPaymentType",
+ },
+ {
+ label: "鐧昏浜�",
+ prop: "registrant",
+ },
+ {
+ label: "鐧昏鏃ユ湡",
+ prop: "createTime",
+ width:100
+ },
+]);
+const tableData = ref([]);
+const selectedRows = ref([]);
+const tableLoading = ref(false);
+const page = reactive({
+ current: 1,
+ size: 100,
+ total: 0,
+});
+const total = ref(0);
+
+const { form: searchForm } = useFormData({
+ searchText: undefined,
+ receiptPaymentDate: [
+ dayjs().startOf("month").format("YYYY-MM-DD"),
+ dayjs().endOf("month").format("YYYY-MM-DD"),
+ ],
+ receiptPaymentDateStart: dayjs()
+ .startOf("month")
+ .format("YYYY-MM-DD 00:00:00"),
+ receiptPaymentDateEnd: dayjs().endOf("month").format("YYYY-MM-DD 23:59:59"),
+ customerContractNo: undefined,
+ projectName: undefined,
+});
+const { receipt_payment_type } = proxy.useDict("receipt_payment_type");
+const isShowSummarySon = ref(true);
+// 鏌ヨ鍒楄〃
+/** 鎼滅储鎸夐挳鎿嶄綔 */
+const handleQuery = () => {
+ page.current = 1;
+ getList();
+};
+const pagination = (obj) => {
+ page.current = obj.page;
+ page.size = obj.limit;
+ getList();
+};
+const getList = () => {
+ tableLoading.value = true;
+ const { receiptPaymentDate, ...rest } = searchForm;
+ receiptPaymentHistoryListPage({ ...rest, ...page }).then((res) => {
+ tableLoading.value = false;
+ tableData.value = res.records;
+ page.total = res.total;
+ });
+};
+// 瀛愯〃鍚堣鏂规硶
+const summarizeMainTable1 = (param) => {
+ return proxy.summarizeTable(param, ["receiptPaymentAmount"], {
+ ticketsNum: { noDecimal: true }, // 涓嶄繚鐣欏皬鏁�
+ futureTickets: { noDecimal: true }, // 涓嶄繚鐣欏皬鏁�
+ });
+};
+// 琛ㄦ牸閫夋嫨鏁版嵁
+const handleSelectionChange = (selection) => {
+ selectedRows.value = selection;
+};
+
+const changeDateRange = (date) => {
+ if (date) {
+ searchForm.receiptPaymentDateStart = dayjs(date[0]).format(
+ "YYYY-MM-DD 00:00:00"
+ );
+ searchForm.receiptPaymentDateEnd = dayjs(date[1]).format(
+ "YYYY-MM-DD 23:59:59"
+ );
+ getList();
+ }
+};
+
+const clearRange = () => {
+ searchForm.receiptPaymentDate = [];
+ searchForm.receiptPaymentDateStart = undefined;
+ searchForm.receiptPaymentDateEnd = undefined;
+ getList();
+};
+
+onMounted(() => {
+ getList();
+});
+</script>
+
+<style scoped lang="scss">
+.table_list {
+ margin-top: unset;
+}
+</style>
diff --git a/src/views/salesManagement/salesLedger/index.vue b/src/views/salesManagement/salesLedger/index.vue
new file mode 100644
index 0000000..51ff6a5
--- /dev/null
+++ b/src/views/salesManagement/salesLedger/index.vue
@@ -0,0 +1,1018 @@
+<template>
+ <div class="app-container">
+ <div class="search_form">
+ <el-form :model="searchForm" :inline="true">
+ <el-form-item label="瀹㈡埛鍚嶇О锛�">
+ <el-input v-model="searchForm.customerName" placeholder="璇疯緭鍏�" clearable prefix-icon="Search"
+ @change="handleQuery" />
+ </el-form-item>
+ <el-form-item label="瀹㈡埛鍚堝悓鍙凤細">
+ <el-input v-model="searchForm.customerContractNo" placeholder="璇疯緭鍏�" clearable prefix-icon="Search"
+ @change="handleQuery" />
+ </el-form-item>
+ <el-form-item label="閿�鍞悎鍚屽彿锛�">
+ <el-input v-model="searchForm.salesContractNo" placeholder="璇疯緭鍏�" clearable prefix-icon="Search"
+ @change="handleQuery" />
+ </el-form-item>
+ <el-form-item label="椤圭洰鍚嶇О锛�">
+ <el-input v-model="searchForm.projectName" placeholder="璇疯緭鍏�" clearable prefix-icon="Search"
+ @change="handleQuery" />
+ </el-form-item>
+ <el-form-item label="褰曞叆鏃ユ湡锛�">
+ <el-date-picker v-model="searchForm.entryDate" value-format="YYYY-MM-DD" format="YYYY-MM-DD" type="daterange"
+ placeholder="璇烽�夋嫨" clearable @change="changeDaterange" />
+ </el-form-item>
+ <el-form-item>
+ <el-button type="primary" @click="handleQuery"> 鎼滅储 </el-button>
+ </el-form-item>
+ </el-form>
+ </div>
+ <div class="table_list">
+ <div class="actions">
+ <div></div>
+ <div>
+ <el-button type="primary" @click="openForm('add')">
+ 鏂板鍙拌处
+ </el-button>
+ <el-button @click="handleOut">瀵煎嚭</el-button>
+ <el-button type="danger" plain @click="handleDelete">鍒犻櫎</el-button>
+ </div>
+ </div>
+ <el-table :data="tableData" border v-loading="tableLoading" @selection-change="handleSelectionChange"
+ :expand-row-keys="expandedRowKeys" :row-key="(row) => row.id" show-summary style="width: 100%"
+ :summary-method="summarizeMainTable" @expand-change="expandChange" height="calc(100vh - 18.5em)" stripe>
+ <el-table-column align="center" type="selection" width="55" />
+ <el-table-column type="expand">
+ <template #default="props">
+ <el-table :data="props.row.children" border show-summary :summary-method="summarizeChildrenTable" stripe>
+ <el-table-column align="center" label="搴忓彿" type="index" width="60" />
+ <el-table-column label="浜у搧澶х被" prop="productCategory" />
+ <el-table-column label="瑙勬牸鍨嬪彿" prop="specificationModel" />
+ <el-table-column label="鍗曚綅" prop="unit" />
+ <el-table-column label="鏁伴噺" prop="quantity" />
+ <el-table-column label="绋庣巼(%)" prop="taxRate" />
+ <el-table-column label="鍚◣鍗曚环(鍏�)" prop="taxInclusiveUnitPrice" :formatter="formattedNumber" />
+ <el-table-column label="鍚◣鎬讳环(鍏�)" prop="taxInclusiveTotalPrice" :formatter="formattedNumber" />
+ <el-table-column label="涓嶅惈绋庢�讳环(鍏�)" prop="taxExclusiveTotalPrice" :formatter="formattedNumber" />
+ </el-table>
+ </template>
+ </el-table-column>
+ <el-table-column align="center" label="搴忓彿" type="index" width="60" />
+ <el-table-column label="閿�鍞悎鍚屽彿" prop="salesContractNo" width="180" show-overflow-tooltip />
+ <el-table-column label="瀹㈡埛鍚堝悓鍙�" prop="customerContractNo" width="180" show-overflow-tooltip />
+ <el-table-column label="瀹㈡埛鍚嶇О" prop="customerName" width="300" show-overflow-tooltip />
+ <el-table-column label="涓氬姟鍛�" prop="salesman" width="100" show-overflow-tooltip />
+ <el-table-column label="椤圭洰鍚嶇О" prop="projectName" width="180" show-overflow-tooltip />
+ <el-table-column label="浠樻鏂瑰紡" prop="paymentMethod" show-overflow-tooltip />
+ <el-table-column label="鍚堝悓閲戦(鍏�)" prop="contractAmount" width="220" show-overflow-tooltip
+ :formatter="formattedNumber" />
+ <el-table-column label="宸插紑绁ㄩ噾棰�(鍏�)" prop="invoiceTotal" width="220" show-overflow-tooltip
+ :formatter="formattedNumber" />
+ <el-table-column label="鏈紑绁ㄩ噾棰�(鍏�)" prop="noInvoiceAmountTotal" width="220" show-overflow-tooltip
+ :formatter="formattedNumber" />
+ <el-table-column label="鍥炴閲戦(鍏�)" prop="receiptPaymentAmountTotal" width="220" show-overflow-tooltip
+ :formatter="formattedNumber" />
+ <el-table-column label="寰呭洖娆鹃噾棰�(鍏�)" prop="noReceiptAmount" width="220" show-overflow-tooltip
+ :formatter="formattedNumber" />
+ <el-table-column label="褰曞叆浜�" prop="entryPersonName" width="100" show-overflow-tooltip />
+ <el-table-column label="褰曞叆鏃ユ湡" prop="entryDate" width="120" show-overflow-tooltip />
+ <el-table-column label="绛捐鏃ユ湡" prop="executionDate" width="120" show-overflow-tooltip />
+ <el-table-column fixed="right" label="鎿嶄綔" min-width="140" align="center">
+ <template #default="scope">
+ <el-button link type="primary" size="small" :disabled="scope.row.invoiceTotal>0 || scope.row.entryPersonName !== userStore.nickName" @click="openForm('edit', scope.row)">缂栬緫</el-button>
+<!-- <el-button link type="primary" size="small" @click="openForm('view', scope.row)">璇︽儏</el-button>-->
+ <el-button link type="primary" size="small" @click="downLoadFile(scope.row)">闄勪欢</el-button>
+ </template>
+ </el-table-column>
+ </el-table>
+ <pagination v-show="total > 0" :total="total" layout="total, sizes, prev, pager, next, jumper"
+ :page="page.current" :limit="page.size" @pagination="paginationChange" />
+ </div>
+ <el-dialog v-model="dialogFormVisible" :title="operationType === 'add' ? '鏂板閿�鍞彴璐﹂〉闈�' : '缂栬緫閿�鍞彴璐﹂〉闈�'" width="70%"
+ @close="closeDia">
+ <el-form :model="form" label-width="140px" label-position="top" :rules="rules" ref="formRef">
+ <el-row :gutter="30">
+ <el-col :span="12">
+ <el-form-item label="閿�鍞悎鍚屽彿锛�" prop="salesContractNo">
+ <el-input v-model="form.salesContractNo" placeholder="鑷姩鐢熸垚" clearable disabled />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="涓氬姟鍛橈細" prop="salesman">
+ <el-input
+ v-model="form.salesman"
+ placeholder="璇疯緭鍏�"
+ clearable
+ :disabled="operationType === 'view'"
+ />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="30">
+ <el-col :span="12">
+ <el-form-item label="瀹㈡埛鍚堝悓鍙凤細" prop="customerContractNo">
+ <el-input v-model="form.customerContractNo" placeholder="璇疯緭鍏�" clearable :disabled="operationType === 'view'"/>
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="瀹㈡埛鍚嶇О锛�" prop="customerId">
+ <el-select v-model="form.customerId" placeholder="璇烽�夋嫨" clearable :disabled="operationType === 'view'">
+ <el-option v-for="item in customerOption" :key="item.id" :label="item.customerName" :value="item.id">
+ {{
+ item.customerName + "鈥斺��" + item.taxpayerIdentificationNumber
+ }}
+ </el-option>
+ </el-select>
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="30">
+ <el-col :span="12">
+ <el-form-item label="椤圭洰鍚嶇О锛�" prop="projectName">
+ <el-input v-model="form.projectName" placeholder="璇疯緭鍏�" clearable :disabled="operationType === 'view'" />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="绛捐鏃ユ湡锛�" prop="executionDate">
+ <el-date-picker style="width: 100%" v-model="form.executionDate" value-format="YYYY-MM-DD"
+ format="YYYY-MM-DD" type="date" placeholder="璇烽�夋嫨" clearable :disabled="operationType === 'view'" />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="30">
+ <el-col :span="12">
+ <el-form-item label="褰曞叆浜猴細" prop="entryPerson">
+ <el-select v-model="form.entryPerson" placeholder="璇烽�夋嫨" clearable @change="changs" disabled>
+ <el-option v-for="item in userList" :key="item.userId" :label="item.nickName" :value="item.userId" />
+ </el-select>
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="褰曞叆鏃ユ湡锛�" prop="entryDate">
+ <el-date-picker style="width: 100%" v-model="form.entryDate" value-format="YYYY-MM-DD" format="YYYY-MM-DD"
+ type="date" placeholder="璇烽�夋嫨" clearable disabled />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="30">
+ <el-col :span="12">
+ <el-form-item label="浠樻鏂瑰紡">
+ <el-input v-model="form.paymentMethod" placeholder="璇疯緭鍏�" clearable :disabled="operationType === 'view'" />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row>
+ <el-form-item label="浜у搧淇℃伅锛�" prop="entryDate">
+ <el-button v-if="operationType !== 'view'" type="primary" @click="openProductForm('add')">娣诲姞</el-button>
+ <el-button v-if="operationType !== 'view'" plain type="danger" @click="deleteProduct" >鍒犻櫎</el-button>
+ </el-form-item>
+ </el-row>
+ <el-table :data="productData" border @selection-change="productSelected" show-summary stripe
+ :summary-method="summarizeMainTable">
+ <el-table-column align="center" type="selection" width="55" v-if="operationType !== 'view'" />
+ <el-table-column align="center" label="搴忓彿" type="index" width="60" />
+ <el-table-column label="浜у搧澶х被" prop="productCategory" />
+ <el-table-column label="瑙勬牸鍨嬪彿" prop="specificationModel" />
+ <el-table-column label="鍗曚綅" prop="unit" />
+ <el-table-column label="鏁伴噺" prop="quantity" />
+ <el-table-column label="绋庣巼(%)" prop="taxRate" />
+ <el-table-column label="鍚◣鍗曚环(鍏�)" prop="taxInclusiveUnitPrice" :formatter="formattedNumber" />
+ <el-table-column label="鍚◣鎬讳环(鍏�)" prop="taxInclusiveTotalPrice" :formatter="formattedNumber" />
+ <el-table-column label="涓嶅惈绋庢�讳环(鍏�)" prop="taxExclusiveTotalPrice" :formatter="formattedNumber" />
+ <el-table-column fixed="right" label="鎿嶄綔" min-width="60" align="center" v-if="operationType !== 'view'">
+ <template #default="scope">
+ <el-button link type="primary" size="small" @click="openProductForm('edit', scope.row,scope.$index)">缂栬緫</el-button>
+ </template>
+ </el-table-column>
+ </el-table>
+ <el-row :gutter="30">
+ <el-col :span="24">
+ <el-form-item label="澶囨敞路锛�" prop="remark">
+ <el-input v-model="form.remark" placeholder="璇疯緭鍏�" clearable type="textarea" :rows="2" :disabled="operationType === 'view'" />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="30">
+ <el-col :span="24">
+ <el-form-item label="闄勪欢鏉愭枡锛�" prop="remark">
+ <el-upload v-model:file-list="fileList" :action="upload.url" multiple ref="fileUpload" auto-upload
+ :headers="upload.headers" :before-upload="handleBeforeUpload" :on-error="handleUploadError"
+ :on-success="handleUploadSuccess" :on-remove="handleRemove">
+ <el-button type="primary" v-if="operationType !== 'view'">涓婁紶</el-button>
+ <template #tip v-if="operationType !== 'view'">
+ <div class="el-upload__tip">
+ 鏂囦欢鏍煎紡鏀寔
+ doc锛宒ocx锛寈ls锛寈lsx锛宲pt锛宲ptx锛宲df锛宼xt锛寈ml锛宩pg锛宩peg锛宲ng锛実if锛宐mp锛宺ar锛寊ip锛�7z
+ </div>
+ </template>
+ </el-upload>
+ </el-form-item>
+ </el-col>
+ </el-row>
+ </el-form>
+ <template #footer>
+ <div class="dialog-footer">
+ <el-button type="primary" @click="submitForm">纭</el-button>
+ <el-button @click="closeDia">鍙栨秷</el-button>
+ </div>
+ </template>
+ </el-dialog>
+ <el-dialog v-model="productFormVisible" :title="productOperationType === 'add' ? '鏂板浜у搧' : '缂栬緫浜у搧'" width="40%"
+ @close="closeProductDia">
+ <el-form :model="productForm" label-width="140px" label-position="top" :rules="productRules" ref="productFormRef">
+ <el-row :gutter="30">
+ <el-col :span="24">
+ <el-form-item label="浜у搧澶х被锛�" prop="productCategory">
+ <!-- <el-select v-model="productForm.productCategory" placeholder="璇烽�夋嫨" clearable>
+ <el-option v-for="item in userList" :key="item.nickName" :label="item.nickName" :value="item.nickName"/>
+ </el-select> -->
+ <el-tree-select v-model="productForm.productCategory" placeholder="璇烽�夋嫨" clearable check-strictly
+ @change="getModels" :data="productOptions" :render-after-expand="false" style="width: 100%" />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="30">
+ <el-col :span="24">
+ <el-form-item label="瑙勬牸鍨嬪彿锛�" prop="productModelId">
+ <el-select v-model="productForm.productModelId" placeholder="璇烽�夋嫨" clearable @change="getProductModel">
+ <el-option v-for="item in modelOptions" :key="item.id" :label="item.model" :value="item.id" />
+ </el-select>
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="30">
+ <el-col :span="12">
+ <el-form-item label="鍗曚綅锛�" prop="unit">
+ <el-input v-model="productForm.unit" placeholder="璇疯緭鍏�" clearable />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="绋庣巼(%)锛�" prop="taxRate">
+ <el-select v-model="productForm.taxRate" placeholder="璇烽�夋嫨" clearable @change="calculateFromTaxRate">
+ <el-option label="1" value="1" />
+ <el-option label="6" value="6" />
+ <el-option label="13" value="13" />
+ </el-select>
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="30">
+ <el-col :span="12">
+ <el-form-item label="鍚◣鍗曚环(鍏�)锛�" prop="taxInclusiveUnitPrice">
+ <el-input-number :step="0.01" :min="0" v-model="productForm.taxInclusiveUnitPrice" style="width: 100%"
+ :precision="2"
+ placeholder="璇疯緭鍏�" clearable @change="calculateFromUnitPrice" />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="鏁伴噺锛�" prop="quantity">
+ <el-input-number :step="0.1" :min="0" v-model="productForm.quantity" placeholder="璇疯緭鍏�" clearable
+ :precision="2"
+ @change="calculateFromQuantity" style="width: 100%" />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="30">
+ <el-col :span="12">
+ <el-form-item label="鍚◣鎬讳环(鍏�)锛�" prop="taxInclusiveTotalPrice">
+ <el-input v-model="productForm.taxInclusiveTotalPrice" placeholder="璇疯緭鍏�" clearable @change="calculateFromTotalPrice" />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="涓嶅惈绋庢�讳环(鍏�)锛�" prop="taxExclusiveTotalPrice">
+ <el-input v-model="productForm.taxExclusiveTotalPrice" placeholder="璇疯緭鍏�" clearable @change="calculateFromExclusiveTotalPrice" />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="30">
+ <el-col :span="12">
+ <el-form-item label="鍙戠エ绫诲瀷锛�" prop="invoiceType">
+ <el-select v-model="productForm.invoiceType" placeholder="璇烽�夋嫨" clearable>
+ <el-option label="澧炴櫘绁�" value="澧炴櫘绁�" />
+ <el-option label="澧炰笓绁�" value="澧炰笓绁�" />
+ </el-select>
+ </el-form-item>
+ </el-col>
+ </el-row>
+ </el-form>
+ <template #footer>
+ <div class="dialog-footer">
+ <el-button type="primary" @click="submitProduct">纭</el-button>
+ <el-button @click="closeProductDia">鍙栨秷</el-button>
+ </div>
+ </template>
+ </el-dialog>
+ <FileList ref="fileListRef" />
+ </div>
+</template>
+
+<script setup>
+import { getToken } from "@/utils/auth";
+import pagination from "@/components/PIMTable/Pagination.vue";
+import {onMounted, ref} from "vue";
+import { ElMessageBox } from "element-plus";
+import useUserStore from "@/store/modules/user";
+import { userListNoPage } from "@/api/system/user.js";
+import FileList from "./fileList.vue";
+import {
+ ledgerListPage,
+ productList,
+ customerList,
+ addOrUpdateSalesLedger,
+ getSalesLedgerWithProducts,
+ delLedger,
+ addOrUpdateSalesLedgerProduct,
+ delProduct,
+ delLedgerFile,
+} from "@/api/salesManagement/salesLedger.js";
+import { modelList, productTreeList } from "@/api/basicData/product.js";
+import useFormData from "@/hooks/useFormData.js";
+import dayjs from "dayjs";
+
+const userStore = useUserStore();
+const { proxy } = getCurrentInstance();
+const tableData = ref([]);
+const productData = ref([]);
+const selectedRows = ref([]);
+const productSelectedRows = ref([]);
+const userList = ref([]);
+const customerOption = ref([]);
+const productOptions = ref([]);
+const modelOptions = ref([]);
+const tableLoading = ref(false);
+const page = reactive({
+ current: 1,
+ size: 100,
+});
+const total = ref(0);
+const fileList = ref([]);
+
+// 鐢ㄦ埛淇℃伅琛ㄥ崟寮规鏁版嵁
+const operationType = ref("");
+const dialogFormVisible = ref(false);
+const data = reactive({
+ searchForm: {
+ customerName: "", // 瀹㈡埛鍚嶇О
+ customerContractNo: "", // 瀹㈡埛鍚堝悓缂栧彿
+ salesContractNo: "", // 閿�鍞悎鍚岀紪鍙�
+ projectName: "", // 椤圭洰鍚嶇О
+ entryDate: [
+ dayjs().format("YYYY-MM-DD"),
+ dayjs().add(1, "day").format("YYYY-MM-DD"),
+ ], // 褰曞叆鏃ユ湡
+ entryDateStart: dayjs().format("YYYY-MM-DD"),
+ entryDateEnd: dayjs().add(1, "day").format("YYYY-MM-DD"),
+ },
+ form: {
+ salesContractNo: "",
+ salesman: "",
+ customerContractNo: "",
+ customerId: "",
+ projectName: "",
+ entryPerson: "",
+ entryDate: "",
+ maintenanceTime: "",
+ productData: [],
+ executionDate: "",
+ paymentMethod: "",
+ },
+ rules: {
+ salesman: [{ required: true, message: "璇烽�夋嫨", trigger: "change" }],
+ customerContractNo: [
+ { required: true, message: "璇疯緭鍏�", trigger: "blur" },
+ ],
+ customerId: [{ required: true, message: "璇烽�夋嫨", trigger: "change" }],
+ projectName: [{ required: true, message: "璇疯緭鍏�", trigger: "blur" }],
+ entryPerson: [{ required: true, message: "璇烽�夋嫨", trigger: "change" }],
+ entryDate: [{ required: true, message: "璇烽�夋嫨", trigger: "change" }],
+ executionDate: [{ required: true, message: "璇烽�夋嫨", trigger: "change" }],
+ },
+});
+const { form, rules } = toRefs(data);
+const { form: searchForm } = useFormData(data.searchForm);
+// 浜у搧琛ㄥ崟寮规鏁版嵁
+const productFormVisible = ref(false);
+const productOperationType = ref("");
+const currentId = ref("");
+const productFormData = reactive({
+ productForm: {
+ productCategory: "",
+ specificationModel: "",
+ unit: "",
+ quantity: "",
+ taxInclusiveUnitPrice: "",
+ taxRate: "",
+ taxInclusiveTotalPrice: "",
+ taxExclusiveTotalPrice: "",
+ invoiceType: "",
+ },
+ productRules: {
+ productCategory: [{ required: true, message: "璇烽�夋嫨", trigger: "change" }],
+ productModelId: [{ required: true, message: "璇烽�夋嫨", trigger: "change" }],
+ specificationModel: [
+ { required: true, message: "璇烽�夋嫨", trigger: "change" },
+ ],
+ unit: [{ required: true, message: "璇疯緭鍏�", trigger: "blur" }],
+ quantity: [{ required: true, message: "璇疯緭鍏�", trigger: "blur" }],
+ taxInclusiveUnitPrice: [
+ { required: true, message: "璇疯緭鍏�", trigger: "blur" },
+ ],
+ taxRate: [{ required: true, message: "璇烽�夋嫨", trigger: "change" }],
+ taxInclusiveTotalPrice: [
+ { required: true, message: "璇疯緭鍏�", trigger: "blur" },
+ ],
+ taxExclusiveTotalPrice: [
+ { required: true, message: "璇疯緭鍏�", trigger: "blur" },
+ ],
+ invoiceType: [{ required: true, message: "璇烽�夋嫨", trigger: "change" }],
+ },
+});
+const { productForm, productRules } = toRefs(productFormData);
+// 闃叉寰幆璁$畻鐨勬爣蹇�
+const isCalculating = ref(false);
+const upload = reactive({
+ // 涓婁紶鐨勫湴鍧�
+ url: import.meta.env.VITE_APP_BASE_API + "/file/upload",
+ // 璁剧疆涓婁紶鐨勮姹傚ご閮�
+ headers: { Authorization: "Bearer " + getToken() },
+});
+
+const changeDaterange = (value) => {
+ if (value) {
+ searchForm.entryDateStart = dayjs(value[0]).format("YYYY-MM-DD");
+ searchForm.entryDateEnd = dayjs(value[1]).format("YYYY-MM-DD");
+ } else {
+ searchForm.entryDateStart = undefined;
+ searchForm.entryDateEnd = undefined;
+ }
+ handleQuery();
+};
+
+// 鏌ヨ鍒楄〃
+/** 鎼滅储鎸夐挳鎿嶄綔 */
+const handleQuery = () => {
+ page.current = 1;
+ expandedRowKeys.value = [];
+ getList();
+};
+const paginationChange = (obj) => {
+ page.current = obj.page;
+ page.size = obj.limit;
+ getList();
+};
+const getList = () => {
+ tableLoading.value = true;
+ const { entryDate, ...rest } = searchForm;
+ ledgerListPage({ ...rest, ...page })
+ .then((res) => {
+ tableLoading.value = false;
+ tableData.value = res.records;
+ tableData.value.map((item) => {
+ item.children = [];
+ });
+ total.value = res.total;
+ })
+ .catch(() => {
+ tableLoading.value = false;
+ });
+};
+// 鑾峰彇浜у搧澶х被tree鏁版嵁
+const getProductOptions = () => {
+ productTreeList().then((res) => {
+ productOptions.value = convertIdToValue(res);
+ });
+};
+const formattedNumber = (row, column, cellValue) => {
+ return parseFloat(cellValue).toFixed(2);
+};
+// 鑾峰彇tree瀛愭暟鎹�
+const getModels = (value) => {
+ productForm.value.productCategory = findNodeById(productOptions.value, value);
+ modelList({ id: value }).then((res) => {
+ modelOptions.value = res;
+ });
+};
+const getProductModel = (value) => {
+ console.log("value", value);
+ const index = modelOptions.value.findIndex((item) => item.id === value);
+ if (index !== -1) {
+ productForm.value.specificationModel = modelOptions.value[index].model;
+ productForm.value.unit = modelOptions.value[index].unit;
+ } else {
+ productForm.value.specificationModel = null;
+ productForm.value.unit = null;
+ }
+};
+const findNodeById = (nodes, productId) => {
+ for (let i = 0; i < nodes.length; i++) {
+ if (nodes[i].value === productId) {
+ return nodes[i].label; // 鎵惧埌鑺傜偣锛岃繑鍥炶鑺傜偣
+ }
+ if (nodes[i].children && nodes[i].children.length > 0) {
+ const foundNode = findNodeById(nodes[i].children, productId);
+ if (foundNode) {
+ return foundNode; // 鍦ㄥ瓙鑺傜偣涓壘鍒帮紝杩斿洖璇ヨ妭鐐�
+ }
+ }
+ }
+ return null; // 娌℃湁鎵惧埌鑺傜偣锛岃繑鍥瀗ull
+};
+function convertIdToValue(data) {
+ return data.map((item) => {
+ const { id, children, ...rest } = item;
+ const newItem = {
+ ...rest,
+ value: id, // 灏� id 鏀逛负 value
+ };
+ if (children && children.length > 0) {
+ newItem.children = convertIdToValue(children);
+ }
+
+ return newItem;
+ });
+}
+// 琛ㄦ牸閫夋嫨鏁版嵁
+const handleSelectionChange = (selection) => {
+ // 杩囨护鎺夊瓙鏁版嵁
+ selectedRows.value = selection.filter((item) => item.children !== undefined);
+ console.log("selection", selectedRows.value);
+};
+const productSelected = (selectedRows) => {
+ productSelectedRows.value = selectedRows;
+};
+const expandedRowKeys = ref([]);
+// 灞曞紑琛�
+const expandChange = (row, expandedRows) => {
+ if (expandedRows.length > 0) {
+ expandedRowKeys.value = [];
+ try {
+ productList({ salesLedgerId: row.id, type: 1 }).then((res) => {
+ const index = tableData.value.findIndex((item) => item.id === row.id);
+ if (index > -1) {
+ tableData.value[index].children = res.data;
+ }
+ expandedRowKeys.value.push(row.id);
+ });
+ } catch (error) {
+ console.log(error);
+ }
+ } else {
+ expandedRowKeys.value = [];
+ }
+};
+// 涓昏〃鍚堣鏂规硶
+const summarizeMainTable = (param) => {
+ return proxy.summarizeTable(param, [
+ "contractAmount",
+ "taxInclusiveTotalPrice",
+ "taxExclusiveTotalPrice",
+ 'invoiceTotal',
+ 'noInvoiceAmountTotal',
+ 'receiptPaymentAmountTotal',
+ 'noReceiptAmount',
+ ]);
+};
+// 瀛愯〃鍚堣鏂规硶
+const summarizeChildrenTable = (param) => {
+ return proxy.summarizeTable(param, [
+ "taxInclusiveUnitPrice",
+ "taxInclusiveTotalPrice",
+ "taxExclusiveTotalPrice",
+ ]);
+};
+// 鎵撳紑寮规
+const openForm = async (type, row) => {
+ operationType.value = type;
+ form.value = {};
+ productData.value = [];
+ let userLists = await userListNoPage();
+ userList.value = userLists.data;
+ customerList().then((res) => {
+ customerOption.value = res;
+ });
+ form.value.entryPerson = userStore.id;
+ if (type !== "add") {
+ currentId.value = row.id;
+ getSalesLedgerWithProducts({ id: row.id, type: 1 }).then((res) => {
+ form.value = { ...res };
+ form.value.entryPerson = Number(res.entryPerson);
+ productData.value = form.value.productData;
+ fileList.value = form.value.salesLedgerFiles;
+ });
+ }
+ // let userAll = await userStore.getInfo()
+ // userList.value.forEach(element => {
+ // if(userAll.user.nickName === element.nickName && userAll.user.userName === element.userName) {
+ // form.value.entryPerson = userAll.user.userId // 璁剧疆榛樿涓氬姟鍛樹负褰撳墠鐢ㄦ埛
+ // }
+ // });
+ form.value.entryDate = getCurrentDate(); // 璁剧疆榛樿褰曞叆鏃ユ湡涓哄綋鍓嶆棩鏈�
+ dialogFormVisible.value = true;
+};
+function changs(val) {
+ console.log(val);
+}
+// 涓婁紶鍓嶆牎妫�
+function handleBeforeUpload(file) {
+ // 鏍℃鏂囦欢澶у皬
+ // if (file.size > 1024 * 1024 * 10) {
+ // proxy.$modal.msgError("涓婁紶鏂囦欢澶у皬涓嶈兘瓒呰繃10MB!");
+ // return false;
+ // }
+ proxy.$modal.loading("姝e湪涓婁紶鏂囦欢锛岃绋嶅��...");
+ return true;
+}
+// 涓婁紶澶辫触
+function handleUploadError(err) {
+ proxy.$modal.msgError("涓婁紶鏂囦欢澶辫触");
+ proxy.$modal.closeLoading();
+}
+// 涓婁紶鎴愬姛鍥炶皟
+function handleUploadSuccess(res, file, uploadFiles) {
+ proxy.$modal.closeLoading();
+ if (res.code === 200) {
+ file.tempId = res.data.tempId;
+ proxy.$modal.msgSuccess("涓婁紶鎴愬姛");
+ } else {
+ proxy.$modal.msgError(res.msg);
+ proxy.$refs.fileUpload.handleRemove(file);
+ }
+}
+// 绉婚櫎鏂囦欢
+function handleRemove(file) {
+ if (operationType.value === "edit") {
+ let ids = [];
+ ids.push(file.id);
+ delLedgerFile(ids).then((res) => {
+ proxy.$modal.msgSuccess("鍒犻櫎鎴愬姛");
+ });
+ }
+}
+// 鎻愪氦琛ㄥ崟
+const submitForm = () => {
+ proxy.$refs["formRef"].validate((valid) => {
+ if (valid) {
+ console.log('productData.value--', productData.value)
+ if (productData.value !== null && productData.value.length > 0) {
+ form.value.productData = proxy.HaveJson(productData.value);
+ } else {
+ proxy.$modal.msgWarning("璇锋坊鍔犱骇鍝佷俊鎭�");
+ return;
+ }
+ let tempFileIds = [];
+ if (fileList.value !== null && fileList.value.length > 0) {
+ tempFileIds = fileList.value.map((item) => item.tempId);
+ }
+ form.value.tempFileIds = tempFileIds;
+ form.value.type = 1;
+ addOrUpdateSalesLedger(form.value).then((res) => {
+ proxy.$modal.msgSuccess("鎻愪氦鎴愬姛");
+ closeDia();
+ getList();
+ });
+ }
+ });
+};
+// 鍏抽棴寮规
+const closeDia = () => {
+ proxy.resetForm("formRef");
+ dialogFormVisible.value = false;
+};
+
+const productIndex = ref(0);
+// 鎵撳紑浜у搧寮规
+const openProductForm = (type, row,index) => {
+ productOperationType.value = type;
+ productForm.value = {};
+ proxy.resetForm("productFormRef");
+ if (type === "edit") {
+ productForm.value = { ...row };
+ productIndex.value = index;
+ }
+ productFormVisible.value = true;
+ getProductOptions();
+};
+// 鎻愪氦浜у搧琛ㄥ崟
+const submitProduct = () => {
+ proxy.$refs["productFormRef"].validate((valid) => {
+ if (valid) {
+ if (operationType.value === "edit") {
+ submitProductEdit();
+ } else {
+ if(productOperationType.value === "add"){
+ productData.value.push({ ...productForm.value });
+ }else{
+ productData.value[productIndex.value] = { ...productForm.value }
+ }
+ closeProductDia();
+ }
+ }
+ });
+};
+const submitProductEdit = () => {
+ productForm.value.salesLedgerId = currentId.value;
+ productForm.value.type = 1
+ addOrUpdateSalesLedgerProduct(productForm.value).then((res) => {
+ proxy.$modal.msgSuccess("鎻愪氦鎴愬姛");
+ closeProductDia();
+ getSalesLedgerWithProducts({ id: currentId.value, type: 1 }).then((res) => {
+ productData.value = res.productData;
+ });
+ });
+};
+// 鍒犻櫎浜у搧
+const deleteProduct = () => {
+ if (productSelectedRows.value.length === 0) {
+ proxy.$modal.msgWarning("璇烽�夋嫨鏁版嵁");
+ return;
+ }
+ if (operationType.value === "add") {
+ productSelectedRows.value.forEach((selectedRow) => {
+ const index = productData.value.findIndex(
+ (product) => product.id === selectedRow.id
+ );
+ if (index !== -1) {
+ productData.value.splice(index, 1);
+ }
+ });
+ } else {
+ let ids = [];
+ if (productSelectedRows.value.length > 0) {
+ ids = productSelectedRows.value.map((item) => item.id);
+ }
+ ElMessageBox.confirm("閫変腑鐨勫唴瀹瑰皢琚垹闄わ紝鏄惁纭鍒犻櫎锛�", "瀵煎嚭", {
+ confirmButtonText: "纭",
+ cancelButtonText: "鍙栨秷",
+ type: "warning",
+ })
+ .then(() => {
+ delProduct(ids).then((res) => {
+ proxy.$modal.msgSuccess("鍒犻櫎鎴愬姛");
+ closeProductDia();
+ getSalesLedgerWithProducts({ id: currentId.value, type: 1 }).then(
+ (res) => {
+ productData.value = res.productData;
+ }
+ );
+ });
+ })
+ .catch(() => {
+ proxy.$modal.msg("宸插彇娑�");
+ });
+ }
+};
+// 鍏抽棴浜у搧寮规
+const closeProductDia = () => {
+ proxy.resetForm("productFormRef");
+ productFormVisible.value = false;
+};
+// 瀵煎嚭
+const handleOut = () => {
+ ElMessageBox.confirm("閫変腑鐨勫唴瀹瑰皢琚鍑猴紝鏄惁纭瀵煎嚭锛�", "瀵煎嚭", {
+ confirmButtonText: "纭",
+ cancelButtonText: "鍙栨秷",
+ type: "warning",
+ })
+ .then(() => {
+ proxy.download("/sales/ledger/export", {}, "閿�鍞彴璐�.xlsx");
+ })
+ .catch(() => {
+ proxy.$modal.msg("宸插彇娑�");
+ });
+};
+// 鍒犻櫎
+const handleDelete = () => {
+ let ids = [];
+ if (selectedRows.value.length > 0) {
+ ids = selectedRows.value.map((item) => item.id);
+ } else {
+ proxy.$modal.msgWarning("璇烽�夋嫨鏁版嵁");
+ return;
+ }
+ ElMessageBox.confirm("閫変腑鐨勫唴瀹瑰皢琚垹闄わ紝鏄惁纭鍒犻櫎锛�", "瀵煎嚭", {
+ confirmButtonText: "纭",
+ cancelButtonText: "鍙栨秷",
+ type: "warning",
+ })
+ .then(() => {
+ delLedger(ids).then((res) => {
+ proxy.$modal.msgSuccess("鍒犻櫎鎴愬姛");
+ getList();
+ });
+ })
+ .catch(() => {
+ proxy.$modal.msg("宸插彇娑�");
+ });
+};
+// 鑾峰彇褰撳墠鏃ユ湡骞舵牸寮忓寲涓� YYYY-MM-DD
+function getCurrentDate() {
+ const today = new Date();
+ const year = today.getFullYear();
+ const month = String(today.getMonth() + 1).padStart(2, "0"); // 鏈堜唤浠�0寮�濮�
+ const day = String(today.getDate()).padStart(2, "0");
+ return `${year}-${month}-${day}`;
+}
+
+const mathNum = () => {
+ console.log("productForm.value", productForm.value);
+ if (!productForm.value.taxInclusiveUnitPrice) {
+ return;
+ }
+ if (!productForm.value.quantity) {
+ return;
+ }
+ // 鍚◣鎬讳环璁$畻
+ productForm.value.taxInclusiveTotalPrice =
+ proxy.calculateTaxIncludeTotalPrice(
+ productForm.value.taxInclusiveUnitPrice,
+ productForm.value.quantity
+ );
+ if (productForm.value.taxRate) {
+ // 涓嶅惈绋庢�讳环璁$畻
+ productForm.value.taxExclusiveTotalPrice =
+ proxy.calculateTaxExclusiveTotalPrice(
+ productForm.value.taxInclusiveTotalPrice,
+ productForm.value.taxRate
+ );
+ }
+};
+
+// 鏍规嵁鍚◣鎬讳环璁$畻鍚◣鍗曚环鍜屾暟閲�
+const calculateFromTotalPrice = () => {
+ if (isCalculating.value) return;
+
+ const totalPrice = parseFloat(productForm.value.taxInclusiveTotalPrice);
+ const quantity = parseFloat(productForm.value.quantity);
+
+ if (!totalPrice || !quantity || quantity <= 0) {
+ return;
+ }
+
+ isCalculating.value = true;
+
+ // 璁$畻鍚◣鍗曚环 = 鍚◣鎬讳环 / 鏁伴噺
+ productForm.value.taxInclusiveUnitPrice = (totalPrice / quantity).toFixed(2);
+
+ // 濡傛灉鏈夌◣鐜囷紝璁$畻涓嶅惈绋庢�讳环
+ if (productForm.value.taxRate) {
+ productForm.value.taxExclusiveTotalPrice =
+ proxy.calculateTaxExclusiveTotalPrice(
+ totalPrice,
+ productForm.value.taxRate
+ );
+ }
+
+ isCalculating.value = false;
+};
+
+// 鏍规嵁涓嶅惈绋庢�讳环璁$畻鍚◣鍗曚环鍜屾暟閲�
+const calculateFromExclusiveTotalPrice = () => {
+ if (!productForm.value.taxRate) {
+ proxy.$modal.msgWarning("璇峰厛閫夋嫨绋庣巼");
+ return;
+ }
+ if (isCalculating.value) return;
+
+ const exclusiveTotalPrice = parseFloat(productForm.value.taxExclusiveTotalPrice);
+ const quantity = parseFloat(productForm.value.quantity);
+ const taxRate = parseFloat(productForm.value.taxRate);
+
+ if (!exclusiveTotalPrice || !quantity || quantity <= 0 || !taxRate) {
+ return;
+ }
+
+ isCalculating.value = true;
+
+ // 鍏堣绠楀惈绋庢�讳环 = 涓嶅惈绋庢�讳环 / (1 - 绋庣巼/100)
+ const taxRateDecimal = taxRate / 100;
+ const inclusiveTotalPrice = exclusiveTotalPrice / (1 - taxRateDecimal);
+ productForm.value.taxInclusiveTotalPrice = inclusiveTotalPrice.toFixed(2);
+
+ // 璁$畻鍚◣鍗曚环 = 鍚◣鎬讳环 / 鏁伴噺
+ productForm.value.taxInclusiveUnitPrice = (inclusiveTotalPrice / quantity).toFixed(2);
+
+ isCalculating.value = false;
+};
+
+// 鏍规嵁鏁伴噺鍙樺寲璁$畻鎬讳环
+const calculateFromQuantity = () => {
+ if (!productForm.value.taxRate) {
+ proxy.$modal.msgWarning("璇峰厛閫夋嫨绋庣巼");
+ return;
+ }
+ if (isCalculating.value) return;
+
+ const quantity = parseFloat(productForm.value.quantity);
+ const unitPrice = parseFloat(productForm.value.taxInclusiveUnitPrice);
+
+ if (!quantity || quantity <= 0 || !unitPrice) {
+ return;
+ }
+
+ isCalculating.value = true;
+
+ // 璁$畻鍚◣鎬讳环
+ productForm.value.taxInclusiveTotalPrice = (unitPrice * quantity).toFixed(2);
+
+ // 濡傛灉鏈夌◣鐜囷紝璁$畻涓嶅惈绋庢�讳环
+ if (productForm.value.taxRate) {
+ productForm.value.taxExclusiveTotalPrice =
+ proxy.calculateTaxExclusiveTotalPrice(
+ productForm.value.taxInclusiveTotalPrice,
+ productForm.value.taxRate
+ );
+ }
+
+ isCalculating.value = false;
+};
+
+// 鏍规嵁鍚◣鍗曚环鍙樺寲璁$畻鎬讳环
+const calculateFromUnitPrice = () => {
+ if (!productForm.value.taxRate) {
+ proxy.$modal.msgWarning("璇峰厛閫夋嫨绋庣巼");
+ return;
+ }
+ if (isCalculating.value) return;
+
+ const quantity = parseFloat(productForm.value.quantity);
+ const unitPrice = parseFloat(productForm.value.taxInclusiveUnitPrice);
+
+ if (!quantity || quantity <= 0 || !unitPrice) {
+ return;
+ }
+
+ isCalculating.value = true;
+
+ // 璁$畻鍚◣鎬讳环
+ productForm.value.taxInclusiveTotalPrice = (unitPrice * quantity).toFixed(2);
+
+ // 濡傛灉鏈夌◣鐜囷紝璁$畻涓嶅惈绋庢�讳环
+ if (productForm.value.taxRate) {
+ productForm.value.taxExclusiveTotalPrice =
+ proxy.calculateTaxExclusiveTotalPrice(
+ productForm.value.taxInclusiveTotalPrice,
+ productForm.value.taxRate
+ );
+ }
+
+ isCalculating.value = false;
+};
+
+// 鏍规嵁绋庣巼鍙樺寲璁$畻涓嶅惈绋庢�讳环
+const calculateFromTaxRate = () => {
+ if (!productForm.value.taxRate) {
+ proxy.$modal.msgWarning("璇峰厛閫夋嫨绋庣巼");
+ return;
+ }
+ if (isCalculating.value) return;
+
+ const inclusiveTotalPrice = parseFloat(productForm.value.taxInclusiveTotalPrice);
+ const taxRate = parseFloat(productForm.value.taxRate);
+
+ if (!inclusiveTotalPrice || !taxRate) {
+ return;
+ }
+
+ isCalculating.value = true;
+
+ // 璁$畻涓嶅惈绋庢�讳环
+ productForm.value.taxExclusiveTotalPrice =
+ proxy.calculateTaxExclusiveTotalPrice(
+ inclusiveTotalPrice,
+ taxRate
+ );
+
+ isCalculating.value = false;
+};
+/**
+ * 涓嬭浇鏂囦欢
+ *
+ * @param row 涓嬭浇鏂囦欢鐨勭浉鍏充俊鎭璞�
+ */
+const fileListRef = ref(null)
+const downLoadFile = (row) => {
+ getSalesLedgerWithProducts({ id: row.id, type: 1 }).then((res) => {
+ fileListRef.value.open(res.salesLedgerFiles)
+ });
+
+}
+onMounted(() => {
+ getList();
+});
+</script>
+
+<style scoped lang="scss">
+.ml-10 {
+ margin-left: 10px;
+}
+
+.table_list {
+ margin-top: unset;
+}
+
+.actions {
+ display: flex;
+ justify-content: space-between;
+ margin-bottom: 10px;
+}
+</style>
--
Gitblit v1.9.3