feat(ems): 优化物联网数据导出功能,请求工具中添加无限制超时设置

- 将导出功能改为弹窗模式,支持时间范围和设备类型选择
- 新增导出对话框,包含时间范围选择和设备分组选择功能
- 实现按设备类型(温度、温湿度、噪声等)分组显示和选择
- 添加全选设备、清空选择和按类型全选功能
- 优化振动设备导出逻辑,添加时间范围和设备选择验证
- 增加导出文件名的类型信息,便于识别数据类型
- 在请求工具中添加无限制超时设置,确保大文件导出成功
boardTest
zangch@mesnac.com 2 weeks ago
parent 06ecbcc11e
commit a30a31e383

@ -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);

@ -180,7 +180,7 @@
plain
icon="el-icon-download"
size="mini"
@click="handleExport"
@click="openExportDialog"
v-hasPermi="['ems/record:recordIotenvInstant:export']"
>导出</el-button>
</el-col>
@ -295,6 +295,126 @@
<el-button @click="cancel"> </el-button>
</div>
</el-dialog>
<!-- 导出对话框 -->
<el-dialog title="导出物联网数据" :visible.sync="exportDialogVisible" width="700px" append-to-body>
<el-form ref="exportForm" :model="exportForm" label-width="100px">
<!-- 时间范围选择 -->
<el-form-item label="时间范围" prop="timeRange" required>
<el-date-picker
v-model="exportForm.timeRange"
style="width: 100%"
value-format="yyyy-MM-dd HH:mm:ss"
type="datetimerange"
range-separator="-"
start-placeholder="开始时间"
end-placeholder="结束时间"
></el-date-picker>
</el-form-item>
<!-- 设备选择区域 -->
<el-form-item label="设备选择">
<div style="margin-bottom: 10px;">
<el-button type="primary" size="mini" @click="selectAllDevices"></el-button>
<el-button type="info" size="mini" @click="clearAllDevices"></el-button>
<span style="margin-left: 15px; color: #909399;">
已选择 <span style="color: #409EFF; font-weight: bold;">{{ selectedDevicesCount }}</span> 个设备
</span>
</div>
<!-- 按能源类型分组显示设备 -->
<div class="device-select-container">
<el-checkbox-group v-model="exportForm.selectedDeviceCodes" @change="handleDeviceSelectionChange">
<!-- 温度设备 (type=5) -->
<div v-if="deviceGroups.temperature.length > 0" class="device-group">
<div class="device-group-header">
<span class="device-group-title">温度设备</span>
<el-button type="text" size="mini" @click="selectGroupDevices('temperature')">
{{ isGroupAllSelected('temperature') ? '取消全选' : '全选该类型' }}
</el-button>
</div>
<el-row :gutter="10">
<el-col :span="12" v-for="device in deviceGroups.temperature" :key="device.monitorCode">
<el-checkbox :label="device.monitorCode" :disabled="device.isAmmeter === '0'">
{{ device.monitorName }}
<span v-if="device.isAmmeter === '0'" style="color: #F56C6C; font-size: 12px;">()</span>
</el-checkbox>
</el-col>
</el-row>
</div>
<!-- 温湿度设备 (type=6) -->
<div v-if="deviceGroups.humidity.length > 0" class="device-group">
<div class="device-group-header">
<span class="device-group-title">温湿度设备</span>
<el-button type="text" size="mini" @click="selectGroupDevices('humidity')">
{{ isGroupAllSelected('humidity') ? '取消全选' : '全选该类型' }}
</el-button>
</div>
<el-row :gutter="10">
<el-col :span="12" v-for="device in deviceGroups.humidity" :key="device.monitorCode">
<el-checkbox :label="device.monitorCode" :disabled="device.isAmmeter === '0'">
{{ device.monitorName }}
<span v-if="device.isAmmeter === '0'" style="color: #F56C6C; font-size: 12px;">()</span>
</el-checkbox>
</el-col>
</el-row>
</div>
<!-- 噪声设备 (type=7) -->
<div v-if="deviceGroups.noise.length > 0" class="device-group">
<div class="device-group-header">
<span class="device-group-title">噪声设备</span>
<el-button type="text" size="mini" @click="selectGroupDevices('noise')">
{{ isGroupAllSelected('noise') ? '取消全选' : '全选该类型' }}
</el-button>
</div>
<el-row :gutter="10">
<el-col :span="12" v-for="device in deviceGroups.noise" :key="device.monitorCode">
<el-checkbox :label="device.monitorCode" :disabled="device.isAmmeter === '0'">
{{ device.monitorName }}
<span v-if="device.isAmmeter === '0'" style="color: #F56C6C; font-size: 12px;">()</span>
</el-checkbox>
</el-col>
</el-row>
</div>
<!-- 其他设备 -->
<div v-if="deviceGroups.other.length > 0" class="device-group">
<div class="device-group-header">
<span class="device-group-title">其他设备</span>
<el-button type="text" size="mini" @click="selectGroupDevices('other')">
{{ isGroupAllSelected('other') ? '取消全选' : '全选该类型' }}
</el-button>
</div>
<el-row :gutter="10">
<el-col :span="12" v-for="device in deviceGroups.other" :key="device.monitorCode">
<el-checkbox :label="device.monitorCode" :disabled="device.isAmmeter === '0'">
{{ device.monitorName }}
<span v-if="device.isAmmeter === '0'" style="color: #F56C6C; font-size: 12px;">()</span>
</el-checkbox>
</el-col>
</el-row>
</div>
<!-- 空状态处理当所有设备组都为空时显示 -->
<div v-if="deviceGroups.temperature.length === 0 &&
deviceGroups.humidity.length === 0 &&
deviceGroups.noise.length === 0 &&
deviceGroups.other.length === 0"
class="no-devices-empty">
<i class="el-icon-info"></i>
<span>暂无可用设备请先配置物联网采集设备</span>
</div>
</el-checkbox-group>
</div>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button type="primary" @click="confirmExport"></el-button>
<el-button @click="exportDialogVisible = false">取消</el-button>
</div>
</el-dialog>
</div>
</template>
@ -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,910)
}
};
},
@ -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() {
// 5300000
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,910)
};
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;
}
</style>

@ -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() {

Loading…
Cancel
Save