diff --git a/dist.zip b/dist.zip index 26ae3c52..05227db6 100644 Binary files a/dist.zip and b/dist.zip differ diff --git a/src/api/core/monitoring/auto.js b/src/api/core/monitoring/auto.js index 47c6f166..52ad2fc8 100644 --- a/src/api/core/monitoring/auto.js +++ b/src/api/core/monitoring/auto.js @@ -118,3 +118,10 @@ export function getProcessAutoReportLastGroup(data) { data: data, }); } +export function getPLlistByFactory(data) { + return request({ + url: 'base/production-line/listByFactory', + method: 'post', + data: data, + }); +} diff --git a/src/views/base/material/add-or-updata.vue b/src/views/base/material/add-or-updata.vue index 60cdde54..1f25c329 100644 --- a/src/views/base/material/add-or-updata.vue +++ b/src/views/base/material/add-or-updata.vue @@ -88,7 +88,32 @@ export default { }; }, created() {}, - methods: { - }, + methods: { + dataFormSubmit() { + this.$refs["dataForm"].validate((valid) => { + if (!valid) { + return false; + } + // 修改的提交 + if (this.dataForm.id) { + this.urlOptions.updateURL(this.dataForm).then(response => { + this.$modal.msgSuccess("修改成功"); + this.visible = false; + this.$emit("refreshDataList"); + }); + return; + } + // 添加的提交 + this.urlOptions.createURL(this.dataForm).then(response => { + if (response.code === 1001033 || response.code === 1001034) { + return this.$modal.msgError(response.msg); + } + this.$modal.msgSuccess("新增成功"); + this.visible = false; + this.$emit("refreshDataList"); + }); + }); + }, + }, }; diff --git a/src/views/base/materialPricing/add-or-updata.vue b/src/views/base/materialPricing/add-or-updata.vue index 6b2ed2e8..60f84f8a 100644 --- a/src/views/base/materialPricing/add-or-updata.vue +++ b/src/views/base/materialPricing/add-or-updata.vue @@ -120,7 +120,30 @@ export default { .label; } }); - }, + }, + dataFormSubmit() { + this.$refs["dataForm"].validate((valid) => { + if (!valid) { + return false; + } + // 修改的提交 + if (this.dataForm.id) { + this.urlOptions.updateURL(this.dataForm).then(response => { + this.$modal.msgSuccess("修改成功"); + this.visible = false; + this.$emit("refreshDataList"); + }); + return; + } + // 添加的提交 + this.urlOptions.createURL(this.dataForm).then(response => { + console.log('response', response); + this.$modal.msgSuccess("新增成功"); + this.visible = false; + this.$emit("refreshDataList"); + }); + }); + }, }, }; diff --git a/src/views/core/analysis/balanceAnalysis/index.vue b/src/views/core/analysis/balanceAnalysis/index.vue index dda15b11..50cc1147 100644 --- a/src/views/core/analysis/balanceAnalysis/index.vue +++ b/src/views/core/analysis/balanceAnalysis/index.vue @@ -6,65 +6,39 @@ * @Description: --> diff --git a/src/views/core/monitoring/lineAuto/index.vue b/src/views/core/monitoring/lineAuto/index.vue index 68cf54d0..9f830faf 100644 --- a/src/views/core/monitoring/lineAuto/index.vue +++ b/src/views/core/monitoring/lineAuto/index.vue @@ -106,7 +106,7 @@ {{ reportTypeMap[listQuery.reportType] }} {{ parseTime(listQuery.startTime) }} 至{{ - parseTime(listQuery.endTime) }} + parseTime(listQuery.endTime) }} 生产数据 @@ -163,7 +163,7 @@ {{ reportTypeMap[listQuery.reportType] }} {{ parseTime(listQuery.startTime) }} 至{{ - parseTime(listQuery.endTime) }} + parseTime(listQuery.endTime) }} 生产数据 @@ -190,175 +190,6 @@ import barChart from './BarChart.vue'; import ButtonNav from '@/components/ButtonNav'; import { listData } from '@/api/system/dict/data'; // 表格列配置 -const tableProps = [ - { - prop: 'factoryName', - label: '工厂', - fixed: true - }, - { - prop: 'lineName', - label: '产线', - fixed: true - }, - { - prop: 'sizes', - label: '规格', - width: 105, - showOverflowtooltip: true, - fixed: true - }, - { - prop: 'process', - label: '产品工艺', - fixed: true - }, - { - prop: 'inputN', - label: '投入', - align: 'center', - children: [ - { prop: 'inputNum', label: '投入数量/片' }, - { - prop: 'inputArea', - label: '投入面积/m²', - filter: (val) => (val != null ? val.toFixed(2) : '-') - } - ] - }, - { - prop: 'outputN', - label: '产出', - align: 'center', - children: [ - { prop: 'outputNum', label: '产出数量/片' }, - { - prop: 'outputArea', - label: '产出面积/㎡', - filter: (val) => (val != null ? val.toFixed(2) : '-') - } - ] - }, - { - prop: 'lossN', - label: '不良', - align: 'center', - children: [ - { prop: 'lossNum', label: '不良数量/片' }, - { - prop: 'lossArea', - label: '不良面积/㎡', - filter: (val) => (val != null ? val.toFixed(2) : '-') - } - ] - }, - { - prop: 'lossRatio', - label: '不良率/%', - filter: (val) => (val != null ? val.toFixed(2) : '-') - }, - { - prop: 'outputRatio', - label: '投入产出率/%', - filter: (val) => (val != null ? val.toFixed(2) : '-') - }, - { - prop: 'processingRatio', - label: '加工成品率/%', - filter: (val) => (val != null ? val.toFixed(2) : '-') - }, - { - label: '不良详情', - align: 'center', - children: [ - { - label: '原片', - align: 'center', - children: [ - { prop: 'originalLossNum', label: '原片不良/片' }, - { - prop: 'originalLossArea', - label: '原片不良/㎡', - filter: (val) => (val != null ? val.toFixed(2) : '-') - } - ] - }, - { - label: '磨边', - align: 'center', - children: [ - { prop: 'edgeLossNum', label: '磨边不良/片' }, - { - prop: 'edgeLossArea', - label: '磨边不良/㎡', - filter: (val) => (val != null ? val.toFixed(2) : '-') - } - ] - }, - { - label: '打孔', - align: 'center', - children: [ - { prop: 'drillLossNum', label: '打孔不良/片' }, - { - prop: 'drillLossArea', - label: '打孔不良/㎡', - filter: (val) => (val != null ? val.toFixed(2) : '-') - } - ] - }, - { - label: '镀膜', - align: 'center', - children: [ - { prop: 'coatingLossNum', label: '镀膜不良/片' }, - { - prop: 'coatingLossArea', - label: '镀膜不良/㎡', - filter: (val) => (val != null ? val.toFixed(2) : '-') - } - ] - }, - { - label: '丝印', - align: 'center', - children: [ - { prop: 'silkLossNum', label: '丝印不良/片' }, - { - prop: 'silkLossArea', - label: '丝印不良/㎡', - filter: (val) => (val != null ? val.toFixed(2) : '-') - } - ] - }, - { - label: '钢化', - align: 'center', - children: [ - { prop: 'temperingLossNum', label: '钢化不良/片' }, - { - prop: 'temperingLossArea', - label: '钢化不良/㎡', - filter: (val) => (val != null ? val.toFixed(2) : '-') - } - ] - }, - { - - label: '包装', - align: 'center', - children: [ - { prop: 'packingLossNum', label: '包装不良/片' }, - { - prop: 'packingLossArea', - label: '包装不良/㎡', - filter: (val) => (val != null ? val.toFixed(2) : '-') - } - ] - } - ] - } -]; export default { components: { barChart, ButtonNav, baseTableS }, @@ -372,6 +203,7 @@ export default { total: 1, timeType: 1 // 1-当天,2-自定义 }, + isPeriodicReport: false, reportTypeMap: { 1: '日报', 2: '周报', @@ -381,9 +213,8 @@ export default { activeLabel: 'table', // 当班数据tab activeLabelDay: 'table', // 当天数据tab fileName: '', - headFormValue:{}, + headFormValue: {}, dataListLoading: false, - tableProps, factoryColumns: [], factoryColumnsDay: [], activeName: 'product', @@ -511,6 +342,184 @@ export default { }; }, computed: { + tableProps() { + const props = [ + { + prop: 'factoryName', + label: '工厂', + fixed: true + }, + { + prop: 'lineName', + label: '产线', + fixed: true + }, + { + prop: 'sizes', + label: '规格', + width: 105, + showOverflowtooltip: true, + fixed: true + }, + { + prop: 'process', + label: '产品工艺', + fixed: true + }, + (this.isPeriodicReport ? { + prop: 'reportDate', + label: '日期', + fixed: true, + width: 120 + } : ''), + { + prop: 'inputN', + label: '投入', + align: 'center', + children: [ + { prop: 'inputNum', label: '投入数量/片' }, + { + prop: 'inputArea', + label: '投入面积/m²', + filter: (val) => (val != null ? val.toFixed(2) : '-') + } + ] + }, + { + prop: 'outputN', + label: '产出', + align: 'center', + children: [ + { prop: 'outputNum', label: '产出数量/片' }, + { + prop: 'outputArea', + label: '产出面积/㎡', + filter: (val) => (val != null ? val.toFixed(2) : '-') + } + ] + }, + { + prop: 'lossN', + label: '不良', + align: 'center', + children: [ + { prop: 'lossNum', label: '不良数量/片' }, + { + prop: 'lossArea', + label: '不良面积/㎡', + filter: (val) => (val != null ? val.toFixed(2) : '-') + } + ] + }, + { + prop: 'lossRatio', + label: '不良率/%', + filter: (val) => (val != null ? val.toFixed(2) : '-') + }, + { + prop: 'outputRatio', + label: '投入产出率/%', + filter: (val) => (val != null ? val.toFixed(2) : '-') + }, + { + prop: 'processingRatio', + label: '加工成品率/%', + filter: (val) => (val != null ? val.toFixed(2) : '-') + }, + { + label: '不良详情', + align: 'center', + children: [ + { + label: '原片', + align: 'center', + children: [ + { prop: 'originalLossNum', label: '原片不良/片' }, + { + prop: 'originalLossArea', + label: '原片不良/㎡', + filter: (val) => (val != null ? val.toFixed(2) : '-') + } + ] + }, + { + label: '磨边', + align: 'center', + children: [ + { prop: 'edgeLossNum', label: '磨边不良/片' }, + { + prop: 'edgeLossArea', + label: '磨边不良/㎡', + filter: (val) => (val != null ? val.toFixed(2) : '-') + } + ] + }, + { + label: '打孔', + align: 'center', + children: [ + { prop: 'drillLossNum', label: '打孔不良/片' }, + { + prop: 'drillLossArea', + label: '打孔不良/㎡', + filter: (val) => (val != null ? val.toFixed(2) : '-') + } + ] + }, + { + label: '镀膜', + align: 'center', + children: [ + { prop: 'coatingLossNum', label: '镀膜不良/片' }, + { + prop: 'coatingLossArea', + label: '镀膜不良/㎡', + filter: (val) => (val != null ? val.toFixed(2) : '-') + } + ] + }, + { + label: '丝印', + align: 'center', + children: [ + { prop: 'silkLossNum', label: '丝印不良/片' }, + { + prop: 'silkLossArea', + label: '丝印不良/㎡', + filter: (val) => (val != null ? val.toFixed(2) : '-') + } + ] + }, + { + label: '钢化', + align: 'center', + children: [ + { prop: 'temperingLossNum', label: '钢化不良/片' }, + { + prop: 'temperingLossArea', + label: '钢化不良/㎡', + filter: (val) => (val != null ? val.toFixed(2) : '-') + } + ] + }, + { + + label: '包装', + align: 'center', + children: [ + { prop: 'packingLossNum', label: '包装不良/片' }, + { + prop: 'packingLossArea', + label: '包装不良/㎡', + filter: (val) => (val != null ? val.toFixed(2) : '-') + } + ] + } + ] + } + ]; + return props + }, productTableProps() { // 当班数据的完整表头(基础列 + 当班动态表头) const baseColumns = [ @@ -530,14 +539,20 @@ export default { prop: 'processType', label: '产品类型', filter: (val) => (val === 1 ? '面板' : '背板'), - fixed: true, sortable: true, + fixed: true }, { prop: 'factoryName', label: '工厂', fixed: true - } + }, + (this.isPeriodicReport ? { + prop: 'reportDate', + label: '日期', + fixed: true, + width: 120 + } : ''), ]; return [...baseColumns, ...this.factoryColumns]; }, @@ -559,7 +574,7 @@ export default { { prop: 'processType', label: '产品类型', - filter: (val) => (val === 1 ? '面板' : '背板'), + filter: (val) => (val != 1 ? '面板' : '背板'), sortable: true, fixed: true }, @@ -567,7 +582,13 @@ export default { prop: 'factoryName', label: '工厂', fixed: true - } + }, + (this.isPeriodicReport ? { + prop: 'reportDate', + label: '日期', + fixed: true, + width: 120 + } : ''), ]; return [...baseColumns, ...this.factoryColumnsDay]; } @@ -704,94 +725,157 @@ export default { } }, - // 导出表格 - handleExport() { + // 导出表格(改为async方法,支持await调用) + async handleExport() { // 按产线监控 if (this.activeName === 'productLine') { if (this.listQuery.timeType === 1) { // 产线-当天:导出当班 + 当天两个表格(自定义汇总文件名) - this.exportMultipleTables([ + await this.exportMultipleTables([ { ref: 'lineCurrentShiftTable', name: '产线监控_当班数据' }, { ref: 'lineTodayTable', name: '产线监控_当天数据' } ], '产线监控_当班及当天数据汇总'); // 传入自定义汇总文件名 - } else if (this.listQuery.timeType === 1) { - // 产线-当天:导出当班 + 当天两个表格(自定义汇总文件名) - this.exportMultipleTables([ - { ref: 'lineCurrentShiftTable', name: '产线监控_上一班数据' }, - { ref: 'lineTodayTable', name: '产线监控_当天数据' } - ], '产线监控_当班及当天数据汇总'); // 传入自定义汇总文件名 + } else if (this.listQuery.timeType === 3) { + // 修复:注释错误,原为“产线-当天”,实际是“产线-上一班” + // 产线-上一班:导出单个表格 + await this.exportSingleTable('lineCustomTable', '产线监控_上一班时间数据'); } else { // 产线-自定义:导出一个表格(自定义文件名) - this.exportSingleTable('lineCustomTable', '产线监控_自定义时间数据'); + await this.exportSingleTable('lineCustomTable', '产线监控_自定义时间数据'); } } // 按产品监控 else { if (this.listQuery.timeType === 1) { // 产品-当天:导出当班 + 当天两个表格(自定义汇总文件名) - this.exportMultipleTables([ + await this.exportMultipleTables([ { ref: 'productCurrentShiftTable', name: '产品监控_当班数据' }, - { ref: 'productTodayTable', name: '产品监控_当天数据' } + { ref: 'productCurrentShiftTable', name: '产品监控_当天数据' } ], '产品监控_当班及当天数据汇总'); // 传入自定义汇总文件名 + } else if (this.listQuery.timeType === 3) { + // 修复:注释错误,原为“产线-当天”,实际是“产品-上一班” + // 产品-上一班:导出单个表格 + await this.exportSingleTable('productCustomTable', '产品监控_上一班时间数据'); } else { // 产品-自定义:导出一个表格(自定义文件名) - this.exportSingleTable('productCustomTable', '产品监控_自定义时间数据'); + await this.exportSingleTable('productCustomTable', '产品监控_自定义时间数据'); } } }, - // 导出单个表格(保持原有逻辑,文件名由调用方传入,已区分不同场景) - exportSingleTable(refName, fileName) { - const table = this.getTableDom(refName); - if (!table) return; + // 导出单个表格(改为async方法,使用await获取DOM) + async exportSingleTable(refName, fileName) { + try { + // 关键修复:使用await等待getTableDom返回DOM元素(而非Promise) + const table = await this.getTableDom(refName); + if (!table) { + this.$message.warning(`无法导出:${refName}表格DOM获取失败`); + return; + } - const clonedTable = table.cloneNode(true); - this.adjustTableForExport(clonedTable); + // 确认table是原生DOM元素后再调用cloneNode + if (!(table instanceof HTMLElement)) { + this.$message.error(`无法导出:${refName}表格不是合法的DOM元素`); + return; + } - const workbook = XLSX.utils.table_to_book(clonedTable); - const fullName = `${fileName}_${this.formatDate()}.xlsx`; - XLSX.writeFile(workbook, fullName); - }, - - // 导出多个表格(新增自定义汇总文件名参数,适配不同选项卡) - exportMultipleTables(tables, customSummaryName) { - const workbook = XLSX.utils.book_new(); - let hasValidTable = false; - - tables.forEach(({ ref, name }) => { - const table = this.getTableDom(ref); - if (!table) return; - - hasValidTable = true; const clonedTable = table.cloneNode(true); this.adjustTableForExport(clonedTable); - const worksheet = XLSX.utils.table_to_sheet(clonedTable); - XLSX.utils.book_append_sheet(workbook, worksheet, name); - }); - - if (!hasValidTable) { - this.$message.warning('未找到有效表格数据'); - return; + const workbook = XLSX.utils.table_to_book(clonedTable); + const fullName = `${fileName}_${this.formatDate()}.xlsx`; + XLSX.writeFile(workbook, fullName); + } catch (error) { + this.$message.error(`导出表格失败:${error.message}`); + console.error('单个表格导出错误:', error); } - - // 动态生成汇总文件名:自定义名称 + 日期 - const fullName = `${customSummaryName}_${this.formatDate()}.xlsx`; - XLSX.writeFile(workbook, fullName); }, - // 辅助方法:获取表格DOM元素(包含表头) - getTableDom(refName) { - const tableComponent = this.$refs[refName]; - if (!tableComponent) { - this.$message.warning(`未找到${refName}表格`); - return null; + + // 导出多个表格(改为async方法,使用await获取DOM) + async exportMultipleTables(tables, customSummaryName) { + try { + const workbook = XLSX.utils.book_new(); + let hasValidTable = false; + + // 遍历表格配置,使用await获取每个表格的DOM + for (const { ref, name } of tables) { // 改用for...of循环,支持await + const table = await this.getTableDom(ref); + if (!table) { + this.$message.warning(`${ref}表格DOM获取失败,跳过该表格`); + continue; + } + + // 确认table是原生DOM元素 + if (!(table instanceof HTMLElement)) { + this.$message.warning(`${ref}表格不是合法的DOM元素,跳过该表格`); + continue; + } + + hasValidTable = true; + const clonedTable = table.cloneNode(true); + this.adjustTableForExport(clonedTable); + + const worksheet = XLSX.utils.table_to_sheet(clonedTable); + XLSX.utils.book_append_sheet(workbook, worksheet, name); + } + + if (!hasValidTable) { + this.$message.warning('未找到有效表格数据,导出失败'); + return; + } + + // 动态生成汇总文件名:自定义名称 + 日期 + const fullName = `${customSummaryName}_${this.formatDate()}.xlsx`; + XLSX.writeFile(workbook, fullName); + } catch (error) { + this.$message.error(`导出多个表格失败:${error.message}`); + console.error('多个表格导出错误:', error); } - // 获取完整表格容器(包含表头和表体) + }, + + /** + * 获取表格DOM元素(支持异步重试) + * @param {string} refName - 表格的ref名称 + * @param {number} retryCount - 当前重试次数(内部使用,外部调用无需传参) + * @returns {Promise} 表格DOM元素Promise + */ + async getTableDom(refName, retryCount = 0) { + // 1. 配置常量,便于维护 + const maxRetry = 5; // 最大重试次数 + const retryDelay = 300; // 每次重试延迟时间(ms) + + // 2. 设置激活标签,确保重试时也能执行该逻辑 + this.activeLabel = 'table'; + this.activeLabelDay = 'table'; + // 若需调试,可保留日志 + // console.log(`[getTableDom-${refName}] activeLabel:`, this.activeLabel, 'activeLabelDay:', this.activeLabelDay); + + // 3. 获取表格组件 + const tableComponent = this.$refs[refName]; + + // 4. 表格组件不存在时,处理重试逻辑 + if (!tableComponent) { + // 达到最大重试次数,返回失败 + if (retryCount >= maxRetry) { + this.$message.warning(`未找到${refName}表格,已达到最大重试次数(${maxRetry}次)`); + return null; + } + + // 未达到最大重试次数,延迟后重试 + this.$message.warning(`未找到${refName}表格,将进行第${retryCount + 1}次重试...`); + await new Promise(resolve => setTimeout(resolve, retryDelay)); + // 递归调用并返回结果 + return this.getTableDom(refName, retryCount + 1); + } + + // 5. 获取完整表格容器(包含表头和表体) const tableContainer = tableComponent.$el.querySelector('.el-table'); if (!tableContainer) { - this.$message.warning(`${refName}表格数据为空`); + this.$message.warning(`${refName}表格容器不存在(可能表格数据为空)`); return null; } + + // 6. 成功获取表格DOM,返回结果 return tableContainer; }, @@ -856,7 +940,7 @@ export default { if (mainTable && allHeaderRows.length > 0) { const newThead = document.createElement('thead'); // 按层级添加表头行 - allHeaderRows.forEach(cells => { + allHeaderRows.forEach((cells, rowIndex) => { const newTr = document.createElement('tr'); cells.forEach(cell => { // 复制单元格(避免DOM残留引用问题) @@ -867,6 +951,18 @@ export default { }); // 将新表头插入主表体最前面 mainTable.insertBefore(newThead, mainTable.firstChild); + + // ================ 新增:修改第一列表头为“序号” ================ + // 找到第一层级的第一个表头单元格() + const firstHeaderCell = newThead.querySelector('tr:first-child th:first-child'); + if (firstHeaderCell) { + // 清空原有内容,设置为“序号” + firstHeaderCell.innerHTML = ''; // 清空原有内容(包括子元素) + firstHeaderCell.textContent = '序号'; // 设置纯文本“序号”(更稳定) + // 可选:添加样式(如居中,根据需求调整) + // firstHeaderCell.style.textAlign = 'center'; + } + // ============================================================= } // 7. 合并固定列数据到主表体(仅处理tbody数据) @@ -897,6 +993,23 @@ export default { } }); } + + // ================ 可选优化:为tbody第一列添加序号数字 ================ + // 如果表格体的第一列原本无数据,可补充1、2、3...的序号 + if (mainTable) { + const tbodyRows = mainTable.querySelectorAll('tbody tr'); + tbodyRows.forEach((row, index) => { + const firstTd = row.querySelector('td:first-child'); + if (firstTd) { + // 清空原有内容,设置为序号(index+1,因为索引从0开始) + firstTd.innerHTML = ''; + firstTd.textContent = index + 1; + // 可选:居中显示 + // firstTd.style.textAlign = 'center'; + } + }); + } + // ============================================================= }, // 辅助方法:格式化日期(用于文件名) @@ -913,7 +1026,7 @@ export default { // 获取产线和工厂列表 getPdLineList() { getPLlistByFactory({ - factoryIds:this.listQuery.factoryIds + factoryIds: this.listQuery.factoryIds }).then(res => { this.formConfig[3].selectOptions = res.data || []; }); @@ -1517,6 +1630,7 @@ export default { // 搜索栏下拉选择变化 handleSearchBarChanged({ param, value }) { if (param === 'timeType') { + this.isPeriodicReport = false this.tableData2 = [] this.tableDataCustom = [] this.tableData = [] @@ -1584,15 +1698,59 @@ export default { } } else if (param === 'searchType') { + this.isPeriodicReport = value === 2; + if (this.$refs.searchBarForm && this.$refs.searchBarForm.formInline) { + const formInline = this.$refs.searchBarForm.formInline; + formInline.reportType = '' + // 精准判断:只有字段存在时,才置为undefined(不存在则不处理) + if ('timeVal' in formInline) { + formInline.timeVal = undefined; + } + if ('timeValWeek' in formInline) { + formInline.timeValWeek = undefined; + } + if ('timeValMonth' in formInline) { + formInline.timeValMonth = undefined; + } + if ('timeValYear' in formInline) { + formInline.timeValYear = undefined; + } + } + // const formInline = this.$refs.searchBarForm.formInline; + this.listQuery.startTime = undefined + this.listQuery.endTime = undefined + console.log(this.listQuery, 'list'); if (value === 1) { // 统计数据:显示时间范围,隐藏报表类型 - this.formConfig[7].type = 'datePicker'; + this.formConfig[7] = { + type: 'datePicker', + label: '时间范围', + dateType: 'datetimerange', + format: 'yyyy-MM-dd HH:mm:ss', + valueFormat: 'timestamp', + rangeSeparator: '-', + startPlaceholder: '开始时间', + endPlaceholder: '结束时间', + param: 'timeVal', + width: 350 + }; this.formConfig[6].type = ''; - this.$refs.searchBarForm.formInline.timeVal = undefined; + } else { // 周期性报表:显示报表类型,隐藏时间范围 this.formConfig[6].type = 'select'; - this.formConfig[7].type = 'datePicker'; + this.formConfig[7] = { + type: 'datePicker', + label: '时间范围', + dateType: 'datetimerange', + format: 'yyyy-MM-dd HH:mm:ss', + valueFormat: 'timestamp', + rangeSeparator: '-', + startPlaceholder: '开始时间', + endPlaceholder: '结束时间', + param: 'timeVal', + width: 350 + }; } } else if (param === 'reportType') { if (this.$refs.searchBarForm && this.$refs.searchBarForm.formInline) { @@ -1671,7 +1829,7 @@ export default { this.listQuery.lineId = []; this.$refs.searchBarForm.formInline.lineId = undefined; getPLlistByFactory({ - factoryIds:value + factoryIds: value }).then(res => { this.formConfig[3].selectOptions = res.data || []; }); diff --git a/src/views/core/monitoring/productAuto/index.vue b/src/views/core/monitoring/productAuto/index.vue index ef2f9a23..4ff18b95 100644 --- a/src/views/core/monitoring/productAuto/index.vue +++ b/src/views/core/monitoring/productAuto/index.vue @@ -190,176 +190,6 @@ import barChart from './BarChart.vue'; import ButtonNav from '@/components/ButtonNav'; import { listData } from '@/api/system/dict/data'; // 表格列配置 -const tableProps = [ - { - prop: 'factoryName', - label: '工厂', - fixed: true - }, - { - prop: 'lineName', - label: '产线', - fixed: true - }, - { - prop: 'sizes', - label: '规格', - width: 105, - showOverflowtooltip: true, - fixed: true - }, - { - prop: 'process', - label: '产品工艺', - fixed: true - }, - { - prop: 'inputN', - label: '投入', - align: 'center', - children: [ - { prop: 'inputNum', label: '投入数量/片' }, - { - prop: 'inputArea', - label: '投入面积/m²', - filter: (val) => (val != null ? val.toFixed(2) : '-') - } - ] - }, - { - prop: 'outputN', - label: '产出', - align: 'center', - children: [ - { prop: 'outputNum', label: '产出数量/片' }, - { - prop: 'outputArea', - label: '产出面积/㎡', - filter: (val) => (val != null ? val.toFixed(2) : '-') - } - ] - }, - { - prop: 'lossN', - label: '不良', - align: 'center', - children: [ - { prop: 'lossNum', label: '不良数量/片' }, - { - prop: 'lossArea', - label: '不良面积/㎡', - filter: (val) => (val != null ? val.toFixed(2) : '-') - } - ] - }, - { - prop: 'lossRatio', - label: '不良率/%', - filter: (val) => (val != null ? val.toFixed(2) : '-') - }, - { - prop: 'outputRatio', - label: '投入产出率/%', - filter: (val) => (val != null ? val.toFixed(2) : '-') - }, - { - prop: 'processingRatio', - label: '加工成品率/%', - filter: (val) => (val != null ? val.toFixed(2) : '-') - }, - { - label: '不良详情', - align: 'center', - children: [ - { - label: '原片', - align: 'center', - children: [ - { prop: 'originalLossNum', label: '原片不良/片' }, - { - prop: 'originalLossArea', - label: '原片不良/㎡', - filter: (val) => (val != null ? val.toFixed(2) : '-') - } - ] - }, - { - label: '磨边', - align: 'center', - children: [ - { prop: 'edgeLossNum', label: '磨边不良/片' }, - { - prop: 'edgeLossArea', - label: '磨边不良/㎡', - filter: (val) => (val != null ? val.toFixed(2) : '-') - } - ] - }, - { - label: '打孔', - align: 'center', - children: [ - { prop: 'drillLossNum', label: '打孔不良/片' }, - { - prop: 'drillLossArea', - label: '打孔不良/㎡', - filter: (val) => (val != null ? val.toFixed(2) : '-') - } - ] - }, - { - label: '镀膜', - align: 'center', - children: [ - { prop: 'coatingLossNum', label: '镀膜不良/片' }, - { - prop: 'coatingLossArea', - label: '镀膜不良/㎡', - filter: (val) => (val != null ? val.toFixed(2) : '-') - } - ] - }, - { - label: '丝印', - align: 'center', - children: [ - { prop: 'silkLossNum', label: '丝印不良/片' }, - { - prop: 'silkLossArea', - label: '丝印不良/㎡', - filter: (val) => (val != null ? val.toFixed(2) : '-') - } - ] - }, - { - label: '钢化', - align: 'center', - children: [ - { prop: 'temperingLossNum', label: '钢化不良/片' }, - { - prop: 'temperingLossArea', - label: '钢化不良/㎡', - filter: (val) => (val != null ? val.toFixed(2) : '-') - } - ] - }, - { - - label: '包装', - align: 'center', - children: [ - { prop: 'packingLossNum', label: '包装不良/片' }, - { - prop: 'packingLossArea', - label: '包装不良/㎡', - filter: (val) => (val != null ? val.toFixed(2) : '-') - } - ] - } - ] - } -]; - export default { components: { barChart, ButtonNav, baseTableS }, data() { @@ -382,13 +212,13 @@ export default { activeLabelDay: 'table', // 当天数据tab fileName: '', dataListLoading: false, - tableProps, factoryColumns: [], factoryColumnsDay: [], activeName: 'product', headFormValue: {}, tableBtn: [], showData: [], + isPeriodicReport: false, tableData: [], tableData2: [], // 示例数据,实际从接口获取 productTableData: [], @@ -506,6 +336,184 @@ export default { }; }, computed: { + tableProps() { + const props = [ + { + prop: 'factoryName', + label: '工厂', + fixed: true + }, + { + prop: 'lineName', + label: '产线', + fixed: true + }, + { + prop: 'sizes', + label: '规格', + width: 105, + showOverflowtooltip: true, + fixed: true + }, + { + prop: 'process', + label: '产品工艺', + fixed: true + }, + (this.isPeriodicReport ? { + prop: 'reportDate', + label: '日期', + fixed: true, + width: 120 + } : ''), + { + prop: 'inputN', + label: '投入', + align: 'center', + children: [ + { prop: 'inputNum', label: '投入数量/片' }, + { + prop: 'inputArea', + label: '投入面积/m²', + filter: (val) => (val != null ? val.toFixed(2) : '-') + } + ] + }, + { + prop: 'outputN', + label: '产出', + align: 'center', + children: [ + { prop: 'outputNum', label: '产出数量/片' }, + { + prop: 'outputArea', + label: '产出面积/㎡', + filter: (val) => (val != null ? val.toFixed(2) : '-') + } + ] + }, + { + prop: 'lossN', + label: '不良', + align: 'center', + children: [ + { prop: 'lossNum', label: '不良数量/片' }, + { + prop: 'lossArea', + label: '不良面积/㎡', + filter: (val) => (val != null ? val.toFixed(2) : '-') + } + ] + }, + { + prop: 'lossRatio', + label: '不良率/%', + filter: (val) => (val != null ? val.toFixed(2) : '-') + }, + { + prop: 'outputRatio', + label: '投入产出率/%', + filter: (val) => (val != null ? val.toFixed(2) : '-') + }, + { + prop: 'processingRatio', + label: '加工成品率/%', + filter: (val) => (val != null ? val.toFixed(2) : '-') + }, + { + label: '不良详情', + align: 'center', + children: [ + { + label: '原片', + align: 'center', + children: [ + { prop: 'originalLossNum', label: '原片不良/片' }, + { + prop: 'originalLossArea', + label: '原片不良/㎡', + filter: (val) => (val != null ? val.toFixed(2) : '-') + } + ] + }, + { + label: '磨边', + align: 'center', + children: [ + { prop: 'edgeLossNum', label: '磨边不良/片' }, + { + prop: 'edgeLossArea', + label: '磨边不良/㎡', + filter: (val) => (val != null ? val.toFixed(2) : '-') + } + ] + }, + { + label: '打孔', + align: 'center', + children: [ + { prop: 'drillLossNum', label: '打孔不良/片' }, + { + prop: 'drillLossArea', + label: '打孔不良/㎡', + filter: (val) => (val != null ? val.toFixed(2) : '-') + } + ] + }, + { + label: '镀膜', + align: 'center', + children: [ + { prop: 'coatingLossNum', label: '镀膜不良/片' }, + { + prop: 'coatingLossArea', + label: '镀膜不良/㎡', + filter: (val) => (val != null ? val.toFixed(2) : '-') + } + ] + }, + { + label: '丝印', + align: 'center', + children: [ + { prop: 'silkLossNum', label: '丝印不良/片' }, + { + prop: 'silkLossArea', + label: '丝印不良/㎡', + filter: (val) => (val != null ? val.toFixed(2) : '-') + } + ] + }, + { + label: '钢化', + align: 'center', + children: [ + { prop: 'temperingLossNum', label: '钢化不良/片' }, + { + prop: 'temperingLossArea', + label: '钢化不良/㎡', + filter: (val) => (val != null ? val.toFixed(2) : '-') + } + ] + }, + { + + label: '包装', + align: 'center', + children: [ + { prop: 'packingLossNum', label: '包装不良/片' }, + { + prop: 'packingLossArea', + label: '包装不良/㎡', + filter: (val) => (val != null ? val.toFixed(2) : '-') + } + ] + } + ] + } + ]; + return props + }, productTableProps() { // 当班数据的完整表头(基础列 + 当班动态表头) const baseColumns = [ @@ -532,7 +540,13 @@ export default { prop: 'factoryName', label: '工厂', fixed: true - } + }, + (this.isPeriodicReport ? { + prop: 'reportDate', + label: '日期', + fixed: true, + width: 120 + } : ''), ]; return [...baseColumns, ...this.factoryColumns]; }, @@ -562,7 +576,13 @@ export default { prop: 'factoryName', label: '工厂', fixed: true - } + }, + (this.isPeriodicReport ? { + prop: 'reportDate', + label: '日期', + fixed: true, + width: 120 + } : ''), ]; return [...baseColumns, ...this.factoryColumnsDay]; } @@ -787,97 +807,159 @@ export default { } }, - // 导出表格 - handleExport() { + // 导出表格(改为async方法,支持await调用) + async handleExport() { // 按产线监控 if (this.activeName === 'productLine') { if (this.listQuery.timeType === 1) { // 产线-当天:导出当班 + 当天两个表格(自定义汇总文件名) - this.exportMultipleTables([ + await this.exportMultipleTables([ { ref: 'lineCurrentShiftTable', name: '产线监控_当班数据' }, { ref: 'lineTodayTable', name: '产线监控_当天数据' } ], '产线监控_当班及当天数据汇总'); // 传入自定义汇总文件名 - } else if (this.listQuery.timeType === 1) { - // 产线-当天:导出当班 + 当天两个表格(自定义汇总文件名) - this.exportMultipleTables([ - { ref: 'lineCurrentShiftTable', name: '产线监控_上一班数据' }, - { ref: 'lineTodayTable', name: '产线监控_当天数据' } - ], '产线监控_当班及当天数据汇总'); // 传入自定义汇总文件名 + } else if (this.listQuery.timeType === 3) { + // 修复:注释错误,原为“产线-当天”,实际是“产线-上一班” + // 产线-上一班:导出单个表格 + await this.exportSingleTable('lineCustomTable', '产线监控_上一班时间数据'); } else { // 产线-自定义:导出一个表格(自定义文件名) - this.exportSingleTable('lineCustomTable', '产线监控_自定义时间数据'); + await this.exportSingleTable('lineCustomTable', '产线监控_自定义时间数据'); } } // 按产品监控 else { if (this.listQuery.timeType === 1) { // 产品-当天:导出当班 + 当天两个表格(自定义汇总文件名) - this.exportMultipleTables([ + await this.exportMultipleTables([ { ref: 'productCurrentShiftTable', name: '产品监控_当班数据' }, - { ref: 'productTodayTable', name: '产品监控_当天数据' } + { ref: 'productCurrentShiftTable', name: '产品监控_当天数据' } ], '产品监控_当班及当天数据汇总'); // 传入自定义汇总文件名 + } else if (this.listQuery.timeType === 3) { + // 修复:注释错误,原为“产线-当天”,实际是“产品-上一班” + // 产品-上一班:导出单个表格 + await this.exportSingleTable('productCustomTable', '产品监控_上一班时间数据'); } else { // 产品-自定义:导出一个表格(自定义文件名) - this.exportSingleTable('productCustomTable', '产品监控_自定义时间数据'); + await this.exportSingleTable('productCustomTable', '产品监控_自定义时间数据'); } } }, - // 导出单个表格(保持原有逻辑,文件名由调用方传入,已区分不同场景) - exportSingleTable(refName, fileName) { - const table = this.getTableDom(refName); - if (!table) return; + // 导出单个表格(改为async方法,使用await获取DOM) + async exportSingleTable(refName, fileName) { + try { + // 关键修复:使用await等待getTableDom返回DOM元素(而非Promise) + const table = await this.getTableDom(refName); + if (!table) { + this.$message.warning(`无法导出:${refName}表格DOM获取失败`); + return; + } - const clonedTable = table.cloneNode(true); - this.adjustTableForExport(clonedTable); + // 确认table是原生DOM元素后再调用cloneNode + if (!(table instanceof HTMLElement)) { + this.$message.error(`无法导出:${refName}表格不是合法的DOM元素`); + return; + } - const workbook = XLSX.utils.table_to_book(clonedTable); - const fullName = `${fileName}_${this.formatDate()}.xlsx`; - XLSX.writeFile(workbook, fullName); - }, - - // 导出多个表格(新增自定义汇总文件名参数,适配不同选项卡) - exportMultipleTables(tables, customSummaryName) { - const workbook = XLSX.utils.book_new(); - let hasValidTable = false; - - tables.forEach(({ ref, name }) => { - const table = this.getTableDom(ref); - if (!table) return; - - hasValidTable = true; const clonedTable = table.cloneNode(true); this.adjustTableForExport(clonedTable); - const worksheet = XLSX.utils.table_to_sheet(clonedTable); - XLSX.utils.book_append_sheet(workbook, worksheet, name); - }); - - if (!hasValidTable) { - this.$message.warning('未找到有效表格数据'); - return; + const workbook = XLSX.utils.table_to_book(clonedTable); + const fullName = `${fileName}_${this.formatDate()}.xlsx`; + XLSX.writeFile(workbook, fullName); + } catch (error) { + this.$message.error(`导出表格失败:${error.message}`); + console.error('单个表格导出错误:', error); } - - // 动态生成汇总文件名:自定义名称 + 日期 - const fullName = `${customSummaryName}_${this.formatDate()}.xlsx`; - XLSX.writeFile(workbook, fullName); }, - // 辅助方法:获取表格DOM元素(包含表头) - getTableDom(refName) { - const tableComponent = this.$refs[refName]; - if (!tableComponent) { - this.$message.warning(`未找到${refName}表格`); - return null; + + // 导出多个表格(改为async方法,使用await获取DOM) + async exportMultipleTables(tables, customSummaryName) { + try { + const workbook = XLSX.utils.book_new(); + let hasValidTable = false; + + // 遍历表格配置,使用await获取每个表格的DOM + for (const { ref, name } of tables) { // 改用for...of循环,支持await + const table = await this.getTableDom(ref); + if (!table) { + this.$message.warning(`${ref}表格DOM获取失败,跳过该表格`); + continue; + } + + // 确认table是原生DOM元素 + if (!(table instanceof HTMLElement)) { + this.$message.warning(`${ref}表格不是合法的DOM元素,跳过该表格`); + continue; + } + + hasValidTable = true; + const clonedTable = table.cloneNode(true); + this.adjustTableForExport(clonedTable); + + const worksheet = XLSX.utils.table_to_sheet(clonedTable); + XLSX.utils.book_append_sheet(workbook, worksheet, name); + } + + if (!hasValidTable) { + this.$message.warning('未找到有效表格数据,导出失败'); + return; + } + + // 动态生成汇总文件名:自定义名称 + 日期 + const fullName = `${customSummaryName}_${this.formatDate()}.xlsx`; + XLSX.writeFile(workbook, fullName); + } catch (error) { + this.$message.error(`导出多个表格失败:${error.message}`); + console.error('多个表格导出错误:', error); } - // 获取完整表格容器(包含表头和表体) + }, + + /** + * 获取表格DOM元素(支持异步重试) + * @param {string} refName - 表格的ref名称 + * @param {number} retryCount - 当前重试次数(内部使用,外部调用无需传参) + * @returns {Promise} 表格DOM元素Promise + */ + async getTableDom(refName, retryCount = 0) { + // 1. 配置常量,便于维护 + const maxRetry = 5; // 最大重试次数 + const retryDelay = 300; // 每次重试延迟时间(ms) + + // 2. 设置激活标签,确保重试时也能执行该逻辑 + this.activeLabel = 'table'; + this.activeLabelDay = 'table'; + // 若需调试,可保留日志 + // console.log(`[getTableDom-${refName}] activeLabel:`, this.activeLabel, 'activeLabelDay:', this.activeLabelDay); + + // 3. 获取表格组件 + const tableComponent = this.$refs[refName]; + + // 4. 表格组件不存在时,处理重试逻辑 + if (!tableComponent) { + // 达到最大重试次数,返回失败 + if (retryCount >= maxRetry) { + this.$message.warning(`未找到${refName}表格,已达到最大重试次数(${maxRetry}次)`); + return null; + } + + // 未达到最大重试次数,延迟后重试 + this.$message.warning(`未找到${refName}表格,将进行第${retryCount + 1}次重试...`); + await new Promise(resolve => setTimeout(resolve, retryDelay)); + // 递归调用并返回结果 + return this.getTableDom(refName, retryCount + 1); + } + + // 5. 获取完整表格容器(包含表头和表体) const tableContainer = tableComponent.$el.querySelector('.el-table'); if (!tableContainer) { - this.$message.warning(`${refName}表格数据为空`); + this.$message.warning(`${refName}表格容器不存在(可能表格数据为空)`); return null; } + + // 6. 成功获取表格DOM,返回结果 return tableContainer; }, - // 重点修复:调整表格结构(解决表头重复问题) adjustTableForExport(tableContainer) { // 1. 移除空表格提示和分页等无关元素 @@ -939,7 +1021,7 @@ export default { if (mainTable && allHeaderRows.length > 0) { const newThead = document.createElement('thead'); // 按层级添加表头行 - allHeaderRows.forEach(cells => { + allHeaderRows.forEach((cells, rowIndex) => { const newTr = document.createElement('tr'); cells.forEach(cell => { // 复制单元格(避免DOM残留引用问题) @@ -950,6 +1032,18 @@ export default { }); // 将新表头插入主表体最前面 mainTable.insertBefore(newThead, mainTable.firstChild); + + // ================ 新增:修改第一列表头为“序号” ================ + // 找到第一层级的第一个表头单元格() + const firstHeaderCell = newThead.querySelector('tr:first-child th:first-child'); + if (firstHeaderCell) { + // 清空原有内容,设置为“序号” + firstHeaderCell.innerHTML = ''; // 清空原有内容(包括子元素) + firstHeaderCell.textContent = '序号'; // 设置纯文本“序号”(更稳定) + // 可选:添加样式(如居中,根据需求调整) + // firstHeaderCell.style.textAlign = 'center'; + } + // ============================================================= } // 7. 合并固定列数据到主表体(仅处理tbody数据) @@ -980,6 +1074,23 @@ export default { } }); } + + // ================ 可选优化:为tbody第一列添加序号数字 ================ + // 如果表格体的第一列原本无数据,可补充1、2、3...的序号 + if (mainTable) { + const tbodyRows = mainTable.querySelectorAll('tbody tr'); + tbodyRows.forEach((row, index) => { + const firstTd = row.querySelector('td:first-child'); + if (firstTd) { + // 清空原有内容,设置为序号(index+1,因为索引从0开始) + firstTd.innerHTML = ''; + firstTd.textContent = index + 1; + // 可选:居中显示 + // firstTd.style.textAlign = 'center'; + } + }); + } + // ============================================================= }, // 辅助方法:格式化日期(用于文件名) @@ -1003,7 +1114,45 @@ export default { this.formConfig[2].selectOptions = res.data.list || []; }); }, + getWeekTimeRange(date) { + const targetDate = new Date(date); + const day = targetDate.getDay() || 7; // 周日转为7 + const year = targetDate.getFullYear(); + const month = targetDate.getMonth(); + const dateNum = targetDate.getDate(); + // 本周一 00:00:00 + const startDate = new Date(year, month, dateNum - day + 1); + startDate.setHours(0, 0, 0, 0); + + // 本周日 23:59:59 + const endDate = new Date(year, month, dateNum - day + 7); + endDate.setHours(23, 59, 59, 999); + + return { + startTime: startDate.getTime(), + endTime: endDate.getTime() + }; + }, + + // 辅助函数:获取本年的开始和结束时间戳(1月1日00:00:00 到 12月31日23:59:59) + getYearTimeRange(date) { + const targetDate = new Date(date); + const year = targetDate.getFullYear(); + + // 本年1月1日 00:00:00 + const startDate = new Date(year, 0, 1); + startDate.setHours(0, 0, 0, 0); + + // 本年12月31日 23:59:59 + const endDate = new Date(year, 11, 31); + endDate.setHours(23, 59, 59, 999); + + return { + startTime: startDate.getTime(), + endTime: endDate.getTime() + }; + }, // 搜索/导出按钮点击 buttonClick(val) { this.headFormValue = val @@ -1498,6 +1647,7 @@ export default { handleSearchBarChanged({ param, value }) { console.log(value, param); if (param === 'timeType') { + this.isPeriodicReport = false this.tableData = [] this.tableDataCustom = [] this.tableData2 = [] @@ -1566,15 +1716,60 @@ export default { this.formConfig[7].type = ''; } } else if (param === 'searchType') { + this.isPeriodicReport = value === 2; + console.log('this.isPeriodicReport ', this.isPeriodicReport); + if (this.$refs.searchBarForm && this.$refs.searchBarForm.formInline) { + const formInline = this.$refs.searchBarForm.formInline; + formInline.reportType = '' + // 精准判断:只有字段存在时,才置为undefined(不存在则不处理) + if ('timeVal' in formInline) { + formInline.timeVal = undefined; + } + if ('timeValWeek' in formInline) { + formInline.timeValWeek = undefined; + } + if ('timeValMonth' in formInline) { + formInline.timeValMonth = undefined; + } + if ('timeValYear' in formInline) { + formInline.timeValYear = undefined; + } + } + // const formInline = this.$refs.searchBarForm.formInline; + this.listQuery.startTime = undefined + this.listQuery.endTime = undefined + console.log(this.listQuery, 'list'); if (value === 1) { // 统计数据:显示时间范围,隐藏报表类型 - this.formConfig[7].type = 'datePicker'; + this.formConfig[7] = { + type: 'datePicker', + label: '时间范围', + dateType: 'datetimerange', + format: 'yyyy-MM-dd HH:mm:ss', + valueFormat: 'timestamp', + rangeSeparator: '-', + startPlaceholder: '开始时间', + endPlaceholder: '结束时间', + param: 'timeVal', + width: 350 + }; this.formConfig[6].type = ''; - this.$refs.searchBarForm.formInline.timeVal = undefined; + } else { // 周期性报表:显示报表类型,隐藏时间范围 this.formConfig[6].type = 'select'; - this.formConfig[7].type = 'datePicker'; + this.formConfig[7] = { + type: 'datePicker', + label: '时间范围', + dateType: 'datetimerange', + format: 'yyyy-MM-dd HH:mm:ss', + valueFormat: 'timestamp', + rangeSeparator: '-', + startPlaceholder: '开始时间', + endPlaceholder: '结束时间', + param: 'timeVal', + width: 350 + }; } } else if (param === 'reportType') { if (this.$refs.searchBarForm && this.$refs.searchBarForm.formInline) {