index.tsx 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353
  1. import { computed, defineComponent, onBeforeUnmount, onMounted, reactive, ref } from 'vue'
  2. import styles from './index.module.less'
  3. import CircleProgress from './component/CircleProgress'
  4. import iconBackup from './image/icon-backup.png'
  5. import iconEnsemble from './image/icon-ensemble.png'
  6. import * as echarts from 'echarts'
  7. import { listenerMessage, postMessage, removeListenerMessage } from '@/helpers/native-message'
  8. type EChartsOption = echarts.EChartsOption
  9. interface ISubjectItem {
  10. /** 声部名称 */
  11. subjectName: string
  12. /**达标率 */
  13. practiceRate: number
  14. /** 达标人数 */
  15. passNum: number
  16. /** 未达标人数 */
  17. noPassNum: number
  18. /** 非会员 */
  19. noMemberNum: number
  20. }
  21. const symbolData: any = {
  22. type: 'line',
  23. symbol: 'circle',
  24. symbolSize: 6,
  25. triggerLineEvent: true,
  26. stack: 'Total'
  27. }
  28. export default defineComponent({
  29. name: 'subject-echarts',
  30. setup() {
  31. const activeData = reactive({
  32. index: -1,
  33. sum: {
  34. /**达标率 */
  35. practiceRate: 0,
  36. /**达标人数 */
  37. passNum: 0,
  38. /**未达标人数 */
  39. noPassNum: 0,
  40. /**未会员 */
  41. noMemberNum: 0
  42. },
  43. practiceThisWeeks: [] as ISubjectItem[],
  44. colors: [
  45. {
  46. color: '#FF8057',
  47. borderColor: 'rgba(255,128,87,0.5)',
  48. text: '达标率',
  49. select: true
  50. },
  51. {
  52. color: '#2FC58D',
  53. borderColor: 'rgba(47,197,141,0.5)',
  54. text: '达标',
  55. select: true
  56. },
  57. {
  58. color: '#4A99FF',
  59. borderColor: 'rgba(74,153,255,0.5)',
  60. text: '未达标',
  61. select: true
  62. },
  63. {
  64. color: '#9884BA',
  65. borderColor: 'rgba(152,132,186,0.5)',
  66. text: '非会员',
  67. select: true
  68. }
  69. ]
  70. })
  71. const subjects = computed(() => {
  72. return activeData.practiceThisWeeks.map((n) => n.subjectName)
  73. })
  74. let myChart: echarts.ECharts
  75. const handleInit = (data: any) => {
  76. const { content } = data
  77. if (!content) return
  78. if (myChart){
  79. myChart.clear()
  80. }
  81. activeData.sum = content.sum || {}
  82. activeData.practiceThisWeeks =
  83. content.practiceThisWeeks
  84. // .concat(
  85. // ...[
  86. // {
  87. // memberNum: 1,
  88. // noMemberNum: 10,
  89. // noPassNum: 12,
  90. // passNum: 30,
  91. // practiceRate: 20,
  92. // subjectName: '单簧管',
  93. // trainingNum: 0
  94. // },
  95. // {
  96. // memberNum: 1,
  97. // noMemberNum: 20,
  98. // noPassNum: 31,
  99. // passNum: 10,
  100. // practiceRate: 30,
  101. // subjectName: '萨克斯',
  102. // trainingNum: 0
  103. // },
  104. // {
  105. // memberNum: 1,
  106. // noMemberNum: 10,
  107. // noPassNum: 16,
  108. // passNum: 40,
  109. // practiceRate: 20,
  110. // subjectName: '小号',
  111. // trainingNum: 0
  112. // },
  113. // {
  114. // memberNum: 1,
  115. // noMemberNum: 10,
  116. // noPassNum: 16,
  117. // passNum: 40,
  118. // practiceRate: 20,
  119. // subjectName: '小号',
  120. // trainingNum: 0
  121. // }
  122. // ]
  123. // )
  124. || []
  125. const chartDom = document.getElementById('subjectEcharts')!
  126. myChart = echarts.init(chartDom)
  127. const option: EChartsOption = {
  128. tooltip: {
  129. trigger: 'axis',
  130. showContent: false,
  131. axisPointer: {
  132. type: 'line',
  133. lineStyle: {
  134. width: 40,
  135. opacity: 0.5,
  136. cap: 'square'
  137. }
  138. }
  139. },
  140. grid: {
  141. left: 5,
  142. top: 5,
  143. right: 5,
  144. bottom: 5,
  145. containLabel: true
  146. },
  147. legend: {
  148. type: 'scroll',
  149. right: 12,
  150. itemGap: 0,
  151. itemWidth: 2,
  152. itemStyle: {
  153. opacity: 0
  154. },
  155. lineStyle: {
  156. width: 0,
  157. opacity: 0
  158. },
  159. textStyle: {
  160. opacity: 0
  161. }
  162. },
  163. xAxis: {
  164. type: 'category',
  165. axisLabel: {
  166. interval: 0,
  167. color: '#333',
  168. fontSize: 9
  169. },
  170. axisTick: {
  171. show: false
  172. },
  173. boundaryGap: false,
  174. data: subjects.value
  175. },
  176. yAxis: [
  177. {
  178. type: 'value',
  179. position: 'left',
  180. axisLine: {
  181. show: true,
  182. lineStyle: {
  183. color: '#999'
  184. }
  185. },
  186. axisLabel: {
  187. formatter: '{value}'
  188. }
  189. },
  190. {
  191. type: 'value',
  192. position: 'right',
  193. axisLine: {
  194. show: true,
  195. lineStyle: {
  196. color: '#999'
  197. }
  198. },
  199. axisLabel: {
  200. formatter: '{value} %'
  201. }
  202. }
  203. ],
  204. series: ['practiceRate', 'passNum', 'noPassNum', 'noMemberNum'].map(
  205. (key: string, index: number) => {
  206. return {
  207. name: activeData.colors[index].text,
  208. color: activeData.colors[index].color,
  209. ...symbolData,
  210. yAxisIndex: index === 0 ? 1 : 0,
  211. data: activeData.practiceThisWeeks.map((n) => n[key])
  212. }
  213. }
  214. )
  215. }
  216. option && myChart.setOption(option)
  217. myChart.on('highlight', function (params: any) {
  218. console.log("🚀 ~ params:", params)
  219. activeData.index = params.batch[0].dataIndex
  220. })
  221. // console.log('🚀 ~ myChart:', myChart)
  222. }
  223. const handleAction = (index: number) => {
  224. activeData.index = index
  225. myChart?.dispatchAction({
  226. type: 'showTip',
  227. seriesIndex: 0,
  228. dataIndex: index
  229. })
  230. }
  231. const changeLegend = (item: any) => {
  232. myChart?.dispatchAction({
  233. type: item.select ? 'legendUnSelect' : 'legendSelect',
  234. // 图例名称
  235. name: item.text
  236. })
  237. item.select = !item.select
  238. }
  239. onMounted(() => {
  240. // handleInit({ content: { practiceThisWeeks: [], sum: {} } })
  241. listenerMessage('setAccomanyEcharts', handleInit)
  242. postMessage({
  243. api: 'setAccomanyEcharts'
  244. })
  245. })
  246. onBeforeUnmount(() => {
  247. removeListenerMessage('setAccomanyEcharts', handleInit)
  248. })
  249. return () => (
  250. <div class={styles.subjectEcharts}>
  251. <div class={[styles.container, styles.ensemble]}>
  252. <div class={styles.head}>
  253. <div class={styles.headLeft} onClick={() => handleInit({ content: { practiceThisWeeks: [], sum: {} } })}>
  254. <img class={styles.icon} src={iconEnsemble} />
  255. <div>总体情况</div>
  256. </div>
  257. </div>
  258. <div class={styles.content}>
  259. <CircleProgress
  260. value={activeData.sum.practiceRate}
  261. size={80}
  262. color="#FF8057"
  263. strokeWidth={6}
  264. duration={3000}
  265. />
  266. <div class={styles.items}>
  267. <div class={styles.item}>
  268. <div class={styles.itemNum}>
  269. <span class={styles.rect}></span>
  270. <span style={{ color: '#FF8057' }}>{activeData.sum.passNum}</span>
  271. </div>
  272. <div class={styles.itemTitle}>达标人数</div>
  273. </div>
  274. <div class={[styles.item, styles.line]}>
  275. <div class={styles.itemNum}>
  276. <span class={styles.rect} style={{ background: '#FFE7DF' }}></span>
  277. <span>{activeData.sum.noPassNum}</span>
  278. </div>
  279. <div class={styles.itemTitle}>未达标人数</div>
  280. </div>
  281. <div class={styles.item}>
  282. <div class={styles.itemNum}>
  283. <span>{activeData.sum.noMemberNum}</span>
  284. </div>
  285. <div class={styles.itemTitle}>非会员人数</div>
  286. </div>
  287. </div>
  288. </div>
  289. </div>
  290. <div class={[styles.container, styles.ensemble]}>
  291. <div class={styles.head}>
  292. <div class={styles.headLeft}>
  293. <img class={styles.icon} src={iconBackup} />
  294. <div>声部情况</div>
  295. </div>
  296. <div class={styles.headRight}>
  297. {activeData.colors.map((c) => (
  298. <div
  299. style={
  300. c.select
  301. ? { color: '#fff', borderColor: c.color, backgroundColor: c.color }
  302. : { color: c.color, borderColor: c.borderColor }
  303. }
  304. onClick={() => changeLegend(c)}
  305. >
  306. {c.text}
  307. </div>
  308. ))}
  309. </div>
  310. </div>
  311. <div id="subjectEcharts" class={styles.echartsMain}></div>
  312. {!activeData.practiceThisWeeks.length && <div class={styles.emtry}>暂无练习记录</div>}
  313. </div>
  314. <div class={[styles.container, styles.subjectWrap]}>
  315. {activeData.practiceThisWeeks.map((item, index: number) => (
  316. <div
  317. class={[styles.listItem, index === activeData.index ? styles.listItemActive : '']}
  318. onClick={() => handleAction(index)}
  319. >
  320. <div class={styles.dot}></div>
  321. <div class={styles.itemLeft}>
  322. <div class={styles.subjectName}>{item.subjectName}</div>
  323. <div class={styles.subjectType}>声部</div>
  324. </div>
  325. <div class={styles.listitems}>
  326. {activeData.colors.map((c, index: number) => {
  327. return (
  328. <div class={styles.item}>
  329. <div class={styles.itemNum}>
  330. <span style={{ color: c.color }}>{item.practiceRate}</span>
  331. </div>
  332. <div class={styles.itemTitle}>
  333. {c.text}
  334. {index === 0 ? '' : '人数'}
  335. </div>
  336. </div>
  337. )
  338. })}
  339. </div>
  340. </div>
  341. ))}
  342. </div>
  343. </div>
  344. )
  345. }
  346. })