Lmm vor 1 Jahr
Ursprung
Commit
56accf76e4
8 geänderte Dateien mit 960 neuen und 133 gelöschten Zeilen
  1. 247 98
      src/App.vue
  2. 182 0
      src/components/Excel/Blob.js
  3. 270 0
      src/components/Excel/Export2Excel.js
  4. 9 0
      src/js/api.js
  5. 74 0
      src/js/common.js
  6. 133 0
      src/js/export.js
  7. 6 0
      src/js/export_excel.js
  8. 39 35
      src/dataHandler.js

+ 247 - 98
src/App.vue

@@ -1,31 +1,48 @@
 <template>
-	<div>
+	<div style="padding-top: 12px;">
 		<el-form :inline="true" :model="queryParams" ref="queryRef" v-show="true" label-width="60px">
 			<el-form-item>
-				<el-input v-model="queryParams.project_id" placeholder="请输入项目名称" clearable
-					@keyup.enter="handleQuery" />
+				<el-input 
+					v-model="queryParams.project_name" 
+					placeholder="请输入项目名称" 
+					clearable 
+					size="small"
+					@keyup.enter="handleQuery"
+				/>
 			</el-form-item>
 			<el-form-item>
-				<el-input v-model="queryParams.box_id" placeholder="请输入盒子名称" clearable
-					@keyup.enter="handleQuery" />
+				<el-input 
+					v-model="queryParams.box_name" 
+					placeholder="请输入盒子名称" 
+					clearable
+					size="small"
+					@keyup.enter="handleQuery"
+				/>
 			</el-form-item>
 			<el-form-item>
-				<el-select v-model="queryParams.online_status" placeholder="请选择在线状态" clearable>
-					<el-option v-for="item in runStatOptions" :key="item.value" :label="item.label"
-						:value="item.value">
-					</el-option>
+				<el-select v-model="queryParams.online_status" placeholder="请选择在线状态" clearable size="small">
+					<el-option 
+						v-for="item in runStatOptions" 
+						:key="item.value" 
+						:label="item.label"
+						:value="item.value"
+					></el-option>
 				</el-select>
 			</el-form-item>
 			<el-form-item>
-				<el-select v-model="queryParams.run_mode" placeholder="请选择运行模式" clearable>
-					<el-option v-for="item in runModeOptions" :key="item.value" :label="item.label"
-						:value="item.value">
-					</el-option>
+				<el-select v-model="queryParams.run_mode" placeholder="请选择运行模式" clearable size="small">
+					<el-option 
+						v-for="item in runModeOptions" 
+						:key="item.value" 
+						:label="item.label"
+						:value="item.value"
+					></el-option>
 				</el-select>
 			</el-form-item>
 			<el-form-item>
-				<el-button type="primary" size="medium" @click="handleQuery">搜索</el-button>
-				<el-button type="success" size="medium" @click="handleDerive">导出</el-button>
+				<el-button type="primary" size="small" @click="handleQuery">搜索</el-button>
+				<el-button v-if="loading" type="success" size="small" :loding="loading">导出中...</el-button>
+				<el-button v-else type="success" size="small" @click="handleDerive">导出</el-button>
 			</el-form-item>
 		</el-form>
 		<el-table :data="tableData" border :span-method="objectSpanMethod">
@@ -51,37 +68,58 @@
 			<el-table-column label="主机控制器" align="center" width="60">
 				<el-table-column prop="host_order" label="编号" align="center" width="60">
 					<template slot-scope="scope">
-						<span>{{ scope.row.host_order || '-' }}</span>
+						<span>{{ scope.row.host_order || '/' }}</span>
 					</template>
 				</el-table-column>
 				<el-table-column prop="host_connect_status" label="通讯状态" align="center" width="100">
 					<template slot-scope="scope">
-						<span class="el-table-tag" :style="{ background: connectStatColor(scope.row.host_connect_status) }">{{ connectStatText(scope.row.host_connect_status) }}</span>
+						<span 
+							v-if="scope.row.host_connect_status != undefined && scope.row.host_connect_status != null" 
+							class="el-table-tag" 
+							:style="{ background: connectStatColor(scope.row.host_connect_status) }"
+						>
+							{{ connectStatText(scope.row.host_connect_status) }}
+						</span>
+						<span v-else>/</span>
 					</template>
 				</el-table-column>
 				<el-table-column prop="host_fault" label="故障" align="center" width="90">
 					<template slot-scope="scope">
-						<span class="el-table-tag" :style="{ background: scope.row.host_fault == 1 ? 'yellowgreen' : 'yellow' }">{{ scope.row.host_fault == 1 ? '无' : '有' }}</span>
+						<span 
+							v-if="scope.row.host_fault != undefined && scope.row.host_fault != null" 
+							class="el-table-tag" 
+							:style="{ background: faultColor(scope.row.host_fault) }"
+						>
+							{{ faultText(scope.row.host_fault) }}
+						</span>
+						<span v-else>/</span>
 					</template>
 				</el-table-column>
 				<el-table-column prop="host_su_temp" label="供水水温(℃)" align="center" width="120">
 					<template slot-scope="scope">
-						<span class="el-table-tag" :style="{ background: hpTempColor(scope.row.runMode, scope.row.host_su_temp) }">{{ fixedVal(scope.row.host_su_temp) }}</span>
+						<span v-if="scope.row.host_su_temp" class="el-table-tag" :style="{ background: hpTempColor(scope.row.runMode, scope.row.host_su_temp) }">
+							{{ fixedVal(scope.row.host_su_temp) }}
+						</span>
+						<span v-else>/</span>
 					</template>
 				</el-table-column>
 				<el-table-column prop="host_re_temp" label="回水水温(℃)" align="center" width="120">
 					<template slot-scope="scope">
-						<span class="el-table-tag" :style="{ background: hpTempColor(scope.row.runMode, scope.row.host_re_temp) }">{{ fixedVal(scope.row.host_re_temp) }}</span>
+						<span v-if="scope.row.host_re_temp" class="el-table-tag" :style="{ background: hpTempColor(scope.row.runMode, scope.row.host_re_temp) }">
+							{{ fixedVal(scope.row.host_re_temp) }}
+						</span>
+						<span v-else>/</span>
 					</template>
 				</el-table-column>
 				<el-table-column prop="host_temp_diff" label="温差" align="center" width="100">
 					<template slot-scope="scope">
-						<div>
+						<div v-if="scope.row.host_temp_diff">
 							<span class="el-table-tag" :style="{ background: scope.row.host_temp_diff && scope.row.host_temp_diff <= 5 ? 'yellowgreen' : '' }">
 								{{ fixedVal(scope.row.host_temp_diff) }}
 							</span>
 							<i v-if="scope.row.hpCnt > scope.row.maxCnt" class="el-icon-caret-bottom ml10" @click.stop="handleClickMore(scope.row, 'host')"></i>
 						</div>
