|
|
|
|
@ -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,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;
|
|
|
|
|
}
|
|
|
|
|
</style>
|
|
|
|
|
|