大湾社区城市书房智慧大屏
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

311 lines
8.2 KiB

1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
  1. <template>
  2. <!-- 1. 样式抽离到class提升可读性和维护性 -->
  3. <div class="chart-container">
  4. <div ref="todayTypeRef" class="today-type-chart" />
  5. </div>
  6. </template>
  7. <script>
  8. import centerImg from '@/assets/images/circle-bg.png'
  9. export default {
  10. name: 'TodayTypePieChart', // 3. 增加组件名,便于调试和复用
  11. props: {
  12. todayAllNum: {
  13. type: Object,
  14. required: true, // 4. 修正拼写错误 require -> required
  15. default: () => ({}) // 5. 简化默认值写法
  16. }
  17. },
  18. data() {
  19. return {
  20. myChart: null,
  21. initDebounceTimer: null
  22. }
  23. },
  24. watch: {
  25. todayAllNum: {
  26. handler(val) {
  27. if (!val) return
  28. clearTimeout(this.initDebounceTimer)
  29. this.initDebounceTimer = setTimeout(() => {
  30. this.$nextTick(() => {
  31. this.initTodayCircle()
  32. })
  33. }, 300)
  34. },
  35. immediate: true,
  36. deep: true
  37. }
  38. },
  39. mounted() {
  40. this.initTodayCircle()
  41. },
  42. beforeDestroy() {
  43. this.destroyChart()
  44. },
  45. methods: {
  46. /**
  47. * 9. 销毁图表实例和定时器抽离通用逻辑
  48. */
  49. destroyChart() {
  50. clearTimeout(this.initDebounceTimer)
  51. if (this.myChart) {
  52. this.myChart.dispose()
  53. this.myChart = null
  54. }
  55. },
  56. /**
  57. * 10. 计算数组value总和抽离为独立方法提升可读性
  58. * @param {Array} arr - 数据数组
  59. * @returns {Number} 总和
  60. */
  61. calculateTotal(arr) {
  62. if (!Array.isArray(arr) || arr.length === 0) return 0
  63. return arr.reduce((total, item) => total + (item.value || 0), 0)
  64. },
  65. /**
  66. * 11. 构建图例格式化文本抽离独立方法
  67. * @param {String} name - 图例名称
  68. * @param {Array} data - 图表数据
  69. * @returns {Array|String} 格式化后的文本
  70. */
  71. formatLegendName(name, data) {
  72. const total = this.calculateTotal(data)
  73. const targetItem = data.find(item => item.name === name)
  74. if (!targetItem) return name
  75. const percentage = total > 0 ? `${((targetItem.value / total) * 100).toFixed(0)}%` : '0%'
  76. return [`{name|${name}}`, `{num|${percentage}}`]
  77. },
  78. /**
  79. * 初始化今日类型饼图
  80. */
  81. initTodayCircle() {
  82. // 12. 使用ref替代getElementById,更符合Vue规范
  83. const chartDom = this.$refs.todayTypeRef
  84. if (!chartDom) {
  85. console.warn('图表容器未找到')
  86. return
  87. }
  88. // 13. 数据处理抽离,提升代码可读性
  89. const chartData = [
  90. { value: this.todayAllNum.headerLib || 0, name: '大湾' },
  91. { value: this.todayAllNum.branchLib || 0, name: '张家湾' }
  92. ]
  93. // 14. 销毁旧实例,避免内存泄漏
  94. if (this.myChart) {
  95. this.myChart.dispose()
  96. }
  97. // 初始化图表
  98. this.myChart = this.$echarts.init(chartDom)
  99. // 15. 抽离option配置,拆分基础配置和媒体查询配置,提升可维护性
  100. const baseOption = {
  101. tooltip: {
  102. show: false,
  103. trigger: 'item',
  104. position: 'bottom',
  105. textStyle: {
  106. color: '#EEF6FF',
  107. fontSize: 18
  108. }
  109. },
  110. legend: {
  111. orient: 'vertical',
  112. right: 40,
  113. top: 55,
  114. itemWidth: 10,
  115. itemHeight: 10,
  116. icon: 'circle',
  117. selectedMode: false,
  118. data: ['大湾', '张家湾'],
  119. formatter: (name) => this.formatLegendName(name, chartData),
  120. textStyle: {
  121. rich: {
  122. name: {
  123. fontSize: 18,
  124. color: '#EEF6FF',
  125. padding: [20, 0, 20, 4],
  126. fontFamily: 'DingTalk_JinBuTi_Regular'
  127. },
  128. num: {
  129. fontSize: 24,
  130. fontWeight: 600,
  131. padding: [20, 0, 20, 15],
  132. color: '#4C90FF',
  133. fontFamily: 'DingTalk_JinBuTi_Regular'
  134. }
  135. }
  136. }
  137. },
  138. graphic: [
  139. {
  140. type: 'image',
  141. id: 'logo',
  142. left: '8.5%',
  143. top: '22%',
  144. z: -10,
  145. bounding: 'raw',
  146. rotation: 0,
  147. origin: [64.5, 32.5],
  148. scale: [1.0, 1.0],
  149. style: {
  150. image: centerImg,
  151. width: 132,
  152. height: 131,
  153. opacity: 1
  154. }
  155. }
  156. ],
  157. series: [
  158. {
  159. name: '今日累计借阅',
  160. type: 'pie',
  161. left: '-50%',
  162. radius: ['60%', '70%'],
  163. avoidLabelOverlap: false,
  164. label: {
  165. show: false,
  166. position: 'center'
  167. },
  168. labelLine: {
  169. show: true
  170. },
  171. itemStyle: {
  172. borderWidth: 2,
  173. borderColor: 'rgba(16,16,21,0.4)'
  174. },
  175. emphasis: {
  176. label: {
  177. show: true,
  178. formatter: ({ value, name }) => [ // 16. 解构赋值简化代码
  179. `{c| ${value}}`,
  180. `{b| ${name}}`
  181. ].join('\n'),
  182. rich: {
  183. c: {
  184. fontSize: 24,
  185. fontWeight: 600,
  186. color: '#317FFF',
  187. fontFamily: 'DingTalk_JinBuTi_Regular',
  188. lineHeight: 34
  189. },
  190. b: {
  191. fontSize: 18,
  192. color: '#fff',
  193. fontFamily: 'DingTalk_JinBuTi_Regular'
  194. }
  195. }
  196. }
  197. },
  198. color: ['#FFD14F', '#317FFF'],
  199. data: chartData
  200. }
  201. ]
  202. }
  203. const mediaOption = [
  204. {
  205. query: { maxWidth: 317 },
  206. option: {
  207. legend: {
  208. right: 30,
  209. top: 'center',
  210. textStyle: {
  211. rich: {
  212. name: { fontSize: 14 },
  213. num: { fontSize: 16 }
  214. }
  215. }
  216. },
  217. graphic: [
  218. {
  219. left: '8%',
  220. top: '26%',
  221. z: -10,
  222. bounding: 'raw',
  223. rotation: 0,
  224. origin: [64.5, 32.5],
  225. scale: [1.0, 1.0],
  226. style: {
  227. image: centerImg,
  228. width: 110,
  229. height: 110,
  230. opacity: 1
  231. }
  232. }
  233. ],
  234. series: [
  235. {
  236. type: 'pie',
  237. radius: ['50%', '55%'],
  238. emphasis: {
  239. label: {
  240. rich: {
  241. c: { fontSize: 22, lineHeight: 28 },
  242. b: { fontSize: 16 }
  243. }
  244. }
  245. }
  246. }
  247. ]
  248. }
  249. }
  250. ]
  251. // 合并配置并设置
  252. const option = { baseOption, media: mediaOption }
  253. this.myChart.setOption(option)
  254. // 自动轮播tooltip
  255. this.$LoopShowTooltip(this.myChart, baseOption, { loopSeries: true, interval: 4000 })
  256. // 17. 监听窗口大小变化,自适应图表
  257. window.addEventListener('resize', this.handleResize)
  258. },
  259. /**
  260. * 18. 窗口大小变化时重绘图表
  261. */
  262. handleResize() {
  263. if (this.myChart) {
  264. this.myChart.resize()
  265. }
  266. }
  267. },
  268. // 19. 移除无用的created钩子
  269. beforeUnmount() {
  270. // 20. 移除resize监听,避免内存泄漏
  271. window.removeEventListener('resize', this.handleResize)
  272. this.destroyChart()
  273. }
  274. }
  275. </script>
  276. <style lang="scss" scoped> // 21. 添加scoped,避免样式污染
  277. @import "~@/assets/styles/index.scss";
  278. @import "~@/assets/styles/font-some.css";
  279. // 22. 样式抽离,便于维护和修改
  280. .chart-container {
  281. height: calc(100% - 200px);
  282. display: flex;
  283. align-items: center;
  284. justify-content: flex-start;
  285. .today-type-chart {
  286. width: 400px;
  287. height: 230px;
  288. }
  289. }
  290. </style>