+						<span v-else>/</span>
 					</template>
 				</el-table-column>
 			</el-table-column>
@@ -89,30 +127,60 @@
 
 			<!-- 新风机列 Begin -->
 			<el-table-column label="新风机控制器" align="center" width="60">
-				<el-table-column prop="nt_order" label="编号" align="center" width="60" />
+				<el-table-column prop="nt_order" label="编号" align="center" width="60">
+					<template slot-scope="scope">
+						<span>{{ scope.row.nt_order || '/' }}</span>
+					</template>
+				</el-table-column>
 				<el-table-column prop="nt_connect_status" label="通讯状态" align="center" width="100">
 					<template slot-scope="scope">
-						<span class="el-table-tag" :style="{ background: connectStatColor(scope.row.nt_connect_status) }">{{ connectStatText(scope.row.nt_connect_status) }}</span>
+						<span 
+							v-if="scope.row.nt_connect_status != undefined && scope.row.nt_connect_status != null" 
+							class="el-table-tag" 
+							:style="{ background: connectStatColor(scope.row.nt_connect_status) }"
+						>
+							{{ connectStatText(scope.row.nt_connect_status) }}
+						</span>
+						<span v-else>/</span>
 					</template>
 				</el-table-column>
 				<el-table-column prop="nt_fault" label="故障" align="center" width="90">
 					<template slot-scope="scope">
-						<span class="el-table-tag" :style="{ background: scope.row.nt_fault == 1 ? 'yellowgreen' : 'yellow' }">{{ scope.row.nt_fault == 1 ? '无' : '有' }}</span>
+						<span 
+							v-if="scope.row.nt_fault != undefined && scope.row.nt_fault != null" 
+							class="el-table-tag" 
+							:style="{ background: faultColor(scope.row.nt_fault) }"
+						>
+							{{ faultText(scope.row.nt_fault) }}
+						</span>
+						<span v-else>/</span>
 					</template>
 				</el-table-column>
 				<el-table-column prop="nt_in_temp" label="送风温度(℃)" align="center" width="120">
 					<template slot-scope="scope">
-						<span class="el-table-tag" :style="{ background: dhTempColor(scope.row.runMode, scope.row.nt_in_temp) }">{{ fixedVal(scope.row.nt_in_temp) }}</span>
+						<span v-if="scope.row.nt_in_temp" class="el-table-tag" :style="{ background: dhTempColor(scope.row.runMode, scope.row.nt_in_temp) }">
+							{{ fixedVal(scope.row.nt_in_temp) }}
+						</span>
+						<span v-else>/</span>
 					</template>
 				</el-table-column>
 				<el-table-column prop="nt_in_humidity" label="送风湿度(%)" align="center" width="120">
 					<template slot-scope="scope">
-						<span class="el-table-tag" :style="{ background: dhHumiColor(scope.row.runMode, scope.row.nt_in_humidity) }">{{ fixedVal(scope.row.nt_in_humidity) }}</span>
+						<span v-if="scope.row.nt_in_humidity" class="el-table-tag" :style="{ background: dhHumiColor(scope.row.runMode, scope.row.nt_in_humidity) }">
+							{{ fixedVal(scope.row.nt_in_humidity) }}
+						</span>
+						<span v-else>/</span>
 					</template>
 				</el-table-column>
-				<el-table-column prop="nt_dew_point" label="露点(℃)" align="center" width="90">
+				<el-table-column prop="nt_dew_point" label="露点(℃)" align="center" width="120">
 					<template slot-scope="scope">
-						<span class="el-table-tag" :style="{ background: dhDewPointColor(scope.row.nt_dew_point) }">{{ fixedVal(scope.row.nt_dew_point) }}</span>
+						<div v-if="scope.row.nt_dew_point">
+							<span class="el-table-tag" :style="{ background: dhDewPointColor(scope.row.nt_dew_point) }">
+								{{ fixedVal(scope.row.nt_dew_point) }}
+							</span>
+							<i v-if="scope.row.dhCnt > scope.row.maxCnt" class="el-icon-caret-bottom ml10" @click.stop="handleClickMore(scope.row, 'newTrend')"></i>
+						</div>
+						<span v-else>/</span>
 					</template>
 				</el-table-column>
 			</el-table-column>
@@ -120,39 +188,72 @@
 
 			<!-- 换热站列 Begin -->
 			<el-table-column label="换热站控制器" align="center" width="60">
-				<el-table-column prop="hex_order" label="编号" align="center" width="60" />
+				<el-table-column prop="hex_order" label="编号" align="center" width="60">
+					<template slot-scope="scope">
+						<span>{{ scope.row.hex_order || '/' }}</span>
+					</template>
+				</el-table-column>
 				<el-table-column prop="hex_connect_status" label="通讯状态" align="center" width="100">
 					<template slot-scope="scope">
-						<span class="el-table-tag" :style="{ background: connectStatColor(scope.row.hex_connect_status) }">{{ connectStatText(scope.row.hex_connect_status) }}</span>
+						<span 
+							v-if="scope.row.hex_connect_status != undefined && scope.row.hex_connect_status != null" 
+							class="el-table-tag" 
+							:style="{ background: connectStatColor(scope.row.hex_connect_status) }"
+						>
+							{{ connectStatText(scope.row.hex_connect_status)}}
+						</span>
+						<span v-else>/</span>
 					</template>
 				</el-table-column>
 				<el-table-column prop="hex_fault" label="故障" align="center" width="90">
 					<template slot-scope="scope">
-						<span class="el-table-tag" :style="{ background: scope.row.hex_fault == 1 ? 'yellowgreen' : 'yellow' }">{{ scope.row.hex_fault == 1 ? '无' : '有' }}</span>
+						<span 
+							v-if="scope.row.hex_fault != undefined && scope.row.hex_fault != null" 
+							class="el-table-tag" 
+							:style="{ background: faultColor(scope.row.hex_fault) }"
+						>
+							{{ faultText(scope.row.hex_fault) }}
+						</span>
+						<span v-else>/</span>
 					</template>
 				</el-table-column>
 				<el-table-column prop="hex_su_temp" label="供水水温(℃)" align="center" width="120">
 					<template slot-scope="scope">
