CurrentStudent.tsx 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205
  1. import {
  2. PropType,
  3. defineComponent,
  4. nextTick,
  5. onMounted,
  6. ref,
  7. toRefs,
  8. watch
  9. } from 'vue';
  10. import styles from '../index.module.less';
  11. import icons from '../icons.json';
  12. import { Badge, Image, Skeleton, SkeletonImage, SkeletonParagraph } from 'vant';
  13. import * as echarts from 'echarts';
  14. import { IGradeDistribution } from '../type';
  15. import icon_1 from '../image/icon_1.png';
  16. type EChartsOption = echarts.EChartsOption;
  17. const colors = [
  18. ['#94C2FD', '#5B8FF9'],
  19. ['#FBE031', '#F6BD16'],
  20. ['#93EED2', '#5AD8A6'],
  21. ['#F5A181', '#E8684A'],
  22. ['#96A9C4', '#5D7092'],
  23. ['#A6E6F7', '#6DC8EC'],
  24. ['#FFCA7D', '#FF9530'],
  25. ['#DBC6FF', '#B87BDD'],
  26. ['#D2FFC4', '#92DE97'],
  27. ['#94C2FD', '#5B8FF9'],
  28. ['#FBE031', '#F6BD16'],
  29. ['#93EED2', '#5AD8A6'],
  30. ['#F5A181', '#E8684A'],
  31. ['#96A9C4', '#5D7092'],
  32. ['#A6E6F7', '#6DC8EC'],
  33. ['#FFCA7D', '#FF9530'],
  34. ['#DBC6FF', '#B87BDD'],
  35. ['#D2FFC4', '#92DE97'],
  36. ['#94C2FD', '#5B8FF9'],
  37. ['#FBE031', '#F6BD16'],
  38. ['#93EED2', '#5AD8A6'],
  39. ['#F5A181', '#E8684A'],
  40. ['#96A9C4', '#5D7092'],
  41. ['#A6E6F7', '#6DC8EC'],
  42. ['#FFCA7D', '#FF9530'],
  43. ['#DBC6FF', '#B87BDD'],
  44. ['#D2FFC4', '#92DE97']
  45. ];
  46. export default defineComponent({
  47. name: 'CurrentStudent',
  48. props: {
  49. list: {
  50. type: Array as PropType<IGradeDistribution[]>,
  51. default: () => []
  52. }
  53. },
  54. setup(props) {
  55. const firstInit = ref(false);
  56. const echratsRef = ref();
  57. const { list } = toRefs(props);
  58. watch(
  59. () => list.value,
  60. () => {
  61. firstInit.value = true;
  62. nextTick(() => {
  63. handleInit();
  64. });
  65. }
  66. );
  67. let myChart: echarts.ECharts;
  68. const handleInit = () => {
  69. if (!list.value.length) return;
  70. if (myChart) {
  71. myChart.dispose();
  72. }
  73. myChart = echarts.init(echratsRef.value);
  74. const option: EChartsOption = {
  75. title: {
  76. text: list.value
  77. .reduce((total, item: IGradeDistribution) => {
  78. total += item.studentNum;
  79. return total;
  80. }, 0)
  81. .toString(),
  82. subtext: '在读人数',
  83. textAlign: 'center',
  84. left: '48%',
  85. top: '36%',
  86. textStyle: {
  87. fontSize: '22px',
  88. fontWeight: 'bold',
  89. color: '#333',
  90. fontFamily: 'DINAlternate-Bold, DINAlternate'
  91. },
  92. subtextStyle: {
  93. fontSize: '12px',
  94. color: '#777'
  95. }
  96. },
  97. tooltip: {
  98. trigger: 'item',
  99. confine: true,
  100. borderWidth: 0
  101. },
  102. series: [
  103. {
  104. type: 'pie',
  105. radius: ['40%', '62%'],
  106. itemStyle: {
  107. borderRadius: 2,
  108. borderColor: '#fff',
  109. borderWidth: 1
  110. },
  111. label: {
  112. show: false
  113. },
  114. labelLine: {
  115. show: false
  116. },
  117. avoidLabelOverlap: false,
  118. data: list.value.map((item: IGradeDistribution, index: number) => {
  119. return {
  120. value: item.studentNum,
  121. name: item.grade,
  122. itemStyle: {
  123. color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
  124. { offset: 0, color: colors[index][0] },
  125. { offset: 1, color: colors[index][1] }
  126. ])
  127. }
  128. };
  129. })
  130. }
  131. ]
  132. };
  133. option && myChart.setOption(option);
  134. };
  135. return () => (
  136. <div class={styles.item}>
  137. <div class={styles.top}>
  138. <Image class={styles.iconRight} src={icons.right} />
  139. <span>在读学员</span>
  140. <Image class={styles.iconLeft} src={icons.left} />
  141. </div>
  142. <div class={styles.itemTop}>
  143. <Image class={styles.icon} src={icons[1]} />
  144. <div class={styles.title}>年级分布</div>
  145. <div class={styles.des}>(单位:人)</div>
  146. </div>
  147. {!firstInit.value && (
  148. <Skeleton class={[styles.gradeContainer, styles.itemEmtry]}>
  149. {{
  150. template: () => (
  151. <>
  152. <div
  153. class={styles.gradeEcharts}
  154. style={{
  155. display: 'flex',
  156. justifyContent: 'center',
  157. alignItems: 'center'
  158. }}>
  159. <SkeletonImage style={{ width: '80%', height: '80%' }} />
  160. </div>
  161. <div style={{ flex: 1, marginLeft: '16px' }}>
  162. <SkeletonParagraph />
  163. <SkeletonParagraph />
  164. <SkeletonParagraph />
  165. <SkeletonParagraph />
  166. <SkeletonParagraph />
  167. </div>
  168. </>
  169. )
  170. }}
  171. </Skeleton>
  172. )}
  173. <div
  174. style={{ display: list.value.length ? '' : 'none' }}
  175. class={styles.gradeContainer}>
  176. <div class={styles.gradeEcharts} ref={echratsRef}></div>
  177. <div class={styles.tags}>
  178. {list.value.map((item: IGradeDistribution, i: number) => (
  179. <div class={styles.tag}>
  180. <Badge dot color={`linear-gradient(180deg, ${colors[i][0]} 0%, ${colors[i][1]} 100%)`} />
  181. <div class={styles.tagTitle}>{item.grade}</div>
  182. <span class={styles.tagNum}>{item.studentNum}</span>
  183. </div>
  184. ))}
  185. </div>
  186. </div>
  187. {firstInit.value && !list.value.length && (
  188. <div class={[styles.gradeContainer, styles.itemEmtry]}>
  189. <Image src={icon_1} />
  190. </div>
  191. )}
  192. </div>
  193. );
  194. }
  195. });