index.vue 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381
  1. <template>
  2. <div class="connectAnalysis">
  3. <div class="filter-area">
  4. <Row style="width:100%">
  5. <Col :span="6">
  6. <div class="filter-section param-section">
  7. <span class="filter-label">煤矿名称:</span>
  8. <div class="param-selector">
  9. <MineCascader style="width: 300px"></MineCascader>
  10. </div>
  11. </div>
  12. </Col>
  13. <Col :span="6">
  14. <!-- 时间选择 -->
  15. <div class="filter-section param-section">
  16. <span class="filter-label">采空区选择:</span>
  17. <Select ref="select" v-model:value="goafId" style="width: 300px" placeholder="请选择采空区">
  18. <SelectOption v-for="(item, index) in goafOption" :key="index" :value="item.value">{{ item.label }}
  19. </SelectOption>
  20. </Select>
  21. </div>
  22. </Col>
  23. <Col :span="6">
  24. <div class="filter-section param-section">
  25. <span class="filter-label">时间选择:</span>
  26. <RangePicker v-model:value="dateRange" format="YYYY-MM-DD HH:mm:ss" :placeholder="['开始时间', '结束时间']"
  27. style="width: 300px" :show-time="{ format: 'HH:mm:ss' }" @change="changeTime" />
  28. </div>
  29. </Col>
  30. <Col :span="6">
  31. <div class="filter-section">
  32. <Button type="primary" @click="generateChart">生成</Button>
  33. </div>
  34. </Col>
  35. </Row>
  36. <!-- <Row style="width:100%">
  37. <Col :span="8">
  38. <div class="filter-section param-section">
  39. <span class="filter-label">参数选择:</span>
  40. <div class="param-selector">
  41. <Input v-model="selectedParamsText" placeholder="请选择监测参数" readonly style="width: 300px" />
  42. <Button type="primary" @click="showTree = !showTree">+</Button>
  43. <div class="tree-popup" v-if="showTree">
  44. <BasicTree :treeData="treeData" :checkable="true" defaultExpandAll @check="handleTreeCheck" :checkedKeys="checkedTreeKeys" />
  45. </div>
  46. </div>
  47. </div>
  48. </Col>
  49. </Row> -->
  50. </div>
  51. <!-- 动态图表区域-->
  52. <div class="chart-area">
  53. <div class="chart-item" style="flex: 1 1 100%">
  54. <div class="chart-placeholder">
  55. <template v-if="generatedChartData.length">
  56. <CustomChart :chart-data="generatedChartData" :chart-config="generatedChartConfig"
  57. style="height: 100%; width: 100%" />
  58. </template>
  59. <template v-else-if="isChartGenerated">
  60. <div class="empty-chart">暂无匹配数据,请调整筛选条件</div>
  61. </template>
  62. <template v-else>
  63. <div class="empty-chart">请选择时间范围和参数,点击"生成"查看数据</div>
  64. </template>
  65. </div>
  66. </div>
  67. </div>
  68. </div>
  69. </template>
  70. <script setup lang="ts">
  71. import { computed, ref, onMounted, watchEffect } from 'vue';
  72. import dayjs from 'dayjs';
  73. // import { treeData, historicalMockChartData } from './connectAnalysis.data'; // 引入模拟数据
  74. import { Select, SelectOption, Row, Col, DatePicker, Button, message, Input, } from 'ant-design-vue';
  75. // import { BasicTree } from '/@/components/Tree/index';
  76. import CustomChart from '/@/components/Configurable/detail/CustomChart.vue';
  77. import MineCascader from '@/components/Form/src/jeecg/components/MineCascader/MineCascader.vue'
  78. import { getGoafHistory, getGoafList } from './connectAnalysis.api'
  79. import { storeToRefs } from 'pinia';
  80. import { useMineStore } from '/@/store/modules/mine';
  81. import { useRouter } from 'vue-router';
  82. // 组件注册
  83. const RangePicker: any = DatePicker.RangePicker;
  84. const { currentRoute } = useRouter();
  85. const mineCode = ref<any>(currentRoute.value['query']['mineCode'])//传递过来的矿ID
  86. // 筛选相关响应式数据
  87. const dateRange = ref([dayjs().add(-30, 'day'), dayjs()]); // 默认时间范围(近1天)
  88. // const selectedParamsText = ref(''); // 参数选择框显示文本
  89. // const showTree = ref(false); // 控制树形选择器显示/隐藏
  90. // const checkedTreeKeys = ref([]); // 树形选中的key
  91. const selectedParams = ref(['coVal', 'ch4Val', 'c2h4Val', 'c2h2Val', 'co2Val', 'o2Val', 'sourcePressure', 'temperature']); // 选中的参数(实际用于图表)
  92. const generatedChartData = ref<any[]>([]); // 生成的图表数据
  93. const generatedChartConfig = ref({}); // 生成的图表配置
  94. const isChartGenerated = ref(false); // 是否已点击生成
  95. const goafId = ref('')//采空区id
  96. const goafOption = ref<any[]>([])//采空区列表
  97. const filteredData = ref<any[]>([])//曲线数据
  98. const mineStore = useMineStore();
  99. const { getMine, getMineCode, getMinePath, getMineTree } = storeToRefs(mineStore);
  100. const innerValue = computed(() => getMinePath.value.map((e) => e.fax));
  101. // Tree Key 与参数名映射(关键:关联树形节点和实际参数)
  102. const treeKeyToParamMap = computed(() => ({
  103. '0-0-0': 'coVal',
  104. '0-0-1': 'ch4Val',
  105. '0-0-2': 'c2h4Val',
  106. '0-0-3': 'c2h2Val',
  107. '0-0-4': 'co2Val',
  108. '0-0-5': 'o2Val',
  109. '1-1-0': 'sourcePressure',
  110. // '1-1-1': 'outerPressure',
  111. // '1-1-2': 'pressureDiff',
  112. '2-2': 'temperature',
  113. }));
  114. // 参数标签映射(图表系列名称)
  115. const paramLabelMap = computed(() => ({
  116. coVal: 'CO浓度(ppm)',
  117. ch4Val: 'CH4浓度(%)',
  118. c2h4Val: 'C2H4浓度(ppm)',
  119. c2h2Val: 'C2H2浓度(ppm)',
  120. co2Val: 'CO2浓度(%)',
  121. o2Val: 'O2浓度(%)',
  122. sourcePressure: '压力(kPa)',
  123. temperature: '温度(℃)',
  124. // 'innerPressure': '内压力(kPa)',
  125. // 'outerPressure': '外压力(kPa)',
  126. // 'pressureDiff': '压差(kPa)',
  127. // 'temperature': '温度(℃)',
  128. }));
  129. // 参数颜色映射
  130. const paramColorMap = computed(() => ({
  131. coVal: '#f5222d', // 红色
  132. ch4Val: '#1890ff', // 蓝色
  133. c2h4Val: '#faad14', // 橙色
  134. c2h2Val: '#52c41a', // 绿色
  135. co2Val: '#722ed1', // 紫色
  136. o2Val: '#13c2c2', // 青色
  137. sourcePressure: '#ff4d4f', // 浅红
  138. // 'outerPressure': '#40a9ff',// 浅蓝
  139. // 'pressureDiff': '#fa8c16', // 浅橙
  140. temperature: '#9254de', // 浅紫
  141. }));
  142. // // 树形选择事件处理
  143. // const handleTreeCheck = (checkedKeys) => {
  144. // checkedTreeKeys.value = checkedKeys;
  145. // // 转换为实际参数名
  146. // const params = checkedKeys
  147. // .map(key => treeKeyToParamMap.value[key])
  148. // .filter(param => param); // 过滤无效参数
  149. // selectedParams.value = params;
  150. // console.log(selectedParams.value, ' selectedParams')
  151. // // 更新输入框显示文本
  152. // const paramLabels = params.map(param => paramLabelMap.value[param]);
  153. // selectedParamsText.value = paramLabels.join('、');
  154. // console.log(selectedParamsText.value, ' selectedParamsText')
  155. // };
  156. function changeTime(val) {
  157. dateRange.value[0] = val[0]
  158. dateRange.value[1] = val[1]
  159. }
  160. // 生成折线图核心逻辑
  161. async function generateChart() {
  162. // showTree.value = false
  163. //获取采空区历史数据列表
  164. let startTime = dateRange.value[0].format('YYYY-MM-DD HH:mm:ss')
  165. let endTime = dateRange.value[1].format('YYYY-MM-DD HH:mm:ss')
  166. let res = await getGoafHistory({ pageNo: 1, pageSize: 100, startTime: startTime, endTime: endTime, goafId: goafId.value })
  167. if (res && res.records) {
  168. filteredData.value = res.records
  169. isChartGenerated.value = false
  170. } else {
  171. filteredData.value.length = 0
  172. isChartGenerated.value = true;
  173. }
  174. console.log(filteredData.value, 'filteredData')
  175. // 2. 构建图表数据结构(适配 CustomChart 的 line 类型)
  176. const timeMap = new Map();
  177. // 修复变量名:filteredRawData → filteredData(之前未定义)
  178. filteredData.value.forEach((item) => {
  179. const timeStr = dayjs(item.createTime).format('YYYY-MM-DD HH:mm:ss');
  180. if (!timeMap.has(timeStr)) {
  181. timeMap.set(timeStr, { time: timeStr });
  182. }
  183. // 只保留选中的参数数据
  184. selectedParams.value.forEach((param) => {
  185. if (item[param] !== undefined) {
  186. timeMap.get(timeStr)[param] = item[param];
  187. }
  188. });
  189. });
  190. // 转换为数组并按时间排序
  191. const chartData = Array.from(timeMap.values()).sort((a, b) => {
  192. return dayjs(a.time).valueOf() - dayjs(b.time).valueOf();
  193. });
  194. generatedChartData.value = chartData;
  195. console.log(generatedChartData.value, 'generatedChartData')
  196. // 3. 构建图表配置(折线图类型,完善适配逻辑)
  197. generatedChartConfig.value = {
  198. type: 'line', // 折线图
  199. clear: true, // 每次生成清空之前的图表
  200. legend: { show: true, top: 10, right: 10, textStyle: { color: '#fff', fontSize: 14 } },
  201. xAxis: [
  202. {
  203. type: 'category',
  204. axisLabel: {
  205. rotate: 30,
  206. textStyle: {
  207. color: '#fff',
  208. },
  209. formatter: (value) => dayjs(value).format('HH:mm:ss'),
  210. interval: Math.max(1, Math.floor(chartData.length / 10)), // 控制x轴标签密度
  211. },
  212. },
  213. ],
  214. yAxis: selectedParams.value.map((param) => ({
  215. type: 'value',
  216. axisLabel: {
  217. textStyle: {
  218. color: '#fff',
  219. },
  220. },
  221. name: paramLabelMap.value[param].split('(')[1].replace(')', ''), // 显示单位
  222. // nameTextStyle: { color: paramColorMap.value[param] },
  223. nameTextStyle: { color: '#fff' },
  224. axisLine: { lineStyle: { color: paramColorMap.value[param] } },
  225. splitLine: { lineStyle: { opacity: 0.1 } },
  226. })),
  227. series: selectedParams.value.map((param, index) => ({
  228. name: paramLabelMap.value[param],
  229. type: 'line',
  230. readFrom: '', // 适配 CustomChart 的 baseSeries 读取逻辑
  231. label: paramLabelMap.value[param],
  232. xprop: 'time', // 对应图表数据的 x 字段(时间)
  233. yprop: param, // 对应图表数据的 y 字段(参数值)
  234. color: paramColorMap.value[param],
  235. smooth: true,
  236. symbol: 'circle',
  237. symbolSize: 4,
  238. yAxisIndex: index,
  239. })),
  240. tooltip: {
  241. trigger: 'axis',
  242. axisPointer: { type: 'cross' },
  243. formatter: (params) => {
  244. let tooltipHtml = `<div>${dayjs(params[0].axisValue).format('YYYY-MM-DD HH:mm:ss')}</div>`;
  245. params.forEach((param) => {
  246. tooltipHtml += `<div style="color: ${param.color}">${param.seriesName}: ${param.value[1]} ${param.seriesName.split('(')[1].replace(')', '')}</div>`;
  247. });
  248. return tooltipHtml;
  249. },
  250. },
  251. grid: { left: 60, top: 40, right: 60, bottom: 60 },
  252. };
  253. // 无数据提示
  254. if (chartData.length === 0) {
  255. message.info('当前筛选条件下无数据');
  256. }
  257. }
  258. //获取采空区列表
  259. async function getGoafListData() {
  260. let res = await getGoafList({ mineCode: innerValue.value[innerValue.value.length - 1] })
  261. if (res.length) {
  262. goafOption.value = res.map(el => {
  263. return {
  264. label: el.devicePos,
  265. value: el.id,
  266. }
  267. })
  268. goafId.value = goafId.value ? goafId.value : goafOption.value[0]['value']
  269. }
  270. }
  271. watchEffect(() => {
  272. innerValue.value && getGoafListData();
  273. });
  274. </script>
  275. <style lang="less" scoped>
  276. .connectAnalysis {
  277. padding: 16px;
  278. .filter-area {
  279. display: flex;
  280. flex-wrap: wrap;
  281. gap: 16px;
  282. margin-bottom: 20px;
  283. padding: 20px;
  284. border: 1px solid #f0f0f0;
  285. border-radius: 10px;
  286. background: @card-bg-color;
  287. align-items: center;
  288. }
  289. .filter-section {
  290. display: flex;
  291. align-items: center;
  292. gap: 8px;
  293. }
  294. .filter-label {
  295. color: #666;
  296. min-width: 80px;
  297. flex-shrink: 0;
  298. font-weight: 500;
  299. }
  300. .param-section {
  301. flex: 1;
  302. // min-width: 300px;
  303. }
  304. .param-selector {
  305. display: flex;
  306. align-items: center;
  307. gap: 8px;
  308. position: relative;
  309. }
  310. .tree-popup {
  311. position: absolute;
  312. top: 100%;
  313. left: 0;
  314. margin-top: 8px;
  315. width: 300px;
  316. max-height: 300px;
  317. overflow-y: auto;
  318. background: #fff;
  319. border: 1px solid #e8e8e8;
  320. border-radius: 4px;
  321. box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
  322. z-index: 100;
  323. padding: 8px;
  324. }
  325. .chart-area {
  326. display: flex;
  327. flex-wrap: wrap;
  328. gap: 16px;
  329. padding: 20px;
  330. border: 1px solid #f0f0f0;
  331. border-radius: 10px;
  332. background: @card-bg-color;
  333. }
  334. .chart-item {
  335. flex: 1;
  336. min-width: 200px;
  337. }
  338. .chart-placeholder {
  339. width: 100%;
  340. height: 300px;
  341. border-radius: 4px;
  342. overflow: hidden;
  343. background: #333;
  344. border: 1px solid #eee;
  345. }
  346. .empty-chart {
  347. width: 100%;
  348. height: 100%;
  349. display: flex;
  350. align-items: center;
  351. justify-content: center;
  352. color: #999;
  353. font-size: 14px;
  354. }
  355. }
  356. </style>