BarAndLine.vue 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405
  1. <template>
  2. <div v-if="spinning" class="spinning">
  3. <a-spin :spinning="true" />
  4. </div>
  5. <div v-if="!spinning" ref="chartRef" :style="{ height, width }"></div>
  6. </template>
  7. <script lang="ts">
  8. import { defineComponent, PropType, ref, Ref, reactive, watchEffect, watch, nextTick, onMounted } from 'vue';
  9. import { useECharts } from '/@/hooks/web/useECharts';
  10. import { getTableHeaderColumns } from '/@/hooks/web/useWebColumns';
  11. import EchartsUtil from '/@/utils/echartsUtil';
  12. import { merge } from 'lodash-es';
  13. type ChartColumn = {
  14. legend: string;
  15. seriesName: string;
  16. ymax: number;
  17. yname: string;
  18. linetype: string;
  19. yaxispos: string;
  20. color: string;
  21. sort: number;
  22. xRotate: number;
  23. dataIndex: string;
  24. };
  25. type RegValueItem = {
  26. type: string;
  27. value: number | string;
  28. name: string;
  29. color: string;
  30. lineType: 'dashed' | 'solid';
  31. };
  32. export default defineComponent({
  33. name: 'BarAndLine',
  34. props: {
  35. chartsColumns: {
  36. type: Array as PropType<ChartColumn[]>,
  37. default: () => [],
  38. },
  39. chartsColumnsType: {
  40. type: String,
  41. },
  42. chartsType: {
  43. type: String,
  44. default: '',
  45. },
  46. dataSource: {
  47. type: Array,
  48. default: () => [],
  49. },
  50. option: {
  51. type: Object,
  52. default: () => ({}),
  53. },
  54. xAxisPropType: {
  55. type: String,
  56. required: true,
  57. },
  58. width: {
  59. type: String as PropType<string>,
  60. default: '100%',
  61. },
  62. height: {
  63. type: String as PropType<string>,
  64. default: 'calc(100vh - 78px)',
  65. },
  66. regValues: {
  67. type: Array as PropType<RegValueItem[]>,
  68. default: () => [],
  69. },
  70. },
  71. emits: ['refresh'],
  72. setup(props, { emit }) {
  73. const spinning = ref<boolean>(true);
  74. const chartRef = ref<HTMLDivElement | null>(null);
  75. const chartType = ref<string>('');
  76. const { setOptions, echarts } = useECharts(chartRef as Ref<HTMLDivElement>);
  77. const chartData = props.chartsColumnsType ? getTableHeaderColumns(props.chartsColumnsType) : [];
  78. let chartsColumns = (props.chartsColumns.length > 0 ? props.chartsColumns : chartData) as ChartColumn[];
  79. let tempYmax: number[] = [];
  80. let tempYmin: number[] = [];
  81. // let groupedByColumns = {};
  82. const option = reactive({
  83. name: '',
  84. color: ['#7B68EE', '#0000CD', '#6495ED', '#00BFFF', '#AFEEEE', '#008080', '#00FA9A', '#2E8B57', '#FAFAD2', '#DAA520'],
  85. tooltip: {},
  86. grid: {},
  87. toolbox: {
  88. feature: {
  89. saveAsImage: {
  90. iconStyle: {
  91. borderColor: '#ffffff',
  92. },
  93. show: false,
  94. },
  95. },
  96. },
  97. dataZoom: {},
  98. legend: {
  99. textStyle: {
  100. color: '#ffffff', // 字体颜色
  101. },
  102. top: '20',
  103. },
  104. timeline: null,
  105. xAxis: {},
  106. yAxis: null,
  107. series: null,
  108. });
  109. let optionUtil;
  110. onMounted(() => {
  111. // 组件挂载后强制初始化一次(兜底,避免监听漏触发)
  112. if (props.dataSource && props.regValues) {
  113. initCharts(true);
  114. }
  115. });
  116. watchEffect(() => {
  117. props.dataSource && props.xAxisPropType && option.series && initCharts();
  118. });
  119. watch([() => props.chartsType, () => props.chartsColumns], ([newChartsType, newChartsColumns]) => {
  120. spinning.value = true;
  121. chartsColumns = newChartsColumns;
  122. optionUtil.initChartOption(newChartsType, newChartsColumns);
  123. spinning.value = false;
  124. initCharts(true);
  125. emit('refresh');
  126. });
  127. watch(
  128. () => [props.regValues],
  129. (newVal) => {
  130. if (props.regValues && props.regValues.length > 0) {
  131. initCharts(true);
  132. }
  133. // 数据变化时重新渲染图表
  134. },
  135. { deep: true, immediate: true }
  136. );
  137. function initChartsOption() {
  138. // debugger;
  139. optionUtil = new EchartsUtil(merge(option, props.option));
  140. optionUtil.initChartOption(props.chartsType, chartsColumns);
  141. }
  142. initChartsOption();
  143. function initCharts(isRefresh = false) {
  144. if (props.dataSource.length < 1) return;
  145. //轴数据
  146. let isFresh = false;
  147. if (option.series && option.series.length === chartsColumns.length) {
  148. let xAxisData = props.dataSource.map((item) => item[props.xAxisPropType]);
  149. chartsColumns = [...chartsColumns].filter((propType: any, index) => {
  150. if (!propType) return;
  151. if (props.chartsType == 'listMonitor') {
  152. option.series[index].type = 'bar';
  153. }
  154. console.log(option.series[index], '000===');
  155. option.series[index].data = props.dataSource.map((item) => Number(item[propType.dataIndex]) || 0);
  156. if (props.regValues && props.regValues.length > 0) {
  157. // 筛选匹配的regValues
  158. const matchRegValues = props.regValues
  159. .filter((e) => propType.dataIndex == e.type) // 匹配当前dataIndex
  160. .filter((item) => {
  161. //过滤value为null
  162. if (item.value === null || item.value === undefined || item.value === '') return false;
  163. const numValue = Number(item.value);
  164. return !isNaN(numValue); // 确保是有效数字
  165. });
  166. if (matchRegValues.length > 0) {
  167. option.series[index].markLine = {
  168. lineStyle: { width: 4 },
  169. // 多个匹配项生成多条标线
  170. data: matchRegValues
  171. .map((item, index) => [
  172. {
  173. yAxis: Number(item.value) ? Number(item.value) : 0,
  174. xAxis: 0,
  175. label: {
  176. show: true,
  177. position: 'end',
  178. formatter: item.name,
  179. color: '#fff',
  180. fontSize: 14,
  181. offset: [-80, 15],
  182. },
  183. lineStyle: {
  184. color: item.color,
  185. type: item.lineType,
  186. },
  187. },
  188. {
  189. yAxis: Number(item.value) ? Number(item.value) : 0,
  190. xAxis: 'max',
  191. label: {
  192. show: true,
  193. position: 'end',
  194. formatter: item.value,
  195. color: '#fff',
  196. fontSize: 14,
  197. offset: [-80, -10],
  198. },
  199. lineStyle: {
  200. color: item.color,
  201. type: item.lineType,
  202. width: 4,
  203. },
  204. },
  205. ])
  206. .flat(),
  207. };
  208. }
  209. }
  210. // console.log('nnn', option.series[index].data);
  211. // 这里动态计算echarts y轴最大值
  212. const regValuesNum =
  213. props.regValues && props.regValues.length > 0
  214. ? props.regValues.filter((e) => propType.dataIndex == e.type).map((item) => Number(item.value) || 0)
  215. : [];
  216. const max = Math.max(...option.series[index].data, ...regValuesNum);
  217. const min = Math.min(...option.series[index].data, ...regValuesNum);
  218. const digitCount = Math.ceil(Number(max));
  219. const minDigitCount = Math.floor(Number(min));
  220. // if (props.chartsType === 'history') {
  221. // const disLen = Math.abs(max - min);
  222. // propType.ymax = digitCount + disLen / 3;
  223. // propType.ymin = minDigitCount - disLen / 3 > 0 || minDigitCount < 0 ? minDigitCount - disLen / 3 : 0;
  224. // } else {
  225. // let yMax = 0,
  226. // yMin = 0;
  227. // if (digitCount < 2) {
  228. // if (max < 0.5) {
  229. // yMax = 1;
  230. // } else if (max < 0.9) {
  231. // yMax = 1.5;
  232. // } else if (max < 5) {
  233. // yMax = 5;
  234. // } else if (max < 10) {
  235. // yMax = 10;
  236. // }
  237. // } else if (digitCount < 3) {
  238. // const n = Number((Number(max.toFixed(0)) / 10).toFixed(0));
  239. // if (max < n * 10 + 5) {
  240. // yMax = (n + 1) * 10;
  241. // } else {
  242. // yMax = (n + 2) * 10;
  243. // }
  244. // } else if (digitCount < 4) {
  245. // const n = Number((Number(max.toFixed(0)) / 100).toFixed(0));
  246. // if (max < n * 100 + 50) {
  247. // yMax = (n + 1) * 100;
  248. // } else {
  249. // yMax = (n + 2) * 100;
  250. // }
  251. // } else if (digitCount < 5) {
  252. // const n = Number((Number(max.toFixed(0)) / 1000).toFixed(0));
  253. // if (max < n * 1000 + 500) {
  254. // yMax = (n + 1) * 1000;
  255. // } else {
  256. // yMax = (n + 1) * 1000 + 500;
  257. // }
  258. // } else if (digitCount < 6) {
  259. // const n = Number((Number(max.toFixed(0)) / 10000).toFixed(0));
  260. // if (max < n * 10000 + 5000) {
  261. // yMax = (n + 1) * 10000;
  262. // } else {
  263. // yMax = (n + 1) * 10000 + 5000;
  264. // }
  265. // }
  266. // if (minDigitCount < 2) {
  267. // if (min > 1.5) {
  268. // yMin = 1.0;
  269. // } else if (min > 5) {
  270. // yMin = 5;
  271. // } else {
  272. // yMin = 0;
  273. // }
  274. // } else if (minDigitCount < 3) {
  275. // const n = Number((Number(min.toFixed(0)) / 10).toFixed(0));
  276. // if (n > 1) {
  277. // yMin = (n - 1) * 10;
  278. // } else {
  279. // yMin = 10;
  280. // }
  281. // } else if (digitCount < 4) {
  282. // const n = Number((Number(min.toFixed(0)) / 100).toFixed(0));
  283. // if (n > 1) {
  284. // yMin = (n - 1) * 100;
  285. // } else {
  286. // yMin = 100;
  287. // }
  288. // } else if (digitCount < 5) {
  289. // const n = Number((Number(min.toFixed(0)) / 1000).toFixed(0));
  290. // if (n > 1) {
  291. // yMin = (n - 1) * 1000;
  292. // } else {
  293. // yMin = 1000;
  294. // }
  295. // } else if (digitCount < 6) {
  296. // const n = Number((Number(min.toFixed(0)) / 10000).toFixed(0));
  297. // if (n > 1) {
  298. // yMin = (n - 1) * 10000;
  299. // } else {
  300. // yMin = 10000;
  301. // }
  302. // }
  303. // propType.ymax = yMax;
  304. // propType.ymin = yMin;
  305. // }
  306. const disLen = Math.abs(max - min);
  307. if (propType.ymax && propType.ymin >= 0) {
  308. if (max > propType.ymax || min < propType.ymin) {
  309. propType.ymax = digitCount + disLen / 3;
  310. propType.ymin = minDigitCount - disLen / 3 > 0 || minDigitCount < 0 ? minDigitCount - disLen / 3 : 0;
  311. } else {
  312. propType.ymax = digitCount + digitCount / 4;
  313. propType.ymin = minDigitCount - digitCount / 4;
  314. }
  315. } else {
  316. propType.ymax = digitCount + disLen / 3;
  317. propType.ymin = minDigitCount - disLen / 3 > 0 || minDigitCount < 0 ? minDigitCount - disLen / 3 : 0;
  318. }
  319. if (propType.ymax == propType.ymin && propType.ymin == 0) {
  320. propType.ymax = 10;
  321. }
  322. return true;
  323. });
  324. // debugger;
  325. // 根据sort分组
  326. const groupedBy = {};
  327. for (const item of chartsColumns) {
  328. if (groupedBy[item.sort]) {
  329. groupedBy[item.sort].push(item);
  330. } else {
  331. groupedBy[item.sort] = [item];
  332. }
  333. }
  334. // 根据分组找ymax最大值
  335. const newChartsColumns: ChartColumn[] = [];
  336. let index = 0;
  337. for (let sortId in groupedBy) {
  338. const group = groupedBy[sortId];
  339. let ymax = group[0].ymax;
  340. let ymin = group[0].ymin;
  341. for (let i = 1; i < group.length; i++) {
  342. if (group[i].ymax > ymax) {
  343. ymax = group[i].ymax;
  344. }
  345. }
  346. for (let i = 1; i < group.length; i++) {
  347. if (group[i].ymin < ymin) {
  348. ymin = group[i].ymin;
  349. }
  350. }
  351. if (!tempYmax[index] || tempYmax[index] < ymax) {
  352. tempYmax[index] = ymax;
  353. isFresh = true;
  354. }
  355. if (tempYmin[index] > ymin) {
  356. tempYmin[index] = ymin;
  357. isFresh = true;
  358. }
  359. for (let i = 0; i < group.length; i++) {
  360. group[i].ymax = ymax;
  361. group[i].ymin = ymin;
  362. newChartsColumns.push(group[i]);
  363. }
  364. ++index;
  365. }
  366. chartsColumns = newChartsColumns;
  367. option.xAxis[0].data = xAxisData;
  368. if (isFresh) {
  369. spinning.value = true;
  370. optionUtil.initChartOption(props.chartsType, chartsColumns);
  371. spinning.value = false;
  372. initCharts(true);
  373. nextTick(() => {
  374. setOptions(option, true);
  375. emit('refresh');
  376. });
  377. } else {
  378. // console.log('echarts监测列表数据', option.xAxis[0].data);
  379. setOptions(option, isRefresh);
  380. }
  381. setOptions(option, isRefresh);
  382. }
  383. }
  384. setTimeout(() => {
  385. spinning.value = false;
  386. initCharts(true);
  387. }, 1000);
  388. return { chartRef, spinning };
  389. },
  390. });
  391. </script>
  392. <style lang="less" scoped>
  393. .spinning {
  394. display: flex;
  395. width: 100%;
  396. height: 100%;
  397. justify-content: center;
  398. align-items: center;
  399. }
  400. </style>