From 9030f4f4913935611e613a4064d8e26a01cbf070 Mon Sep 17 00:00:00 2001
From: spring <2396852758@qq.com>
Date: 星期一, 16 三月 2026 10:43:06 +0800
Subject: [PATCH] fix: 耗材物流-前端
---
src/views/consumablesLogistics/receiptManagement/index.vue | 36
src/views/consumablesLogistics/stockManagement/index.vue | 33
src/views/equipmentManagement/repair/Modal/RepairModal.vue | 145 ++
src/views/consumablesLogistics/stockManagement/FrozenAndThaw.vue | 164 ++
src/views/equipmentManagement/repair/index.vue | 51
src/api/consumablesLogistics/consumablesUninventory.js | 46
src/views/consumablesLogistics/stockReport/index.vue | 675 ++++++++++++
src/views/consumablesLogistics/stockManagement/New.vue | 273 ++++
src/views/consumablesLogistics/dispatchLog/Record.vue | 719 ++++++++++++
src/views/inventoryManagement/dispatchLog/Record.vue | 24
src/views/qualityManagement/InspectItem/index.vue | 6
src/api/consumablesLogistics/consumablesIn.js | 64 +
src/views/consumablesLogistics/dispatchLog/index.vue | 38
src/api/consumablesLogistics/consumablesOutRecord.js | 19
src/api/consumablesLogistics/consumablesInRecord.js | 26
src/views/consumablesLogistics/stockManagement/Qualified.vue | 211 +++
src/views/consumablesLogistics/stockManagement/Subtract.vue | 285 +++++
src/views/consumablesLogistics/receiptManagement/Record.vue | 265 ++++
src/views/consumablesLogistics/stockManagement/Unqualified.vue | 187 +++
src/views/consumablesLogistics/stockManagement/Import.vue | 93 +
20 files changed, 3,343 insertions(+), 17 deletions(-)
diff --git a/src/api/consumablesLogistics/consumablesIn.js b/src/api/consumablesLogistics/consumablesIn.js
new file mode 100644
index 0000000..7dc1951
--- /dev/null
+++ b/src/api/consumablesLogistics/consumablesIn.js
@@ -0,0 +1,64 @@
+import request from "@/utils/request.js";
+
+// 鍒嗛〉鏌ヨ鑰楁潗搴撳瓨璁板綍鍒楄〃
+export const getConsumablesInListPage = (params) => {
+ return request({
+ url: "/consumablesInventory/pageConsumablesInventory",
+ method: "get",
+ params,
+ });
+};
+
+// 鍒涘缓鑰楁潗搴撳瓨璁板綍锛堟柊澧炲簱瀛橈級
+export const createConsumablesIn = (params) => {
+ return request({
+ url: "/consumablesInventory/addConsumablesInventory",
+ method: "post",
+ data: params,
+ });
+};
+
+// 鍑忓皯鑰楁潗搴撳瓨璁板綍锛堟墸鍑忓簱瀛橈級
+export const subtractConsumablesIn = (params) => {
+ return request({
+ url: "/consumablesInventory/subtractConsumablesInventory",
+ method: "post",
+ data: params,
+ });
+};
+
+// 鑰楁潗搴撳瓨鎶ヨ〃鍒嗛〉
+export const getConsumablesInReportList = (params) => {
+ return request({
+ url: "/consumablesInventory/ConsumablesInventoryPage",
+ method: "get",
+ params,
+ });
+};
+
+// 鑰楁潗杩涘嚭瀛樻姤琛紙缁熻鍚勪釜浜у搧鐨勫叆搴撳拰鍑哄簱璁板綍锛�
+export const getConsumablesInInAndOutReportList = (params) => {
+ return request({
+ url: "/consumablesInventory/ConsumablesInAndOutRecord",
+ method: "get",
+ params,
+ });
+};
+
+// 鍐荤粨鑰楁潗搴撳瓨璁板綍
+export const frozenConsumablesIn = (params) => {
+ return request({
+ url: "/consumablesInventory/frozenConsumables",
+ method: "post",
+ data: params,
+ });
+};
+
+// 瑙e喕鑰楁潗搴撳瓨璁板綍
+export const thawConsumablesIn = (params) => {
+ return request({
+ url: "/consumablesInventory/thawConsumables",
+ method: "post",
+ data: params,
+ });
+};
diff --git a/src/api/consumablesLogistics/consumablesInRecord.js b/src/api/consumablesLogistics/consumablesInRecord.js
new file mode 100644
index 0000000..8b39549
--- /dev/null
+++ b/src/api/consumablesLogistics/consumablesInRecord.js
@@ -0,0 +1,26 @@
+import request from "@/utils/request";
+
+// 鑰楁潗鍏ュ簱绠$悊-鏌ヨ鍏ュ簱璁板綍鍒楄〃
+export const getConsumablesInRecordListPage = (params) => {
+ return request({
+ url: "/consumablesInRecord/listPage",
+ method: "get",
+ params,
+ });
+};
+
+export const updateConsumablesInRecord = (id, data) => {
+ return request({
+ url: "/consumablesInRecord/" + id,
+ method: "put",
+ data: data,
+ });
+};
+
+export const batchDeleteConsumablesInRecords = (ids) => {
+ return request({
+ url: "/consumablesInRecord",
+ method: "delete",
+ data: ids,
+ });
+};
diff --git a/src/api/consumablesLogistics/consumablesOutRecord.js b/src/api/consumablesLogistics/consumablesOutRecord.js
new file mode 100644
index 0000000..f401711
--- /dev/null
+++ b/src/api/consumablesLogistics/consumablesOutRecord.js
@@ -0,0 +1,19 @@
+import request from "@/utils/request";
+
+// 鑰楁潗鍑哄簱鍙拌处-鏌ヨ鍑哄簱鍒楄〃
+export const getConsumablesOutRecordPage = (params) => {
+ return request({
+ url: "/consumablesOutRecord/listPage",
+ method: "get",
+ params,
+ });
+};
+
+// 鍒犻櫎鑰楁潗鍑哄簱淇℃伅
+export const delConsumablesOutRecord = (ids) => {
+ return request({
+ url: "/consumablesOutRecord",
+ method: "delete",
+ data: ids,
+ });
+};
diff --git a/src/api/consumablesLogistics/consumablesUninventory.js b/src/api/consumablesLogistics/consumablesUninventory.js
new file mode 100644
index 0000000..8c47df0
--- /dev/null
+++ b/src/api/consumablesLogistics/consumablesUninventory.js
@@ -0,0 +1,46 @@
+import request from "@/utils/request.js";
+
+// 鍒嗛〉鏌ヨ鑰楁潗涓嶅悎鏍煎簱瀛樿褰曞垪琛�
+export const getConsumablesUninventoryListPage = (params) => {
+ return request({
+ url: "/consumablesUnInventory/pageConsumablesUnInventory",
+ method: "get",
+ params,
+ });
+};
+
+// 鍒涘缓鑰楁潗涓嶅悎鏍煎簱瀛樿褰�
+export const createConsumablesUnInventory = (params) => {
+ return request({
+ url: "/consumablesUninventory/addstockUninventory",
+ method: "post",
+ data: params,
+ });
+};
+
+// 鍑忓皯鑰楁潗涓嶅悎鏍煎簱瀛樿褰�
+export const subtractConsumablesUnInventory = (params) => {
+ return request({
+ url: "/consumablesUninventory/subtractstockUninventory",
+ method: "post",
+ data: params,
+ });
+};
+
+// 鍐荤粨鑰楁潗涓嶅悎鏍煎簱瀛樿褰�
+export const frozenConsumablesUninventory = (params) => {
+ return request({
+ url: "/consumablesUninventory/frozenStock",
+ method: "post",
+ data: params,
+ });
+};
+
+// 瑙e喕鑰楁潗涓嶅悎鏍煎簱瀛樿褰�
+export const thawConsumablesUninventory = (params) => {
+ return request({
+ url: "/consumablesUninventory/thawStock",
+ method: "post",
+ data: params,
+ });
+};
diff --git a/src/views/consumablesLogistics/dispatchLog/Record.vue b/src/views/consumablesLogistics/dispatchLog/Record.vue
new file mode 100644
index 0000000..a9d69ae
--- /dev/null
+++ b/src/views/consumablesLogistics/dispatchLog/Record.vue
@@ -0,0 +1,719 @@
+<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-select v-model="searchForm.recordType"
+ style="width: 240px"
+ placeholder="璇烽�夋嫨"
+ clearable>
+ <el-option v-for="item in stockRecordTypeOptions"
+ :key="item.value"
+ :label="item.label"
+ :value="item.value"/>
+ </el-select>
+ <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>
+ <el-button type="primary" plain @click="handlePrint">鎵撳嵃</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"
+ style="width: 100%"
+ 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="outboundBatches"
+ min-width="100"
+ show-overflow-tooltip
+ />
+ <el-table-column
+ label="鍑哄簱鏃ユ湡"
+ prop="createTime"
+ show-overflow-tooltip
+ />
+ <el-table-column
+ label="浜у搧澶х被"
+ prop="productName"
+ show-overflow-tooltip
+ />
+ <el-table-column
+ label="瑙勬牸鍨嬪彿"
+ prop="model"
+ show-overflow-tooltip
+ />
+ <el-table-column
+ label="鍗曚綅"
+ prop="unit"
+ show-overflow-tooltip
+ />
+ <el-table-column
+ label="鍑哄簱鏁伴噺"
+ prop="stockOutNum"
+ show-overflow-tooltip
+ />
+ <el-table-column
+ label="鍑�閲�(鍚�)"
+ prop="netWeight"
+ show-overflow-tooltip
+ />
+ <el-table-column
+ label="鍑哄簱浜�"
+ prop="createBy"
+ show-overflow-tooltip
+ />
+ <el-table-column label="鏉ユ簮"
+ prop="recordType"
+ show-overflow-tooltip>
+ <template #default="scope">
+ {{ getRecordType(scope.row.recordType) }}
+ </template>
+ </el-table-column>
+ <el-table-column
+ label="杞︾墝"
+ prop="licensePlateNo"
+ 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, getCurrentInstance } from "vue";
+import { ElMessageBox } from "element-plus";
+import useUserStore from "@/store/modules/user";
+import { getCurrentDate } from "@/utils/index.js";
+import {
+ getConsumablesOutRecordPage,
+ delConsumablesOutRecord,
+} from "@/api/consumablesLogistics/consumablesOutRecord.js";
+import {
+ findAllQualifiedStockOutRecordTypeOptions, findAllUnQualifiedStockOutRecordTypeOptions,
+} from "@/api/basicData/enum.js";
+
+const userStore = useUserStore();
+const { proxy } = getCurrentInstance();
+const tableData = ref([]);
+const selectedRows = ref([]);
+const tableLoading = ref(false);
+// 鏉ユ簮绫诲瀷閫夐」
+const stockRecordTypeOptions = ref([]);
+const page = reactive({
+ current: 1,
+ size: 100,
+});
+const total = ref(0);
+
+const props = defineProps({
+ type: {
+ type: String,
+ required: true,
+ default: '0'
+ }
+})
+
+// 鎵撳嵃鐩稿叧
+const printPreviewVisible = ref(false);
+const printData = ref([]);
+
+// 鐢ㄦ埛淇℃伅琛ㄥ崟寮规鏁版嵁
+const data = reactive({
+ searchForm: {
+ supplierName: "",
+ timeStr: "",
+ recordType: "",
+ }
+});
+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;
+ getConsumablesOutRecordPage({ ...searchForm.value, ...page, type: props.type })
+ .then((res) => {
+ tableLoading.value = false;
+ tableData.value = res.data.records;
+ tableData.value.map((item) => {
+ item.children = [];
+ });
+ total.value = res.data.total;
+ })
+ .catch(() => {
+ tableLoading.value = false;
+ });
+};
+
+const getRecordType = (recordType) => {
+ return stockRecordTypeOptions.value.find(item => item.value === recordType)?.label || ''
+}
+
+// 鑾峰彇鏉ユ簮绫诲瀷閫夐」
+const fetchStockRecordTypeOptions = () => {
+ if (props.type === '0') {
+ findAllQualifiedStockOutRecordTypeOptions()
+ .then(res => {
+ stockRecordTypeOptions.value = res.data;
+ })
+ return
+ }
+ findAllUnQualifiedStockOutRecordTypeOptions()
+ .then(res => {
+ stockRecordTypeOptions.value = res.data;
+ })
+}
+
+// 琛ㄦ牸閫夋嫨鏁版嵁
+const handleSelectionChange = (selection) => {
+ // 杩囨护鎺夊瓙鏁版嵁
+ selectedRows.value = selection.filter((item) => item.id);
+ console.log("selection", selectedRows.value);
+};
+const expandedRowKeys = ref([]);
+
+// 瀵煎嚭
+const handleOut = () => {
+ ElMessageBox.confirm("鏄惁纭瀵煎嚭锛�", "瀵煎嚭", {
+ confirmButtonText: "纭",
+ cancelButtonText: "鍙栨秷",
+ type: "warning",
+ })
+ .then(() => {
+ proxy.download("/consumablesOutRecord/exportConsumablesOutRecord", {type: props.type}, props.type === '0' ? "鑰楁潗鍚堟牸鍑哄簱鍙拌处.xlsx" : "鑰楁潗涓嶅悎鏍煎嚭搴撳彴璐�.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(() => {
+ delConsumablesOutRecord(ids).then((res) => {
+ proxy.$modal.msgSuccess("鍒犻櫎鎴愬姛");
+ getList();
+ });
+ })
+ .catch(() => {
+ proxy.$modal.msg("宸插彇娑�");
+ });
+};
+
+// 鎵撳嵃鍔熻兘
+const handlePrint = () => {
+ if (selectedRows.value.length === 0) {
+ proxy.$modal.msgWarning("璇烽�夋嫨瑕佹墦鍗扮殑鏁版嵁");
+ return;
+ }
+ printData.value = [...selectedRows.value];
+ console.log('鎵撳嵃鏁版嵁:', printData.value);
+ printPreviewVisible.value = true;
+};
+
+// 鎵ц鎵撳嵃
+const executePrint = () => {
+ console.log('寮�濮嬫墽琛屾墦鍗帮紝鏁版嵁鏉℃暟:', printData.value.length);
+ console.log('鎵撳嵃鏁版嵁:', printData.value);
+
+ // 鍒涘缓涓�涓柊鐨勬墦鍗扮獥鍙�
+ const printWindow = window.open('', '_blank', 'width=800,height=600');
+
+ // 鏋勫缓鎵撳嵃鍐呭
+ let printContent = `
+ <!DOCTYPE html>
+ <html>
+ <head>
+ <meta charset="UTF-8">
+ <title>鎵撳嵃棰勮</title>
+ <style>
+ body {
+ margin: 0;
+ padding: 0;
+ font-family: "SimSun", serif;
+ background: white;
+ }
+ .print-page {
+ width: 200mm;
+ height: 75mm;
+ padding: 10mm;
+ padding-left: 20mm;
+ background: white;
+ box-sizing: border-box;
+ page-break-after: always;
+ page-break-inside: avoid;
+ }
+ .print-page:last-child {
+ page-break-after: avoid;
+ }
+ .delivery-note {
+ width: 100%;
+ height: 100%;
+ font-size: 12px;
+ line-height: 1.2;
+ display: flex;
+ flex-direction: column;
+ color: #000;
+ }
+ .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: 12px;
+ }
+ .value {
+ margin-right: 20px;
+ min-width: 80px;
+ font-size: 12px;
+ }
+ .table-section {
+ margin-bottom: 40px;
+ // flex: 0.6;
+ }
+ .product-table {
+ width: 100%;
+ border-collapse: collapse;
+ border: 1px solid #000;
+ }
+ .product-table th, .product-table td {
+ border: 1px solid #000;
+ padding: 6px;
+ text-align: center;
+ font-size: 12px;
+ line-height: 1.4;
+ }
+ .product-table th {
+ font-weight: bold;
+ }
+ .total-value {
+ font-weight: bold;
+ }
+ .footer-section {
+ margin-top: auto;
+ }
+ .footer-row {
+ display: flex;
+ margin-bottom: 3px;
+ line-height: 22px;
+ justify-content: space-between;
+ }
+ .footer-item {
+ display: flex;
+ margin-right: 20px;
+ }
+ .footer-item .label {
+ font-weight: bold;
+ width: 80px;
+ font-size: 12px;
+ }
+ .footer-item .value {
+ min-width: 80px;
+ font-size: 12px;
+ }
+ .address-item .address-value {
+ min-width: 200px;
+ }
+ @media print {
+ body {
+ margin: 0;
+ padding: 0;
+ }
+ .print-page {
+ margin: 0;
+ padding: 10mm;
+ /* padding-left: 20mm; */
+ page-break-inside: avoid;
+ page-break-after: always;
+ }
+ .print-page:last-child {
+ page-break-after: avoid;
+ }
+ }
+ </style>
+ </head>
+ <body>
+ `;
+
+ // 涓烘瘡鏉℃暟鎹敓鎴愭墦鍗伴〉闈�
+ printData.value.forEach((item, index) => {
+ printContent += `
+ <div class="print-page">
+ <div class="delivery-note">
+ <div class="header">
+ <div class="company-name">榧庤瘹鐟炲疄涓氭湁闄愯矗浠诲叕鍙�</div>
+ <div class="document-title">闆跺敭鍙戣揣鍗�</div>
+ </div>
+
+ <div class="info-section">
+ <div class="info-row">
+ <div>
+ <span class="label">鍙戣揣鏃ユ湡锛�</span>
+ <span class="value">${formatDate(item.createTime)}</span>
+ </div>
+ <div>
+ <span class="label">瀹㈡埛鍚嶇О锛�</span>
+ <span class="value">${item.supplierName || '寮犵埍鏈�'}</span>
+ </div>
+ </div>
+ <div class="info-row">
+ <span class="label">鍗曞彿锛�</span>
+ <span class="value">${item.code || ''}</span>
+ </div>
+ </div>
+
+ <div class="table-section">
+ <table class="product-table">
+ <thead>
+ <tr>
+ <th>浜у搧鍚嶇О</th>
+ <th>瑙勬牸鍨嬪彿</th>
+ <th>鍗曚綅</th>
+ <th>鍗曚环</th>
+ <th>闆跺敭鏁伴噺</th>
+ <th>闆跺敭閲戦</th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr>
+ <td>${item.productName || '鐮傜伆鐮�'}</td>
+ <td>${item.model || '鏍囧噯'}</td>
+ <td>${item.unit || '鍧�'}</td>
+ <td>${item.taxInclusiveUnitPrice || '0'}</td>
+ <td>${item.inboundNum || '2000'}</td>
+ <td>${item.taxInclusiveTotalPrice || '0'}</td>
+ </tr>
+ </tbody>
+ <tfoot>
+ <tr>
+ <td class="label">鍚堣</td>
+ <td class="total-value"></td>
+ <td class="total-value"></td>
+ <td class="total-value"></td>
+ <td class="total-value">${item.inboundNum || '2000'}</td>
+ <td class="total-value">${item.taxInclusiveTotalPrice || '0'}</td>
+ </tr>
+ </tfoot>
+ </table>
+ </div>
+
+ <div class="footer-section">
+ <div class="footer-row">
+ <div class="footer-item">
+ <span class="label">鏀惰揣鐢佃瘽锛�</span>
+ <span class="value"></span>
+ </div>
+ <div class="footer-item">
+ <span class="label">鏀惰揣浜猴細</span>
+ <span class="value"></span>
+ </div>
+ <div class="footer-item address-item">
+ <span class="label">鏀惰揣鍦板潃锛�</span>
+ <span class="value address-value"></span>
+ </div>
+ </div>
+ <div class="footer-row">
+ <div class="footer-item">
+ <span class="label">鎿嶄綔鍛橈細</span>
+ <span class="value">${userStore.nickName || '鎾曞紑鍓�'}</span>
+ </div>
+ <div class="footer-item">
+ <span class="label">鎵撳嵃鏃ユ湡锛�</span>
+ <span class="value">${formatDateTime(new Date())}</span>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ `;
+ });
+
+ printContent += `
+ </body>
+ </html>
+ `;
+
+ // 鍐欏叆鍐呭鍒版柊绐楀彛
+ printWindow.document.write(printContent);
+ printWindow.document.close();
+
+ // 绛夊緟鍐呭鍔犺浇瀹屾垚鍚庢墦鍗�
+ printWindow.onload = () => {
+ setTimeout(() => {
+ printWindow.print();
+ printWindow.close();
+ printPreviewVisible.value = false;
+ }, 500);
+ };
+};
+
+
+
+// 鏍煎紡鍖栨棩鏈�
+const formatDate = (dateString) => {
+ if (!dateString) return getCurrentDate();
+ const date = new Date(dateString);
+ const year = date.getFullYear();
+ const month = String(date.getMonth() + 1).padStart(2, "0");
+ const day = String(date.getDate()).padStart(2, "0");
+ return `${year}/${month}/${day}`;
+};
+
+// 鏍煎紡鍖栨棩鏈熸椂闂�
+const formatDateTime = (date) => {
+ const year = date.getFullYear();
+ const month = String(date.getMonth() + 1).padStart(2, "0");
+ const day = String(date.getDate()).padStart(2, "0");
+ const hours = String(date.getHours()).padStart(2, "0");
+ const minutes = String(date.getMinutes()).padStart(2, "0");
+ const seconds = String(date.getSeconds()).padStart(2, "0");
+ return `${year}/${month}/${day} ${hours}:${minutes}:${seconds}`;
+};
+onMounted(() => {
+ getList();
+ fetchStockRecordTypeOptions();
+});
+</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/consumablesLogistics/dispatchLog/index.vue b/src/views/consumablesLogistics/dispatchLog/index.vue
new file mode 100644
index 0000000..ec24005
--- /dev/null
+++ b/src/views/consumablesLogistics/dispatchLog/index.vue
@@ -0,0 +1,38 @@
+<template>
+ <div class="app-container">
+ <el-tabs v-model="activeTab" @tab-change="handleTabChange">
+ <el-tab-pane v-for="tab in tabs"
+ :label="tab.label"
+ :name="tab.name"
+ :key="tab.name">
+ <record :type="tab.type" v-if="activeTab === tab.name" />
+ </el-tab-pane>
+ </el-tabs>
+ </div>
+</template>
+
+<script setup>
+import { ref, computed } from "vue";
+import Record from "@/views/consumablesLogistics/dispatchLog/Record.vue";
+const activeTab = ref('qualified')
+const type = ref(0)
+const tabs = computed(() => {
+ return [
+ {
+ label: '鍚堟牸鍑哄簱',
+ name: 'qualified',
+ type: '0'
+ },
+ {
+ label: '涓嶅悎鏍煎嚭搴�',
+ name: 'unqualified',
+ type: '1'
+ }
+ ]
+})
+
+const handleTabChange = (tabName) => {
+ activeTab.value = tabName;
+ type.value = tabName === 'qualified' ? 0 : 1
+}
+</script>
diff --git a/src/views/consumablesLogistics/receiptManagement/Record.vue b/src/views/consumablesLogistics/receiptManagement/Record.vue
new file mode 100644
index 0000000..30d52b6
--- /dev/null
+++ b/src/views/consumablesLogistics/receiptManagement/Record.vue
@@ -0,0 +1,265 @@
+<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.productName"
+ style="width: 240px"
+ placeholder="璇疯緭鍏�"
+ clearable/>
+ <span class="search_title ml10">鏉ユ簮锛�</span>
+ <el-select v-model="searchForm.recordType"
+ style="width: 240px"
+ placeholder="璇烽�夋嫨"
+ clearable>
+ <el-option v-for="item in stockRecordTypeOptions"
+ :key="item.value"
+ :label="item.label"
+ :value="item.value"/>
+ </el-select>
+ <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"
+ style="width: 100%"
+ 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="inboundBatches"
+ width="280"
+ show-overflow-tooltip/>
+ <el-table-column label="鍏ュ簱鏃堕棿"
+ prop="createTime"
+ show-overflow-tooltip/>
+ <el-table-column label="浜у搧澶х被"
+ prop="productName"
+ show-overflow-tooltip/>
+ <el-table-column label="瑙勬牸鍨嬪彿"
+ prop="model"
+ show-overflow-tooltip/>
+ <el-table-column label="鍗曚綅"
+ prop="unit"
+ show-overflow-tooltip/>
+ <el-table-column label="鍏ュ簱鏁伴噺"
+ prop="stockInNum"
+ show-overflow-tooltip/>
+ <el-table-column label="杞︾墝鍙�"
+ prop="licensePlateNo"
+ v-if="type === '0'"
+ show-overflow-tooltip/>
+ <el-table-column label="姣涢噸(鍚�)"
+ prop="grossWeight"
+ v-if="type === '0'"
+ show-overflow-tooltip/>
+ <el-table-column label="鐨噸(鍚�)"
+ prop="tareWeight"
+ v-if="type === '0'"
+ show-overflow-tooltip/>
+ <el-table-column label="鍑�閲�(鍚�)"
+ prop="netWeight"
+ v-if="type === '0'"
+ show-overflow-tooltip/>
+ <el-table-column label="鍏ュ簱浜�"
+ prop="createBy"
+ show-overflow-tooltip/>
+ <el-table-column label="鏉ユ簮"
+ prop="recordType"
+ show-overflow-tooltip>
+ <template #default="scope">
+ {{ getRecordType(scope.row.recordType) }}
+ </template>
+ </el-table-column>
+ <el-table-column label="杩囩鏃ユ湡"
+ prop="weighingDate"
+ v-if="type === '0'"
+ show-overflow-tooltip/>
+ <el-table-column label="杩囩鍛�"
+ prop="weighingOperator"
+ v-if="type === '0'"
+ 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="pageProductChange"/>
+ </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 {
+ getConsumablesInRecordListPage,
+ batchDeleteConsumablesInRecords,
+} from "@/api/consumablesLogistics/consumablesInRecord.js";
+import {
+ findAllQualifiedStockInRecordTypeOptions, findAllUnQualifiedStockInRecordTypeOptions,
+} from "@/api/basicData/enum.js";
+
+const {proxy} = getCurrentInstance();
+
+const props = defineProps({
+ type: {
+ type: String,
+ required: true,
+ default: '0'
+ }
+})
+
+const tableData = ref([]);
+const selectedRows = ref([]);
+const tableLoading = ref(false);
+const stockRecordTypeOptions = ref([]);
+const page = reactive({
+ current: 1,
+ size: 100,
+});
+const total = ref(0);
+
+const data = reactive({
+ searchForm: {
+ productName: "",
+ timeStr: "",
+ recordType: "",
+ },
+});
+const {searchForm} = toRefs(data);
+
+const handleQuery = () => {
+ page.current = 1;
+ getList();
+};
+
+const getRecordType = (recordType) => {
+ return stockRecordTypeOptions.value.find(item => item.value === recordType)?.label || ''
+}
+
+const pageProductChange = obj => {
+ page.current = obj.page;
+ page.size = obj.limit;
+ getList();
+};
+
+const getList = () => {
+ tableLoading.value = true;
+ const params = {...page, type: props.type};
+ params.timeStr = searchForm.value.timeStr;
+ params.productName = searchForm.value.productName;
+ params.recordType = searchForm.value.recordType;
+ getConsumablesInRecordListPage(params)
+ .then(res => {
+ tableData.value = res.data.records || [];
+ total.value = res.data.total ?? 0;
+ }).finally(() => {
+ tableLoading.value = false;
+ })
+};
+
+const fetchStockRecordTypeOptions = () => {
+ if (props.type === '0') {
+ findAllQualifiedStockInRecordTypeOptions()
+ .then(res => {
+ stockRecordTypeOptions.value = res.data;
+ })
+ return
+ }
+ findAllUnQualifiedStockInRecordTypeOptions()
+ .then(res => {
+ stockRecordTypeOptions.value = res.data;
+ })
+}
+
+const handleSelectionChange = selection => {
+ selectedRows.value = selection.filter(item => item.id);
+};
+
+const expandedRowKeys = ref([]);
+
+const handleOut = () => {
+ ElMessageBox.confirm("鏄惁纭瀵煎嚭锛�", "瀵煎嚭", {
+ confirmButtonText: "纭",
+ cancelButtonText: "鍙栨秷",
+ type: "warning",
+ })
+ .then(() => {
+ proxy.download("/consumablesInRecord/exportConsumablesInRecord", {type: props.type}, props.type === '0' ? "鑰楁潗鍚堟牸鍏ュ簱.xlsx" : "鑰楁潗涓嶅悎鏍煎叆搴�.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(() => {
+ batchDeleteConsumablesInRecords(ids)
+ .then(() => {
+ proxy.$modal.msgSuccess("鍒犻櫎鎴愬姛");
+ getList();
+ })
+ .catch(() => {
+ proxy.$modal.msgError("鍒犻櫎澶辫触");
+ });
+ })
+ .catch(() => {
+ proxy.$modal.msg("宸插彇娑�");
+ });
+};
+
+onMounted(() => {
+ getList();
+ fetchStockRecordTypeOptions();
+});
+</script>
+
+<style scoped lang="scss"></style>
diff --git a/src/views/consumablesLogistics/receiptManagement/index.vue b/src/views/consumablesLogistics/receiptManagement/index.vue
new file mode 100644
index 0000000..0277a1d
--- /dev/null
+++ b/src/views/consumablesLogistics/receiptManagement/index.vue
@@ -0,0 +1,36 @@
+<template>
+ <div class="app-container">
+ <el-tabs v-model="activeTab" @tab-change="handleTabChange">
+ <el-tab-pane v-for="tab in tabs"
+ :label="tab.label"
+ :name="tab.name"
+ :key="tab.name">
+ <record :type="tab.type" v-if="activeTab === tab.name" />
+ </el-tab-pane>
+ </el-tabs>
+ </div>
+</template>
+
+<script setup>
+import Record from "@/views/consumablesLogistics/receiptManagement/Record.vue";
+
+const activeTab = ref('qualified')
+const type = ref(0)
+const tabs = ref([
+ {
+ label: '鍚堟牸鍏ュ簱',
+ name: 'qualified',
+ type: '0'
+ },
+ {
+ label: '涓嶅悎鏍煎叆搴�',
+ name: 'unqualified',
+ type: '1'
+ }
+])
+
+const handleTabChange = (tabName) => {
+ activeTab.value = tabName;
+ type.value = tabName === 'qualified' ? 0 : 1
+}
+</script>
diff --git a/src/views/consumablesLogistics/stockManagement/FrozenAndThaw.vue b/src/views/consumablesLogistics/stockManagement/FrozenAndThaw.vue
new file mode 100644
index 0000000..da15378
--- /dev/null
+++ b/src/views/consumablesLogistics/stockManagement/FrozenAndThaw.vue
@@ -0,0 +1,164 @@
+<template>
+ <div>
+ <el-dialog
+ v-model="isShow"
+ :title="operationType === 'frozen' ? '鍐荤粨搴撳瓨' : '瑙e喕搴撳瓨'"
+ width="800"
+ @close="closeModal"
+ >
+ <el-form label-width="140px" :model="formState" ref="formRef">
+ <el-form-item
+ :label="operationType === 'frozen' ? '鍐荤粨鏁伴噺锛�' : '瑙e喕鏁伴噺锛�'"
+ prop="lockedQuantity"
+ >
+ <el-input-number v-model="formState.lockedQuantity" :step="1" :min="1" precision="0" style="width: 100%" :max="maxCount" />
+ </el-form-item>
+ </el-form>
+
+ <template #footer>
+ <div class="dialog-footer">
+ <el-button type="primary" @click="handleSubmit">纭</el-button>
+ <el-button @click="closeModal">鍙栨秷</el-button>
+ </div>
+ </template>
+ </el-dialog>
+ </div>
+</template>
+
+<script setup>
+import {ref, computed, getCurrentInstance} from "vue";
+import {frozenConsumablesIn, thawConsumablesIn} from "@/api/consumablesLogistics/consumablesIn.js";
+import {frozenConsumablesUninventory, thawConsumablesUninventory} from "@/api/consumablesLogistics/consumablesUninventory.js";
+
+const props = defineProps({
+ visible: {
+ type: Boolean,
+ required: true,
+ },
+
+ operationType: {
+ type: String,
+ required: true,
+ default: 'frozen',
+ },
+
+ type: {
+ type: String,
+ required: true,
+ default: 'qualified',
+ },
+
+ record: {
+ type: Object,
+ default: () => {},
+ }
+});
+
+const emit = defineEmits(['update:visible', 'completed']);
+
+// 鍝嶅簲寮忔暟鎹紙鏇夸唬閫夐」寮忕殑 data锛�
+const formState = ref({
+ lockedQuantity: 0,
+});
+
+const isShow = computed({
+ get() {
+ return props.visible;
+ },
+ set(val) {
+ emit('update:visible', val);
+ },
+});
+
+
+let { proxy } = getCurrentInstance()
+
+const closeModal = () => {
+ // 閲嶇疆琛ㄥ崟鏁版嵁
+ formState.value = {
+ lockedQuantity: undefined
+ };
+ isShow.value = false;
+};
+
+const maxCount = computed(() => {
+ // 鍐荤粨搴撳瓨鏈�澶ф暟閲忎负鏈В鍐绘暟閲�
+ if (props.operationType === 'frozen') {
+ return props.record.unLockedQuantity
+ }
+ // 瑙e喕搴撳瓨鏈�澶ф暟閲忎负宸插喕缁撴暟閲�
+ return props.record.lockedQuantity
+})
+
+const handleSubmit = () => {
+ proxy.$refs["formRef"].validate(valid => {
+ if (valid) {
+ const data = Object.assign({id: props.record.id}, formState.value);
+ if (props.type === 'qualified') {
+ // 鍐荤粨
+ if (props.operationType === 'frozen') {
+ frozenConsumablesIn(data).then(res => {
+ if (res.code === 200) {
+ // 鍏抽棴妯℃�佹
+ isShow.value = false;
+ // 鍛婄煡鐖剁粍浠跺凡瀹屾垚
+ emit('completed');
+ proxy.$modal.msgSuccess("鎻愪氦鎴愬姛");
+ } else {
+ proxy.$modal.msgError(res.msg);
+ }
+ })
+ } else {
+ thawConsumablesIn(data).then(res => {
+ if (res.code === 200) {
+ // 鍏抽棴妯℃�佹
+ isShow.value = false;
+ // 鍛婄煡鐖剁粍浠跺凡瀹屾垚
+ emit('completed');
+ proxy.$modal.msgSuccess("鎻愪氦鎴愬姛");
+ } else {
+ proxy.$modal.msgError(res.msg);
+ }
+ })
+ }
+ } else {
+ if (props.operationType === 'frozen') {
+ frozenConsumablesUninventory(data).then(res => {
+ if (res.code === 200) {
+ // 鍏抽棴妯℃�佹
+ isShow.value = false;
+ // 鍛婄煡鐖剁粍浠跺凡瀹屾垚
+ emit('completed');
+ proxy.$modal.msgSuccess("鎻愪氦鎴愬姛");
+ } else {
+ proxy.$modal.msgError(res.msg);
+ }
+ })
+ } else {
+ thawConsumablesUninventory(data).then(res => {
+ if (res.code === 200) {
+ // 鍏抽棴妯℃�佹
+ isShow.value = false;
+ // 鍛婄煡鐖剁粍浠跺凡瀹屾垚
+ emit('completed');
+ proxy.$modal.msgSuccess("鎻愪氦鎴愬姛");
+ } else {
+ proxy.$modal.msgError(res.msg);
+ }
+ })
+ }
+ }
+ }
+ })
+};
+
+onMounted(() => {
+ formState.value.lockedQuantity = maxCount.value;
+})
+
+defineExpose({
+ closeModal,
+ handleSubmit,
+ isShow,
+});
+</script>
diff --git a/src/views/consumablesLogistics/stockManagement/Import.vue b/src/views/consumablesLogistics/stockManagement/Import.vue
new file mode 100644
index 0000000..305e2b3
--- /dev/null
+++ b/src/views/consumablesLogistics/stockManagement/Import.vue
@@ -0,0 +1,93 @@
+<template>
+ <el-dialog v-model="isShow" title="瀵煎叆搴撳瓨" @close="closeModal">
+ <FileUpload
+ ref="fileUploadRef"
+ accept=".xlsx, .xls"
+ :headers="upload.headers"
+ :action="upload.url"
+ :disabled="upload.isUploading"
+ :showTip="true"
+ @success="handleFileSuccess"
+ :downloadTemplate="downloadTemplate"
+ />
+ <template #footer>
+ <div class="dialog-footer">
+ <el-button type="primary" @click="submitFileForm">纭� 瀹�</el-button>
+ <el-button @click="closeModal">鍙� 娑�</el-button>
+ </div>
+ </template>
+ </el-dialog>
+</template>
+
+<script setup>
+import {computed, ref, getCurrentInstance, reactive} from "vue";
+import { getToken } from "@/utils/auth.js";
+import { FileUpload } from "@/components/Upload";
+import { ElMessage } from "element-plus";
+
+defineOptions({
+ name: "瀵煎叆搴撳瓨",
+});
+
+const { proxy } = getCurrentInstance()
+
+const props = defineProps({
+ visible: {
+ type: Boolean,
+ required: true,
+ },
+
+ type: {
+ type: String,
+ required: true,
+ default: 'qualified',
+ },
+});
+
+const emit = defineEmits(['update:visible', 'uploadSuccess']);
+
+
+const isShow = computed({
+ get() {
+ return props.visible;
+ },
+ set(val) {
+ emit('update:visible', val);
+ },
+});
+
+const fileUploadRef = ref();
+const upload = reactive({
+ // 鏄惁鏄剧ず寮瑰嚭灞傦紙搴撳瓨瀵煎叆锛�
+ open: false,
+ // 鏄惁绂佺敤涓婁紶
+ isUploading: false,
+ // 璁剧疆涓婁紶鐨勮姹傚ご閮�
+ headers: { Authorization: "Bearer " + getToken() },
+ // 涓婁紶鐨勫湴鍧�
+ url: import.meta.env.VITE_APP_BASE_API + "/consumablesInventory/importConsumablesInventory",
+});
+
+const submitFileForm = () => {
+ fileUploadRef.value.uploadApi();
+};
+
+const handleFileSuccess = (response) => {
+ const { code, msg } = response;
+ if (code == 200) {
+ ElMessage({ message: "瀵煎叆鎴愬姛", type: "success" });
+ emit('uploadSuccess');
+ closeModal();
+ } else {
+ ElMessage({ message: msg, type: "error" });
+ }
+};
+
+const downloadTemplate = () => {
+ proxy.download("/consumablesInventory/downloadConsumablesInventory", {}, "鑰楁潗搴撳瓨瀵煎叆妯℃澘.xlsx");
+}
+
+const closeModal = () => {
+ isShow.value = false;
+};
+</script>
diff --git a/src/views/consumablesLogistics/stockManagement/New.vue b/src/views/consumablesLogistics/stockManagement/New.vue
new file mode 100644
index 0000000..2100186
--- /dev/null
+++ b/src/views/consumablesLogistics/stockManagement/New.vue
@@ -0,0 +1,273 @@
+<template>
+ <div>
+ <el-dialog
+ v-model="isShow"
+ title="鏂板搴撳瓨"
+ width="800"
+ @close="closeModal"
+ >
+ <el-form label-width="140px" :model="formState" label-position="top" ref="formRef">
+ <el-form-item
+ label="浜у搧鍚嶇О"
+ prop="productModelId"
+ :rules="[
+ {
+ required: true,
+ message: '璇烽�夋嫨浜у搧',
+ trigger: 'change',
+ }
+ ]"
+ >
+ <el-button type="primary" @click="showProductSelectDialog = true">
+ {{ formState.productName ? formState.productName : '閫夋嫨浜у搧' }}
+ </el-button>
+ </el-form-item>
+
+ <el-form-item
+ label="瑙勬牸"
+ prop="productModelName"
+ >
+ <el-input v-model="formState.productModelName" disabled />
+ </el-form-item>
+
+ <el-form-item
+ label="鍗曚綅"
+ prop="unit"
+ >
+ <el-input v-model="formState.unit" disabled />
+ </el-form-item>
+
+ <!-- productType === 0锛氬師鏉愭枡 -->
+ <el-form-item
+ v-if="type === 'qualified' && formState.productType === 0"
+ label="杞︾墝鍙�"
+ prop="licensePlateNo"
+ >
+ <el-input v-model="formState.licensePlateNo" />
+ </el-form-item>
+
+ <el-form-item
+ v-if="type === 'qualified' && formState.productType === 0"
+ label="姣涢噸(鍚�)"
+ prop="grossWeight"
+ >
+ <el-input-number
+ v-model="formState.grossWeight"
+ :step="0.01"
+ :min="0"
+ style="width: 100%"
+ @change="computeNetWeight"
+ />
+ </el-form-item>
+
+ <el-form-item
+ v-if="type === 'qualified' && formState.productType === 0"
+ label="鐨噸(鍚�)"
+ prop="tareWeight"
+ >
+ <el-input-number
+ v-model="formState.tareWeight"
+ :step="0.01"
+ :min="0"
+ style="width: 100%"
+ @change="computeNetWeight"
+ />
+ </el-form-item>
+
+ <el-form-item
+ v-if="type === 'qualified' && formState.productType === 0"
+ label="鍑�閲�(鍚�)"
+ prop="netWeight"
+ >
+ <el-input-number
+ v-model="formState.netWeight"
+ :step="0.01"
+ :min="0"
+ style="width: 100%"
+ disabled
+ />
+ </el-form-item>
+
+ <el-form-item
+ v-if="type === 'qualified' && formState.productType === 0"
+ label="杩囩鏃ユ湡"
+ prop="weighingDate"
+ >
+ <el-date-picker
+ style="width: 100%"
+ v-model="formState.weighingDate"
+ value-format="YYYY-MM-DD HH:mm:ss"
+ format="YYYY-MM-DD HH:mm:ss"
+ type="datetime"
+ placeholder="璇烽�夋嫨杩囩鏃ユ湡"
+ clearable
+ />
+ </el-form-item>
+
+ <el-form-item
+ v-if="type === 'qualified' && formState.productType === 0"
+ label="杩囩鍛�"
+ prop="weighingOperator"
+ >
+ <el-input v-model="formState.weighingOperator" />
+ </el-form-item>
+
+ <el-form-item label="澶囨敞" prop="remark">
+ <el-input v-model="formState.remark" type="textarea" />
+ </el-form-item>
+ </el-form>
+
+ <!-- 浜у搧閫夋嫨寮圭獥 -->
+ <ProductSelectDialog
+ v-model="showProductSelectDialog"
+ @confirm="handleProductSelect"
+ single
+ />
+ <template #footer>
+ <div class="dialog-footer">
+ <el-button type="primary" @click="handleSubmit">纭</el-button>
+ <el-button @click="closeModal">鍙栨秷</el-button>
+ </div>
+ </template>
+ </el-dialog>
+ </div>
+</template>
+
+<script setup>
+import {ref, computed, getCurrentInstance} from "vue";
+import ProductSelectDialog from "@/views/basicData/product/ProductSelectDialog.vue";
+import {createConsumablesIn} from "@/api/consumablesLogistics/consumablesIn.js";
+import {createConsumablesUnInventory} from "@/api/consumablesLogistics/consumablesUninventory.js";
+
+const props = defineProps({
+ visible: {
+ type: Boolean,
+ required: true,
+ },
+
+ type: {
+ type: String,
+ required: true,
+ default: 'qualified',
+ },
+});
+
+const emit = defineEmits(['update:visible', 'completed']);
+
+// 鍝嶅簲寮忔暟鎹紙鏇夸唬閫夐」寮忕殑 data锛�
+const formState = ref({
+ productId: undefined,
+ productModelId: undefined,
+ productName: "",
+ productModelName: "",
+ unit: "",
+ productType: undefined,
+ // 杩囩鐩稿叧瀛楁锛堜粎鍘熸潗鏂欏悎鏍煎搧浣跨敤锛�
+ licensePlateNo: "",
+ grossWeight: undefined,
+ tareWeight: undefined,
+ netWeight: undefined,
+ weighingDate: undefined,
+ weighingOperator: "",
+ remark: '',
+});
+
+const isShow = computed({
+ get() {
+ return props.visible;
+ },
+ set(val) {
+ emit('update:visible', val);
+ },
+});
+
+const showProductSelectDialog = ref(false);
+
+let { proxy } = getCurrentInstance()
+
+const closeModal = () => {
+ // 閲嶇疆琛ㄥ崟鏁版嵁
+ formState.value = {
+ productId: undefined,
+ productModelId: undefined,
+ productName: "",
+ productModelName: "",
+ description: '',
+ };
+ isShow.value = false;
+};
+
+// 浜у搧閫夋嫨澶勭悊
+const handleProductSelect = async (products) => {
+ formState.value.weighingDate = undefined;
+ formState.value.grossWeight = undefined;
+ formState.value.tareWeight = undefined;
+ formState.value.netWeight = undefined;
+ if (products && products.length > 0) {
+ const product = products[0];
+ formState.value.productId = product.productId;
+ formState.value.productName = product.productName;
+ formState.value.productModelName = product.model;
+ formState.value.productModelId = product.id;
+ formState.value.unit = product.unit;
+ formState.value.productType = product.productType;
+ showProductSelectDialog.value = false;
+ // 瑙﹀彂琛ㄥ崟楠岃瘉鏇存柊
+ proxy.$refs["formRef"]?.validateField('productModelId');
+ }
+};
+
+// 鍑�閲� = 姣涢噸 - 鐨噸
+const computeNetWeight = () => {
+ const { grossWeight, tareWeight } = formState.value;
+ if (grossWeight != null && tareWeight != null) {
+ const net = Number(grossWeight) - Number(tareWeight);
+ // 淇濈暀涓や綅灏忔暟锛屼笖涓嶄负璐�
+ const safeNet = Number(net.toFixed(2));
+ formState.value.netWeight = safeNet > 0 ? safeNet : 0;
+ } else {
+ formState.value.netWeight = undefined;
+ }
+};
+
+const handleSubmit = () => {
+ proxy.$refs["formRef"].validate(valid => {
+ if (valid) {
+ // 楠岃瘉鏄惁閫夋嫨浜嗕骇鍝佸拰瑙勬牸
+ if (!formState.value.productModelId) {
+ proxy.$modal.msgError("璇烽�夋嫨浜у搧");
+ return;
+ }
+ if (!formState.value.productModelId) {
+ proxy.$modal.msgError("璇烽�夋嫨瑙勬牸");
+ return;
+ }
+ if (props.type === 'qualified') {
+ createConsumablesIn(formState.value).then(res => {
+ // 鍏抽棴妯℃�佹
+ isShow.value = false;
+ // 鍛婄煡鐖剁粍浠跺凡瀹屾垚
+ emit('completed');
+ proxy.$modal.msgSuccess("鎻愪氦鎴愬姛");
+ })
+ } else {
+ createConsumablesUnInventory(formState.value).then(res => {
+ // 鍏抽棴妯℃�佹
+ isShow.value = false;
+ // 鍛婄煡鐖剁粍浠跺凡瀹屾垚
+ emit('completed');
+ proxy.$modal.msgSuccess("鎻愪氦鎴愬姛");
+ })
+ }
+
+ }
+ })
+};
+
+
+defineExpose({
+ closeModal,
+ handleSubmit,
+ isShow,
+});
+</script>
diff --git a/src/views/consumablesLogistics/stockManagement/Qualified.vue b/src/views/consumablesLogistics/stockManagement/Qualified.vue
new file mode 100644
index 0000000..c559f02
--- /dev/null
+++ b/src/views/consumablesLogistics/stockManagement/Qualified.vue
@@ -0,0 +1,211 @@
+<template>
+ <div class="app-container">
+ <div class="search_form">
+ <div>
+ <span class="search_title ml10">浜у搧澶х被锛�</span>
+ <el-input v-model="searchForm.productName"
+ style="width: 240px"
+ placeholder="璇疯緭鍏�"
+ clearable/>
+ <el-button type="primary" @click="handleQuery" style="margin-left: 10px">鎼滅储</el-button>
+ </div>
+ <div>
+ <el-button type="primary" @click="isShowNewModal = true">鏂板搴撳瓨</el-button>
+ <el-button type="info" plain icon="Upload" @click="isShowImportModal = true">
+ 瀵煎叆搴撳瓨
+ </el-button>
+ <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" style="width: 100%"
+ :row-class-name="tableRowClassName" 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="parentName" show-overflow-tooltip />
+ <el-table-column label="浜у搧澶х被" prop="productName" show-overflow-tooltip />
+ <el-table-column label="瑙勬牸鍨嬪彿" prop="model" show-overflow-tooltip />
+ <el-table-column label="鍗曚綅" prop="unit" show-overflow-tooltip />
+ <el-table-column label="搴撳瓨鏁伴噺" prop="qualitity" show-overflow-tooltip />
+ <el-table-column label="鍐荤粨鏁伴噺" prop="lockedQuantity" show-overflow-tooltip />
+ <!-- <el-table-column label="搴撳瓨棰勮鏁伴噺" prop="warnNum" show-overflow-tooltip /> -->
+ <el-table-column label="鍑�閲�(鍚�)" prop="netWeight" show-overflow-tooltip />
+ <el-table-column label="澶囨敞" prop="remark" show-overflow-tooltip />
+ <el-table-column label="鏈�杩戞洿鏂版椂闂�" prop="updateTime" show-overflow-tooltip />
+ <el-table-column fixed="right" label="鎿嶄綔" min-width="60" align="center">
+ <template #default="scope">
+ <el-button link type="primary" size="small" @click="showSubtractModal(scope.row)" :disabled="scope.row.unLockedQuantity === 0">鍑哄簱</el-button>
+ <el-button link type="primary" size="small" v-if="scope.row.unLockedQuantity > 0" @click="showFrozenModal(scope.row)">鍐荤粨</el-button>
+ <el-button link type="primary" size="small" v-if="scope.row.lockedQuantity > 0" @click="showThawModal(scope.row)">瑙e喕</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>
+ <new-stock-inventory v-if="isShowNewModal"
+ v-model:visible="isShowNewModal"
+ type="qualified"
+ @completed="handleQuery" />
+
+ <subtract-stock-inventory v-if="isShowSubtractModal"
+ v-model:visible="isShowSubtractModal"
+ :record="record"
+ type="qualified"
+ @completed="handleQuery" />
+ <!-- 瀵煎叆搴撳瓨-->
+ <import-stock-inventory v-if="isShowImportModal"
+ v-model:visible="isShowImportModal"
+ type="qualified"
+ @uploadSuccess="handleQuery" />
+ <!-- 鍐荤粨/瑙e喕搴撳瓨-->
+ <frozen-and-thaw-stock-inventory v-if="isShowFrozenAndThawModal"
+ v-model:visible="isShowFrozenAndThawModal"
+ :record="record"
+ :operation-type="operationType"
+ type="qualified"
+ @completed="handleQuery" />
+ </div>
+</template>
+
+<script setup>
+import pagination from '@/components/PIMTable/Pagination.vue'
+import { ref, reactive, toRefs, onMounted, getCurrentInstance } from 'vue'
+import {ElMessage, ElMessageBox} from "element-plus";
+import { getConsumablesInListPage } from "@/api/consumablesLogistics/consumablesIn.js";
+const NewStockInventory = defineAsyncComponent(() => import("@/views/consumablesLogistics/stockManagement/New.vue"));
+const SubtractStockInventory = defineAsyncComponent(() => import("@/views/consumablesLogistics/stockManagement/Subtract.vue"));
+const ImportStockInventory = defineAsyncComponent(() => import("@/views/consumablesLogistics/stockManagement/Import.vue"));
+const FrozenAndThawStockInventory = defineAsyncComponent(() => import("@/views/consumablesLogistics/stockManagement/FrozenAndThaw.vue"));
+const { proxy } = getCurrentInstance()
+const tableData = ref([])
+const selectedRows = ref([])
+const record = ref({})
+const tableLoading = ref(false)
+const page = reactive({
+ current: 1,
+ size: 100,
+})
+const total = ref(0)
+// 鏄惁鏄剧ず鏂板寮规
+const isShowNewModal = ref(false)
+// 鏄惁鏄剧ず棰嗙敤寮规
+const isShowSubtractModal = ref(false)
+// 鏄惁鏄剧ず鍐荤粨/瑙e喕寮规
+const isShowFrozenAndThawModal = ref(false)
+// 鎿嶄綔绫诲瀷
+const operationType = ref('frozen')
+// 鏄惁鏄剧ず瀵煎叆寮规
+const isShowImportModal = ref(false)
+const data = reactive({
+ searchForm: {
+ productName: '',
+ }
+})
+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
+ getConsumablesInListPage({ ...searchForm.value, ...page }).then(res => {
+ tableLoading.value = false
+ tableData.value = res.data.records
+ total.value = res.data.total
+ // 鏁版嵁鍔犺浇瀹屾垚鍚庢鏌ュ簱瀛�
+ // checkStockAndCreatePurchase();
+ }).catch(() => {
+ tableLoading.value = false
+ })
+}
+
+const handleFileSuccess = (response) => {
+ const { code, msg } = response;
+ if (code == 200) {
+ ElMessage({ message: "瀵煎叆鎴愬姛", type: "success" });
+ upload.open = false;
+ emits("uploadSuccess");
+ } else {
+ ElMessage({ message: msg, type: "error" });
+ }
+};
+
+// 鐐瑰嚮棰嗙敤
+const showSubtractModal = (row) => {
+ record.value = row
+ isShowSubtractModal.value = true
+}
+
+// 鐐瑰嚮鍐荤粨
+const showFrozenModal = (row) => {
+ record.value = row
+ isShowFrozenAndThawModal.value = true
+ operationType.value = 'frozen'
+}
+
+// 鐐瑰嚮瑙e喕
+const showThawModal = (row) => {
+ record.value = row
+ isShowFrozenAndThawModal.value = true
+ operationType.value = 'thaw'
+}
+
+// 琛ㄦ牸閫夋嫨鏁版嵁
+const handleSelectionChange = (selection) => {
+ // 杩囨护鎺夊瓙鏁版嵁
+ selectedRows.value = selection.filter(item => item.id);
+ console.log('selection', selectedRows.value)
+}
+const expandedRowKeys = ref([])
+
+// 琛ㄦ牸琛岀被鍚�
+const tableRowClassName = ({ row }) => {
+ const stock = Number(row?.unLockedQuantity ?? 0);
+ const warn = Number(row?.warnNum ?? 0);
+ if (!Number.isFinite(stock) || !Number.isFinite(warn)) {
+ return '';
+ }
+ return stock < warn ? 'row-low-stock' : '';
+};
+
+// 瀵煎嚭
+const handleOut = () => {
+ ElMessageBox.confirm(
+ '鏄惁纭瀵煎嚭锛�',
+ '瀵煎嚭', {
+ confirmButtonText: '纭',
+ cancelButtonText: '鍙栨秷',
+ type: 'warning',
+ }
+ ).then(() => {
+ proxy.download("/consumablesInventory/exportConsumablesInventory", {}, '鑰楁潗鍚堟牸搴撳瓨淇℃伅.xlsx')
+ }).catch(() => {
+ proxy.$modal.msg("宸插彇娑�")
+ })
+}
+
+onMounted(() => {
+ getList()
+})
+</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>
diff --git a/src/views/consumablesLogistics/stockManagement/Subtract.vue b/src/views/consumablesLogistics/stockManagement/Subtract.vue
new file mode 100644
index 0000000..5598000
--- /dev/null
+++ b/src/views/consumablesLogistics/stockManagement/Subtract.vue
@@ -0,0 +1,285 @@
+<template>
+ <div>
+ <el-dialog
+ v-model="isShow"
+ title="棰嗙敤"
+ width="800"
+ @close="closeModal"
+ >
+ <el-form label-width="140px" :model="formState" label-position="top" ref="formRef">
+ <el-form-item
+ label="浜у搧鍚嶇О"
+ prop="productModelId"
+ :rules="[
+ {
+ required: true,
+ message: '璇烽�夋嫨浜у搧',
+ trigger: 'change',
+ }
+ ]"
+ >
+ <el-button type="primary" @click="showProductSelectDialog = true" disabled>
+ {{ formState.productName ? formState.productName : '閫夋嫨浜у搧' }}
+ </el-button>
+ </el-form-item>
+
+ <el-form-item
+ label="瑙勬牸"
+ prop="productModelName"
+ >
+ <el-input v-model="formState.model" disabled />
+ </el-form-item>
+
+ <el-form-item
+ label="鍗曚綅"
+ prop="unit"
+ >
+ <el-input v-model="formState.unit" disabled />
+ </el-form-item>
+
+ <el-form-item
+ label="杞︾墝鍙�"
+ prop="licensePlateNo"
+ >
+ <el-input v-model="formState.licensePlateNo" />
+ </el-form-item>
+
+ <el-form-item
+ label="姣涢噸(鍚�)"
+ prop="grossWeight"
+ >
+ <el-input-number
+ v-model="formState.grossWeight"
+ :step="0.01"
+ :min="0"
+ style="width: 100%"
+ @change="computeNetWeight"
+ />
+ </el-form-item>
+
+ <el-form-item
+ label="鐨噸(鍚�)"
+ prop="tareWeight"
+ >
+ <el-input-number
+ v-model="formState.tareWeight"
+ :step="0.01"
+ :min="0"
+ style="width: 100%"
+ @change="computeNetWeight"
+ />
+ </el-form-item>
+
+ <el-form-item
+ label="鍑�閲�(鍚�)"
+ prop="netWeight"
+ >
+ <el-input-number
+ v-model="formState.netWeight"
+ :step="0.01"
+ :min="0"
+ style="width: 100%"
+ disabled
+ />
+ </el-form-item>
+
+ <el-form-item
+ label="杩囩鏃ユ湡"
+ prop="weighingDate"
+ >
+ <el-date-picker
+ style="width: 100%"
+ v-model="formState.weighingDate"
+ value-format="YYYY-MM-DD HH:mm:ss"
+ format="YYYY-MM-DD HH:mm:ss"
+ type="datetime"
+ placeholder="璇烽�夋嫨杩囩鏃ユ湡"
+ clearable
+ />
+ </el-form-item>
+
+ <el-form-item
+ label="杩囩鍛�"
+ prop="weighingOperator"
+ >
+ <el-input v-model="formState.weighingOperator" />
+ </el-form-item>
+
+ <el-form-item label="澶囨敞" prop="remark">
+ <el-input v-model="formState.remark" type="textarea" />
+ </el-form-item>
+ </el-form>
+
+ <!-- 浜у搧閫夋嫨寮圭獥 -->
+ <ProductSelectDialog
+ v-model="showProductSelectDialog"
+ @confirm="handleProductSelect"
+ single
+ />
+ <template #footer>
+ <div class="dialog-footer">
+ <el-button type="primary" @click="handleSubmit">纭</el-button>
+ <el-button @click="closeModal">鍙栨秷</el-button>
+ </div>
+ </template>
+ </el-dialog>
+ </div>
+</template>
+
+<script setup>
+import {ref, computed, getCurrentInstance, onMounted} from "vue";
+import ProductSelectDialog from "@/views/basicData/product/ProductSelectDialog.vue";
+import {subtractConsumablesIn} from "@/api/consumablesLogistics/consumablesIn.js";
+import {subtractConsumablesUnInventory} from "@/api/consumablesLogistics/consumablesUninventory.js";
+
+const props = defineProps({
+ visible: {
+ type: Boolean,
+ required: true,
+ },
+ record: {
+ type: Object,
+ default: () => {},
+ },
+ type: {
+ type: String,
+ required: true,
+ default: 'qualified',
+ },
+});
+
+const emit = defineEmits(['update:visible', 'completed']);
+
+onMounted(() => {
+ initFormData()
+})
+
+const isRawMaterial = computed(() => {
+ return props.record.parentName === '鍘熸潗鏂�';
+})
+
+const initFormData = () => {
+ if (props.record) {
+ formState.value = {
+ ...props.record,
+ }
+ }
+}
+
+// 鍝嶅簲寮忔暟鎹紙鏇夸唬閫夐」寮忕殑 data锛�
+const formState = ref({
+ productId: undefined,
+ productModelId: undefined,
+ productName: "",
+ model: "",
+ unit: "",
+ // 杩囩鐩稿叧瀛楁
+ licensePlateNo: "",
+ grossWeight: undefined,
+ tareWeight: undefined,
+ netWeight: undefined,
+ weighingDate: undefined,
+ weighingOperator: "",
+ remark: '',
+});
+
+const isShow = computed({
+ get() {
+ return props.visible;
+ },
+ set(val) {
+ emit('update:visible', val);
+ },
+});
+
+const showProductSelectDialog = ref(false);
+
+let { proxy } = getCurrentInstance()
+
+const closeModal = () => {
+ // 閲嶇疆琛ㄥ崟鏁版嵁
+ formState.value = {
+ productId: undefined,
+ productModelId: undefined,
+ productName: "",
+ model: "",
+ unit: "",
+ licensePlateNo: "",
+ grossWeight: undefined,
+ tareWeight: undefined,
+ netWeight: undefined,
+ weighingDate: undefined,
+ weighingOperator: "",
+ remark: '',
+ };
+ isShow.value = false;
+};
+
+// 鍑�閲� = 姣涢噸 - 鐨噸
+const computeNetWeight = () => {
+ const { grossWeight, tareWeight } = formState.value;
+ if (grossWeight != null && tareWeight != null) {
+ const net = Number(grossWeight) - Number(tareWeight);
+ const safeNet = Number(net.toFixed(2));
+ formState.value.netWeight = safeNet > 0 ? safeNet : 0;
+ } else {
+ formState.value.netWeight = undefined;
+ }
+};
+
+// 浜у搧閫夋嫨澶勭悊
+const handleProductSelect = async (products) => {
+ if (products && products.length > 0) {
+ const product = products[0];
+ console.log(product)
+ formState.value.productId = product.productId;
+ formState.value.productName = product.productName;
+ formState.value.productModelName = product.model;
+ formState.value.productModelId = product.id;
+ formState.value.unit = product.unit;
+ showProductSelectDialog.value = false;
+ // 瑙﹀彂琛ㄥ崟楠岃瘉鏇存柊
+ proxy.$refs["formRef"]?.validateField('productModelId');
+ }
+};
+
+const handleSubmit = () => {
+ proxy.$refs["formRef"].validate(valid => {
+ if (valid) {
+ // 楠岃瘉鏄惁閫夋嫨浜嗕骇鍝佸拰瑙勬牸
+ if (!formState.value.productModelId) {
+ proxy.$modal.msgError("璇烽�夋嫨浜у搧");
+ return;
+ }
+ if (!formState.value.productModelId) {
+ proxy.$modal.msgError("璇烽�夋嫨瑙勬牸");
+ return;
+ }
+ if (props.type === 'qualified') {
+ subtractConsumablesIn(formState.value).then(res => {
+ // 鍏抽棴妯℃�佹
+ isShow.value = false;
+ // 鍛婄煡鐖剁粍浠跺凡瀹屾垚
+ emit('completed');
+ proxy.$modal.msgSuccess("鎻愪氦鎴愬姛");
+ })
+ } else {
+ subtractConsumablesUnInventory(formState.value).then(res => {
+ // 鍏抽棴妯℃�佹
+ isShow.value = false;
+ // 鍛婄煡鐖剁粍浠跺凡瀹屾垚
+ emit('completed');
+ proxy.$modal.msgSuccess("鎻愪氦鎴愬姛");
+ })
+ }
+ }
+ })
+};
+
+
+defineExpose({
+ closeModal,
+ handleSubmit,
+ isShow,
+});
+</script>
diff --git a/src/views/consumablesLogistics/stockManagement/Unqualified.vue b/src/views/consumablesLogistics/stockManagement/Unqualified.vue
new file mode 100644
index 0000000..ec92fc8
--- /dev/null
+++ b/src/views/consumablesLogistics/stockManagement/Unqualified.vue
@@ -0,0 +1,187 @@
+<template>
+ <div class="app-container">
+ <div class="search_form">
+ <div>
+ <span class="search_title ml10">浜у搧澶х被锛�</span>
+ <el-input v-model="searchForm.productName"
+ style="width: 240px"
+ placeholder="璇疯緭鍏�"
+ clearable/>
+ <el-button type="primary" @click="handleQuery" style="margin-left: 10px">鎼滅储</el-button>
+ </div>
+ <div>
+ <el-button type="primary" @click="isShowNewModal = true">鏂板搴撳瓨</el-button>
+ <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" style="width: 100%"
+ :row-class-name="tableRowClassName" 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="productName" show-overflow-tooltip />
+ <el-table-column label="瑙勬牸鍨嬪彿" prop="model" show-overflow-tooltip />
+ <el-table-column label="鍗曚綅" prop="unit" show-overflow-tooltip />
+ <el-table-column label="搴撳瓨鏁伴噺" prop="qualitity" show-overflow-tooltip />
+ <el-table-column label="鍐荤粨鏁伴噺" prop="lockedQuantity" show-overflow-tooltip />
+ <el-table-column label="澶囨敞" prop="remark" show-overflow-tooltip />
+ <el-table-column label="鏈�杩戞洿鏂版椂闂�" prop="updateTime" show-overflow-tooltip />
+ <el-table-column fixed="right" label="鎿嶄綔" min-width="60" align="center">
+ <template #default="scope">
+ <el-button link type="primary" size="small" @click="showSubtractModal(scope.row)" :disabled="scope.row.unLockedQuantity === 0">棰嗙敤</el-button>
+ <el-button link type="primary" size="small" v-if="scope.row.unLockedQuantity > 0" @click="showFrozenModal(scope.row)">鍐荤粨</el-button>
+ <el-button link type="primary" size="small" v-if="scope.row.lockedQuantity > 0" @click="showThawModal(scope.row)">瑙e喕</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>
+ <new-stock-inventory v-if="isShowNewModal"
+ v-model:visible="isShowNewModal"
+ type="unqualified"
+ @completed="handleQuery" />
+
+ <subtract-stock-inventory v-if="isShowSubtractModal"
+ v-model:visible="isShowSubtractModal"
+ :record="record"
+ type="unqualified"
+ @completed="handleQuery" />
+ <!-- 鍐荤粨/瑙e喕搴撳瓨-->
+ <frozen-and-thaw-stock-inventory v-if="isShowFrozenAndThawModal"
+ v-model:visible="isShowFrozenAndThawModal"
+ :record="record"
+ :operation-type="operationType"
+ type="unqualified"
+ @completed="handleQuery" />
+ </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 { getConsumablesUninventoryListPage } from "@/api/consumablesLogistics/consumablesUninventory.js";
+const NewStockInventory = defineAsyncComponent(() => import("@/views/consumablesLogistics/stockManagement/New.vue"));
+const SubtractStockInventory = defineAsyncComponent(() => import("@/views/consumablesLogistics/stockManagement/Subtract.vue"));
+const FrozenAndThawStockInventory = defineAsyncComponent(() => import("@/views/consumablesLogistics/stockManagement/FrozenAndThaw.vue"));
+
+const { proxy } = getCurrentInstance()
+const tableData = ref([])
+const selectedRows = ref([])
+const record = ref({})
+const tableLoading = ref(false)
+const page = reactive({
+ current: 1,
+ size: 100,
+})
+const total = ref(0)
+// 鏄惁鏄剧ず鏂板寮规
+const isShowNewModal = ref(false)
+// 鏄惁鏄剧ず棰嗙敤寮规
+const isShowSubtractModal = ref(false)
+// 鏄惁鏄剧ず鍐荤粨/瑙e喕寮规
+const isShowFrozenAndThawModal = ref(false)
+// 鎿嶄綔绫诲瀷
+const operationType = ref('frozen')
+const data = reactive({
+ searchForm: {
+ productName: '',
+ }
+})
+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
+ getConsumablesUninventoryListPage({ ...searchForm.value, ...page }).then(res => {
+ tableLoading.value = false
+ tableData.value = res.data.records
+ total.value = res.data.total
+ // 鏁版嵁鍔犺浇瀹屾垚鍚庢鏌ュ簱瀛�
+ // checkStockAndCreatePurchase();
+ }).catch(() => {
+ tableLoading.value = false
+ })
+}
+
+// 鐐瑰嚮棰嗙敤
+const showSubtractModal = (row) => {
+ record.value = row
+ isShowSubtractModal.value = true
+}
+
+// 鐐瑰嚮鍐荤粨
+const showFrozenModal = (row) => {
+ record.value = row
+ isShowFrozenAndThawModal.value = true
+ operationType.value = 'frozen'
+}
+
+// 鐐瑰嚮瑙e喕
+const showThawModal = (row) => {
+ record.value = row
+ isShowFrozenAndThawModal.value = true
+ operationType.value = 'thaw'
+}
+
+// 琛ㄦ牸閫夋嫨鏁版嵁
+const handleSelectionChange = (selection) => {
+ // 杩囨护鎺夊瓙鏁版嵁
+ selectedRows.value = selection.filter(item => item.id);
+ console.log('selection', selectedRows.value)
+}
+const expandedRowKeys = ref([])
+
+// 琛ㄦ牸琛岀被鍚�
+const tableRowClassName = ({ row }) => {
+ // const stock = Number(row?.unLockedQuantity ?? 0);
+ // const warn = Number(row?.warnNum ?? 0);
+ // if (!Number.isFinite(stock) || !Number.isFinite(warn)) {
+ // return '';
+ // }
+ // return stock < warn ? 'row-low-stock' : '';
+};
+
+// 瀵煎嚭
+const handleOut = () => {
+ ElMessageBox.confirm(
+ '鏄惁纭瀵煎嚭锛�',
+ '瀵煎嚭', {
+ confirmButtonText: '纭',
+ cancelButtonText: '鍙栨秷',
+ type: 'warning',
+ }
+ ).then(() => {
+ proxy.download("/consumablesUninventory/exportStockUninventory", {}, '鑰楁潗涓嶅悎鏍煎簱瀛樹俊鎭�.xlsx')
+ }).catch(() => {
+ proxy.$modal.msg("宸插彇娑�")
+ })
+}
+
+onMounted(() => {
+ getList()
+})
+</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>
diff --git a/src/views/consumablesLogistics/stockManagement/index.vue b/src/views/consumablesLogistics/stockManagement/index.vue
new file mode 100644
index 0000000..aad9650
--- /dev/null
+++ b/src/views/consumablesLogistics/stockManagement/index.vue
@@ -0,0 +1,33 @@
+<template>
+ <div class="app-container">
+ <el-tabs v-model="activeTab" @tab-change="handleTabChange">
+ <el-tab-pane v-for="tab in tabs"
+ :label="tab.label"
+ :name="tab.name"
+ :key="tab.name">
+ <component :is="tab.name === 'qualified' ? QualifiedRecord : UnqualifiedRecord" />
+ </el-tab-pane>
+ </el-tabs>
+ </div>
+</template>
+
+<script setup>
+import QualifiedRecord from "@/views/consumablesLogistics/stockManagement/Qualified.vue";
+import UnqualifiedRecord from "@/views/consumablesLogistics/stockManagement/Unqualified.vue";
+
+const activeTab = ref('qualified')
+const tabs = ref([
+ {
+ label: '鍚堟牸搴撳瓨',
+ name: 'qualified'
+ },
+ {
+ label: '涓嶅悎鏍煎簱瀛�',
+ name: 'unqualified'
+ }
+])
+
+const handleTabChange = (tabName) => {
+ activeTab.value = tabName;
+}
+</script>
diff --git a/src/views/consumablesLogistics/stockReport/index.vue b/src/views/consumablesLogistics/stockReport/index.vue
new file mode 100644
index 0000000..0305289
--- /dev/null
+++ b/src/views/consumablesLogistics/stockReport/index.vue
@@ -0,0 +1,675 @@
+<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="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
+ label="鍏ュ簱鏃堕棿"
+ prop="createTime"
+ width="200"
+ show-overflow-tooltip
+ v-if="searchForm.reportType !== 'inout'"
+ />
+ <el-table-column
+ label="鍏ュ簱鎵规"
+ prop="inboundBatches"
+ width="240"
+ show-overflow-tooltip
+ v-if="searchForm.reportType !== 'inout'"
+ />
+ <el-table-column
+ label="浜у搧澶х被"
+ prop="productName"
+ show-overflow-tooltip
+ />
+ <el-table-column
+ label="瑙勬牸鍨嬪彿"
+ prop="model"
+ show-overflow-tooltip
+ />
+ <el-table-column
+ label="鍗曚綅"
+ prop="unit"
+ show-overflow-tooltip
+ />
+ <el-table-column
+ label="鍏ュ簱鏁伴噺"
+ prop="totalStockIn"
+ align="center"
+ v-if="searchForm.reportType === 'inout'"
+ />
+ <el-table-column
+ label="鍏ュ簱鏁伴噺"
+ prop="stockInNum"
+ align="center"
+ v-else
+ />
+ <el-table-column
+ label="鍑哄簱鏁伴噺"
+ prop="totalStockOut"
+ width="100"
+ align="center"
+ v-if="searchForm.reportType === 'inout'"
+ />
+ <el-table-column
+ label="鐜板湪搴撳瓨"
+ prop="currentStock"
+ align="center"
+ />
+ <el-table-column
+ label="鐜板噣閲�(鍚�)"
+ prop="currentWeight"
+ align="center"
+ />
+ <el-table-column label="鏉ユ簮"
+ prop="recordType"
+ v-if="searchForm.reportType !== 'inout'"
+ show-overflow-tooltip>
+ <template #default="scope">
+ {{ getRecordType(scope.row.recordType) }}
+ </template>
+ </el-table-column>
+ <el-table-column
+ label="鍏ュ簱浜�"
+ prop="createBy"
+ width="80"
+ v-if="searchForm.reportType !== 'inout'"
+ show-overflow-tooltip
+ />
+ </el-table>
+ </el-card>
+ </div>
+ </div>
+</template>
+
+<script setup>
+import { ref, reactive, onMounted, nextTick, getCurrentInstance } from 'vue'
+import { ElMessage } from 'element-plus'
+import * as echarts from 'echarts'
+import {
+ getConsumablesInInAndOutReportList,
+ getConsumablesInReportList
+} from "@/api/consumablesLogistics/consumablesIn.js";
+import {
+ findAllQualifiedStockInRecordTypeOptions,
+} from "@/api/basicData/enum.js";
+
+
+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 stockRecordTypeOptions = ref([])
+
+const getRecordType = (recordType) => {
+ return stockRecordTypeOptions.value.find(item => item.value === recordType)?.label || ''
+}
+
+// 鑾峰彇鏉ユ簮绫诲瀷閫夐」
+const fetchStockRecordTypeOptions = () => {
+ findAllQualifiedStockInRecordTypeOptions()
+ .then(res => {
+ stockRecordTypeOptions.value = res.data;
+ })
+}
+
+// 鑾峰彇琛ㄦ牸鏍囬
+const getTableTitle = () => {
+ const typeMap = {
+ daily: '鏃ユ姤璇︾粏鏁版嵁',
+ monthly: '鏈堟姤璇︾粏鏁版嵁',
+ 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
+
+ if (searchForm.reportType === 'inout') {
+ response = await getConsumablesInInAndOutReportList(params)
+ } else {
+ response = await getConsumablesInReportList(params)
+ }
+ if (response.code === 200) {
+ reportData.value.tableData = response.data.records
+ // 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 === '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("/consumablesInventory/exportConsumablesInventory", 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]
+ ]
+
+ fetchStockRecordTypeOptions()
+})
+</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/equipmentManagement/repair/Modal/RepairModal.vue b/src/views/equipmentManagement/repair/Modal/RepairModal.vue
index 1aa82ec..62ea81d 100644
--- a/src/views/equipmentManagement/repair/Modal/RepairModal.vue
+++ b/src/views/equipmentManagement/repair/Modal/RepairModal.vue
@@ -72,6 +72,28 @@
</el-form-item>
</el-col>
</el-row>
+ <el-row>
+ <el-col :span="24">
+ <el-form-item label="鐜板満鐓х墖">
+ <div class="repair-upload-area">
+ <el-upload
+ v-model:file-list="uploadFileList"
+ :action="uploadUrl"
+ :headers="uploadHeaders"
+ accept="image/*"
+ list-type="picture-card"
+ :limit="uploadLimit"
+ :on-success="handleUploadSuccess"
+ :on-remove="handleRemoveFile"
+ :before-upload="beforeUpload"
+ >
+ <el-icon><Plus /></el-icon>
+ </el-upload>
+ <div v-if="repairFileList.length === 0" class="upload-tip">璇蜂笂浼犵幇鍦虹収鐗囷紝鏈�澶� {{ uploadLimit }} 寮�</div>
+ </div>
+ </el-form-item>
+ </el-col>
+ </el-row>
</el-form>
</FormDialog>
</template>
@@ -84,10 +106,12 @@
getRepairById,
} from "@/api/equipmentManagement/repair";
import { ElMessage } from "element-plus";
+import { Plus } from "@element-plus/icons-vue";
import dayjs from "dayjs";
import useFormData from "@/hooks/useFormData";
import { getDeviceLedger } from "@/api/equipmentManagement/ledger";
import useUserStore from "@/store/modules/user";
+import { getToken } from "@/utils/auth";
defineOptions({
name: "璁惧鎶ヤ慨寮圭獥",
@@ -102,24 +126,83 @@
const userStore = useUserStore();
const deviceOptions = ref([]);
+// 鐜板満鐓х墖涓婁紶锛堜笌 APP 瀛楁涓�鑷达細fileList / commonFileList锛�
+const uploadUrl = import.meta.env.VITE_APP_BASE_API + "/file/upload";
+const uploadLimit = 10;
+const uploadFileList = ref([]);
+const repairFileList = ref([]);
+const uploadHeaders = { Authorization: "Bearer " + getToken() };
+const baseApi = import.meta.env.VITE_APP_BASE_API || "";
+
+const formatFileUrl = (url) => {
+ if (!url) return "";
+ if (url.startsWith("http://") || url.startsWith("https://")) return url;
+ const path = url.replace(/^\/+/, "");
+ return path ? baseApi + "/" + path : baseApi;
+};
+
const loadDeviceName = async () => {
const { data } = await getDeviceLedger();
deviceOptions.value = data;
};
const { form, resetForm } = useFormData({
- deviceLedgerId: undefined, // 璁惧Id
- deviceName: undefined, // 璁惧鍚嶇О
- deviceModel: undefined, // 瑙勬牸鍨嬪彿
- repairTime: dayjs().format("YYYY-MM-DD"), // 鎶ヤ慨鏃ユ湡锛岄粯璁ゅ綋澶�
- repairName: userStore.nickName, // 鎶ヤ慨浜�
- remark: undefined, // 鏁呴殰鐜拌薄
- status: 0, // 鎶ヤ慨鐘舵��
+ deviceLedgerId: undefined,
+ deviceName: undefined,
+ deviceModel: undefined,
+ repairTime: dayjs().format("YYYY-MM-DD"),
+ repairName: userStore.nickName,
+ remark: undefined,
+ status: 0,
});
const setDeviceModel = (deviceId) => {
const option = deviceOptions.value.find((item) => item.id === deviceId);
form.deviceModel = option.deviceModel;
+};
+
+const beforeUpload = (file) => {
+ const isImage = file.type.startsWith("image/");
+ if (!isImage) {
+ ElMessage.warning("鍙兘涓婁紶鍥剧墖");
+ return false;
+ }
+ if (repairFileList.value.length >= uploadLimit) {
+ ElMessage.warning(`鏈�澶氫笂浼� ${uploadLimit} 寮犲浘鐗嘸);
+ return false;
+ }
+ return true;
+};
+
+const handleUploadSuccess = (res, file) => {
+ if (res?.code !== 200 && res?.code !== 0) {
+ ElMessage.error(res?.msg || "涓婁紶澶辫触");
+ const idx = uploadFileList.value.findIndex((f) => f.uid === file.uid);
+ if (idx > -1) uploadFileList.value.splice(idx, 1);
+ return;
+ }
+ const d = res?.data !== undefined ? res.data : res;
+ if (!d) return;
+ const item = {
+ uid: file.uid,
+ id: d.id,
+ url: d.url || d.downloadUrl || d.tempPath,
+ downloadUrl: d.downloadUrl || d.url,
+ bucketFilename: d.bucketFilename || d.originalFilename || file.name,
+ originalFilename: d.originalFilename || d.bucketFilename || file.name,
+ name: d.bucketFilename || d.originalFilename || file.name,
+ size: d.size ?? d.byteSize ?? file.size,
+ byteSize: d.byteSize ?? d.size ?? file.size,
+ uploadResponse: res,
+ };
+ repairFileList.value.push(item);
+};
+
+const handleRemoveFile = (file) => {
+ const targetUrl = file.url || file.response?.data?.url || file.response?.data?.downloadUrl;
+ repairFileList.value = repairFileList.value.filter(
+ (f) => f.uid !== file.uid && (f.url || f.downloadUrl) !== targetUrl
+ );
};
const setForm = (data) => {
@@ -130,14 +213,39 @@
form.repairName = data.repairName;
form.remark = data.remark;
form.status = data.status;
+ const list = data.fileList || data.commonFileList || [];
+ repairFileList.value = (Array.isArray(list) ? list : []).map((f, i) => ({
+ uid: f.id || "existing-" + i,
+ id: f.id,
+ url: f.url || f.downloadUrl,
+ downloadUrl: f.downloadUrl || f.url,
+ bucketFilename: f.bucketFilename || f.originalFilename || f.name,
+ originalFilename: f.originalFilename || f.bucketFilename || f.name,
+ name: f.bucketFilename || f.originalFilename || f.name || "鍥剧墖",
+ size: f.size ?? f.byteSize,
+ byteSize: f.byteSize ?? f.size,
+ ...f,
+ }));
+ uploadFileList.value = repairFileList.value.map((f, i) => ({
+ uid: f.uid || "existing-" + i,
+ name: f.name || f.bucketFilename || "鍥剧墖",
+ url: formatFileUrl(f.url || f.downloadUrl),
+ status: "success",
+ }));
};
const sendForm = async () => {
loading.value = true;
try {
+ const fileList = repairFileList.value.map((f) => {
+ const d = f.uploadResponse?.data !== undefined ? f.uploadResponse.data : f.uploadResponse;
+ return d ? { ...d } : (f.id || f.url ? { ...f } : null);
+ }).filter(Boolean);
+ const submitData = { ...form };
+ if (fileList.length) submitData.fileList = fileList;
const { code } = id.value
- ? await editRepair({ id: unref(id), ...form })
- : await addRepair(form);
+ ? await editRepair({ id: unref(id), ...submitData })
+ : await addRepair(submitData);
if (code == 200) {
ElMessage.success(`${id.value ? "缂栬緫" : "鏂板"}鎶ヤ慨鎴愬姛`);
visible.value = false;
@@ -150,16 +258,22 @@
const handleCancel = () => {
resetForm();
+ repairFileList.value = [];
+ uploadFileList.value = [];
visible.value = false;
};
const handleClose = () => {
resetForm();
+ repairFileList.value = [];
+ uploadFileList.value = [];
visible.value = false;
};
const openAdd = async () => {
id.value = undefined;
+ repairFileList.value = [];
+ uploadFileList.value = [];
visible.value = true;
await nextTick();
await loadDeviceName();
@@ -180,4 +294,15 @@
});
</script>
-<style lang="scss" scoped></style>
+<style lang="scss" scoped>
+.repair-upload-area {
+ :deep(.el-upload-list--picture-card) {
+ --el-upload-list-picture-card-size: 100px;
+ }
+}
+.upload-tip {
+ font-size: 12px;
+ color: var(--el-text-color-secondary);
+ margin-top: 8px;
+}
+</style>
diff --git a/src/views/equipmentManagement/repair/index.vue b/src/views/equipmentManagement/repair/index.vue
index 1e7af53..0f305ee 100644
--- a/src/views/equipmentManagement/repair/index.vue
+++ b/src/views/equipmentManagement/repair/index.vue
@@ -112,6 +112,13 @@
缂栬緫
</el-button>
<el-button
+ type="info"
+ link
+ @click="viewAttachments(row)"
+ >
+ 鏌ョ湅闄勪欢
+ </el-button>
+ <el-button
type="success"
link
:disabled="row.status === 1"
@@ -132,17 +139,26 @@
</div>
<RepairModal ref="repairModalRef" @ok="getTableData"/>
<MaintainModal ref="maintainModalRef" @ok="getTableData"/>
+ <FileListDialog
+ ref="fileListDialogRef"
+ v-model="fileDialogVisible"
+ title="鏌ョ湅闄勪欢"
+ :show-upload-button="false"
+ :show-delete-button="false"
+ name-column-label="闄勪欢鍚嶇О"
+ />
</div>
</template>
<script setup>
import { onMounted, getCurrentInstance, computed } from "vue";
import {usePaginationApi} from "@/hooks/usePaginationApi";
-import {getRepairPage, delRepair} from "@/api/equipmentManagement/repair";
+import {getRepairPage, delRepair, getRepairById} from "@/api/equipmentManagement/repair";
import RepairModal from "./Modal/RepairModal.vue";
import {ElMessageBox, ElMessage} from "element-plus";
import dayjs from "dayjs";
import MaintainModal from "./Modal/MaintainModal.vue";
+import FileListDialog from "@/components/Dialog/FileListDialog.vue";
defineOptions({
name: "璁惧鎶ヤ慨",
@@ -153,6 +169,37 @@
// 妯℃�佹瀹炰緥
const repairModalRef = ref();
const maintainModalRef = ref();
+const fileListDialogRef = ref();
+const fileDialogVisible = ref(false);
+
+const baseApi = import.meta.env.VITE_APP_BASE_API || "";
+const formatFileUrl = (url) => {
+ if (!url) return "";
+ if (url.startsWith("http://") || url.startsWith("https://")) return url;
+ const path = String(url).replace(/^\/+/, "");
+ return path ? baseApi + "/" + path : baseApi;
+};
+
+// 鏌ョ湅闄勪欢锛堜笌 APP 瀛楁涓�鑷达細fileList / commonFileList锛�
+const viewAttachments = async (row) => {
+ try {
+ const { code, data } = await getRepairById(row.id);
+ if (code === 200 && data) {
+ const list = data.fileList || data.commonFileList || [];
+ const mapped = (Array.isArray(list) ? list : []).map((f) => ({
+ id: f.id,
+ name: f.originalFilename || f.bucketFilename || f.name || "闄勪欢",
+ url: formatFileUrl(f.url || f.downloadUrl),
+ raw: f,
+ }));
+ fileListDialogRef.value?.open(mapped);
+ } else {
+ ElMessage.warning("鑾峰彇闄勪欢澶辫触");
+ }
+ } catch (e) {
+ ElMessage.error("鑾峰彇闄勪欢澶辫触");
+ }
+};
// 琛ㄦ牸澶氶�夋閫変腑椤�
const multipleList = ref([]);
@@ -232,7 +279,7 @@
dataType: "slot",
slot: "operation",
align: "center",
- width: "300px",
+ width: "360px",
},
]
);
diff --git a/src/views/inventoryManagement/dispatchLog/Record.vue b/src/views/inventoryManagement/dispatchLog/Record.vue
index ed1723e..08cbd74 100644
--- a/src/views/inventoryManagement/dispatchLog/Record.vue
+++ b/src/views/inventoryManagement/dispatchLog/Record.vue
@@ -77,6 +77,21 @@
show-overflow-tooltip
/>
<el-table-column
+ label="杞︾墝鍙�"
+ prop="licensePlateNo"
+ show-overflow-tooltip
+ />
+ <el-table-column
+ label="姣涢噸(鍚�)"
+ prop="grossWeight"
+ show-overflow-tooltip
+ />
+ <el-table-column
+ label="鐨噸(鍚�)"
+ prop="tareWeight"
+ show-overflow-tooltip
+ />
+ <el-table-column
label="鍑�閲�(鍚�)"
prop="netWeight"
show-overflow-tooltip
@@ -94,8 +109,13 @@
</template>
</el-table-column>
<el-table-column
- label="杞︾墝"
- prop="licensePlateNo"
+ label="杩囩鏃ユ湡"
+ prop="weighingDate"
+ show-overflow-tooltip
+ />
+ <el-table-column
+ label="杩囩鍛�"
+ prop="weighingOperator"
show-overflow-tooltip
/>
</el-table>
diff --git a/src/views/qualityManagement/InspectItem/index.vue b/src/views/qualityManagement/InspectItem/index.vue
index 6748a2e..faeb847 100644
--- a/src/views/qualityManagement/InspectItem/index.vue
+++ b/src/views/qualityManagement/InspectItem/index.vue
@@ -47,9 +47,9 @@
<el-form-item label="鍐呮帶鍊�" prop="internalControl">
<el-input v-model="form.internalControl" placeholder="璇疯緭鍏ュ唴鎺у��" clearable style="width: 280px" />
</el-form-item>
- <el-form-item label="鍖栭獙鍊�" prop="testValue">
+ <!-- <el-form-item label="鍖栭獙鍊�" prop="testValue">
<el-input v-model="form.testValue" placeholder="璇疯緭鍏ュ寲楠屽��" clearable style="width: 280px" />
- </el-form-item>
+ </el-form-item> -->
</el-form>
<template #footer>
<div class="dialog-footer">
@@ -98,7 +98,7 @@
{ label: "鍗曚綅", prop: "unit", width: 120 },
{ label: "鏍囧噯鍊�", prop: "standardValue", width: 160 },
{ label: "鍐呮帶鍊�", prop: "internalControl", width: 160 },
- { label: "鍖栭獙鍊�", prop: "testValue", width: 160 },
+ // { label: "鍖栭獙鍊�", prop: "testValue", width: 160 },
{
dataType: "action",
label: "鎿嶄綔",
--
Gitblit v1.9.3