From a30a31e383c700544e28c3dae26382cb8930c764 Mon Sep 17 00:00:00 2001 From: "zangch@mesnac.com" Date: Wed, 7 Jan 2026 14:06:12 +0800 Subject: [PATCH] =?UTF-8?q?feat(ems):=20=E4=BC=98=E5=8C=96=E7=89=A9?= =?UTF-8?q?=E8=81=94=E7=BD=91=E6=95=B0=E6=8D=AE=E5=AF=BC=E5=87=BA=E5=8A=9F?= =?UTF-8?q?=E8=83=BD=EF=BC=8C=E8=AF=B7=E6=B1=82=E5=B7=A5=E5=85=B7=E4=B8=AD?= =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E6=97=A0=E9=99=90=E5=88=B6=E8=B6=85=E6=97=B6?= =?UTF-8?q?=E8=AE=BE=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 将导出功能改为弹窗模式,支持时间范围和设备类型选择 - 新增导出对话框,包含时间范围选择和设备分组选择功能 - 实现按设备类型(温度、温湿度、噪声等)分组显示和选择 - 添加全选设备、清空选择和按类型全选功能 - 优化振动设备导出逻辑,添加时间范围和设备选择验证 - 增加导出文件名的类型信息,便于识别数据类型 - 在请求工具中添加无限制超时设置,确保大文件导出成功 --- src/utils/request.js | 1 + .../ems/record/recordIOTInstant/index.vue | 395 +++++++++++++++++- .../record/recordVibrationInstant/index.vue | 18 +- 3 files changed, 407 insertions(+), 7 deletions(-) diff --git a/src/utils/request.js b/src/utils/request.js index a94a448..3a776dc 100644 --- a/src/utils/request.js +++ b/src/utils/request.js @@ -137,6 +137,7 @@ export function download(url, params, filename, config) { transformRequest: [(params) => { return tansParams(params) }], headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, responseType: 'blob', + timeout: -1, // 导出操作超时时间无限制 ...config }).then(async (data) => { const isBlob = blobValidate(data); diff --git a/src/views/ems/record/recordIOTInstant/index.vue b/src/views/ems/record/recordIOTInstant/index.vue index 2850cc1..102bcaf 100644 --- a/src/views/ems/record/recordIOTInstant/index.vue +++ b/src/views/ems/record/recordIOTInstant/index.vue @@ -180,7 +180,7 @@ plain icon="el-icon-download" size="mini" - @click="handleExport" + @click="openExportDialog" v-hasPermi="['ems/record:recordIotenvInstant:export']" >导出 @@ -295,6 +295,126 @@ 取 消 + + + + + + + + + + + +
+ 全选设备 + 清空选择 + + 已选择 {{ selectedDevicesCount }} 个设备 + +
+ + +
+ + +
+
+ 温度设备 + + {{ isGroupAllSelected('temperature') ? '取消全选' : '全选该类型' }} + +
+ + + + {{ device.monitorName }} + (虚拟) + + + +
+ + +
+
+ 温湿度设备 + + {{ isGroupAllSelected('humidity') ? '取消全选' : '全选该类型' }} + +
+ + + + {{ device.monitorName }} + (虚拟) + + + +
+ + +
+
+ 噪声设备 + + {{ isGroupAllSelected('noise') ? '取消全选' : '全选该类型' }} + +
+ + + + {{ device.monitorName }} + (虚拟) + + + +
+ + +
+
+ 其他设备 + + {{ isGroupAllSelected('other') ? '取消全选' : '全选该类型' }} + +
+ + + + {{ device.monitorName }} + (虚拟) + + + +
+ + +
+ + 暂无可用设备,请先配置物联网采集设备 +
+
+
+
+
+ +
@@ -368,6 +488,24 @@ export default { form: {}, // 表单校验 rules: { + }, + // 导出对话框相关 + exportDialogVisible: false, + exportForm: { + timeRange: [], + selectedDeviceCodes: [] + }, + // 所有设备列表(用于导出选择) + allDevicesList: [], + // 设备列表缓存(避免重复请求) + allDevicesListCache: null, + cacheTimestamp: 0, + // 按类型分组的设备(不含振动设备,振动在专用页面处理) + deviceGroups: { + temperature: [], // 温度设备 (type=5) + humidity: [], // 温湿度设备 (type=6) + noise: [], // 噪声设备 (type=7) + other: [] // 其他设备 (8,9等,不含振动10) } }; }, @@ -375,6 +513,10 @@ export default { // 计算是否已选择设备 hasSelectedDevice() { return this.queryParams.monitorId || (this.queryParams.monitorIds && this.queryParams.monitorIds.length > 0); + }, + // 计算已选择的设备数量 + selectedDevicesCount() { + return this.exportForm.selectedDeviceCodes ? this.exportForm.selectedDeviceCodes.length : 0; } }, created() { @@ -607,13 +749,195 @@ export default { this.$modal.msgSuccess("删除成功"); }).catch(() => {}); }, - /** 导出按钮操作 */ - handleExport() { - this.download('ems/record/recordIotenvInstant/export', { - ...this.queryParams - }, `recordIotenvInstant_${new Date().getTime()}.xlsx`) + + /** ========== 导出相关方法 ========== */ + + /** 打开导出对话框 */ + openExportDialog() { + // 初始化导出表单 + this.exportForm.timeRange = this.daterangeRecordTime ? [...this.daterangeRecordTime] : []; + this.exportForm.selectedDeviceCodes = []; + + // 加载所有设备列表 + this.loadAllDevicesForExport(); + this.exportDialogVisible = true; }, + /** 加载所有设备列表(用于导出,使用缓存优化性能) */ + loadAllDevicesForExport() { + // 缓存5分钟有效(300000毫秒) + const now = Date.now(); + if (this.allDevicesListCache && (now - this.cacheTimestamp) < 300000) { + // 使用缓存数据 + this.allDevicesList = this.allDevicesListCache; + this.groupDevicesByType(); + return; + } + + // 缓存失效,重新加载(不含振动设备type=10) + listBaseMonitorInfo({ monitorTypeList: [5, 6, 7, 8, 9] }).then(response => { + this.allDevicesList = response.data; + this.allDevicesListCache = response.data; + this.cacheTimestamp = now; + this.groupDevicesByType(); + }); + }, + + /** 按设备类型分组(不含振动设备,振动在专用页面处理) */ + groupDevicesByType() { + // 重置分组 + this.deviceGroups = { + temperature: [], // 温度设备 (type=5) + humidity: [], // 温湿度设备 (type=6) + noise: [], // 噪声设备 (type=7) + other: [] // 其他设备 (8,9等,不含振动10) + }; + + this.allDevicesList.forEach(device => { + // 跳过虚拟设备 + if (device.isAmmeter === '0') return; + // 跳过振动设备(振动设备在专用页面处理) + if (device.monitorType === 10) return; + + switch (device.monitorType) { + case 5: + this.deviceGroups.temperature.push(device); + break; + case 6: + this.deviceGroups.humidity.push(device); + break; + case 7: + this.deviceGroups.noise.push(device); + break; + default: + this.deviceGroups.other.push(device); + break; + } + }); + }, + + /** 全选设备(不含振动设备) */ + selectAllDevices() { + // 获取所有非虚拟、非振动设备的设备编号 + const allCodes = this.allDevicesList + .filter(device => device.isAmmeter !== '0' && device.monitorType !== 10) + .map(device => device.monitorCode); + this.exportForm.selectedDeviceCodes = allCodes; + }, + + /** 清空设备选择 */ + clearAllDevices() { + this.exportForm.selectedDeviceCodes = []; + }, + + /** 处理设备选择变化 */ + handleDeviceSelectionChange() { + // 可以在这里添加额外的逻辑,比如更新显示等 + }, + + /** 判断某个类型的设备是否全部选中 */ + isGroupAllSelected(groupName) { + const group = this.deviceGroups[groupName] || []; + // 获取该组非虚拟设备的编码列表 + const validCodes = group + .filter(device => device.isAmmeter !== '0') + .map(device => device.monitorCode); + if (validCodes.length === 0) return false; + // 检查是否全部在已选列表中 + return validCodes.every(code => this.exportForm.selectedDeviceCodes.includes(code)); + }, + + /** 全选/取消全选某个类型的设备 */ + selectGroupDevices(groupName) { + const group = this.deviceGroups[groupName] || []; + // 获取该组非虚拟设备的编码列表 + const validCodes = group + .filter(device => device.isAmmeter !== '0') + .map(device => device.monitorCode); + + if (this.isGroupAllSelected(groupName)) { + // 已全选,则取消该组所有设备 + this.exportForm.selectedDeviceCodes = this.exportForm.selectedDeviceCodes + .filter(code => !validCodes.includes(code)); + } else { + // 未全选,则添加该组所有设备 + const newCodes = validCodes.filter(code => !this.exportForm.selectedDeviceCodes.includes(code)); + this.exportForm.selectedDeviceCodes = [...this.exportForm.selectedDeviceCodes, ...newCodes]; + } + }, + + /** 确认导出 */ + confirmExport() { + // 校验时间范围 + if (!this.exportForm.timeRange || this.exportForm.timeRange.length !== 2) { + this.$modal.msgWarning("请先选择时间范围"); + return; + } + // 校验设备选择 + if (this.exportForm.selectedDeviceCodes.length === 0) { + this.$modal.msgWarning("请至少选择一个设备"); + return; + } + + // 计算选中的设备类型集合(用于后端动态导出列) + const selectedMonitorTypes = this.getSelectedMonitorTypes(); + + // 构建导出参数 + const exportParams = { + params: { + beginRecordTime: this.exportForm.timeRange[0], + endRecordTime: this.exportForm.timeRange[1], + // 传递设备类型,后端根据类型动态选择导出列 + monitorTypes: selectedMonitorTypes.join(',') + }, + monitorIds: this.exportForm.selectedDeviceCodes, + monitorId: null // 清空单个设备ID,使用多设备数组 + }; + + // 生成文件名(简化格式,移除日期中的横杠以缩短文件名) + const beginDate = this.exportForm.timeRange[0].substring(0, 10).replace(/-/g, ''); + const endDate = this.exportForm.timeRange[1].substring(0, 10).replace(/-/g, ''); + const deviceCount = this.exportForm.selectedDeviceCodes.length; + // 文件名包含能源类型信息 + const typeNames = this.getTypeNamesForFileName(selectedMonitorTypes); + const fileName = `IOT数据_${typeNames}_${beginDate}_${endDate}_${deviceCount}设备.xlsx`; + + // 执行导出 + this.download('ems/record/recordIotenvInstant/export', exportParams, fileName); + this.exportDialogVisible = false; + }, + + /** 获取选中设备的类型集合 */ + getSelectedMonitorTypes() { + const selectedCodes = this.exportForm.selectedDeviceCodes; + const types = new Set(); + + // 遍历所有设备列表,找出选中设备的类型 + this.allDevicesList.forEach(device => { + if (selectedCodes.includes(device.monitorCode) && device.monitorType) { + types.add(device.monitorType); + } + }); + + return Array.from(types); + }, + + /** 根据类型生成文件名中的类型描述(不含振动) */ + getTypeNamesForFileName(types) { + if (types.length === 0) return '混合'; + if (types.length > 2) return '混合'; + + const typeMap = { + 5: '温度', + 6: '温湿度', + 7: '噪声' + }; + + return types.map(t => typeMap[t] || '其他').join('_'); + }, + + /** ========== 原有方法 ========== */ + getTreeselect() { getMonitorInfoTree({ monitorTypeList: [5,6,7,8,9]}).then(response => { this.monitorInfoOptions = response.data @@ -636,4 +960,63 @@ export default { .no-device-tip .el-empty { padding: 0; } + +/* 导出对话框样式 */ +.device-select-container { + max-height: 400px; + overflow-y: auto; + border: 1px solid #DCDFE6; + border-radius: 4px; + padding: 10px; +} + +.device-group { + margin-bottom: 15px; + padding-bottom: 15px; + border-bottom: 1px solid #EBEEF5; +} + +.device-group:last-child { + border-bottom: none; + margin-bottom: 0; + padding-bottom: 0; +} + +.device-group-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 8px; +} + +.device-group-title { + font-weight: bold; + color: #303133; + padding-left: 5px; + border-left: 3px solid #409EFF; +} + +.device-select-container .el-checkbox { + margin-right: 0; + margin-bottom: 8px; + display: block; +} + +/* 空状态样式 */ +.no-devices-empty { + text-align: center; + padding: 40px 20px; + color: #909399; + font-size: 14px; +} + +.no-devices-empty i { + font-size: 24px; + margin-right: 8px; + color: #E6A23C; +} + +.device-select-container .el-col { + margin-bottom: 5px; +} diff --git a/src/views/ems/record/recordVibrationInstant/index.vue b/src/views/ems/record/recordVibrationInstant/index.vue index 43aefa5..4df889e 100644 --- a/src/views/ems/record/recordVibrationInstant/index.vue +++ b/src/views/ems/record/recordVibrationInstant/index.vue @@ -586,9 +586,25 @@ export default { }, /** 导出按钮操作 */ handleExport() { + // 校验时间范围是否已选择 + if (!this.daterangeRecordTime || this.daterangeRecordTime.length !== 2) { + this.$modal.msgWarning("请先选择记录时间范围再导出"); + return; + } + // 校验是否已选择设备 + if (!this.queryParams.monitorId && (!this.queryParams.monitorIds || this.queryParams.monitorIds.length === 0)) { + this.$modal.msgWarning("请先在左侧树中选择设备再导出"); + return; + } + // 设置时间范围参数 + this.queryParams.params['beginRecordTime'] = this.daterangeRecordTime[0]; + this.queryParams.params['endRecordTime'] = this.daterangeRecordTime[1]; + // 振动页面固定传递monitorTypes=10,确保后端只导出振动相关列 + this.queryParams.params['monitorTypes'] = '10'; + this.download('ems/record/recordIotenvInstant/export', { ...this.queryParams - }, `recordIotenvInstant_${new Date().getTime()}.xlsx`) + }, `振动数据_${this.daterangeRecordTime[0].substring(0,10)}_${this.daterangeRecordTime[1].substring(0,10)}.xlsx`) }, getTreeselect() {