dsStop.vue 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844
  1. <template>
  2. <div class="app-container">
  3. <el-form :model="queryParams" ref="queryRef" :inline="true" v-show="showSearch" label-width="68px">
  4. <el-form-item label="统计方式" prop="remark">
  5. <el-select
  6. v-model="queryParams.remark"
  7. placeholder="请选择统计方式"
  8. clearable
  9. style="width: 150px;"
  10. @change="handleRemarkChange"
  11. >
  12. <el-option label="按月" value="month"></el-option>
  13. <el-option label="按天" value="day"></el-option>
  14. </el-select>
  15. </el-form-item>
  16. <el-form-item label="选择月份" prop="dataDate" v-if="queryParams.remark === 'month'">
  17. <el-date-picker
  18. v-model="queryParams.dataDate"
  19. type="month"
  20. placeholder="请选择月份"
  21. style="width: 150px;"
  22. value-format="YYYY-MM"
  23. @change="handleDateChange"
  24. />
  25. </el-form-item>
  26. <el-form-item label="选择日期" prop="dataDate" v-if="queryParams.remark === 'day'">
  27. <el-date-picker
  28. v-model="queryParams.dataDate"
  29. type="date"
  30. placeholder="请选择日期"
  31. style="width: 150px;"
  32. value-format="YYYY-MM-DD"
  33. @change="handleDateChange"
  34. />
  35. </el-form-item>
  36. <el-form-item label="机台号" prop="deviceId">
  37. <el-input
  38. v-model="queryParams.deviceId"
  39. placeholder="请输入机台号"
  40. clearable
  41. @input="handleDeviceIdChange"
  42. />
  43. </el-form-item>
  44. <el-form-item label="分析结果" prop="result">
  45. <el-select
  46. v-model="queryParams.result"
  47. placeholder="请选择分析结果"
  48. clearable
  49. style="width: 150px;"
  50. @change="handleResultChange"
  51. >
  52. <el-option label="平稳" value="平稳"></el-option>
  53. <el-option label="上升" value="上升"></el-option>
  54. <el-option label="明显上升" value="明显上升"></el-option>
  55. <el-option label="下降" value="下降"></el-option>
  56. <el-option label="明显下降" value="明显下降"></el-option>
  57. </el-select>
  58. </el-form-item>
  59. <el-form-item>
  60. <el-button type="primary" icon="Search" @click="handleFilter">搜索</el-button>
  61. <el-button icon="Refresh" @click="resetFilter">重置</el-button>
  62. </el-form-item>
  63. </el-form>
  64. <!-- 双图表展示 -->
  65. <el-row :gutter="15" style="margin-bottom: 20px;" v-if="statistics">
  66. <!-- 趋势分析南丁格尔图 -->
  67. <el-col :span="12">
  68. <el-card>
  69. <div ref="trendChartRef" style="width: 100%; height: 250px;"></div>
  70. </el-card>
  71. </el-col>
  72. <!-- 当月断纱趋势折线图 -->
  73. <el-col :span="12">
  74. <el-card>
  75. <div ref="deviceChartRef" style="width: 100%; height: 250px;"></div>
  76. </el-card>
  77. </el-col>
  78. </el-row>
  79. <!-- 群体分析结果展示 -->
  80. <el-card class="statistic-card" v-if="groupAnalysisResult">
  81. <div class="group-analysis">
  82. <el-alert
  83. :title="groupAnalysisResult.message"
  84. :type="groupAnalysisResult.type"
  85. show-icon
  86. :closable="false"
  87. />
  88. </div>
  89. </el-card>
  90. <el-table v-loading="loading" :data="filteredData" @selection-change="handleSelectionChange">
  91. <el-table-column type="selection" width="55" align="center"/>
  92. <!-- <el-table-column label="ID" align="center" prop="id" />-->
  93. <el-table-column label="机台号" align="center" prop="deviceId"/>
  94. <el-table-column label="数据量" align="center" prop="dataNum"/>
  95. <el-table-column label="斜率" align="center" prop="slope">
  96. <template #default="scope">
  97. {{ (scope.row.slope).toFixed(2) }}
  98. </template>
  99. </el-table-column>
  100. <el-table-column label="标准差" align="center" prop="stdDev">
  101. <template #default="scope">
  102. {{ (scope.row.stdDev).toFixed(2) }}
  103. </template>
  104. </el-table-column>
  105. <el-table-column label="趋势分析" align="center" prop="result">
  106. <template #default="scope">
  107. <el-link type="primary" @click="handleResultClick(scope.row)">{{ scope.row.result }}</el-link>
  108. </template>
  109. </el-table-column>
  110. </el-table>
  111. <!-- 图表弹窗 -->
  112. <el-dialog :title="'设备[' + currentRow.deviceId + ']分析结果详情'" v-model="chartOpen" width="800px" append-to-body>
  113. <div style="text-align: center; margin: 20px 0;">
  114. <p>设备名称: {{ currentRow.deviceName }}</p>
  115. <p>分析结果: {{ currentRow.result }}</p>
  116. </div>
  117. <div v-if="currentRow.list && currentRow.list.length > 0">
  118. <!-- 使用ECharts展示折线图 -->
  119. <div ref="chartRef" style="width: 100%; height: 400px;"></div>
  120. </div>
  121. <div v-else>
  122. <p style="text-align: center;">暂无详细数据</p>
  123. </div>
  124. <template #footer>
  125. <div class="dialog-footer">
  126. <el-button @click="closeChart">关 闭</el-button>
  127. </div>
  128. </template>
  129. </el-dialog>
  130. </div>
  131. </template>
  132. <script setup name="CalcStop">
  133. import {dsStop} from "@/api/calc/calcStop";
  134. import {getCurrentInstance, nextTick, onBeforeUnmount, onMounted, reactive, ref, toRefs, watch} from 'vue';
  135. import * as echarts from 'echarts';
  136. const {proxy} = getCurrentInstance();
  137. const calcStopList = ref([]);
  138. const filteredData = ref([]);
  139. const open = ref(false);
  140. const chartOpen = ref(false); // 添加图表弹窗状态
  141. const loading = ref(true);
  142. const showSearch = ref(true);
  143. const ids = ref([]);
  144. const single = ref(true);
  145. const multiple = ref(true);
  146. const total = ref(0);
  147. const title = ref("");
  148. const currentRow = ref({}); // 添加当前行数据
  149. const chartRef = ref(null);
  150. const statistics = ref(null); // 添加统计数据
  151. const groupAnalysisResult = ref(null); // 群体分析结果
  152. const trendChartRef = ref(null); // 趋势图容器引用
  153. const deviceChartRef = ref(null); // 设备统计图容器引用
  154. const monthList = ref([]); // 专门用于存储月度数据
  155. let chartInstance = null;
  156. let trendChartInstance = null; // 趋势图实例
  157. let deviceChartInstance = null; // 设备统计图实例
  158. // 计算默认日期(当前时间减去31小时)
  159. const getDefaultDate = () => {
  160. const now = new Date();
  161. const defaultDate = new Date(now.getTime() - 31 * 60 * 60 * 1000);
  162. return defaultDate.toISOString().split('T')[0]; // 格式化为 YYYY-MM-DD
  163. };
  164. // 获取当前年月
  165. const getCurrentMonth = () => {
  166. const now = new Date();
  167. const year = now.getFullYear();
  168. const month = (now.getMonth() + 1).toString().padStart(2, '0');
  169. return `${year}-${month}`;
  170. };
  171. const data = reactive({
  172. form: {},
  173. queryParams: {
  174. pageNum: 1,
  175. pageSize: 1000,
  176. deviceId: null,
  177. dataDate: getDefaultDate(),
  178. hour: null,
  179. startTime: getDefaultDate(),
  180. endTime: getDefaultDate(),
  181. stopType: 2,
  182. result: "", // 设置默认选中值
  183. remark: "day", // 统计方式: month-按月, day-按天,默认按天
  184. selectedMonth: null, // 选择的月份(已废弃,使用dataDate替代)
  185. selectedDate: null // 选择的日期(已废弃,使用dataDate替代)
  186. },
  187. rules: {}
  188. });
  189. const {queryParams, form, rules} = toRefs(data);
  190. /** 查询停机数据统计列表 */
  191. function getList() {
  192. loading.value = true;
  193. // 根据统计方式设置查询参数
  194. if (queryParams.value.remark === 'month' && queryParams.value.dataDate) {
  195. // 按月查询
  196. queryParams.value.startTime = queryParams.value.dataDate + "-01";
  197. const year = parseInt(queryParams.value.dataDate.split("-")[0]);
  198. const month = parseInt(queryParams.value.dataDate.split("-")[1]);
  199. const lastDay = new Date(year, month, 0).getDate();
  200. queryParams.value.endTime = queryParams.value.dataDate + "-" + (lastDay < 10 ? "0" + lastDay : lastDay);
  201. } else if (queryParams.value.remark === 'day') {
  202. // 按天查询,如果未选择日期则使用默认日期
  203. if (!queryParams.value.dataDate) {
  204. const defaultDate = getDefaultDate();
  205. queryParams.value.dataDate = defaultDate;
  206. }
  207. queryParams.value.startTime = queryParams.value.dataDate;
  208. queryParams.value.endTime = queryParams.value.dataDate;
  209. }
  210. // 将分析天数作为参数传递给后端
  211. dsStop(queryParams.value).then(response => {
  212. calcStopList.value = response.data;
  213. filteredData.value = response.data; // 初始化过滤数据
  214. statistics.value = response.statistics; // 保存统计数据
  215. monthList.value = response.monthList; // 保存月度数据
  216. // 在数据加载完成后触发一次过滤,确保默认选中项生效
  217. filterData();
  218. loading.value = false;
  219. // 在数据加载完成后绘制图表
  220. nextTick(() => {
  221. drawTrendChart();
  222. drawDeviceChart();
  223. });
  224. });
  225. }
  226. // 统计方式变化处理
  227. function handleRemarkChange() {
  228. // 清空之前选择的日期
  229. queryParams.value.dataDate = null;
  230. // 重置其他查询条件
  231. queryParams.value.deviceId = null;
  232. queryParams.value.result = "";
  233. // 如果切换到按天统计,设置默认日期
  234. if (queryParams.value.remark === 'day') {
  235. const defaultDate = getDefaultDate();
  236. queryParams.value.dataDate = defaultDate;
  237. queryParams.value.startTime = defaultDate;
  238. queryParams.value.endTime = defaultDate;
  239. }
  240. // 如果切换到按月统计,设置默认月份
  241. else if (queryParams.value.remark === 'month') {
  242. const currentMonth = getCurrentMonth();
  243. queryParams.value.dataDate = currentMonth;
  244. queryParams.value.startTime = currentMonth + "-01";
  245. // 设置为该月的最后一天作为结束时间
  246. const year = parseInt(currentMonth.split("-")[0]);
  247. const month = parseInt(currentMonth.split("-")[1]);
  248. const lastDay = new Date(year, month, 0).getDate();
  249. queryParams.value.endTime = currentMonth + "-" + (lastDay < 10 ? "0" + lastDay : lastDay);
  250. }
  251. // 统计方式改变后自动查询
  252. handleFilter();
  253. }
  254. // 月份选择变化处理
  255. function handleMonthChange() {
  256. if (queryParams.value.dataDate && queryParams.value.remark === 'month') {
  257. // 设置为该月的第一天作为开始时间
  258. queryParams.value.startTime = queryParams.value.dataDate + "-01";
  259. // 设置为该月的最后一天作为结束时间
  260. const year = parseInt(queryParams.value.dataDate.split("-")[0]);
  261. const month = parseInt(queryParams.value.dataDate.split("-")[1]);
  262. const lastDay = new Date(year, month, 0).getDate();
  263. queryParams.value.endTime = queryParams.value.dataDate + "-" + (lastDay < 10 ? "0" + lastDay : lastDay);
  264. } else {
  265. queryParams.value.startTime = null;
  266. queryParams.value.endTime = null;
  267. }
  268. }
  269. // 日期选择变化处理
  270. function handleDateChange() {
  271. if (queryParams.value.dataDate && queryParams.value.remark === 'day') {
  272. queryParams.value.startTime = queryParams.value.dataDate;
  273. queryParams.value.endTime = queryParams.value.dataDate;
  274. } else if (queryParams.value.dataDate && queryParams.value.remark === 'month') {
  275. // 设置为该月的第一天作为开始时间
  276. queryParams.value.startTime = queryParams.value.dataDate + "-01";
  277. // 设置为该月的最后一天作为结束时间
  278. const year = parseInt(queryParams.value.dataDate.split("-")[0]);
  279. const month = parseInt(queryParams.value.dataDate.split("-")[1]);
  280. const lastDay = new Date(year, month, 0).getDate();
  281. queryParams.value.endTime = queryParams.value.dataDate + "-" + (lastDay < 10 ? "0" + lastDay : lastDay);
  282. } else {
  283. queryParams.value.startTime = null;
  284. queryParams.value.endTime = null;
  285. }
  286. // 日期改变后自动查询
  287. handleFilter();
  288. }
  289. // 分析天数变化处理
  290. function handleDaysChange() {
  291. getList(); // 分析天数变化时重新从后端获取数据
  292. }
  293. // 绘制趋势分析图(南丁格尔图)
  294. function drawTrendChart() {
  295. if (!trendChartRef.value || !statistics.value) return;
  296. // 销毁之前的实例
  297. if (trendChartInstance) {
  298. trendChartInstance.dispose();
  299. }
  300. // 初始化新的实例
  301. trendChartInstance = echarts.init(trendChartRef.value);
  302. // 准备数据
  303. const trendData = Object.entries(statistics.value.trendCounts).map(([name, value]) => ({
  304. name,
  305. value
  306. }));
  307. // 计算总数量,用于百分比计算
  308. const totalValue = trendData.reduce((sum, item) => sum + item.value, 0);
  309. // 定义颜色调色板,使颜色更加协调
  310. const colorPalette = ['#5470c6', '#91cc75', '#fac858', '#ee6666', '#73c0de'];
  311. // 配置图表选项
  312. const option = {
  313. color: colorPalette,
  314. title: {
  315. text: '趋势分析统计',
  316. left: 'center'
  317. },
  318. tooltip: {
  319. trigger: 'item',
  320. formatter: '{a} <br/>{b} : {c} ({d}%)'
  321. },
  322. legend: {
  323. bottom: 10,
  324. left: 'center',
  325. data: trendData.map(item => item.name)
  326. },
  327. series: [
  328. {
  329. name: '趋势分析',
  330. type: 'pie',
  331. radius: [30, 80],
  332. center: ['50%', '50%'],
  333. roseType: 'radius',
  334. itemStyle: {
  335. borderRadius: 5,
  336. borderColor: '#fff',
  337. borderWidth: 2
  338. },
  339. label: {
  340. show: true,
  341. formatter: (params) => {
  342. const percentage = totalValue > 0 ? ((params.value / totalValue) * 100).toFixed(2) : 0;
  343. return `{name|${params.name}}\n{value|${params.value}} {percentage|${percentage}%}`;
  344. },
  345. rich: {
  346. name: {
  347. fontSize: 12,
  348. color: '#666',
  349. lineHeight: 14
  350. },
  351. value: {
  352. fontSize: 14,
  353. fontWeight: 'bold',
  354. color: '#333',
  355. lineHeight: 16
  356. },
  357. percentage: {
  358. fontSize: 12,
  359. color: '#999',
  360. lineHeight: 14
  361. }
  362. }
  363. },
  364. labelLine: {
  365. length: 5,
  366. length2: 10
  367. },
  368. data: trendData
  369. },
  370. {
  371. name: '设备总数',
  372. type: 'pie',
  373. radius: [0, 30],
  374. center: ['50%', '50%'],
  375. itemStyle: {
  376. color: '#5470c6'
  377. },
  378. label: {
  379. show: true,
  380. position: 'center',
  381. formatter: '设备总数\n{c}',
  382. fontSize: 16,
  383. fontWeight: 'bold',
  384. lineHeight: 20,
  385. rich: {
  386. a: {
  387. fontSize: 16,
  388. color: '#333'
  389. }
  390. }
  391. },
  392. data: [{value: statistics.value.totalDevices, name: '设备总数'}]
  393. }
  394. ]
  395. };
  396. // 设置配置项
  397. trendChartInstance.setOption(option);
  398. }
  399. // 绘制设备统计图(当月断纱趋势折线图)
  400. function drawDeviceChart() {
  401. if (!deviceChartRef.value) return;
  402. // 销毁之前的实例
  403. if (deviceChartInstance) {
  404. deviceChartInstance.dispose();
  405. }
  406. // 初始化新的实例
  407. deviceChartInstance = echarts.init(deviceChartRef.value);
  408. // 准备数据 - 使用专门定义的monthList
  409. let dates = [];
  410. let times = [];
  411. // 获取monthList数据
  412. if (monthList.value && Array.isArray(monthList.value) && monthList.value.length > 0) {
  413. dates = monthList.value.map(item => {
  414. // 确保item和item.date存在
  415. return item && item.date ? item.date : '';
  416. }).filter(date => date !== ''); // 过滤掉空值
  417. times = monthList.value.map(item => {
  418. // 确保item和item.times存在
  419. return item && typeof item.times !== 'undefined' ? item.times : 0;
  420. });
  421. }
  422. // 如果没有数据,显示提示信息
  423. if (dates.length === 0) {
  424. deviceChartInstance.clear();
  425. deviceChartInstance.setOption({
  426. title: {
  427. text: '暂无数据',
  428. left: 'center',
  429. top: 'center'
  430. }
  431. });
  432. return;
  433. }
  434. // 配置图表选项
  435. const option = {
  436. title: {
  437. text: '当月断纱趋势',
  438. left: 'center'
  439. },
  440. tooltip: {
  441. trigger: 'axis',
  442. formatter: (params) => {
  443. // 格式化日期显示,只显示日期部分
  444. const date = params[0].axisValue.split(' ')[0];
  445. return `${date}<br/>断纱次数: ${params[0].data}`;
  446. }
  447. },
  448. xAxis: {
  449. type: 'category',
  450. data: dates.map(date => date.split(' ')[0]), // 只显示日期部分
  451. name: '日期'
  452. },
  453. yAxis: {
  454. type: 'value',
  455. name: '断纱次数'
  456. },
  457. series: [{
  458. data: times,
  459. type: 'line',
  460. smooth: true,
  461. itemStyle: {
  462. color: '#5470c6'
  463. },
  464. areaStyle: {
  465. color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
  466. {offset: 0, color: 'rgba(84, 112, 198, 0.3)'},
  467. {offset: 1, color: 'rgba(84, 112, 198, 0.05)'}
  468. ])
  469. },
  470. markPoint: {
  471. data: [
  472. {type: 'max', name: '最大值'},
  473. {type: 'min', name: '最小值'}
  474. ]
  475. },
  476. markLine: {
  477. data: [
  478. {type: 'average', name: '平均值'}
  479. ]
  480. }
  481. }]
  482. };
  483. // 设置配置项
  484. deviceChartInstance.setOption(option);
  485. }
  486. // 计算统计数据
  487. function calculateStatistics(data) {
  488. if (!data || !Array.isArray(data)) {
  489. return;
  490. }
  491. // 计算设备总数
  492. const totalDevices = data.length;
  493. // 统计各趋势的数量
  494. const trendCounts = {
  495. "明显上升": 0,
  496. "上升": 0,
  497. "平稳": 0,
  498. "下降": 0,
  499. "明显下降": 0
  500. };
  501. data.forEach(item => {
  502. if (trendCounts.hasOwnProperty(item.result)) {
  503. trendCounts[item.result]++;
  504. }
  505. });
  506. // 只有在没有从后端获取到统计数据时才使用计算的统计数据
  507. if (!statistics.value) {
  508. statistics.value = {
  509. totalDevices,
  510. trendCounts
  511. };
  512. }
  513. }
  514. // 机台号变化处理
  515. function handleDeviceIdChange() {
  516. filterData();
  517. }
  518. // 分析结果变化处理
  519. function handleResultChange() {
  520. filterData();
  521. }
  522. // 过滤数据
  523. function filterData() {
  524. if (!calcStopList.value || !Array.isArray(calcStopList.value)) {
  525. filteredData.value = [];
  526. return;
  527. }
  528. // 如果没有过滤条件,返回所有数据
  529. if (!queryParams.value.deviceId && !queryParams.value.result) {
  530. filteredData.value = calcStopList.value;
  531. // 根据完整数据重新计算统计数据
  532. calculateStatistics(calcStopList.value);
  533. return;
  534. }
  535. filteredData.value = calcStopList.value.filter(item => {
  536. // 根据机台号过滤(数字模糊匹配)
  537. if (queryParams.value.deviceId &&
  538. item.deviceId &&
  539. item.deviceId.toString().indexOf(queryParams.value.deviceId) === -1) {
  540. return false;
  541. }
  542. // 根据分析结果过滤
  543. if (queryParams.value.result &&
  544. item.result !== queryParams.value.result) {
  545. return false;
  546. }
  547. return true;
  548. });
  549. // 根据过滤后的数据重新计算统计数据
  550. calculateStatistics(filteredData.value);
  551. }
  552. /** 过滤操作 */
  553. function handleFilter() {
  554. getList(); // 点击搜索按钮时重新从后端获取数据
  555. }
  556. /** 重置过滤操作 */
  557. function resetFilter() {
  558. proxy.resetForm("queryRef");
  559. queryParams.value.deviceId = null;
  560. queryParams.value.result = ""; // 重置分析结果
  561. queryParams.value.remark = 'day'; // 重置为按天统计
  562. queryParams.value.dataDate = getDefaultDate(); // 重置为默认日期
  563. queryParams.value.startTime = getDefaultDate(); // 重置为默认日期
  564. queryParams.value.endTime = getDefaultDate(); // 重置为默认日期
  565. filterData(); // 改为调用filterData而不是getList()
  566. }
  567. /** 处理分析结果点击事件 */
  568. function handleResultClick(row) {
  569. currentRow.value = row;
  570. chartOpen.value = true;
  571. // 在弹窗打开后初始化图表
  572. setTimeout(() => {
  573. initChart();
  574. }, 100);
  575. }
  576. // 初始化图表
  577. function initChart() {
  578. if (chartRef.value) {
  579. // 销毁之前的实例
  580. if (chartInstance) {
  581. chartInstance.dispose();
  582. }
  583. // 初始化新的实例
  584. chartInstance = echarts.init(chartRef.value);
  585. // 准备数据
  586. let dates = currentRow.value.list.map(item => item.date);
  587. const times = currentRow.value.list.map(item => item.times);
  588. // 如果是按月统计,只显示日期部分,不显示时间
  589. if (queryParams.value.remark === 'month') {
  590. dates = dates.map(date => {
  591. // 只保留日期部分 YYYY-MM-DD
  592. if (date.includes(' ')) {
  593. return date.split(' ')[0];
  594. }
  595. return date;
  596. });
  597. }
  598. // 配置图表选项
  599. const option = {
  600. title: {
  601. text: '断纱次数趋势图',
  602. left: 'center'
  603. },
  604. tooltip: {
  605. trigger: 'axis',
  606. formatter: function (params) {
  607. // 如果是按月统计,提示框中也只显示日期
  608. if (queryParams.value.remark === 'month') {
  609. const date = params[0].axisValue;
  610. const count = params[0].data;
  611. return `${date}<br />次数: ${count}`;
  612. }
  613. return params[0].axisValue + '<br />' + params[0].seriesName + ': ' + params[0].data;
  614. }
  615. },
  616. xAxis: {
  617. type: 'category',
  618. data: dates,
  619. name: '日期'
  620. },
  621. yAxis: {
  622. type: 'value',
  623. name: '次数(次)'
  624. },
  625. series: [{
  626. data: times,
  627. type: 'line',
  628. smooth: true,
  629. markPoint: {
  630. data: [
  631. { type: 'max', name: '最大值' },
  632. { type: 'min', name: '最小值' }
  633. ]
  634. },
  635. markLine: {
  636. data: [
  637. { type: 'average', name: '平均值' }
  638. ]
  639. }
  640. }]
  641. };
  642. // 设置配置项
  643. chartInstance.setOption(option);
  644. }
  645. }
  646. /** 关闭图表弹窗 */
  647. function closeChart() {
  648. chartOpen.value = false;
  649. // 销毁图表实例
  650. if (chartInstance) {
  651. chartInstance.dispose();
  652. chartInstance = null;
  653. }
  654. }
  655. // 群体分析算法
  656. function analyzeGroupPattern() {
  657. if (!statistics.value || !statistics.value.trendCounts) {
  658. groupAnalysisResult.value = null;
  659. return;
  660. }
  661. const { trendCounts } = statistics.value;
  662. // 计算上升趋势的设备数量(包括"上升"和"明显上升")
  663. const risingCount = (trendCounts["上升"] || 0) + (trendCounts["明显上升"] || 0);
  664. // 计算总设备数
  665. const totalCount = Object.values(trendCounts).reduce((sum, count) => sum + count, 0);
  666. // 如果没有设备数据,则不显示分析结果
  667. if (totalCount === 0) {
  668. groupAnalysisResult.value = null;
  669. return;
  670. }
  671. // 计算上升趋势设备占比
  672. const risingPercentage = (risingCount / totalCount) * 100;
  673. // 判断标准:如果超过60%的设备呈现上升趋势,则判断为群体上升现象
  674. if (risingPercentage > 60) {
  675. groupAnalysisResult.value = {
  676. type: "warning",
  677. message: `群体分析:${risingPercentage.toFixed(1)}%的设备断纱频次呈上升趋势,判断纱线或环境(温湿度)可能存在质量问题,请重点关注!`
  678. };
  679. } else {
  680. groupAnalysisResult.value = {
  681. type: "success",
  682. message: `群体分析:当前设备断纱频次趋势正常,无明显群体性质量问题。`
  683. };
  684. }
  685. }
  686. // 监听统计数据变化,触发群体分析
  687. watch(statistics, () => {
  688. analyzeGroupPattern();
  689. }, { deep: true });
  690. // 监听窗口大小变化,自动调整图表大小
  691. const handleResize = () => {
  692. if (trendChartInstance) {
  693. trendChartInstance.resize();
  694. }
  695. if (deviceChartInstance) {
  696. deviceChartInstance.resize();
  697. }
  698. if (chartInstance) {
  699. chartInstance.resize();
  700. }
  701. };
  702. onMounted(() => {
  703. getList();
  704. window.addEventListener('resize', handleResize);
  705. });
  706. onBeforeUnmount(() => {
  707. window.removeEventListener('resize', handleResize);
  708. // 销毁图表实例
  709. if (trendChartInstance) {
  710. trendChartInstance.dispose();
  711. }
  712. if (deviceChartInstance) {
  713. deviceChartInstance.dispose();
  714. }
  715. if (chartInstance) {
  716. chartInstance.dispose();
  717. }
  718. });
  719. getList();
  720. </script>
  721. <style scoped>
  722. .statistic-card {
  723. margin-bottom: 20px;
  724. background-color: #f5f7fa;
  725. border: 1px solid #ebeef5;
  726. border-radius: 4px;
  727. }
  728. .statistic-container {
  729. display: flex;
  730. align-items: center;
  731. flex-wrap: wrap;
  732. }
  733. .statistic-item {
  734. display: flex;
  735. align-items: center;
  736. margin-right: 20px;
  737. margin-bottom: 10px;
  738. }
  739. .statistic-label {
  740. font-weight: bold;
  741. margin-right: 5px;
  742. color: #606266;
  743. }
  744. .statistic-value {
  745. font-size: 16px;
  746. font-weight: bold;
  747. color: #409eff;
  748. }
  749. .statistic-divider {
  750. width: 1px;
  751. height: 20px;
  752. background-color: #dcdfe6;
  753. margin-right: 20px;
  754. margin-bottom: 10px;
  755. }
  756. .group-analysis {
  757. margin-top: 15px;
  758. padding-top: 15px;
  759. border-top: 1px solid #ebeef5;
  760. }
  761. </style>