-
-
-
- 校验导入(推荐)
- 直接导入(不校验)
-
-
-
-
-
+
选择Excel文件
-
-
+
校验数据
-
开始导入
-
重置
@@ -54,12 +48,7 @@
-
+
@@ -67,61 +56,52 @@
-
-
+
-
-
-
-
-
-
-
-
-
-
-
-
+
-
-
+
已导入 {{ importedCount }} / {{ totalCount }} 条数据
-
-
+
-
@@ -132,63 +112,219 @@ import { addMaterialCoil } from '@/api/wms/coil';
import { addPendingAction } from '@/api/wms/pendingAction';
import { listRawMaterial } from '@/api/wms/rawMaterial';
import { listProduct } from '@/api/wms/product';
+import { listWarehouse } from '@/api/wms/warehouse';
+
+// ===================== 枚举定义 =====================
+/**
+ * 导入状态枚举
+ */
+const ImportStatus = Object.freeze({
+ IDLE: 'idle', // 闲置
+ PROCESSING: 'processing', // 处理中
+ FINISHED: 'finished', // 完成
+ ERROR: 'error' // 失败
+});
+
+/**
+ * 错误类型枚举(含校验逻辑)
+ */
+const ErrorType = Object.freeze({
+ // 表头错误
+ HEADER_COUNT_ERROR: {
+ message: (required, actual) => `表头数量不匹配,要求${required}列,实际${actual}列`
+ },
+ HEADER_NAME_ERROR: {
+ message: (index, required, actual) => `第${index + 1}列表头错误,要求:"${required}",实际:"${actual}"`
+ },
+ // 字段校验错误
+ FIELD_REQUIRED: {
+ message: (label) => `${label}不能为空`,
+ validator: (rowObj, rowNum, errorList) => {
+ const requiredFields = [
+ { key: 'type', label: '类型' },
+ { key: 'logicWarehouse', label: '逻辑库区' },
+ { key: 'inboundCoilNo', label: '入场卷号' },
+ { key: 'name', label: '名称' },
+ { key: 'specification', label: '规格' },
+ { key: 'weight', label: '重量(吨)' }
+ ];
+
+ requiredFields.forEach(field => {
+ const value = rowObj[field.key];
+ if (!value || (typeof value === 'string' && value.trim() === '')) {
+ errorList.push({
+ rowNum,
+ errorMsg: ErrorType.FIELD_REQUIRED.message(field.label)
+ });
+ }
+ });
+ }
+ },
+ // 类型错误
+ TYPE_ERROR: {
+ message: '类型只能是"原料"或"成品"',
+ validator: (rowObj, rowNum, errorList) => {
+ if (rowObj.type && !['原料', '成品'].includes(rowObj.type.trim())) {
+ errorList.push({
+ rowNum,
+ errorMsg: ErrorType.TYPE_ERROR.message
+ });
+ }
+ }
+ },
+ // 重量错误
+ WEIGHT_ERROR: {
+ message: '重量必须是大于0的数字',
+ validator: (rowObj, rowNum, errorList) => {
+ if (rowObj.weight) {
+ const weight = Number(rowObj.weight);
+ if (isNaN(weight) || weight <= 0) {
+ errorList.push({
+ rowNum,
+ errorMsg: ErrorType.WEIGHT_ERROR.message
+ });
+ }
+ }
+ }
+ },
+ // 仓库名不规范或不存在
+ WAREHOUSE_ERROR: {
+ message: (warehouseName) => `仓库名"${warehouseName}"不规范或不存在`,
+ validator: (rowObj, rowNum, errorList, warehouseMap) => {
+ const warehouseId = warehouseMap[rowObj.logicWarehouse];
+ if (!warehouseId) {
+ errorList.push({
+ rowNum,
+ errorMsg: ErrorType.WAREHOUSE_ERROR.message(rowObj.logicWarehouse)
+ });
+ }
+ }
+ },
+ // 物料ID不存在
+ ITEM_ID_NOT_FOUND: {
+ message: (type, name, spec, material, surfaceTreatmentDesc, manufacturer, zincLayer) => `未找到唯一匹配的${type}类型(名称:${name},规格:${spec},材质:${material || '无'},表面处理:${surfaceTreatmentDesc || '无'},厂家:${manufacturer || '无'},锌层:${zincLayer || '无'})`
+ }
+});
+
+/**
+ * 表格列配置枚举
+ */
+const TableColumnEnum = Object.freeze([
+ { prop: 'type', label: '类型', width: 80 },
+ { prop: 'logicWarehouse', label: '逻辑库区', width: 120 },
+ { prop: 'inboundCoilNo', label: '入场卷号', width: 150 },
+ { prop: 'factoryCoilNo', label: '厂家卷号', width: 150 },
+ { prop: 'weight', label: '重量(吨)', width: 120 },
+ { prop: 'remark', label: '备注', minWidth: 100 },
+ { prop: 'name', label: '名称', minWidth: 100 },
+ { prop: 'specification', label: '规格', minWidth: 100 },
+ { prop: 'material', label: '材质', width: 100 },
+ { prop: 'manufacturer', label: '厂家', minWidth: 100 },
+ { prop: 'surfaceTreatmentDesc', label: '表面处理', width: 120 },
+ { prop: 'zincLayer', label: '锌层', width: 100 }
+]);
+
+/**
+ * 系统常量枚举
+ */
+const SystemConstant = Object.freeze({
+ // Excel表头配置
+ REQUIRED_HEADERS: TableColumnEnum.map(col => col.label),
+ HEADER_MAP: TableColumnEnum.reduce((map, col) => {
+ map[col.label] = col.prop;
+ return map;
+ }, {}),
+ // 接口常量
+ COIL_DATA_TYPE: 10, // 钢卷数据类型
+ PENDING_ACTION_STATUS: 0, // 待处理状态
+ OPERATION_TYPE_IMPORT: 'import', // 操作类型-导入
+ PENDING_ACTION_TYPE_RECEIVE: 401, // 待处理类型-入库
+ // 物料类型映射
+ ITEM_TYPE_MAP: {
+ '原料': 'raw_material',
+ '成品': 'product'
+ },
+ // 物料ID字段映射
+ ITEM_ID_FIELD: {
+ 'raw_material': 'rawMaterialId',
+ 'product': 'productId'
+ }
+});
export default {
name: 'MaterialCoilImportWizard',
+ props: {
+ planId: {
+ type: String,
+ default: 0
+ }
+ },
data() {
return {
- // 导入模式:validate-校验导入 direct-直接导入
- importMode: 'validate',
+ // 枚举挂载到实例,方便模板使用
+ ImportStatus,
+ TableColumnEnum,
+
// 文件对象
file: null,
// 解析后的原始数据
rawData: [],
// 格式化后用于展示/导入的数据
tableData: [],
- // 校验错误列表
+ // 校验错误列表(为空表示校验通过)
errorList: [],
+ // 是否点击过校验按钮(新增状态)
+ isValidated: false,
// 导入进度(0-100)
progress: 0,
// 已导入条数
importedCount: 0,
// 总数据条数
totalCount: 0,
- // 导入状态:idle-闲置 processing-处理中 finished-完成 error-失败
- importStatus: 'idle',
+ // 导入状态
+ importStatus: ImportStatus.IDLE,
// 导入错误信息
importErrorMsg: '',
- // 规定的表头(顺序和名称必须匹配)
- requiredHeaders: [
- '类型', '逻辑库区', '入场卷号', '厂家卷号', '重量(吨)', '备注',
- '名称', '规格', '材质', '厂家', '表面处理', '锌层'
- ],
- // 表头映射(中文表头 -> 字段名)
- headerMap: {
- '类型': 'type',
- '逻辑库区': 'logicWarehouse',
- '入场卷号': 'inboundCoilNo',
- '厂家卷号': 'factoryCoilNo',
- '重量(吨)': 'weight',
- '备注': 'remark',
- '名称': 'name',
- '规格': 'specification',
- '材质': 'material',
- '厂家': 'manufacturer',
- '表面处理': 'surfaceTreatmentDesc',
- '锌层': 'zincLayer'
- }
+ // 仓库名到仓库id的映射关系
+ warehouseMap: {},
+ // 防呆loading状态
+ validateLoading: false, // 校验按钮loading
+ importLoading: false // 导入按钮loading
};
},
methods: {
/**
- * 处理文件选择
+ * 处理文件选择(新增防呆:操作中禁止切换文件)
*/
handleFileChange(file) {
+ // 操作中禁止切换文件
+ if (this.validateLoading || this.importLoading || this.importStatus === ImportStatus.PROCESSING) {
+ this.$message.warning('当前有操作正在进行中,请完成后再选择新文件');
+ return;
+ }
this.file = file.raw;
- // 选择文件后自动读取Excel
+ this.isValidated = false; // 选择新文件后重置校验状态
this.readExcel();
},
+ /**
+ * 获取所有的仓库,建立仓库名到仓库id的映射关系
+ */
+ async getWarehouseMap() {
+ try {
+ const response = await listWarehouse({ pageNum: 1, pageSize: 1000 });
+ const map = {};
+
+ for (let item of response.data) {
+ map[item.warehouseName] = item.warehouseId;
+ }
+ console.log(map, response);
+ this.warehouseMap = map;
+ } catch (error) {
+ this.handleError(`获取仓库列表失败:${error.message}`);
+ }
+ },
+
/**
* 读取Excel文件内容
*/
@@ -198,35 +334,29 @@ export default {
try {
const fileReader = new FileReader();
fileReader.readAsArrayBuffer(this.file);
-
+
fileReader.onload = async (e) => {
try {
const data = new Uint8Array(e.target.result);
const workbook = XLSX.read(data, { type: 'array' });
- // 取第一个sheet
const sheetName = workbook.SheetNames[0];
const worksheet = workbook.Sheets[sheetName];
- // 解析为JSON(跳过表头,从第二行开始)
const jsonData = XLSX.utils.sheet_to_json(worksheet, { header: 1 });
// 校验表头
this.validateHeaders(jsonData[0]);
- // 解析数据行(从第二行开始)
- this.rawData = jsonData.slice(1).filter(row => row.length > 0); // 过滤空行
+ // 解析数据行(过滤空行)
+ this.rawData = jsonData.slice(1).filter(row => row.length > 0);
// 格式化数据
this.formatExcel();
this.$message.success(`成功解析Excel,共读取到 ${this.rawData.length} 条数据`);
} catch (error) {
- this.$message.error(`解析Excel失败:${error.message}`);
- this.importStatus = 'error';
- this.importErrorMsg = `解析Excel失败:${error.message}`;
+ this.handleError(`解析Excel失败:${error.message}`);
}
};
} catch (error) {
- this.$message.error(`读取文件失败:${error.message}`);
- this.importStatus = 'error';
- this.importErrorMsg = `读取文件失败:${error.message}`;
+ this.handleError(`读取文件失败:${error.message}`);
}
},
@@ -235,21 +365,22 @@ export default {
*/
validateHeaders(headers) {
this.errorList = [];
- // 校验表头数量和名称
- if (headers.length !== this.requiredHeaders.length) {
+ const { REQUIRED_HEADERS } = SystemConstant;
+
+ // 校验表头数量
+ if (headers.length !== REQUIRED_HEADERS.length) {
this.errorList.push({
rowNum: 1,
- errorMsg: `表头数量不匹配,要求${this.requiredHeaders.length}列,实际${headers.length}列`
+ errorMsg: ErrorType.HEADER_COUNT_ERROR.message(REQUIRED_HEADERS.length, headers.length)
});
- return;
}
- // 校验每列表头名称
+ // 校验表头名称
headers.forEach((header, index) => {
- if (header !== this.requiredHeaders[index]) {
+ if (header !== REQUIRED_HEADERS[index]) {
this.errorList.push({
rowNum: 1,
- errorMsg: `第${index + 1}列表头错误,要求:"${this.requiredHeaders[index]}",实际:"${header}"`
+ errorMsg: ErrorType.HEADER_NAME_ERROR.message(index, REQUIRED_HEADERS[index], header)
});
}
});
@@ -260,81 +391,64 @@ export default {
},
/**
- * 校验Excel数据格式
+ * 校验Excel数据格式(唯一校验入口,添加防重复点击)
*/
- async validateExcel() {
+ async handleValidate() {
+ // 防重复点击校验
+ if (this.validateLoading) {
+ this.$message.warning('正在校验数据,请稍候...');
+ return;
+ }
+ if (!this.file) {
+ this.$message.warning('请先选择Excel文件');
+ return;
+ }
+
+ // 标记为已点击过校验
+ this.isValidated = true;
+
if (this.rawData.length === 0) {
this.$message.warning('暂无数据可校验');
return;
}
+ this.validateLoading = true; // 开启校验loading
this.errorList = [];
- // 逐行校验
- for (let i = 0; i < this.rawData.length; i++) {
- const row = this.rawData[i];
- const rowNum = i + 2; // 数据行从第二行开始,行号=索引+2
- const rowObj = this.formatRowData(row, rowNum);
+ try {
+ // 逐行校验
+ for (let i = 0; i < this.rawData.length; i++) {
+ const row = this.rawData[i];
+ const rowNum = i + 2; // 数据行从第二行开始
+ const rowObj = this.formatRowData(row, rowNum);
- // 1. 校验必填项
- const requiredFields = [
- { key: 'type', label: '类型', value: rowObj.type },
- { key: 'logicWarehouse', label: '逻辑库区', value: rowObj.logicWarehouse },
- { key: 'inboundCoilNo', label: '入场卷号', value: rowObj.inboundCoilNo },
- { key: 'name', label: '名称', value: rowObj.name },
- { key: 'specification', label: '规格', value: rowObj.specification },
- ];
+ // 执行各类校验器
+ ErrorType.FIELD_REQUIRED.validator(rowObj, rowNum, this.errorList);
+ ErrorType.TYPE_ERROR.validator(rowObj, rowNum, this.errorList);
+ ErrorType.WEIGHT_ERROR.validator(rowObj, rowNum, this.errorList);
+ ErrorType.WAREHOUSE_ERROR.validator(rowObj, rowNum, this.errorList, this.warehouseMap);
- // 检查必填项为空
- for (const field of requiredFields) {
- if (!field.value || field.value.toString().trim() === '') {
- this.errorList.push({
- rowNum,
- errorMsg: `${field.label}不能为空`
- });
+ // 校验物料ID是否存在(仅当当前行无基础错误时)
+ if (!this.errorList.some(err => err.rowNum === rowNum) && rowObj.type) {
+ const itemId = await this._findItemId(rowObj);
+ if (!itemId) {
+ this.errorList.push({
+ rowNum,
+ errorMsg: ErrorType.ITEM_ID_NOT_FOUND.message(rowObj.type, rowObj.name, rowObj.specification, rowObj.material, rowObj.surfaceTreatmentDesc, rowObj.manufacturer, rowObj.zincLayer)
+ });
+ }
}
}
- // 2. 校验类型只能是“原料”或“成品”
- if (rowObj.type && !['原料', '成品'].includes(rowObj.type.trim())) {
- this.errorList.push({
- rowNum,
- errorMsg: '类型只能是"原料"或"成品"'
- });
- }
-
- // 3. 校验重量是数字且大于0
- if (rowObj.weight) {
- const weight = Number(rowObj.weight);
- if (isNaN(weight) || weight <= 0) {
- this.errorList.push({
- rowNum,
- errorMsg: '重量必须是大于0的数字'
- });
- }
+ if (this.errorList.length > 0) {
+ this.$message.error(`数据校验失败,共发现${this.errorList.length}条错误`);
} else {
- this.errorList.push({
- rowNum,
- errorMsg: '重量不能为空'
- });
+ this.$message.success('数据校验通过,可以开始导入');
}
-
- // 4. 预校验itemId是否存在(仅校验模式)
- if (rowObj.type && ['原料', '成品'].includes(rowObj.type.trim()) && !this.errorList.some(err => err.rowNum === rowNum)) {
- const itemId = await this._findItemId(rowObj);
- if (!itemId) {
- this.errorList.push({
- rowNum,
- errorMsg: `未找到唯一匹配的${rowObj.type}(名称:${rowObj.name},规格:${rowObj.specification})`
- });
- }
- }
- }
-
- if (this.errorList.length > 0) {
- this.$message.error(`数据校验失败,共发现${this.errorList.length}条错误`);
- } else {
- this.$message.success('数据校验通过,可以开始导入');
+ } catch (error) {
+ this.handleError(`校验数据时发生错误:${error.message}`);
+ } finally {
+ this.validateLoading = false; // 关闭校验loading
}
},
@@ -345,7 +459,6 @@ export default {
this.tableData = [];
if (this.rawData.length === 0) return;
- // 逐行格式化
this.rawData.forEach((row, index) => {
const rowNum = index + 2;
const rowObj = this.formatRowData(row, rowNum);
@@ -357,51 +470,54 @@ export default {
* 格式化单行数据
*/
formatRowData(row, rowNum) {
+ const { HEADER_MAP, REQUIRED_HEADERS } = SystemConstant;
const rowObj = {};
+
// 映射表头和字段
- this.requiredHeaders.forEach((header, index) => {
- const field = this.headerMap[header];
- // 处理空值,统一转为字符串
+ REQUIRED_HEADERS.forEach((header, index) => {
+ const field = HEADER_MAP[header];
rowObj[field] = row[index] ? row[index].toString().trim() : '';
});
+
// 重量转数字
rowObj.weight = rowObj.weight ? Number(rowObj.weight) : 0;
// 增加行号
rowObj.rowNum = rowNum;
+
return rowObj;
},
/**
- * 处理校验按钮点击
- */
- async handleValidate() {
- if (!this.file) {
- this.$message.warning('请先选择Excel文件');
- return;
- }
- await this.validateExcel();
- },
-
- /**
- * 开始导入数据
+ * 开始导入数据(添加防重复点击和二次确认)
*/
async startImport() {
+ // 防重复点击导入
+ if (this.importLoading) {
+ this.$message.warning('正在导入数据,请勿重复操作...');
+ return;
+ }
if (!this.file || this.tableData.length === 0) {
this.$message.warning('暂无数据可导入');
return;
}
- // 直接导入模式下先快速校验基础格式
- if (this.importMode === 'direct') {
- await this.validateExcel();
- if (this.errorList.length > 0) {
- this.$message.error('直接导入模式下基础格式校验失败,请修正');
- return;
+ // 二次确认,防止误操作
+ const confirm = await this.$confirm(
+ '确认导入已校验通过的数据?导入过程中请勿刷新页面或关闭浏览器!',
+ '导入确认',
+ {
+ confirmButtonText: '确认导入',
+ cancelButtonText: '取消',
+ type: 'warning',
+ dangerouslyUseHTMLString: true
}
- }
+ ).catch(() => false);
+ if (!confirm) return;
+
+ this.importLoading = true; // 开启导入loading
// 初始化导入状态
- this.importStatus = 'processing';
+ this.importStatus = ImportStatus.PROCESSING;
this.progress = 0;
this.importedCount = 0;
this.totalCount = this.tableData.length;
@@ -409,12 +525,12 @@ export default {
try {
await this.batchImport();
- this.importStatus = 'finished';
+ this.importStatus = ImportStatus.FINISHED;
this.$message.success(`导入完成!共成功导入${this.importedCount}条数据`);
} catch (error) {
- this.importStatus = 'error';
- this.importErrorMsg = `导入失败:${error.message},已导入${this.importedCount}条数据`;
- this.$message.error(this.importErrorMsg);
+ this.handleError(`导入失败:${error.message},已导入${this.importedCount}条数据`);
+ } finally {
+ this.importLoading = false; // 关闭导入loading
}
},
@@ -422,18 +538,20 @@ export default {
* 批量导入数据
*/
async batchImport() {
- // 遍历所有数据行,逐个导入
for (let i = 0; i < this.tableData.length; i++) {
- if (this.importStatus === 'error') break; // 发生错误则停止
+ // 若导入已失败,终止后续操作
+ if (this.importStatus === ImportStatus.ERROR) break;
const row = this.tableData[i];
try {
await this.importOneRecord(row);
this.importedCount++;
- // 更新进度
- this.progress = Math.round(((i + 1) / this.totalCount) * 100);
+ // 更新进度(避免进度跳动过大)
+ const currentProgress = Math.round(((i + 1) / this.totalCount) * 100);
+ this.progress = currentProgress;
+ // 给浏览器渲染时间,避免卡死
+ await new Promise(resolve => setTimeout(resolve, 50));
} catch (error) {
- // 单条失败可选择继续或终止,这里选择终止
throw new Error(`第${row.rowNum}行导入失败:${error.message}`);
}
}
@@ -444,42 +562,48 @@ export default {
*/
async importOneRecord(row) {
try {
- // 1. 查找itemId
+ // 1. 查找物料ID(再次校验,防止数据篡改)
const itemId = await this._findItemId(row);
if (!itemId) {
- throw new Error(`未找到唯一的${row.type}信息`);
+ throw new Error(ErrorType.ITEM_ID_NOT_FOUND.message(row.type, row.name, row.specification, row.material, row.surfaceTreatmentDesc, row.manufacturer, row.zincLayer));
}
- const itemType = row.type === '原料' ? 'raw_material' : 'product';
+ const itemType = SystemConstant.ITEM_TYPE_MAP[row.type];
- // 2. 插入钢卷数据(dataType=10)
+ // 2. 插入钢卷数据
const coilParams = {
itemId,
itemType,
- logicWarehouse: row.logicWarehouse,
- inboundCoilNo: row.inboundCoilNo,
- factoryCoilNo: row.factoryCoilNo,
- weight: row.weight,
+ materialType: row.type,
+ warehouseId: this.warehouseMap[row.logicWarehouse],
+ enterCoilNo: row.inboundCoilNo,
+ currentCoilNo: row.inboundCoilNo,
+ supplierCoilNo: row.factoryCoilNo,
+ grossWeight: row.weight,
+ netWeight: row.weight,
remark: row.remark,
- dataType: 10 // 钢卷数据类型固定为10
+ dataType: SystemConstant.COIL_DATA_TYPE
};
const coilRes = await addMaterialCoil(coilParams);
- if (!coilRes.success) { // 假设接口返回success标识
+ if (coilRes.code !== 200) {
throw new Error(`钢卷数据插入失败:${coilRes.message || '接口返回异常'}`);
}
- const coilId = coilRes.data?.coilId; // 假设返回钢卷ID
+ const coilId = coilRes.data?.coilId;
- // 3. 插入待处理操作(actionStatus=0)
+ // 3. 插入待处理操作
const actionParams = {
coilId,
- actionStatus: 0, // 待处理状态固定为0
+ currentCoilNo: row.inboundCoilNo,
+ actionStatus: SystemConstant.PENDING_ACTION_STATUS,
itemType,
itemId,
- operationType: 'import', // 操作类型:导入
- remark: `Excel导入:${row.inboundCoilNo}`
+ actionType: SystemConstant.PENDING_ACTION_TYPE_RECEIVE,
+ operationType: SystemConstant.OPERATION_TYPE_IMPORT,
+ remark: `Excel导入:${row.inboundCoilNo}`,
+ warehouseId: this.planId
};
const actionRes = await addPendingAction(actionParams);
- if (!actionRes.success) {
+ if (actionRes.code !== 200) {
throw new Error(`待处理操作插入失败:${actionRes.message || '接口返回异常'}`);
}
@@ -489,48 +613,46 @@ export default {
},
/**
- * 根据条件查找唯一的itemId
+ * 根据条件查找唯一的物料ID
*/
async _findItemId(row) {
- const itemType = row.type === '原料' ? 'raw_material' : 'product';
- let res = null;
+ const { ITEM_TYPE_MAP, ITEM_ID_FIELD } = SystemConstant;
+ const itemType = ITEM_TYPE_MAP[row.type];
+ if (!itemType) return null;
try {
- if (itemType === 'raw_material') {
- res = await listRawMaterial({
- rawMaterialName: row.name,
- specification: row.specification,
- material: row.material,
- manufacturer: row.manufacturer,
- surfaceTreatmentDesc: row.surfaceTreatmentDesc,
- zincLayer: row.zincLayer
- });
- } else {
- res = await listProduct({
- productName: row.name,
- specification: row.specification,
- material: row.material,
- manufacturer: row.manufacturer,
- surfaceTreatmentDesc: row.surfaceTreatmentDesc,
- zincLayer: row.zincLayer
- });
- }
+ // 构建查询参数
+ const queryParams = {
+ [row.type === '原料' ? 'rawMaterialName' : 'productName']: row.name,
+ specification: row.specification,
+ material: row.material,
+ manufacturer: row.manufacturer,
+ surfaceTreatmentDesc: row.surfaceTreatmentDesc,
+ zincLayer: row.zincLayer
+ };
- // 校验返回结果数量
- // 如果锌层,表面处理,厂家未传递,则需要从结果中取出这些值未空的记录
- const record = res.data.filter(item =>
- (!row.zincLayer || item.zincLayer === row.zincLayer) &&
- (!row.surfaceTreatmentDesc || item.surfaceTreatmentDesc === row.surfaceTreatmentDesc) &&
- (!row.manufacturer || item.manufacturer === row.manufacturer)
- );
- if (record.length !== 1) {
- return null;
- }
+ // 执行查询
+ const res = itemType === 'raw_material'
+ ? await listRawMaterial(queryParams)
+ : await listProduct(queryParams);
- // 返回对应ID
- return itemType === 'raw_material'
- ? res.data[0].rawMaterialId
- : res.data[0].productId;
+ // 空值判断函数
+ const isEmpty = (value) => value === null || value === undefined || value === '';
+
+ // 筛选匹配记录
+ const matchedRecords = res.rows.filter(item => {
+ if (isEmpty(row.zincLayer) && !isEmpty(item.zincLayer)) return false;
+ if (isEmpty(row.surfaceTreatmentDesc) && !isEmpty(item.surfaceTreatmentDesc)) return false;
+ if (isEmpty(row.manufacturer) && !isEmpty(item.manufacturer)) return false;
+ if (isEmpty(row.material) && !isEmpty(item.material)) return false;
+ return true;
+ });
+
+ // 必须返回唯一记录
+ if (matchedRecords.length !== 1) return null;
+
+ // 返回物料ID
+ return matchedRecords[0][ITEM_ID_FIELD[itemType]];
} catch (error) {
this.$message.error(`查询${row.type}信息失败:${error.message}`);
return null;
@@ -538,21 +660,60 @@ export default {
},
/**
- * 重置所有状态
+ * 重置所有状态(添加防呆和二次确认)
*/
- reset() {
+ async reset() {
+ // 操作中禁止重置
+ if (this.validateLoading || this.importLoading || this.importStatus === ImportStatus.PROCESSING) {
+ this.$message.warning('当前有操作正在进行中,无法重置');
+ return;
+ }
+
+ // 有数据时二次确认,防止误清空
+ if (this.file || this.tableData.length > 0 || this.errorList.length > 0) {
+ const confirm = await this.$confirm(
+ '确认重置所有状态?已选择的文件、解析的数据和校验结果将被清空!',
+ '重置确认',
+ {
+ confirmButtonText: '确认重置',
+ cancelButtonText: '取消',
+ type: 'info'
+ }
+ ).catch(() => false);
+
+ if (!confirm) return;
+ }
+
+ // 重置所有状态
this.file = null;
this.rawData = [];
this.tableData = [];
this.errorList = [];
+ this.isValidated = false;
this.progress = 0;
this.importedCount = 0;
this.totalCount = 0;
- this.importStatus = 'idle';
+ this.importStatus = ImportStatus.IDLE;
this.importErrorMsg = '';
- this.$refs.upload?.clearFiles(); // 清空上传组件文件
+ this.$refs.upload?.clearFiles();
+ this.$message.success('已重置所有状态');
+ },
+
+ /**
+ * 统一错误处理(确保loading状态关闭)
+ */
+ handleError(message) {
+ this.$message.error(message);
+ this.importStatus = ImportStatus.ERROR;
+ this.importErrorMsg = message;
+ // 异常场景下强制关闭loading,避免界面卡死
+ this.validateLoading = false;
+ this.importLoading = false;
}
- }
+ },
+ mounted() {
+ this.getWarehouseMap();
+ },
};
@@ -563,18 +724,12 @@ export default {
border-radius: 4px;
}
-.import-mode-selector {
- margin-bottom: 20px;
- padding: 10px;
- background: #f5f7fa;
- border-radius: 4px;
-}
-
.file-upload-area {
margin-bottom: 20px;
display: flex;
align-items: center;
gap: 10px;
+ flex-wrap: wrap; /* 适配小屏幕 */
}
.file-upload-area.disabled {
@@ -603,7 +758,16 @@ export default {
font-size: 14px;
}
-.import-finished, .import-error {
+.import-finished,
+.import-error {
margin-bottom: 20px;
}
+
+/* 优化按钮间距,适配小屏幕 */
+@media (max-width: 768px) {
+ .file-upload-area {
+ flex-direction: column;
+ align-items: flex-start;
+ }
+}
\ No newline at end of file
diff --git a/klp-ui/src/views/wms/receive/detail/index.vue b/klp-ui/src/views/wms/receive/detail/index.vue
index 2ca1a28c..8eeaeb16 100644
--- a/klp-ui/src/views/wms/receive/detail/index.vue
+++ b/klp-ui/src/views/wms/receive/detail/index.vue
@@ -81,6 +81,11 @@
未收货
+
+
+ 签收
+
+
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+