-						<span class="el-table-tag" :style="{ background: hexTempColor(scope.row.runMode, scope.row.hex_su_temp) }">{{ fixedVal(scope.row.hex_su_temp) }}</span>
+						<span v-if="scope.row.hex_su_temp" class="el-table-tag" :style="{ background: hexTempColor(scope.row.runMode, scope.row.hex_su_temp) }">
+							{{ fixedVal(scope.row.hex_su_temp) }}
+						</span>
+						<span v-else>/</span>
 					</template>
 				</el-table-column>
 				<el-table-column prop="hex_re_temp" label="回水水温(℃)" align="center" width="120">
 					<template slot-scope="scope">
-						<span class="el-table-tag" :style="{ background: hexTempColor(scope.row.runMode, scope.row.hex_re_temp) }">{{ fixedVal(scope.row.hex_re_temp) }}</span>
+						<span v-if="scope.row.hex_re_temp" class="el-table-tag" :style="{ background: hexTempColor(scope.row.runMode, scope.row.hex_re_temp) }">
+							{{ fixedVal(scope.row.hex_re_temp) }}
+						</span>
+						<span v-else>/</span>
 					</template>
 				</el-table-column>
 				<el-table-column prop="hex_temp_diff" label="温差(℃)" align="center" width="90">
 					<template slot-scope="scope">
-						<span class="el-table-tag" :style="{ background: scope.row.hex_temp_diff && scope.row.hex_temp_diff <= 5 ? 'yellowgreen' : '' }">
+						<span 
+							v-if="scope.row.hex_temp_diff != undefined && scope.row.hex_temp_diff != null" 
+							class="el-table-tag" 
+							:style="{ background: scope.row.hex_temp_diff <= 5 ? 'yellowgreen' : '' }"
+						>
 							{{ fixedVal(scope.row.hex_temp_diff) }}
 						</span>
+						<span v-else>/</span>
 					</template>
 				</el-table-column>
 				<el-table-column prop="hex_pump_status" label="水泵状态" align="center" width="100">
 					<template slot-scope="scope">
-						<span class="el-table-tag" :style="{ background: scope.row.hex_pump_status == 0 ? 'yellow' : 'yellowgreen' }">
-							{{ scope.row.hex_pump_status == 0 ? '停止' : '运行' }}
-						</span>
+						<div v-if="scope.row.hex_pump_status != undefined && scope.row.hex_pump_status != null">
+							<span class="el-table-tag" :style="{ background: hexPumpStateColor(scope.row.hex_pump_status) }">
+								{{ hexPumpStateText(scope.row.hex_pump_status) }}
+							</span>
+							<i v-if="scope.row.hexCnt > scope.row.maxCnt" class="el-icon-caret-bottom ml10" @click.stop="handleClickMore(scope.row, 'heatExchange')"></i>
+						</div>
+						<span v-else>/</span>
 					</template>
 				</el-table-column>
 			</el-table-column>
@@ -163,7 +264,10 @@
 				<el-table-column prop="end_order" label="编号" align="center" width="60" />
 				<el-table-column prop="end_exception_num" label="通讯异常数量" align="center" width="120">
 					<template slot-scope="scope">
-						<span>{{ scope.row.end_exception_num > 0 ? scope.row.end_exception_num : '无' }}</span>
+						<span v-if="scope.row.end_exception_num != undefined && scope.row.end_exception_num != null">
+							{{ scope.row.end_exception_num }}
+						</span>
+						<span v-else>/</span>
 					</template>
 				</el-table-column>
 				<el-table-column prop="end_min_temp" label="最低室内温度(℃)" align="center" width="140">
@@ -214,15 +318,17 @@
 			@current-change="handleCurrentChange"
 		></el-pagination>
 		<!-- 更多信息对话框 -->
-		<el-dialog :title="dialog.title" :visible.sync="dialog.show" width="76%">
+		<el-dialog :title="dialog.title" :visible.sync="dialog.show" width="75%">
 			<pop-more-table :visible="dialog.show" :tableData="dialog.tableData" :columns="dialog.tableColumns"></pop-more-table>
 		</el-dialog>
 	</div>
 </template>
 
 <script>
