calc.vue 50 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618
  1. <template>
  2. <div class="app-container">
  3. <!-- 头部操作区域 -->
  4. <el-card style="margin-bottom: 15px;">
  5. <el-row :gutter="15" align="middle">
  6. <!-- 时间选择 -->
  7. <el-col :span="4">
  8. <div style="display: flex; align-items: center; justify-content: center;">
  9. <el-button icon="ArrowLeft" @click="navigateDay(-1)" style="margin-right: 10px;"></el-button>
  10. <el-date-picker clearable
  11. v-model="queryParams.workDay"
  12. type="date"
  13. value-format="YYYY-MM-DD"
  14. placeholder="请选择日期"
  15. style="width: 130px"
  16. @change="handleQuery">
  17. </el-date-picker>
  18. <el-button icon="ArrowRight" @click="navigateDay(1)" style="margin-left: 10px;"></el-button>
  19. </div>
  20. </el-col>
  21. <!-- 设备类型和参数选择 -->
  22. <el-col :span="6">
  23. <div style="display: flex; align-items: center; flex-wrap: nowrap; gap: 10px;">
  24. <span style="white-space: nowrap;">类型:</span>
  25. <el-select v-model="selectedEquipmentType" placeholder="设备类型"
  26. @change="handleEquipmentTypeChange" clearable style="flex: 1; min-width: 100px;">
  27. <el-option
  28. v-for="item in equipmentTypeOptions"
  29. :key="item.typeId"
  30. :label="item.typeName"
  31. :value="item.typeId">
  32. </el-option>
  33. </el-select>
  34. <span style="white-space: nowrap; margin-left: 10px;">参数:</span>
  35. <el-select v-model="selectedEquipmentParam" placeholder="设备参数"
  36. @change="handleEquipmentParamChange" clearable style="flex: 1; min-width: 100px;">
  37. <el-option
  38. v-for="item in equipmentParamOptions"
  39. :key="item.paraCode"
  40. :label="item.paraName"
  41. :value="item.paraCode">
  42. </el-option>
  43. </el-select>
  44. </div>
  45. </el-col>
  46. <!-- 产线选择 -->
  47. <el-col :span="4">
  48. <div style="display: flex; align-items: center;">
  49. <span style="white-space: nowrap; margin-right: 10px;">产线:</span>
  50. <el-select
  51. v-model="selectedLines"
  52. multiple
  53. placeholder="请选择产线(最多2条)"
  54. style="width: 180px;"
  55. @change="handleLineSelectionChange"
  56. >
  57. <el-option
  58. v-for="line in allLines"
  59. :key="line"
  60. :label="line + '号线'"
  61. :value="line">
  62. </el-option>
  63. </el-select>
  64. </div>
  65. </el-col>
  66. <el-col :span="10">
  67. <div v-if="selectedLines.length === 1"
  68. style="display: flex; align-items: center; flex-wrap: wrap; gap: 10px; padding: 8px; font-size: 12px;">
  69. <span style="font-weight: bold;">同产线设备对比:</span>
  70. <span>{{ selectedLines[0] }}#产线</span>
  71. <el-select
  72. v-model="selectedMetrics[selectedLines[0] + '_0']"
  73. placeholder="设备1"
  74. size="small"
  75. @change="updateQueryData"
  76. style="width: 150px;"
  77. clearable>
  78. <el-option
  79. v-for="item in equipmentList[selectedLines[0]]"
  80. :key="item.id"
  81. :label="item.name"
  82. :value="item.id">
  83. </el-option>
  84. </el-select>
  85. <span>VS</span>
  86. <el-select
  87. v-model="selectedMetrics[selectedLines[0] + '_1']"
  88. placeholder="设备2"
  89. size="small"
  90. @change="updateQueryData"
  91. style="width: 150px;"
  92. clearable>
  93. <el-option
  94. v-for="item in equipmentList[selectedLines[0]]"
  95. :key="item.id"
  96. :label="item.name"
  97. :value="item.id">
  98. </el-option>
  99. </el-select>
  100. </div>
  101. <!-- 跨产线设备对比(选择两条产线时) -->
  102. <div v-if="selectedLines.length === 2"
  103. style="display: flex; align-items: center; flex-wrap: wrap; gap: 10px; padding: 8px; font-size: 12px;">
  104. <template v-for="(line, index) in selectedLines" :key="line">
  105. <div v-if="index > 0" style="width: 1px; height: 20px; background-color: #dcdfe6; margin: 0 5px;"></div>
  106. <span style="font-weight: bold;">{{ index === 0 ? '产线A' : '产线B' }}:</span>
  107. <span>{{ line }}#产线</span>
  108. <el-select
  109. v-model="selectedMetrics[line]"
  110. placeholder="请选择设备"
  111. size="small"
  112. @change="updateQueryData"
  113. style="width: 150px;"
  114. clearable>
  115. <el-option
  116. v-for="item in equipmentList[line]"
  117. :key="item.id"
  118. :label="item.name"
  119. :value="item.id">
  120. </el-option>
  121. </el-select>
  122. </template>
  123. </div>
  124. </el-col>
  125. </el-row>
  126. </el-card>
  127. <!-- 显著性分析结果展示区域 -->
  128. <el-card style="margin-bottom: 15px;" v-if="significantData && significantData.length > 0">
  129. <div slot="header">
  130. <span>Mann-Whitney U检验结果</span>
  131. </div>
  132. <!-- 折叠面板 -->
  133. <el-collapse v-model="activeCollapseNames">
  134. <el-collapse-item name="significance-results">
  135. <template #title>
  136. <span style="font-weight: bold;">显著性分析摘要</span>
  137. <el-tag size="mini" style="margin-left: 10px;">{{ significantData.length }} 个显著差异</el-tag>
  138. </template>
  139. <!-- 统计说明 -->
  140. <el-alert
  141. title="统计说明"
  142. description="P值:表示两个设备参数分布差异的显著性概率,P<0.05认为差异显著;U值:Mann-Whitney U检验统计量,反映两组数据的秩和差异"
  143. type="info"
  144. show-icon
  145. :closable="false"
  146. style="margin-bottom: 10px;"
  147. />
  148. <!-- 详细表格 -->
  149. <el-table :data="significantData" height="300" style="width: 100%" stripe>
  150. <el-table-column prop="typeName" label="设备类型"></el-table-column>
  151. <el-table-column prop="paraName" label="参数名称"></el-table-column>
  152. <el-table-column prop="deviceName1" label="设备1"></el-table-column>
  153. <el-table-column prop="deviceName2" label="设备2"></el-table-column>
  154. <el-table-column prop="result" label="显著性结果" width="100">
  155. <template #default="scope">
  156. <el-tag :type="scope.row.result === '显著' ? 'danger' : 'success'">{{ scope.row.result }}</el-tag>
  157. </template>
  158. </el-table-column>
  159. <el-table-column label="P值" width="100">
  160. <template #default="scope">
  161. {{ scope.row.pvalue ? scope.row.pvalue.toFixed(6) : '' }}
  162. </template>
  163. </el-table-column>
  164. <el-table-column prop="uvalue" label="U值" width="100"></el-table-column>
  165. </el-table>
  166. </el-collapse-item>
  167. </el-collapse>
  168. </el-card>
  169. <!-- 折线图和表格左右布局 -->
  170. <el-row :gutter="15">
  171. <!-- 左侧折线图 -->
  172. <el-col :span="12">
  173. <el-card style="margin-bottom: 15px; height: 350px;">
  174. <div ref="chartRef0" style="width: 100%; height: 320px;"></div>
  175. </el-card>
  176. <el-card style="margin-bottom: 15px;">
  177. <el-table :data="rzLineList" height="400">
  178. <el-table-column label="小时" align="center" prop="hour" width="60"/>
  179. <el-table-column label="设备" align="center" prop="deviceName" width="360"/>
  180. <el-table-column :label="getTableColumnLabel(rzLineList)" align="center">
  181. <template #default="scope">
  182. {{ scope.row.paraValue }}{{ scope.row.updatedBy ? ' (' + scope.row.updatedBy + ')' : '' }}
  183. </template>
  184. </el-table-column>
  185. </el-table>
  186. </el-card>
  187. </el-col>
  188. <!-- 右侧表格 -->
  189. <el-col :span="12">
  190. <el-card style="margin-bottom: 15px; height: 350px;">
  191. <div ref="chartRef1" style="width: 100%; height: 320px;"></div>
  192. </el-card>
  193. <el-card style="margin-bottom: 15px;">
  194. <el-table :data="rzLineList1" height="400">
  195. <el-table-column label="小时" align="center" prop="hour" width="60"/>
  196. <el-table-column label="设备" align="center" prop="deviceName" width="360"/>
  197. <el-table-column :label="getTableColumnLabel(rzLineList1)" align="center">
  198. <template #default="scope">
  199. {{ scope.row.paraValue }}{{ scope.row.updatedBy ? ' (' + scope.row.updatedBy + ')' : '' }}
  200. </template>
  201. </el-table-column>
  202. </el-table>
  203. </el-card>
  204. </el-col>
  205. </el-row>
  206. <!-- 分钟级数据弹窗 -->
  207. <el-dialog
  208. :title="minuteDialogTitle"
  209. v-model="minuteDialogVisible"
  210. width="80%"
  211. :before-close="handleMinuteDialogClose"
  212. append-to-body
  213. >
  214. <el-row :gutter="15">
  215. <!-- 左侧分钟级折线图 -->
  216. <el-col :span="12">
  217. <el-card style="margin-bottom: 15px;">
  218. <div ref="minuteChartRef0" style="width: 100%; height: 400px;"></div>
  219. </el-card>
  220. <el-card style="margin-bottom: 15px;">
  221. <el-table :data="minuteTableData0" height="400">
  222. <el-table-column prop="time" label="分钟" width="100" align="center"></el-table-column>
  223. <el-table-column prop="value" :label="getMinuteTableColumnLabel(minuteDataLabel0, minuteDataUnit0)"
  224. align="center"></el-table-column>
  225. </el-table>
  226. </el-card>
  227. </el-col>
  228. <!-- 右侧分钟级折线图 -->
  229. <el-col :span="12">
  230. <el-card style="margin-bottom: 15px;">
  231. <div ref="minuteChartRef1" style="width: 100%; height: 400px;"></div>
  232. </el-card>
  233. <el-card style="margin-bottom: 15px;">
  234. <el-table :data="minuteTableData1" height="400">
  235. <el-table-column prop="time" label="分钟" width="100" align="center"></el-table-column>
  236. <el-table-column prop="value" :label="getMinuteTableColumnLabel(minuteDataLabel1, minuteDataUnit1)"
  237. align="center"></el-table-column>
  238. </el-table>
  239. </el-card>
  240. </el-col>
  241. </el-row>
  242. </el-dialog>
  243. </div>
  244. </template>
  245. <script setup name="dyeHourCalc">
  246. import * as echarts from 'echarts';
  247. import {addRzLine, delRzLine, getRzLine, updateRzLine} from "@/api/dyeing/rzLine";
  248. import {listDeviceTypes} from "@/api/dyeing/gyfx"; // 添加导入listDeviceTypes接口
  249. import {listDevice} from "@/api/dye/device";
  250. import {calcHour, listHour} from "@/api/dye/hour.js";
  251. import {nextTick, ref} from 'vue';
  252. const {proxy} = getCurrentInstance();
  253. const rzLineList = ref([]);
  254. const rzLineList1 = ref([]);
  255. const open = ref(false);
  256. const loading = ref(true);
  257. const showSearch = ref(true);
  258. const ids = ref([]);
  259. const single = ref(true);
  260. const multiple = ref(true);
  261. const total = ref(0);
  262. const title = ref("");
  263. const chartRef0 = ref(null);
  264. const chartRef1 = ref(null);
  265. let chartInstance0 = null;
  266. let chartInstance1 = null;
  267. // 分钟级数据相关变量
  268. const minuteDialogVisible = ref(false);
  269. const minuteDialogTitle = ref('');
  270. // 左侧分钟级数据
  271. const minuteChartRef0 = ref(null);
  272. let minuteChartInstance0 = null;
  273. const minuteTableData0 = ref([]);
  274. const minuteDataLabel0 = ref('');
  275. const minuteDataUnit0 = ref(''); // 左侧分钟级数据单位
  276. // 右侧分钟级数据
  277. const minuteChartRef1 = ref(null);
  278. let minuteChartInstance1 = null;
  279. const minuteTableData1 = ref([]);
  280. const minuteDataLabel1 = ref('');
  281. const minuteDataUnit1 = ref(''); // 右侧分钟级数据单位
  282. // 表格引用
  283. const tableRef0 = ref(null);
  284. const tableRef1 = ref(null);
  285. // 显著性分析相关变量
  286. const significantData = ref([]); // 存储显著性分析结果(仅显著的)
  287. const activeCollapseNames = ref([]); // 控制折叠面板展开状态
  288. const data = reactive({
  289. form: {},
  290. queryParams: {
  291. pageNum: 1,
  292. pageSize: 10000,
  293. workDay: new Date(new Date().getTime() - 24 * 60 * 60 * 1000).Format('yyyy-MM-dd'),
  294. hour: null,
  295. lines: null,
  296. openRate: null,
  297. length: null,
  298. tmp: null,
  299. speed: null,
  300. energy: null,
  301. amp: null,
  302. createdBy: null,
  303. createdTime: null,
  304. updatedBy: null,
  305. updatedTime: null,
  306. remark: null
  307. },
  308. rules: {}
  309. });
  310. const selectedEquipmentType = ref('');
  311. const selectedEquipmentParam = ref('');
  312. const selectedMetrics = ref({});
  313. // 修改默认选中的产线为空数组
  314. const selectedLines = ref([]);
  315. const equipmentList = ref({});
  316. const equipmentTypeList = ref([]);
  317. const equipmentParamOptions = ref([]);
  318. const equipmentTypeOptions = ref([]);
  319. const allLines = ref(['1', '2', '3', '4', '5', '6', '7', '8']);
  320. const oldSelectLines = ref([]);
  321. const {queryParams, form, rules} = toRefs(data);
  322. // 显示详情弹窗
  323. /* function showDetailDialog() {
  324. detailDialogVisible.value = true;
  325. } */
  326. // 按产线分组的数据
  327. const groupedRzLineList = computed(() => {
  328. const grouped = {};
  329. rzLineList.value.forEach(item => {
  330. if (!grouped[item.line]) {
  331. grouped[item.line] = [];
  332. }
  333. grouped[item.line].push(item);
  334. });
  335. // 按产线编号排序
  336. const sortedGrouped = {};
  337. Object.keys(grouped).sort().forEach(key => {
  338. sortedGrouped[key] = grouped[key];
  339. });
  340. return sortedGrouped;
  341. });
  342. // 合并单元格处理函数
  343. const spanMethod = ({row, column, rowIndex, columnIndex}) => {
  344. if (columnIndex === 0) { // 小时列
  345. // 获取当前行小时值
  346. const currentHour = row.hour;
  347. // 计算当前小时值第一次出现的位置
  348. let firstIndex = -1;
  349. for (let i = 0; i < rzLineList.value.length; i++) {
  350. if (rzLineList.value[i].hour === currentHour) {
  351. firstIndex = i;
  352. break;
  353. }
  354. }
  355. // 如果当前行是该小时值第一次出现的位置
  356. if (firstIndex === rowIndex) {
  357. // 计算该小时值连续出现的次数
  358. let spanCount = 0;
  359. for (let i = firstIndex; i < rzLineList.value.length; i++) {
  360. if (rzLineList.value[i].hour === currentHour) {
  361. spanCount++;
  362. } else {
  363. break;
  364. }
  365. }
  366. return [spanCount, 1];
  367. } else {
  368. // 如果不是第一次出现,隐藏该单元格
  369. return [0, 0];
  370. }
  371. }
  372. return [1, 1]; // 其他列不合并
  373. };
  374. function navigateDay(offset) {
  375. const currentDate = new Date(queryParams.value.workDay);
  376. currentDate.setDate(currentDate.getDate() + offset);
  377. queryParams.value.workDay = currentDate.toISOString().split('T')[0];
  378. handleQuery();
  379. }
  380. /** 查询染整线产线小时统计数据列表 */
  381. function getList() {
  382. // 初始化标记设为false
  383. window.isInitialized = false;
  384. // 先获取设备类型列表
  385. listDeviceTypes({pageSize: 10000}).then(response => {
  386. equipmentTypeList.value = response.rows;
  387. // 提取设备类型选项
  388. equipmentTypeOptions.value = response.rows.map(item => {
  389. return {
  390. typeId: item.typeId,
  391. typeName: item.typeName
  392. };
  393. });
  394. // 默认选择第一条设备类型
  395. if (response.rows.length > 0 && !selectedEquipmentType.value) {
  396. selectedEquipmentType.value = response.rows[0].typeId;
  397. // 触发设备类型变化事件,加载对应的参数
  398. handleEquipmentTypeChange(selectedEquipmentType.value);
  399. } else if (selectedEquipmentType.value) {
  400. // 如果已有设备类型,直接触发参数变化
  401. handleEquipmentTypeChange(selectedEquipmentType.value);
  402. }
  403. // 在设备类型和参数加载完成后,默认选择5号和6号产线
  404. if (selectedLines.value.length === 0) {
  405. selectedLines.value = ['5', '6'];
  406. // 获取设备列表
  407. getDeviceList();
  408. }
  409. });
  410. }
  411. function difference(arr1, arr2) {
  412. const set2 = new Set(arr2);
  413. return arr1.filter(item => !set2.has(item));
  414. }
  415. /**
  416. * 获取设备列表
  417. * @returns {void}
  418. */
  419. function getDeviceList() {
  420. // 检查是否选择了产线
  421. if (selectedLines.value.length === 0) {
  422. // 初始化时不要弹出提示,只有用户主动操作时才提示
  423. // 通过检查是否已完成初始化来判断是否是用户主动操作
  424. // isInitialized标志表示是否已完成初始化过程
  425. if (window.isInitialized) {
  426. proxy.$modal.msgWarning("请至少选择一条产线进行对比");
  427. }
  428. return;
  429. }
  430. // 只有在非初始化状态下才检查产线数量限制
  431. if (window.isInitialized && selectedLines.value.length > 2) {
  432. let tmp = difference(selectedLines.value, oldSelectLines.value);
  433. selectedLines.value = selectedLines.value.filter(item => !tmp.includes(item));
  434. oldSelectLines.value = JSON.parse(JSON.stringify(selectedLines.value));
  435. proxy.$modal.msgWarning("最多只能选择两条产线进行对比");
  436. return;
  437. }
  438. // 处理取消选择产线的情况(仅在非初始化状态下)
  439. if (window.isInitialized && selectedLines.value.length < oldSelectLines.value.length) {
  440. // 取消了一条产线
  441. let tmp = difference(oldSelectLines.value, selectedLines.value);
  442. // 清除取消产线的相关设备选择
  443. if (selectedMetrics.value[tmp[0]]) {
  444. selectedMetrics.value[tmp[0]] = '';
  445. }
  446. // 如果是单一产线情况,清除相关的设备选择
  447. if (selectedMetrics.value[tmp[0] + '_0']) selectedMetrics.value[tmp[0] + '_0'] = '';
  448. if (selectedMetrics.value[tmp[0] + '_1']) selectedMetrics.value[tmp[0] + '_1'] = '';
  449. updateQueryData();
  450. return;
  451. }
  452. // 检查是否选择了设备类型和参数(即使在初始化过程中也需要检查)
  453. if (!selectedEquipmentType.value) {
  454. // 初始化过程中不提示错误
  455. return;
  456. }
  457. if (!selectedEquipmentParam.value) {
  458. // 初始化过程中不提示错误
  459. return;
  460. }
  461. // 控制是否需要更新数据
  462. let needUpdateData = false;
  463. let loadedCount = 0;
  464. const totalLines = selectedLines.value.length;
  465. // 为所有选中的产线获取设备列表
  466. selectedLines.value.forEach((line) => {
  467. listDevice({
  468. pageSize: 10000,
  469. pageNum: 1,
  470. paraCode: selectedEquipmentParam.value,
  471. typeId: selectedEquipmentType.value,
  472. line: line
  473. }).then(response => {
  474. // 提取设备参数选项
  475. equipmentList.value[line] = response.rows.map(item => {
  476. return {
  477. id: item.deviceId,
  478. name: item.deviceName
  479. };
  480. });
  481. // 默认选中第一台设备
  482. if (response.rows.length > 0) {
  483. // 如果是单条产线情况,设置同产线设备对比的第一台设备
  484. if (selectedLines.value.length === 1) {
  485. const lineKey = selectedLines.value[0];
  486. if (!selectedMetrics.value[lineKey + '_0']) {
  487. selectedMetrics.value[lineKey + '_0'] = response.rows[0].deviceId;
  488. needUpdateData = true;
  489. }
  490. }
  491. // 如果是两条产线情况,设置对应产线的第一台设备
  492. else if (selectedLines.value.length === 2) {
  493. if (!selectedMetrics.value[line]) {
  494. selectedMetrics.value[line] = response.rows[0].deviceId;
  495. needUpdateData = true;
  496. }
  497. }
  498. }
  499. loadedCount++;
  500. // 只有当所有设备列表都加载完成后再决定是否更新数据
  501. if (loadedCount === totalLines) {
  502. oldSelectLines.value = JSON.parse(JSON.stringify(selectedLines.value));
  503. // 如果需要更新数据或者已经选择了产线和设备,检查是否所有产线都已设置设备
  504. if (needUpdateData || (selectedLines.value.length >= 1 && Object.keys(selectedMetrics.value).length > 0)) {
  505. let allLinesHaveDevices = true;
  506. if (selectedLines.value.length === 1) {
  507. const lineKey = selectedLines.value[0];
  508. if (!selectedMetrics.value[lineKey + '_0']) {
  509. allLinesHaveDevices = false;
  510. }
  511. } else if (selectedLines.value.length === 2) {
  512. for (const line of selectedLines.value) {
  513. if (!selectedMetrics.value[line]) {
  514. allLinesHaveDevices = false;
  515. break;
  516. }
  517. }
  518. }
  519. // 如果所有产线都已设置设备,则更新数据
  520. // 只有在所有线路都有设备时才更新数据,避免重复调用
  521. if (allLinesHaveDevices) {
  522. // 防止重复调用
  523. clearTimeout(window.updateQueryDataTimer);
  524. window.updateQueryDataTimer = setTimeout(() => {
  525. updateQueryData();
  526. }, 100);
  527. }
  528. // 当needUpdateData为true时才调用updateQueryData
  529. else if (needUpdateData) {
  530. // 防止重复调用
  531. clearTimeout(window.updateQueryDataTimer);
  532. window.updateQueryDataTimer = setTimeout(() => {
  533. updateQueryData();
  534. }, 100);
  535. }
  536. }
  537. // 标记初始化完成
  538. if (!window.isInitialized) {
  539. window.isInitialized = true;
  540. }
  541. }
  542. }).catch(error => {
  543. loadedCount++;
  544. console.error("获取设备列表时出错:", error);
  545. });
  546. });
  547. }
  548. function updateQueryData() {
  549. // 清除之前的定时器,防止重复调用
  550. if (window.updateQueryDataTimer) {
  551. clearTimeout(window.updateQueryDataTimer);
  552. window.updateQueryDataTimer = null;
  553. }
  554. let calcPara = {
  555. date: queryParams.value.workDay,
  556. line1: selectedLines.value[0],
  557. line2: selectedLines.value[1]
  558. }
  559. calcHour(calcPara).then(res => {
  560. // 处理显著性分析结果(后台已过滤,全是显著结果)
  561. significantData.value = res.data || [];
  562. });
  563. initChart();
  564. rzLineList.value = [];
  565. rzLineList1.value = [];
  566. let queryPara = {
  567. paraCode: selectedEquipmentParam.value,
  568. typeId: selectedEquipmentType.value,
  569. workDay: queryParams.value.workDay,
  570. pageSize: 10000,
  571. pageNum: 1,
  572. };
  573. // 情况1:选择了一条产线,需要比较该产线下的两个设备
  574. if (selectedLines.value.length == 1) {
  575. const line = selectedLines.value[0];
  576. // 检查是否选择了两个设备进行对比
  577. if (selectedMetrics.value[line + '_0'] && selectedMetrics.value[line + '_1']) {
  578. // 查询第一个设备的数据
  579. queryPara['line'] = line;
  580. queryPara['deviceId'] = selectedMetrics.value[line + '_0'];
  581. listHour(queryPara).then(res => {
  582. rzLineList.value = res.rows;
  583. nextTick(() => {
  584. updateChart();
  585. });
  586. });
  587. // 查询第二个设备的数据
  588. queryPara['deviceId'] = selectedMetrics.value[line + '_1'];
  589. listHour(queryPara).then(res => {
  590. rzLineList1.value = res.rows;
  591. nextTick(() => {
  592. updateChart1();
  593. });
  594. });
  595. }
  596. }
  597. // 情况2:选择了两条产线,每条产线选择一个设备进行对比
  598. else if (selectedLines.value.length == 2) {
  599. let loadedCount = 0;
  600. // 查询第一条产线设备的数据
  601. if (selectedMetrics.value[selectedLines.value[0]]) {
  602. queryPara['line'] = selectedLines.value[0];
  603. queryPara['deviceId'] = selectedMetrics.value[selectedLines.value[0]];
  604. listHour(queryPara).then(res => {
  605. rzLineList.value = res.rows;
  606. loadedCount++;
  607. if (loadedCount === 2) {
  608. nextTick(() => {
  609. updateChart();
  610. updateChart1();
  611. });
  612. } else if (loadedCount === 1 && rzLineList.value.length > 0) {
  613. nextTick(() => {
  614. updateChart();
  615. });
  616. }
  617. });
  618. } else {
  619. loadedCount++; // 如果没有数据,也要增加计数
  620. }
  621. // 查询第二条产线设备的数据
  622. if (selectedMetrics.value[selectedLines.value[1]]) {
  623. const queryPara1 = {...queryPara}; // 创建独立的查询参数对象
  624. queryPara1['line'] = selectedLines.value[1];
  625. queryPara1['deviceId'] = selectedMetrics.value[selectedLines.value[1]];
  626. listHour(queryPara1).then(res => {
  627. rzLineList1.value = res.rows;
  628. loadedCount++;
  629. if (loadedCount === 2) {
  630. nextTick(() => {
  631. updateChart();
  632. updateChart1();
  633. });
  634. } else if (loadedCount === 1 && rzLineList1.value.length > 0) {
  635. nextTick(() => {
  636. updateChart1();
  637. });
  638. }
  639. });
  640. } else {
  641. loadedCount++; // 如果没有数据,也要增加计数
  642. }
  643. }
  644. }
  645. // 初始化图表
  646. function initChart() {
  647. if (chartInstance0) {
  648. chartInstance0.dispose();
  649. }
  650. if (chartInstance1) {
  651. chartInstance1.dispose();
  652. }
  653. if (chartRef0.value) {
  654. chartInstance0 = echarts.init(chartRef0.value);
  655. }
  656. if (chartRef1.value) {
  657. chartInstance1 = echarts.init(chartRef1.value);
  658. }
  659. //建立图表联动
  660. if (chartInstance0 && chartInstance1) {
  661. echarts.connect([
  662. chartInstance0,
  663. chartInstance1,
  664. ]);
  665. }
  666. }
  667. // 初始化分钟级数据图表0
  668. function initMinuteChart0(minuteData, targetData) {
  669. if (minuteChartInstance0) {
  670. minuteChartInstance0.dispose();
  671. }
  672. if (minuteChartRef0.value) {
  673. minuteChartInstance0 = echarts.init(minuteChartRef0.value);
  674. // 准备分钟级数据
  675. // 创建包含0-59分钟的完整时间数组
  676. const timePoints = Array.from({length: 60}, (_, i) => i.toString());
  677. // 为数据创建包含空值的完整数组
  678. const completeValues = Array(60).fill(null);
  679. minuteData.forEach(item => {
  680. const index = parseInt(item.time);
  681. if (index >= 0 && index < 60) {
  682. completeValues[index] = item.value !== null ? parseFloat(item.value) : null;
  683. }
  684. });
  685. // 获取单位信息
  686. const param = equipmentParamOptions.value.find(p => p.paraCode === selectedEquipmentParam.value);
  687. const unit = param ? param.updatedBy : '';
  688. const option = {
  689. title: {
  690. text: targetData.deviceName + ' ' + targetData.paraName + (unit ? ' (' + unit + ')' : ''),
  691. left: 'center'
  692. },
  693. tooltip: {
  694. trigger: 'axis',
  695. formatter: function (params) {
  696. let result = params[0].axisValue + '分<br/>';
  697. params.forEach(param => {
  698. let valueText = '';
  699. if (param.value !== null) {
  700. valueText = unit ? param.value + ' (' + unit + ')' : param.value;
  701. } else {
  702. valueText = '无数据';
  703. }
  704. result += `<div style="display:flex;align-items:center;">
  705. <span style="display:inline-block;margin-right:5px;border-radius:10px;width:10px;height:10px;background-color:${param.color};"></span>
  706. ${param.seriesName}: ${valueText}
  707. </div>`;
  708. });
  709. return result;
  710. }
  711. },
  712. legend: {
  713. top: 40,
  714. left: 'center',
  715. textStyle: {
  716. fontSize: 10
  717. },
  718. itemWidth: 10,
  719. itemHeight: 10
  720. },
  721. grid: {
  722. left: '8%', // 增加左边距,确保y轴标签完整显示
  723. right: '8%', // 增加右边距
  724. top: '25%',
  725. bottom: 40,
  726. containLabel: true
  727. },
  728. xAxis: {
  729. type: 'category',
  730. boundaryGap: false,
  731. data: timePoints,
  732. name: '分钟',
  733. nameGap: 20
  734. },
  735. yAxis: {
  736. type: 'value',
  737. name: targetData.paraName + (unit ? ' (' + unit + ')' : ''),
  738. nameGap: 40 // 增加y轴名称与轴的距离
  739. },
  740. series: [{
  741. name: targetData.deviceName + '-' + targetData.paraName + (unit ? ' (' + unit + ')' : ''),
  742. type: 'line',
  743. data: completeValues,
  744. smooth: true,
  745. showSymbol: false,
  746. itemStyle: {
  747. color: '#5470c6' // 蓝色系
  748. },
  749. markPoint: {
  750. data: [
  751. {type: 'max', name: '最大值'},
  752. {type: 'min', name: '最小值'}
  753. ]
  754. },
  755. markLine: {
  756. data: [
  757. {type: 'average', name: '平均值'}
  758. ]
  759. }
  760. }]
  761. };
  762. minuteChartInstance0.setOption(option);
  763. }
  764. }
  765. // 初始化分钟级数据图表1
  766. function initMinuteChart1(minuteData, targetData) {
  767. if (minuteChartInstance1) {
  768. minuteChartInstance1.dispose();
  769. }
  770. if (minuteChartRef1.value) {
  771. minuteChartInstance1 = echarts.init(minuteChartRef1.value);
  772. // 准备分钟级数据
  773. // 创建包含0-59分钟的完整时间数组
  774. const timePoints = Array.from({length: 60}, (_, i) => i.toString());
  775. // 为数据创建包含空值的完整数组
  776. const completeValues = Array(60).fill(null);
  777. minuteData.forEach(item => {
  778. const index = parseInt(item.time);
  779. if (index >= 0 && index < 60) {
  780. completeValues[index] = item.value !== null ? parseFloat(item.value) : null;
  781. }
  782. });
  783. // 获取单位信息
  784. const param = equipmentParamOptions.value.find(p => p.paraCode === selectedEquipmentParam.value);
  785. const unit = param ? param.updatedBy : '';
  786. const option = {
  787. title: {
  788. text: targetData.deviceName + ' ' + targetData.paraName + (unit ? ' (' + unit + ')' : ''),
  789. left: 'center'
  790. },
  791. tooltip: {
  792. trigger: 'axis',
  793. formatter: function (params) {
  794. let result = params[0].axisValue + '分<br/>';
  795. params.forEach(param => {
  796. let valueText = '';
  797. if (param.value !== null) {
  798. valueText = unit ? param.value + ' (' + unit + ')' : param.value;
  799. } else {
  800. valueText = '无数据';
  801. }
  802. result += `<div style="display:flex;align-items:center;">
  803. <span style="display:inline-block;margin-right:5px;border-radius:10px;width:10px;height:10px;background-color:${param.color};"></span>
  804. ${param.seriesName}: ${valueText}
  805. </div>`;
  806. });
  807. return result;
  808. }
  809. },
  810. legend: {
  811. top: 40,
  812. left: 'center',
  813. textStyle: {
  814. fontSize: 10
  815. },
  816. itemWidth: 10,
  817. itemHeight: 10
  818. },
  819. grid: {
  820. left: '8%', // 增加左边距,确保y轴标签完整显示
  821. right: '8%', // 增加右边距
  822. top: '25%',
  823. bottom: 40,
  824. containLabel: true
  825. },
  826. xAxis: {
  827. type: 'category',
  828. boundaryGap: false,
  829. data: timePoints,
  830. name: '分钟',
  831. nameGap: 20
  832. },
  833. yAxis: {
  834. type: 'value',
  835. name: targetData.paraName + (unit ? ' (' + unit + ')' : ''),
  836. nameGap: 40 // 增加y轴名称与轴的距离
  837. },
  838. series: [{
  839. name: targetData.deviceName + '-' + targetData.paraName + (unit ? ' (' + unit + ')' : ''),
  840. type: 'line',
  841. data: completeValues,
  842. smooth: true,
  843. showSymbol: false,
  844. itemStyle: {
  845. color: '#91cc75' // 绿色系
  846. },
  847. markPoint: {
  848. data: [
  849. {type: 'max', name: '最大值'},
  850. {type: 'min', name: '最小值'}
  851. ]
  852. },
  853. markLine: {
  854. data: [
  855. {type: 'average', name: '平均值'}
  856. ]
  857. }
  858. }]
  859. };
  860. minuteChartInstance1.setOption(option);
  861. }
  862. }
  863. // 更新图表0
  864. function updateChart() {
  865. if (chartInstance0 && rzLineList.value.length > 0) {
  866. // x轴只显示小时
  867. const hours = rzLineList.value.map(item => item.hour.toString().padStart(2, '0') + ':00');
  868. const values = rzLineList.value.map(item => item.paraValue);
  869. // 获取单位信息
  870. const param = equipmentParamOptions.value.find(p => p.paraCode === selectedEquipmentParam.value);
  871. const unit = param ? param.updatedBy : '';
  872. const option = {
  873. title: {
  874. text: rzLineList.value[0].deviceName + ' ' + rzLineList.value[0].paraName + (unit ? ' (' + unit + ')' : ''),
  875. left: 'center'
  876. },
  877. tooltip: {
  878. trigger: 'axis',
  879. formatter: function (params) {
  880. // tooltip显示完整日期和小时
  881. const dataIndex = params[0].dataIndex;
  882. const dataItem = rzLineList.value[dataIndex];
  883. const fullTime = `${dataItem.dataDate} ${dataItem.hour.toString().padStart(2, '0')}:00`;
  884. let result = fullTime + '<br/>';
  885. params.forEach(param => {
  886. let valueText = '';
  887. if (param.value !== null) {
  888. valueText = unit ? param.value + ' (' + unit + ')' : param.value;
  889. } else {
  890. valueText = '无数据';
  891. }
  892. result += `<div style="display:flex;align-items:center;">
  893. <span style="display:inline-block;margin-right:5px;border-radius:10px;width:10px;height:10px;background-color:${param.color};"></span>
  894. ${param.seriesName}: ${valueText}
  895. </div>`;
  896. });
  897. return result;
  898. }
  899. },
  900. legend: {
  901. top: 40,
  902. left: 'center',
  903. textStyle: {
  904. fontSize: 10
  905. },
  906. itemWidth: 10,
  907. itemHeight: 10
  908. },
  909. grid: {
  910. left: '8%', // 增加左边距,确保y轴标签完整显示
  911. right: '8%', // 增加右边距
  912. top: '25%',
  913. bottom: 40,
  914. containLabel: true
  915. },
  916. xAxis: {
  917. type: 'category',
  918. boundaryGap: false,
  919. data: hours,
  920. name: '时间',
  921. nameGap: 20
  922. },
  923. yAxis: {
  924. type: 'value',
  925. name: rzLineList.value[0].paraName + (unit ? ' (' + unit + ')' : ''),
  926. nameGap: 40 // 增加y轴名称与轴的距离
  927. },
  928. series: [{
  929. name: rzLineList.value[0].deviceName,
  930. type: 'line',
  931. data: values,
  932. smooth: true,
  933. showSymbol: false,
  934. itemStyle: {
  935. color: '#5470c6' // 蓝色系
  936. },
  937. markPoint: {
  938. data: [
  939. {type: 'max', name: '最大值'},
  940. {type: 'min', name: '最小值'}
  941. ]
  942. },
  943. markLine: {
  944. data: [
  945. {type: 'average', name: '平均值'}
  946. ]
  947. }
  948. }]
  949. };
  950. chartInstance0.setOption(option);
  951. // 先移除之前的点击事件监听,避免重复绑定
  952. chartInstance0.off('click');
  953. // 添加点击事件监听
  954. chartInstance0.on('click', (params) => {
  955. handleChartClick(params, 'chart0');
  956. });
  957. } else if (chartInstance0) {
  958. // 如果没有数据,清空图表
  959. chartInstance0.setOption({
  960. title: {
  961. text: '暂无数据',
  962. left: 'center',
  963. top: 'center'
  964. },
  965. xAxis: {
  966. type: 'category',
  967. data: []
  968. },
  969. yAxis: {
  970. type: 'value'
  971. },
  972. series: []
  973. });
  974. // 移除点击事件监听
  975. chartInstance0.off('click');
  976. }
  977. // 更新数据后重新初始化表格滚动同步
  978. nextTick(() => {
  979. });
  980. }
  981. // 更新图表1
  982. function updateChart1() {
  983. if (chartInstance1 && rzLineList1.value.length > 0) {
  984. // x轴只显示小时
  985. const hours = rzLineList1.value.map(item => item.hour.toString().padStart(2, '0') + ':00');
  986. const values = rzLineList1.value.map(item => item.paraValue);
  987. // 获取单位信息
  988. const param = equipmentParamOptions.value.find(p => p.paraCode === selectedEquipmentParam.value);
  989. const unit = param ? param.updatedBy : '';
  990. const option = {
  991. title: {
  992. text: rzLineList1.value[0].deviceName + ' ' + rzLineList1.value[0].paraName + (unit ? ' (' + unit + ')' : ''),
  993. left: 'center'
  994. },
  995. tooltip: {
  996. trigger: 'axis',
  997. formatter: function (params) {
  998. // tooltip显示完整日期和小时
  999. const dataIndex = params[0].dataIndex;
  1000. const dataItem = rzLineList1.value[dataIndex];
  1001. const fullTime = `${dataItem.dataDate} ${dataItem.hour.toString().padStart(2, '0')}:00`;
  1002. let result = fullTime + '<br/>';
  1003. params.forEach(param => {
  1004. let valueText = '';
  1005. if (param.value !== null) {
  1006. valueText = unit ? param.value + ' (' + unit + ')' : param.value;
  1007. } else {
  1008. valueText = '无数据';
  1009. }
  1010. result += `<div style="display:flex;align-items:center;">
  1011. <span style="display:inline-block;margin-right:5px;border-radius:10px;width:10px;height:10px;background-color:${param.color};"></span>
  1012. ${param.seriesName}: ${valueText}
  1013. </div>`;
  1014. });
  1015. return result;
  1016. }
  1017. },
  1018. legend: {
  1019. top: 40,
  1020. left: 'center',
  1021. textStyle: {
  1022. fontSize: 10
  1023. },
  1024. itemWidth: 10,
  1025. itemHeight: 10
  1026. },
  1027. grid: {
  1028. left: '8%', // 增加左边距,确保y轴标签完整显示
  1029. right: '8%', // 增加右边距
  1030. top: '25%',
  1031. bottom: 40,
  1032. containLabel: true
  1033. },
  1034. xAxis: {
  1035. type: 'category',
  1036. boundaryGap: false,
  1037. data: hours,
  1038. name: '时间',
  1039. nameGap: 20
  1040. },
  1041. yAxis: {
  1042. type: 'value',
  1043. name: rzLineList1.value[0].paraName + (unit ? ' (' + unit + ')' : ''),
  1044. nameGap: 40 // 增加y轴名称与轴的距离
  1045. },
  1046. series: [{
  1047. name: rzLineList1.value[0].deviceName,
  1048. type: 'line',
  1049. data: values,
  1050. smooth: true,
  1051. showSymbol: false,
  1052. itemStyle: {
  1053. color: '#91cc75' // 绿色系
  1054. },
  1055. markPoint: {
  1056. data: [
  1057. {type: 'max', name: '最大值'},
  1058. {type: 'min', name: '最小值'}
  1059. ]
  1060. },
  1061. markLine: {
  1062. data: [
  1063. {type: 'average', name: '平均值'}
  1064. ]
  1065. }
  1066. }]
  1067. };
  1068. chartInstance1.setOption(option);
  1069. // 先移除之前的点击事件监听,避免重复绑定
  1070. chartInstance1.off('click');
  1071. // 添加点击事件监听
  1072. chartInstance1.on('click', (params) => {
  1073. handleChartClick(params, 'chart1');
  1074. });
  1075. } else if (chartInstance1) {
  1076. // 如果没有数据,清空图表
  1077. chartInstance1.setOption({
  1078. title: {
  1079. text: '暂无数据',
  1080. left: 'center',
  1081. top: 'center'
  1082. },
  1083. xAxis: {
  1084. type: 'category',
  1085. data: []
  1086. },
  1087. yAxis: {
  1088. type: 'value'
  1089. },
  1090. series: []
  1091. });
  1092. // 移除点击事件监听
  1093. chartInstance1.off('click');
  1094. }
  1095. // 更新数据后重新初始化表格滚动同步
  1096. nextTick(() => {
  1097. });
  1098. }
  1099. // 图表点击事件处理函数
  1100. function handleChartClick(params, chartType) {
  1101. // 获取点击的小时
  1102. const hour = params.name;
  1103. // 查找对应小时的数据(无论点击的是哪个图表,都要显示两个设备的数据)
  1104. // 尝试多种匹配方式来查找数据
  1105. let targetData0 = null;
  1106. let targetData1 = null;
  1107. // 对于第一个数据集
  1108. for (let i = 0; i < rzLineList.value.length; i++) {
  1109. const item = rzLineList.value[i];
  1110. const itemHour = item.hour.toString().padStart(2, '0');
  1111. const clickedHour = hour.replace(':00', ''); // 去掉可能的 ':00' 后缀
  1112. if (itemHour === clickedHour) {
  1113. targetData0 = item;
  1114. break;
  1115. }
  1116. }
  1117. // 对于第二个数据集
  1118. for (let i = 0; i < rzLineList1.value.length; i++) {
  1119. const item = rzLineList1.value[i];
  1120. const itemHour = item.hour.toString().padStart(2, '0');
  1121. const clickedHour = hour.replace(':00', ''); // 去掉可能的 ':00' 后缀
  1122. if (itemHour === clickedHour) {
  1123. targetData1 = item;
  1124. break;
  1125. }
  1126. }
  1127. // 检查两个设备是否都有对应小时的数据
  1128. if ((targetData0 && targetData0.paraMValue) || (targetData1 && targetData1.paraMValue)) {
  1129. try {
  1130. // 设置对话框标题
  1131. minuteDialogTitle.value = `小时 ${hour} 的分钟级数据对比`;
  1132. // 获取单位信息
  1133. const param = equipmentParamOptions.value.find(p => p.paraCode === selectedEquipmentParam.value);
  1134. const unit = param ? param.updatedBy : '';
  1135. // 处理左侧设备的分钟级数据
  1136. if (targetData0 && targetData0.paraMValue) {
  1137. let minuteData0;
  1138. try {
  1139. minuteData0 = typeof targetData0.paraMValue === 'string'
  1140. ? JSON.parse(targetData0.paraMValue)
  1141. : targetData0.paraMValue;
  1142. } catch (e) {
  1143. // 尝试其他格式
  1144. if (Array.isArray(targetData0.paraMValue)) {
  1145. minuteData0 = targetData0.paraMValue;
  1146. } else {
  1147. minuteData0 = [];
  1148. }
  1149. }
  1150. // 设置左侧分钟级数据
  1151. minuteTableData0.value = [];
  1152. for (let i = 0; i < 60; i++) {
  1153. const dataItem = minuteData0.find(item => parseInt(item.time) === i);
  1154. minuteTableData0.value.push({
  1155. time: i.toString(),
  1156. value: dataItem ? dataItem.value : null
  1157. });
  1158. }
  1159. minuteDataLabel0.value = targetData0.paraName;
  1160. minuteDataUnit0.value = unit || ''; // 设置单位
  1161. // 初始化左侧分钟级图表
  1162. nextTick(() => {
  1163. initMinuteChart0(minuteData0, targetData0);
  1164. // 建立分钟级图表联动
  1165. if (minuteChartInstance0 && minuteChartInstance1) {
  1166. echarts.connect([minuteChartInstance0, minuteChartInstance1]);
  1167. }
  1168. });
  1169. } else {
  1170. // 如果没有数据,清空左侧内容
  1171. minuteTableData0.value = Array.from({length: 60}, (_, i) => ({time: i.toString(), value: null}));
  1172. minuteDataLabel0.value = '';
  1173. minuteDataUnit0.value = ''; // 清空单位
  1174. if (minuteChartInstance0) {
  1175. minuteChartInstance0.dispose();
  1176. minuteChartInstance0 = null;
  1177. }
  1178. }
  1179. // 处理右侧设备的分钟级数据
  1180. if (targetData1 && targetData1.paraMValue) {
  1181. let minuteData1;
  1182. try {
  1183. minuteData1 = typeof targetData1.paraMValue === 'string'
  1184. ? JSON.parse(targetData1.paraMValue)
  1185. : targetData1.paraMValue;
  1186. } catch (e) {
  1187. // 尝试其他格式
  1188. if (Array.isArray(targetData1.paraMValue)) {
  1189. minuteData1 = targetData1.paraMValue;
  1190. } else {
  1191. minuteData1 = [];
  1192. }
  1193. }
  1194. // 设置右侧分钟级数据
  1195. minuteTableData1.value = [];
  1196. for (let i = 0; i < 60; i++) {
  1197. const dataItem = minuteData1.find(item => parseInt(item.time) === i);
  1198. minuteTableData1.value.push({
  1199. time: i.toString(),
  1200. value: dataItem ? dataItem.value : null
  1201. });
  1202. }
  1203. minuteDataLabel1.value = targetData1.paraName;
  1204. minuteDataUnit1.value = unit || ''; // 设置单位
  1205. // 初始化右侧分钟级图表
  1206. nextTick(() => {
  1207. initMinuteChart1(minuteData1, targetData1);
  1208. // 建立分钟级图表联动
  1209. if (minuteChartInstance0 && minuteChartInstance1) {
  1210. echarts.connect([minuteChartInstance0, minuteChartInstance1]);
  1211. }
  1212. });
  1213. } else {
  1214. // 如果没有数据,清空右侧内容
  1215. minuteTableData1.value = Array.from({length: 60}, (_, i) => ({time: i.toString(), value: null}));
  1216. minuteDataLabel1.value = '';
  1217. minuteDataUnit1.value = ''; // 清空单位
  1218. if (minuteChartInstance1) {
  1219. minuteChartInstance1.dispose();
  1220. minuteChartInstance1 = null;
  1221. }
  1222. }
  1223. // 显示对话框
  1224. showMinuteDialog();
  1225. } catch (error) {
  1226. proxy.$modal.msgError("数据解析失败");
  1227. }
  1228. } else {
  1229. // 显示提示信息
  1230. proxy.$modal.msgWarning("该小时没有分钟级数据");
  1231. }
  1232. }
  1233. // 窗口大小改变时重置图表大小
  1234. function resizeChart() {
  1235. if (chartInstance0) {
  1236. chartInstance0.resize();
  1237. }
  1238. if (chartInstance1) {
  1239. chartInstance1.resize();
  1240. }
  1241. if (minuteChartInstance0) {
  1242. minuteChartInstance0.resize();
  1243. }
  1244. if (minuteChartInstance1) {
  1245. minuteChartInstance1.resize();
  1246. }
  1247. }
  1248. // 关闭分钟级数据弹窗
  1249. function handleMinuteDialogClose() {
  1250. minuteDialogVisible.value = false;
  1251. }
  1252. // 显示分钟级数据弹窗
  1253. function showMinuteDialog() {
  1254. minuteDialogVisible.value = true;
  1255. }
  1256. // 初始化主表表格滚动同步
  1257. function initMainTableScrollSync() {
  1258. // 清理之前的事件监听器
  1259. cleanups.forEach(cleanup => cleanup());
  1260. cleanups = [];
  1261. // 初始化新的滚动同步
  1262. const newCleanups = initTableScrollSync(tableRef0, tableRef1, isSyncing);
  1263. cleanups = newCleanups;
  1264. }
  1265. onMounted(() => {
  1266. initChart();
  1267. window.addEventListener('resize', resizeChart);
  1268. // 默认选择所有产线
  1269. //selectedLines.value = [...allLines.value];
  1270. // 初始加载数据
  1271. getList();
  1272. // 添加一个微任务确保图表正确初始化
  1273. nextTick(() => {
  1274. if (!chartInstance0 && chartRef0.value) {
  1275. chartInstance0 = echarts.init(chartRef0.value);
  1276. }
  1277. if (!chartInstance1 && chartRef1.value) {
  1278. chartInstance1 = echarts.init(chartRef1.value);
  1279. }
  1280. // 初始化主表滚动同步
  1281. });
  1282. // 确保在DOM准备好后更新数据
  1283. // setTimeout(() => {
  1284. // updateQueryData();
  1285. // }, 100);
  1286. });
  1287. onUnmounted(() => {
  1288. window.removeEventListener('resize', resizeChart);
  1289. if (chartInstance0) {
  1290. chartInstance0.dispose();
  1291. }
  1292. if (chartInstance1) {
  1293. chartInstance1.dispose();
  1294. }
  1295. if (minuteChartInstance0) {
  1296. minuteChartInstance0.dispose();
  1297. }
  1298. if (minuteChartInstance1) {
  1299. minuteChartInstance1.dispose();
  1300. }
  1301. // 清除定时器
  1302. if (window.updateQueryDataTimer) {
  1303. clearTimeout(window.updateQueryDataTimer);
  1304. }
  1305. // 删除清理滚动同步的代码
  1306. // cleanups.forEach(cleanup => cleanup());
  1307. });
  1308. onActivated(() => {
  1309. resizeChart();
  1310. });
  1311. // 取消按钮
  1312. function cancel() {
  1313. open.value = false;
  1314. reset();
  1315. }
  1316. // 表单重置
  1317. function reset() {
  1318. form.value = {
  1319. id: null,
  1320. workDay: null,
  1321. hour: null,
  1322. line: null,
  1323. openRate: null,
  1324. length: null,
  1325. tmp: null,
  1326. speed: null,
  1327. energy: null,
  1328. amp: null,
  1329. createdBy: null,
  1330. createdTime: null,
  1331. updatedBy: null,
  1332. updatedTime: null,
  1333. remark: null
  1334. };
  1335. proxy.resetForm("rzLineRef");
  1336. }
  1337. /** 搜索按钮操作 */
  1338. function handleQuery() {
  1339. queryParams.value.pageNum = 1;
  1340. // 检查查询条件
  1341. if (!queryParams.value.workDay) {
  1342. proxy.$modal.msgWarning("请选择日期");
  1343. return;
  1344. }
  1345. // 不要重置已选择的产线和设备,仅更新数据
  1346. updateQueryData();
  1347. }
  1348. /** 重置按钮操作 */
  1349. function resetQuery() {
  1350. proxy.resetForm("queryRef");
  1351. // 设置默认日期为当前日期
  1352. const now = new Date();
  1353. const year = now.getFullYear();
  1354. const month = (now.getMonth() + 1).toString().padStart(2, '0');
  1355. const day = now.getDate().toString().padStart(2, '0');
  1356. queryParams.value.workDay = `${year}-${month}-${day}`;
  1357. // 重置产线和设备选择
  1358. selectedLines.value = ['5', '6']; // 重置为默认的5号和6号产线
  1359. selectedMetrics.value = {};
  1360. equipmentList.value = {};
  1361. // 用户主动点击重置按钮时调用handleQuery,这会触发数据更新
  1362. handleQuery();
  1363. }
  1364. // 处理设备类型变化
  1365. function handleEquipmentTypeChange(val) {
  1366. // 清空已选的设备参数
  1367. selectedEquipmentParam.value = '';
  1368. // 如果有选择设备类型,则更新设备参数选项
  1369. if (val) {
  1370. // 在设备类型列表中找到当前选中的设备类型
  1371. const selectedType = equipmentTypeList.value.find(item => item.typeId === val);
  1372. // 查找选中的参数信息
  1373. if (selectedType && selectedType.dyeTypeParaList) {
  1374. // 提取设备参数选项
  1375. equipmentParamOptions.value = selectedType.dyeTypeParaList.map(item => {
  1376. return {
  1377. paraCode: item.paraCode,
  1378. paraName: item.paraName,
  1379. updatedBy: item.updatedBy // 保存单位信息
  1380. };
  1381. });
  1382. // 默认选择第一条设备参数
  1383. if (selectedType.dyeTypeParaList.length > 0 && !selectedEquipmentParam.value) {
  1384. selectedEquipmentParam.value = selectedType.dyeTypeParaList[0].paraCode;
  1385. // 触发设备参数变化事件
  1386. handleEquipmentParamChange(); // 不传参数,让函数自己判断是否是初始化状态
  1387. } else if (selectedEquipmentParam.value) {
  1388. // 如果已有参数,直接触发设备列表更新
  1389. handleEquipmentParamChange(); // 不传参数,让函数自己判断是否是初始化状态
  1390. }
  1391. } else {
  1392. equipmentParamOptions.value = [];
  1393. }
  1394. } else {
  1395. // 如果没有选择设备类型,清空设备参数选项
  1396. equipmentParamOptions.value = [];
  1397. }
  1398. }
  1399. // 处理设备参数变化
  1400. function handleEquipmentParamChange() {
  1401. // 清空设备列表和已选择的设备
  1402. equipmentList.value = {};
  1403. selectedMetrics.value = {};
  1404. // 获取新设备列表,让用户主动操作时调用
  1405. getDeviceList();
  1406. }
  1407. // 处理产线选择变化
  1408. function handleLineSelectionChange(val) {
  1409. if (val.length > 2) {
  1410. // 限制最多选择两条产线
  1411. selectedLines.value = val.slice(0, 2);
  1412. proxy.$modal.msgWarning("最多只能选择两条产线进行对比");
  1413. }
  1414. // 当取消选择所有产线时,清空相关设备选择
  1415. if (val.length === 0) {
  1416. selectedMetrics.value = {};
  1417. }
  1418. // 用户主动操作时调用getDeviceList
  1419. getDeviceList();
  1420. }
  1421. /** 新增按钮操作 */
  1422. function handleAdd() {
  1423. reset();
  1424. open.value = true;
  1425. title.value = "添加染整线产线小时统计数据";
  1426. }
  1427. /** 修改按钮操作 */
  1428. function handleUpdate(row) {
  1429. reset();
  1430. const _id = row.id || ids.value
  1431. getRzLine(_id).then(response => {
  1432. form.value = response.data;
  1433. open.value = true;
  1434. title.value = "修改染整线产线小时统计数据";
  1435. });
  1436. }
  1437. /** 提交按钮 */
  1438. function submitForm() {
  1439. proxy.$refs["rzLineRef"].validate(valid => {
  1440. if (valid) {
  1441. if (form.value.id != null) {
  1442. updateRzLine(form.value).then(response => {
  1443. proxy.$modal.msgSuccess("修改成功");
  1444. open.value = false;
  1445. getList();
  1446. });
  1447. } else {
  1448. addRzLine(form.value).then(response => {
  1449. proxy.$modal.msgSuccess("新增成功");
  1450. open.value = false;
  1451. getList();
  1452. });
  1453. }
  1454. }
  1455. });
  1456. }
  1457. /** 删除按钮操作 */
  1458. function handleDelete(row) {
  1459. const _ids = row.id || ids.value;
  1460. proxy.$modal.confirm('是否确认删除染整线产线小时统计数据编号为"' + _ids + '"的数据项?').then(function () {
  1461. return delRzLine(_ids);
  1462. }).then(() => {
  1463. getList();
  1464. proxy.$modal.msgSuccess("删除成功");
  1465. }).catch(() => {
  1466. });
  1467. }
  1468. /** 导出按钮操作 */
  1469. function handleExport() {
  1470. proxy.download('dyeing/rzLine/export', {
  1471. ...queryParams.value
  1472. }, `rzLine_${new Date().getTime()}.xlsx`)
  1473. }
  1474. const getTableColumnLabel = (list) => {
  1475. if (list && list.length > 0 && list[0]) {
  1476. const item = list[0];
  1477. // 从参数选项中查找对应的单位信息
  1478. const param = equipmentParamOptions.value.find(p => p.paraCode === selectedEquipmentParam.value);
  1479. const unit = param ? param.updatedBy : '';
  1480. return item.paraName + (unit ? ' (' + unit + ')' : '');
  1481. }
  1482. return '';
  1483. };
  1484. const getMinuteTableColumnLabel = (label, unit) => {
  1485. return label + (unit ? ' (' + unit + ')' : '');
  1486. };
  1487. </script>