-import axios from 'axios';
-import { expandAllNodes, mergeTableRows } from './dataHandler.js';
+import { listProjects, listArgs } from './js/api.js';
+import { expandAllNodes, mergeTableRows } from './js/merge.js';
+import { exportToExcel } from './js/export.js';
+import { runStatDict, runModeDict, connectStatDict, faultDict, hexPumpStatDict } from './js/common.js';
 import PopMoreTable from './components/PopMoreTable.vue';
 export default {
 	components: {
@@ -231,21 +337,33 @@ export default {
 	computed: {
 		connectStatColor() {
 			return function(state) {
-				if (this.connectStatMap[state]) {
-					return this.connectStatMap[state].color;
-				} else {
-					return '';
-				}
-			};
+				return this.connectStatMap[state] ? this.connectStatMap[state].color : ''
+			}
 		},
 		connectStatText() {
 			return function(state) {
-				if (this.connectStatMap[state]) {
-					return this.connectStatMap[state].label;
-				} else {
-					return '/';
-				}
-			};
+				return this.connectStatMap[state] ? this.connectStatMap[state].label : '/'
+			}
+		},
+		faultColor() {
+			return function(state) {
+				return this.faultMap[state] ? this.faultMap[state].color : ''
+			}
+		},
+		faultText() {
+			return function(state) {
+				return this.faultMap[state] ? this.faultMap[state].label : '/'
+			}
+		},
+		hexPumpStateColor() {
+			return function(state) {
+				return this.hexPumpStatMap[state] ? this.hexPumpStatMap[state].color : ''
+			}
+		},
+		hexPumpStateText() {
+			return function(state) {
+				return this.hexPumpStatMap[state] ? this.hexPumpStatMap[state].label : '/'
+			}
 		},
 		// 主机供水温度与回水温度颜色判定
 		hpTempColor() {
@@ -337,6 +455,25 @@ export default {
 				return row.end_max_dew_point > 18 ? 'yellow' : 'yellowgreen';
 			};
 		},
+		calcArgsColor() {
+			return function(row, prop) {
+				let items = this.argsList.filter(a => a.En === prop);
+				if (!items) {
+					return '';
+				}
+				if (items.length > 1) {
+					// items.forEach(v => {
+					// 	if (v.RunMode > 0) {
+							
+					// 	} else {
+							
+					// 	}
+					// });
+				} else {
+					return this.getArgsColor(row, prop, items[0].Action, items[0].Threshold);
+				}
+			}
+		},
 		fixedVal() {
 			return function(val) {
 				if (val == 0) return 0;
@@ -350,28 +487,21 @@ export default {
 			queryParams: {
 				page_index: 1,
 				page_size: 10,
-				project_id: undefined,
-				box_id: undefined,
+				project_name: undefined,
+				box_name: undefined,
 				online_status: undefined,
 				run_mode: undefined
 			},
 			total: 0,
 			dataSource: [], // 数据源
 			tableData: [],
-			mergeColums: ['projectName', 'boxName', 'runStatus'],
-			runStatMap: {
-				0: { label: '离线', color: 'lightgray' },
-				1: { label: '在线', color: 'yellowgreen' },
-				2: { label: '未关联', color: 'lightgray' }
-			},
-			runModeMap: {
-				0: { label: '无', color: 'lightgray' },
-				1: { label: '制冷', color: 'darkgreen' },
-				2: { label: '采暖', color: 'orange' },
-				3: { label: '除湿', color: 'rgb(182, 172, 195)' },
-				4: { label: '预热', color: 'yellow' },
-				5: { label: '通风', color: 'rgb(182, 174, 227)' }
-			},
+			argsList: [],
+			mergeColums: ['projectName', 'boxName', 'runStatus', 'runMode'],
+			runStatMap: runStatDict,
+			runModeMap: runModeDict,
+			connectStatMap: connectStatDict,
+			hexPumpStatMap: hexPumpStatDict,
+			faultMap: faultDict,
 			runStatOptions: [ 
 				{ label:'在线', value:'1' }, 
 				{ label:'离线', value:'0' },
@@ -383,49 +513,64 @@ export default {
 				{ label:'预热', value:'4' },
 				{ label:'通风', value:'5' },
 			],
-			connectStatMap: {
-				0: { label: '异常', color: 'yellow' },
-				1: { label: '正常', color: 'yellowgreen' }
-			},
 			dialog: {
 				show: false,
 				title: '',
 				tableColumns: [],
-				tableData: [],
-				totalWidth: 0
-			}
+				tableData: []
+			},
+			loading: false
 		};
 	},
 	created() {
 		this.getProjects();
+		this.getArgsList();
 	},
 	methods: {
 		/** 获取项目列表 */
 		getProjects() {
 			var params = {
-				page_index: Math.max(0, this.queryParams.page_index - 1),
+				page_index: this.queryParams.page_index,
 				page_size: this.queryParams.page_size,
-				project_id: this.queryParams.project_id,
-				box_id: this.queryParams.box_id,
+				project_name: this.queryParams.project_name,
+				box_name: this.queryParams.box_name,
 				online_status: this.queryParams.online_status,
 				run_mode: this.queryParams.run_mode
 			};
-			axios
-				.post('http://47.110.15.40:9001/proj/reportList', params)
-				.then(resp => {
-					let data = resp.data || {};
-					let result = data.result || {};
-					this.total = result.count;
-					this.dataSource = result.data || [];
-					// 展开所有项目下的设备及单机
-					this.tableData = expandAllNodes(this.dataSource);
-					let dataSolve = mergeTableRows(this.tableData, this.mergeColums);
-					this.tableData = dataSolve;
-					console.log('>>> tableData: ', this.tableData);
-				})
-				.catch(err => {
-					console.log('请求错误: ', err);
-				});
+			listProjects(params).then(resp => {
+				let data = resp.data || {};
+				let result = data.result || {};
+				this.total = result.count;
+				this.dataSource = result.data || [];
+				// 展开所有项目下的设备及单机
+				this.tableData = expandAllNodes(this.dataSource);
+				let dataSolve = mergeTableRows(this.tableData, this.mergeColums);
+				this.tableData = dataSolve;
+				console.log('>>> tableData: ', this.tableData);
+			}).catch(err => {
+				console.log('[reportList]请求错误: ', err);
+			});
+		},
+		/** 获取指标数据 */
+		getArgsList() {
+			listArgs().then(resp => {
+				let data = resp.data || {};
+				this.argsList = data.result || [];
+			}).catch(err => {
+				console.log('[args/list]请求错误: ', err);
+			});
+		},
+		/** 计算颜色 */
+		getArgsColor(row, prop, action, threshold) {
+			if (action === ">") {
+				return row[prop] > threshold ? 'yellow' : "yellowgreen";
+			} else if (action === "<") {
+				return row[prop] < threshold ? 'yellow' : "yellowgreen";
+			} else if (action === ">=") {
+				return row[prop] >= threshold ? 'yellow' : "yellowgreen";
+			} else if (action === "<=") {
+				return row[prop] <= threshold ? 'yellow' : "yellowgreen";
+			}
 		},
 		/** 数据行合并 */
 		objectSpanMethod({ row, column, rowIndex, columnIndex }) {
@@ -590,12 +735,16 @@ export default {
 		},
 		/** 搜索 */
 		handleQuery(){
-			this.queryParams.page_index = 0;
+			this.queryParams.page_index = 1;
 			this.getProjects();
 		},
 		/** 导出 */
-		handleDerive(){
-			
+		handleDerive() {
+			var that = this;
+			that.loading = true;
+			exportToExcel(function() {
+				that.loading = false;
+			});
 		},
 		/** 分页切换 */
 		handleSizeChange(val) {

+ 182 - 0
src/components/Excel/Blob.js

@@ -0,0 +1,182 @@
+/* eslint-disable */
+/* Blob.js
+ * A Blob implementation.
+ * 2014-05-27
+ *
+ * By Eli Grey, http://eligrey.com
+ * By Devin Samarin, https://github.com/eboyjr
+ * License: X11/MIT
+ *   See LICENSE.md
+ */
+
+/*global self, unescape */
+/*jslint bitwise: true, regexp: true, confusion: true, es5: true, vars: true, white: true,
+ plusplus: true */
+
+/*! @source http://purl.eligrey.com/github/Blob.js/blob/master/Blob.js */
+
+const blob = function (view) {
+  "use strict";
+
+  view.URL = view.URL || view.webkitURL;
+
+  if (view.Blob && view.URL) {
+    try {
+      new Blob;
+      return;
+    } catch (e) {}
+  }
+
+  // Internally we use a BlobBuilder implementation to base Blob off of
+  // in order to support older browsers that only have BlobBuilder
+  var BlobBuilder = view.BlobBuilder || view.WebKitBlobBuilder || view.MozBlobBuilder || (function(view) {
+    var
+      get_class = function(object) {
+        return Object.prototype.toString.call(object).match(/^\[object\s(.*)\]$/)[1];
+      }
+      , FakeBlobBuilder = function BlobBuilder() {
+        this.data = [];
+      }
+      , FakeBlob = function Blob(data, type, encoding) {
+        this.data = data;
+        this.size = data.length;
+        this.type = type;
+        this.encoding = encoding;
+      }
+      , FBB_proto = FakeBlobBuilder.prototype
+      , FB_proto = FakeBlob.prototype
+      , FileReaderSync = view.FileReaderSync
+      , FileException = function(type) {
+        this.code = this[this.name = type];
+      }
+      , file_ex_codes = (
+        "NOT_FOUND_ERR SECURITY_ERR ABORT_ERR NOT_READABLE_ERR ENCODING_ERR "
+        + "NO_MODIFICATION_ALLOWED_ERR INVALID_STATE_ERR SYNTAX_ERR"
+      ).split(" ")
+      , file_ex_code = file_ex_codes.length
+      , real_URL = view.URL || view.webkitURL || view
+      , real_create_object_URL = real_URL.createObjectURL
+      , real_revoke_object_URL = real_URL.revokeObjectURL
+      , URL = real_URL
+      , btoa = view.btoa
+      , atob = view.atob
+      , ArrayBuffer = view.ArrayBuffer
+      , Uint8Array = view.Uint8Array
+    ;
+    FakeBlob.fake = FB_proto.fake = true;
+    while (file_ex_code--) {
+      FileException.prototype[file_ex_codes[file_ex_code]] = file_ex_code + 1;
+    }
+    if (!real_URL.createObjectURL) {
+      URL = view.URL = {};
+    }
+    URL.createObjectURL = function(blob) {
+      var
+        type = blob.type
+        , data_URI_header
+      ;
+      if (type === null) {
+        type = "application/octet-stream";
+      }
+      if (blob instanceof FakeBlob) {
+        data_URI_header = "data:" + type;
+        if (blob.encoding === "base64") {
+          return data_URI_header + ";base64," + blob.data;
+        } else if (blob.encoding === "URI") {
+          return data_URI_header + "," + decodeURIComponent(blob.data);
+        } if (btoa) {
+          return data_URI_header + ";base64," + btoa(blob.data);
+        } else {
+          return data_URI_header + "," + encodeURIComponent(blob.data);
+        }
+      } else if (real_create_object_URL) {
+        return real_create_object_URL.call(real_URL, blob);
+      }
+    };
+    URL.revokeObjectURL = function(object_URL) {
+      if (object_URL.substring(0, 5) !== "data:" && real_revoke_object_URL) {
+        real_revoke_object_URL.call(real_URL, object_URL);
+      }
+    };
+    FBB_proto.append = function(data/*, endings*/) {
+      var bb = this.data;
+      // decode data to a binary string
+      if (Uint8Array && (data instanceof ArrayBuffer || data instanceof Uint8Array)) {
+        var
+          str = ""
+          , buf = new Uint8Array(data)
+          , i = 0
+          , buf_len = buf.length
+        ;
+        for (; i < buf_len; i++) {
+          str += String.fromCharCode(buf[i]);
+        }
+        bb.push(str);
+      } else if (get_class(data) === "Blob" || get_class(data) === "File") {
+        if (FileReaderSync) {
+          var fr = new FileReaderSync;
+          bb.push(fr.readAsBinaryString(data));
+        } else {
+          // async FileReader won't work as BlobBuilder is sync
+          throw new FileException("NOT_READABLE_ERR");
+        }
+      } else if (data instanceof FakeBlob) {
+        if (data.encoding === "base64" && atob) {
+          bb.push(atob(data.data));
+        } else if (data.encoding === "URI") {
+          bb.push(decodeURIComponent(data.data));
+        } else if (data.encoding === "raw") {
+          bb.push(data.data);
+        }
+      } else {
+        if (typeof data !== "string") {
+          data += ""; // convert unsupported types to strings
+        }
+        // decode UTF-16 to binary string
+        bb.push(unescape(encodeURIComponent(data)));
+      }
+    };
+    FBB_proto.getBlob = function(type) {
+      if (!arguments.length) {
+        type = null;
+      }
+      return new FakeBlob(this.data.join(""), type, "raw");
+    };
+    FBB_proto.toString = function() {
+      return "[object BlobBuilder]";
+    };
+    FB_proto.slice = function(start, end, type) {
+      var args = arguments.length;
+      if (args < 3) {
+        type = null;
+      }
+      return new FakeBlob(
+        this.data.slice(start, args > 1 ? end : this.data.length)
+        , type
+        , this.encoding
+      );
+    };
+    FB_proto.toString = function() {
+      return "[object Blob]";
+    };
+    FB_proto.close = function() {
+      this.size = this.data.length = 0;
+    };
+    return FakeBlobBuilder;
+  }(view));
+
+  view.Blob = function Blob(blobParts, options) {
+    var type = options ? (options.type || "") : "";
+    var builder = new BlobBuilder();
+    if (blobParts) {
+      for (var i = 0, len = blobParts.length; i < len; i++) {
+        builder.append(blobParts[i]);
+      }
+    }
+    return builder.getBlob(type);
+  };
+}
+// exports.blob = blob;
+module.exports = {
+	blob: blob
+};

+ 270 - 0
src/components/Excel/Export2Excel.js

@@ -0,0 +1,270 @@
+/* eslint-disable */
+require('script-loader!file-saver'); //For saving files
+require('./Blob.js'); //For binary conversion
+require('script-loader!xlsx/dist/xlsx.core.min'); //xlsx core
+
+function generateArray(table) {
+	var out = [];
+	var rows = table.querySelectorAll('tr');
+	var ranges = [];
+	for (var R = 0; R < rows.length; ++R) {
+		var outRow = [];
+		var row = rows[R];
+		var columns = row.querySelectorAll('td');
+		for (var C = 0; C < columns.length; ++C) {
+			var cell = columns[C];
+			var colspan = cell.getAttribute('colspan');
+			var rowspan = cell.getAttribute('rowspan');
+			var cellValue = cell.innerText;
+			if (cellValue !== "" && cellValue == +cellValue) cellValue = +cellValue;
+
+			//Skip ranges
+			ranges.forEach(function(range) {
+				if (R >= range.s.r && R <= range.e.r && outRow.length >= range.s.c && outRow.length <= range.e
+					.c) {
+					for (var i = 0; i <= range.e.c - range.s.c; ++i) outRow.push(null);
+				}
+			});
+
+			//Handle Row Span
+			if (rowspan || colspan) {
+				rowspan = rowspan || 1;
+				colspan = colspan || 1;
+				ranges.push({
+					s: {
+						r: R,
+						c: outRow.length
+					},
+					e: {
+						r: R + rowspan - 1,
+						c: outRow.length + colspan - 1
+					}
+				});
+			};
+
+			//Handle Value
+			outRow.push(cellValue !== "" ? cellValue : null);
+
+			//Handle Colspan
+			if (colspan)
+				for (var k = 0; k < colspan - 1; ++k) outRow.push(null);
+		}
+		out.push(outRow);
+	}
+	return [out, ranges];
+};
+
+function datenum(v, date1904) {
+	if (date1904) v += 1462;
+	var epoch = Date.parse(v);
+	return (epoch - new Date(Date.UTC(1899, 11, 30))) / (24 * 60 * 60 * 1000);
+}
+
+function sheet_from_array_of_arrays(data, opts) {
+	var ws = {};
+	var range = {
+		s: {
+			c: 10000000,
+			r: 10000000
+		},
+		e: {
+			c: 0,
+			r: 0
+		}
+	};
+	for (var R = 0; R != data.length; ++R) {
+		for (var C = 0; C != data[R].length; ++C) {
+			if (range.s.r > R) range.s.r = R;
+			if (range.s.c > C) range.s.c = C;
+			if (range.e.r < R) range.e.r = R;
+			if (range.e.c < C) range.e.c = C;
+			var cell = {
+				v: data[R][C]
+			};
+			if (cell.v == null) continue;
+			var cell_ref = XLSX.utils.encode_cell({
+				c: C,
+				r: R
+			});
+
+			if (typeof cell.v === 'number') cell.t = 'n';
+			else if (typeof cell.v === 'boolean') cell.t = 'b';
+			else if (cell.v instanceof Date) {
+				cell.t = 'n';
+				cell.z = XLSX.SSF._table[14];
+				cell.v = datenum(cell.v);
+			} else cell.t = 's';
+
+			ws[cell_ref] = cell;
+		}
+	}
+	if (range.s.c < 10000000) ws['!ref'] = XLSX.utils.encode_range(range);
+	return ws;
+}
+
+function Workbook() {
+	if (!(this instanceof Workbook)) return new Workbook();
+	this.SheetNames = [];
+	this.Sheets = {};
+}
+
+function s2ab(s) {
+	var buf = new ArrayBuffer(s.length);
+	var view = new Uint8Array(buf);
+	for (var i = 0; i != s.length; ++i) view[i] = s.charCodeAt(i) & 0xFF;
+	return buf;
+}
+
+export function export_table_to_excel(id) {
+	var theTable = document.getElementById(id);
+	var oo = generateArray(theTable);
+	var ranges = oo[1];
+
+	/* original data */
+	var data = oo[0];
+	var ws_name = "SheetJS";
+	// console.log(data);
+
+	var wb = new Workbook(),
+		ws = sheet_from_array_of_arrays(data);
+
+	/* add ranges to worksheet */
+	// ws['!cols'] = ['apple', 'banan'];
+	ws['!merges'] = ranges;
+
+	/* add worksheet to workbook */
+	wb.SheetNames.push(ws_name);
+	wb.Sheets[ws_name] = ws;
+
+	var wbout = XLSX.write(wb, {
+		bookType: 'xlsx',
+		bookSST: false,
+		type: 'binary'
+	});
+
+	saveAs(new Blob([s2ab(wbout)], {
+		type: "application/octet-stream"
+	}), "test.xlsx")
+}
+
+function formatJson(jsonData) {
+	console.log(jsonData)
+}
+
+export function export_json_to_excel(th, jsonData, defaultTitle) {
+
+	var data = jsonData;
+
+	// 添加标题
+	for (var i = 0; i < th.length; i++) {
+		data[i].unshift([th[i]])
+	}
+
+	// 这里是定义sheet的名称 有几个sheet就加几个
+	var ws_name = [];
+	th.forEach(item => {
+		ws_name.push(item);
+	});
+
+	var wb = new Workbook(),
+		ws = [];
+	//数据转换
+	for (var j = 0; j < th.length; j++) {
+		ws.push(sheet_from_array_of_arrays(data[j]))
+	}
+
+	//生成多个sheet
+	for (var k = 0; k < th.length; k++) {
+		wb.SheetNames.push(ws_name[k])
+		wb.Sheets[ws_name[k]] = ws[k]
+	}
+
+	var wbout = XLSX.write(wb, {
+		bookType: 'xlsx',
+		bookSST: false,
+		type: 'binary'
+	});
+
+	var title = defaultTitle || '列表'
+	saveAs(new Blob([s2ab(wbout)], {
+		type: "application/octet-stream"
+	}), title + ".xlsx");
+}
+
+export function export_json_to_excel_merges({
+	multiHeader = [],
+	header,
+	data,
+	filename,
+	merges = [],
+	autoWidth = true,
+	bookType = 'xlsx'
+} = {}) {
+	filename = filename || 'excel-list'
+	data = [...data]
+	data.unshift(header)
+
+	for (let i = multiHeader.length - 1; i > -1; i--) {
+		data.unshift(multiHeader[i])
+	}
+
+	var wsName = 'SheetJS'
+	var wb = new Workbook()
+	var ws = sheetFromArrayOfArrays(data)
+
+	if (merges.length > 0) {
+		// ws[!merges]:存放一些单元格合并信息,是一个数组,每个数组由包含s和e构成的对象组成,s表示开始,e表示结束,r表示行,c表示列
+		if (!ws['!merges']) ws['!merges'] = []
+		merges.forEach(item => {
+			ws['!merges'].push(XLSX.utils.decode_range(item))
+		})
+	}
+
+	if (autoWidth) {
+		/* 设置worksheet每列的最大宽度 */
+		const colWidth = data.map(row => row.map(val => {
+			/* 先判断是否为null/undefined */
+			if (val == null) {
+				return {
+					'wch': 10
+				}
+			} else if (val.toString().charCodeAt(0) > 255) {
+				/* 再判断是否为中文 */
+				return {
+					'wch': val.toString().length * 2
+				}
+			} else {
+				return {
+					'wch': val.toString().length
+				}
+			}
+		}))
+		/* 以第一行为初始值 */
+		let result = colWidth[0]
+		for (let i = 1; i < colWidth.length; i++) {
+			for (let j = 0; j < colWidth[i].length; j++) {
+				if (result[j] && result[j]['wch'] < colWidth[i][j]['wch']) {
+					result[j]['wch'] = colWidth[i][j]['wch']
+				}
+			}
+		}
+		// ws['!cols']设置单元格宽度, [{'wch': 10},{'wch': 10}] ===> 第一列和第二列设置了宽度
+		ws['!cols'] = result
+	}
+
+	/* add worksheet to workbook */
+	wb.SheetNames.push(wsName)
+	wb.Sheets[wsName] = ws
+
+	var wbout = XLSX.write(wb, {
+		bookType: bookType,
+		bookSST: false, // 是否生成Shared String Table,官方解释是,如果开启生成速度会下降,但在低版本IOS设备上有更好的兼容性
+		type: 'binary'
+	})
+	saveAs(
+		new Blob([s2ab(wbout)], {
+			type: 'application/octet-stream'
+		}),
+		`${filename}.${bookType}`
+	)
+}

+ 9 - 0
src/js/api.js

@@ -0,0 +1,9 @@
+import axios from 'axios'
+
+export function listProjects(queryParams) {
+	return axios.post('http://47.110.15.40:9001/proj/reportList', queryParams)
+}
+
+export function listArgs() {
+	return axios.get('http://47.110.15.40:9001/proj/args/list')
+}

+ 74 - 0
src/js/common.js

@@ -0,0 +1,74 @@
+export const runStatDict = {
+	0: {
+		label: '离线',
+		color: 'lightgray'
+	},
+	1: {
+		label: '在线',
+		color: 'yellowgreen'
+	},
+	2: {
+		label: '未关联',
+		color: 'lightgray'
+	}
+}
+
+export const runModeDict = {
+	0: {
+		label: '无',
+		color: 'lightgray'
+	},
+	1: {
+		label: '制冷',
+		color: 'darkgreen'
+	},
+	2: {
+		label: '采暖',
+		color: 'orange'
+	},
+	3: {
+		label: '除湿',
+		color: 'rgb(182, 172, 195)'
+	},
+	4: {
+		label: '预热',
+		color: 'yellow'
+	},
+	5: {
+		label: '通风',
+		color: 'rgb(182, 174, 227)'
+	}
+}
+
+export const connectStatDict = {
+	0: {
+		label: '异常',
+		color: 'yellow'
+	},
+	1: {
+		label: '正常',
+		color: 'yellowgreen'
+	}
+}
+
+export const faultDict = {
+	0: {
+		label: '无',
+		color: 'yellowgreen'
+	},
+	1: {
+		label: '有',
+		color: 'yellow'
+	}
+}
+
+export const hexPumpStatDict = {
+	0: {
+		label: '停止',
+		color: 'yellow'
+	},
+	1: {
+		label: '运行',
+		color: 'yellowgreen'
+	}
+}

+ 133 - 0
src/js/export.js

@@ -0,0 +1,133 @@
+import { Export2Excel } from './export_excel.js'
+import { listProjects } from './api'
+import { runStatDict, runModeDict, connectStatDict, faultDict, hexPumpStatDict } from './common.js'
+
+var config = {
+	rowDatas: [],  // 数据
+	page: 1,  // 当前页
+	load: 0,  // 已加载数量
+}
+
+function reset() {
+	config.rowDatas = []
+	config.page = 1
+	config.load = 0
+}
+
+export function exportToExcel(fn) {
+	reset()
+	_listAllProjects(function(finished) {
+		let defaultFilename = 'LINKSEE-智能报表';
+		let tableDatas = [[
+			'项目名称', '盒子名称', '在线状态', '运行模式', 
+			'编号', '通讯状态', '故障', '供水水温(℃)', '回水水温(℃)', '温差',
+			'编号', '通讯状态', '故障', '送风温度(℃)', '送风湿度(%)', '露点(℃)',
+			'编号', '通讯状态', '故障', '供水水温(℃)', '回水水温(℃)', '温差', '水泵状态',
+			'编号', '通讯异常数量', '最低室内温度(℃)', '最低室内湿度(%)', '最低室内露点(℃)', 
+			'最高室内温度(℃)', '最高室内湿度(%)', '最高室内露点(℃)'
+		]]
+		config.rowDatas.forEach(row => {
+			tableDatas.push(row)
+		})
+		Export2Excel([], [tableDatas], defaultFilename)
+		fn()
+	})
+}
+
+function _listAllProjects(fn) {
+	let queryParams = { page_index: config.page, page_size: 20 }
+	listProjects(queryParams).then(resp => {
+		let result = resp.data.result || {}
+		let datas = result.data || []
+		let total = result.count
+		config.load += datas.length;
+		if (reset <= 1) {
+			config.rowDatas = _transferDataToXlsx(datas)
+		} else {
+			config.rowDatas = config.rowDatas.concat(_transferDataToXlsx(datas))
+		}
+		// if (config.load < total && datas.length > 0) {
+		if (config.page > 1) {
+			setTimeout(() => {
+				config.page += 1
+				_listAllProjects(fn)
+			}, 50)
+		} else {
+			fn(true)
+		}
+	})
+}
+
+function _transferDataToXlsx(projects) {
+	let tableData = []
+	projects.forEach(p => {
+		if (Array.isArray(p.boxList)) {
+			p.boxList.forEach(b => {
+				// 继续判断有多少单机,主机、新风机、换热站、末端单机数量取最大值
+				let maxCnt = _calcMaxStandaloneCnt(b)
+				for (let i = 0; i < maxCnt; i++) {
+					let hpStandalone = b.hostCtrl && i < b.hostCtrl.length ? b.hostCtrl[i] : undefined
+					let dhStandalone = b.newTrendCtrl && i < b.newTrendCtrl.length ? b.newTrendCtrl[i] : undefined
+					let hexStandalone = b.hex && i < b.hex.length ? b.hex[i] : undefined
+					let cbStandalone = b.end && i < b.end.length ? b.end[i] : undefined
+					let row = [
+						p.projectName,
+						b.boxName,
+						runStatDict[b.runStatus] ? runStatDict[b.runStatus].label : '/',
+						runModeDict[b.runMode] ? runModeDict[b.runMode].label : '/',
+						hpStandalone ? hpStandalone.host_order : '',
+						hpStandalone ? _getConnectState(hpStandalone.host_connect_status) : '',
+						hpStandalone ? _getFault(hpStandalone.host_fault) : '',
+						hpStandalone ? hpStandalone.host_su_temp : '',
+						hpStandalone ? hpStandalone.host_re_temp : '',
+						hpStandalone ? hpStandalone.host_temp_diff : '',
+						dhStandalone ? dhStandalone.nt_order : '',
+						dhStandalone ? _getConnectState(dhStandalone.nt_connect_status) : '',
+						dhStandalone ? _getFault(dhStandalone.nt_fault) : '',
+						dhStandalone ? dhStandalone.nt_in_temp : '',
+						dhStandalone ? dhStandalone.nt_in_humidity : '',
+						dhStandalone ? dhStandalone.nt_dew_point : '',
+						hexStandalone ? hexStandalone.hex_order : '',
+						hexStandalone ? _getConnectState(hexStandalone.hex_connect_status) : '',
+						hexStandalone ? _getFault(hexStandalone.hex_fault) : '',
+						hexStandalone ? hexStandalone.hex_su_temp : '',
+						hexStandalone ? hexStandalone.hex_re_temp : '',
+						hexStandalone ? hexStandalone.hex_temp_diff : '',
+						hexStandalone ? _getHexPumpState(hexStandalone.hex_pump_status) : '',
+						cbStandalone ? cbStandalone.end_order : '',
+						cbStandalone ? cbStandalone.end_exception_num : '',
+						cbStandalone ? cbStandalone.end_min_temp : '',
+						cbStandalone ? cbStandalone.end_min_humidity : '',
+						cbStandalone ? cbStandalone.end_min_dew_point : '',
+						cbStandalone ? cbStandalone.end_max_temp : '',
+						cbStandalone ? cbStandalone.end_max_humidity : '',
+						cbStandalone ? cbStandalone.end_max_dew_point : '',
+					]
+					tableData.push(row)
+				}
+			})
+		}
+	})
+	return tableData
+}
+
+// 取最大单机数
+function _calcMaxStandaloneCnt(box) {
+	let hpStandaloneCnt = box.hostCtrl ? box.hostCtrl.length : 0
+	let dhStandaloneCnt = box.newTrendCtrl ? box.newTrendCtrl.length : 0
+	let hexStandaloneCnt = box.hex ? box.hex.length : 0
+	let cbStandalineCnt = box.end ? box.end.length : 0
+	return Math.max(hpStandaloneCnt, dhStandaloneCnt, hexStandaloneCnt, cbStandalineCnt)
+}
+
+function _getConnectState(val) {
+	return connectStatDict[val] ? connectStatDict[val].label : '/'
+}
+
+function _getFault(val) {
+	return faultDict[val] ? faultDict[val].label : '/'
+}
+
+function _getHexPumpState(val) {
+	return hexPumpStatDict[val] ? hexPumpStatDict[val].label : '/'
+}

+ 6 - 0
src/js/export_excel.js

@@ -0,0 +1,6 @@
+export function Export2Excel(columns, list, title) {
+  require.ensure([], () => {
+    const { export_json_to_excel } = require('../components/Excel/Export2Excel');
+    export_json_to_excel(columns, list, title);
+  })
+}

+ 39 - 35
src/dataHandler.js

@@ -48,11 +48,7 @@ export function mergeTableRows(data, merge) {
 			if (mList[rowVal] && mList[rowVal].newIndex === index) {
 				let flag = false
 				let mergeSolve = merge.slice(0, spliceLocation)
-				// console.log(">>> mergeSolve: ", mergeSolve)
 				mergeSolve.slice(0, spliceLocation).forEach(mergeItem => {
-					// console.log(">>> mergeItem: ", mergeItem)
-					// console.log(">>> data[index][mergeItem]: ", data[index][mergeItem])
-					// console.log(">>> data[index - 1][mergeItem]: ", data[index - 1][mergeItem])
 					if (data[index][mergeItem] == data[index - 1][mergeItem]) {
 						flag = true
 					}
@@ -110,40 +106,48 @@ function _calcMaxStandaloneCnt(box) {
 // 计算每条记录末端所占的行数
 function _getStandaloneSpanRows(tableData) {
 	tableData.map((td, index) => {
-		let previous = tableData[index - 1]
-		// 主机合并
-		if (td.maxCnt > 1 && td.hpCnt < td.maxCnt && previous.hpSpanRows < 2) {
-			td.hpSpanRows = td.maxCnt
-		} else if (index > 0 && previous.hpSpanRows > 1) {
-			td.hpSpanRows = 0
+		if (index > 0) {
+			let previous = tableData[index - 1]
+			// 主机合并
+			if (td.maxCnt > 1 && td.hpCnt < td.maxCnt && previous.hpSpanRows < 2) {
+				td.hpSpanRows = td.maxCnt
+			} else if (index > 0 && previous.hpSpanRows > 1) {
+				td.hpSpanRows = 0
+			} else {
+				td.hpSpanRows = 1
+			}
+			// 新风机合并
+			if (td.maxCnt > 1 && td.dhCnt < td.maxCnt && previous.dhSpanRows < 2) {
+				td.dhSpanRows = td.maxCnt
+			} else if (index > 0 && previous.dhSpanRows > 1) {
+				td.dhSpanRows = 0
+			} else {
+				td.dhSpanRows = 1
+			}
+			// 换热站合并
+			if (td.maxCnt > 1 && td.hexCnt < td.maxCnt && previous.hexSpanRows < 2) {
+				td.hexSpanRows = td.maxCnt
+			} else if (index > 0 && previous.hexSpanRows > 1) {
+				td.hexSpanRows = 0
+			} else {
+				td.hexSpanRows = 1
+			}
+			// 末端合并
+			if (td.maxCnt > 1 && previous.cbSpanRows < 2) {
+				td.cbSpanRows = td.maxCnt
+			} else if (index > 0 && previous.cbSpanRows > 1) {
+				td.cbSpanRows = 0
+			} else {
+				td.cbSpanRows = 1
+			}
+			tableData[index] = td
 		} else {
-			td.hpSpanRows = 1
-		}
-		// 新风机合并
-		if (td.maxCnt > 1 && td.dhCnt < td.maxCnt && previous.dhSpanRows < 2) {
-			td.dhSpanRows = td.maxCnt
-		} else if (index > 0 && previous.dhSpanRows > 1) {
-			td.dhSpanRows = 0
-		} else {
-			td.dhSpanRows = 1
-		}
-		// 换热站合并
-		if (td.maxCnt > 1 && td.hexCnt < td.maxCnt && previous.hexSpanRows < 2) {
-			td.hexSpanRows = td.maxCnt
-		} else if (index > 0 && previous.hexSpanRows > 1) {
-			td.hexSpanRows = 0
-		} else {
-			td.hexSpanRows = 1
-		}
-		// 末端合并
-		if (td.maxCnt > 1 && previous.cbSpanRows < 2) {
+			td.hpSpanRows = (td.maxCnt > 1 && td.hpCnt < td.maxCnt) ? td.maxCnt : 1
+			td.dhSpanRows = (td.maxCnt > 1 && td.dhCnt < td.maxCnt) ? td.maxCnt : 1
+			td.hexSpanRows = (td.maxCnt > 1 && td.hexCnt < td.maxCnt) ? td.maxCnt : 1
 			td.cbSpanRows = td.maxCnt
-		} else if (index > 0 && previous.cbSpanRows > 1) {
-			td.cbSpanRows = 0
-		} else {
-			td.cbSpanRows = 1
+			tableData[index] = td
 		}
-		tableData[index] = td
 		return td
 	})
 	return tableData