trainData.tsx 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534
  1. import {
  2. Ref,
  3. computed,
  4. defineComponent,
  5. onMounted,
  6. reactive,
  7. ref,
  8. watch
  9. } from 'vue';
  10. import styles from '../index.module.less';
  11. import { NButton, NDataTable, NNumberAnimation, NSpace } from 'naive-ui';
  12. import numeral from 'numeral';
  13. import { useECharts } from '@/hooks/web/useECharts';
  14. import Pagination from '/src/components/pagination';
  15. import { getTimes } from '/src/utils/dateFormat';
  16. import { getTrainingStat } from '../../data-module/api';
  17. import { useRoute, useRouter } from 'vue-router';
  18. import { getTrainingList } from '../../classList/api';
  19. import dayjs from 'dayjs';
  20. import TheEmpty from '/src/components/TheEmpty';
  21. export default defineComponent({
  22. name: 'home-trainData',
  23. props: {
  24. timer: {
  25. type: Array,
  26. defaut: () => []
  27. }
  28. },
  29. setup(props, { expose }) {
  30. const chartRef = ref<HTMLDivElement | null>(null);
  31. const { setOptions } = useECharts(chartRef as Ref<HTMLDivElement>);
  32. const qualifiedFlag = ref(true);
  33. const unqualifiedFlag = ref(true);
  34. const router = useRouter();
  35. const route = useRoute();
  36. const payForm = reactive({
  37. height: '360px',
  38. width: '100%',
  39. studentNum: 0,
  40. paymentAmount: 0,
  41. dateList: [
  42. '2022-10-10',
  43. '2022-10-11',
  44. '2022-10-12',
  45. '2022-10-13',
  46. '2022-10-14',
  47. '2022-10-15',
  48. '2022-10-16'
  49. ],
  50. studentList: [],
  51. payInfoList: []
  52. });
  53. const totalDateRef = ref({
  54. qualifiedRate: 0,
  55. qualifiedStudentCount: 0,
  56. submitStudentCount: 0,
  57. totalStudentCount: 0,
  58. trainingCount: 0,
  59. trainingRate: 0 //
  60. } as any);
  61. const state = reactive({
  62. loading: false,
  63. pagination: {
  64. page: 1,
  65. rows: 10,
  66. pageTotal: 4
  67. },
  68. tableList: [] as any,
  69. goCourseVisiable: false
  70. });
  71. const currentTimer = computed(() => {
  72. return props.timer;
  73. });
  74. const columns = () => {
  75. return [
  76. {
  77. title: '布置老师',
  78. key: 'teacherName'
  79. },
  80. {
  81. title: '布置时间',
  82. key: 'createTime',
  83. render(row: any) {
  84. return <>{row.createTime}</>;
  85. }
  86. },
  87. {
  88. title: '截止时间',
  89. key: 'expireDate',
  90. render(row: any) {
  91. return <>{row.expireDate}</>;
  92. }
  93. },
  94. {
  95. title: '训练状态',
  96. key: 'status',
  97. render(row: any) {
  98. return row.status == 0 ? (
  99. <div class={styles.indDot}>
  100. {' '}
  101. <span></span> 进行中
  102. </div>
  103. ) : (
  104. <div class={styles.endDot}>
  105. <span></span>已结束
  106. </div>
  107. );
  108. }
  109. },
  110. {
  111. title: '布置人数',
  112. key: 'expectNum'
  113. },
  114. {
  115. title: '提交人数',
  116. key: 'trainingNum'
  117. },
  118. {
  119. title: '合格人数',
  120. key: 'standardNum'
  121. },
  122. {
  123. title: '提交率',
  124. key: 'trainingRate',
  125. render(row: any) {
  126. return <>{row.trainingRate}%</>;
  127. }
  128. },
  129. {
  130. title: '合格率',
  131. key: 'qualifiedRate',
  132. render(row: any) {
  133. return <>{row.qualifiedRate}%</>;
  134. }
  135. },
  136. // {
  137. // title: '',
  138. // key: 'sex',
  139. // render(row: any) {
  140. // return <>{row.sex == '0' ? '女' : '男'}</>;
  141. // }
  142. // },
  143. {
  144. title: '操作',
  145. key: 'id',
  146. render(row: any) {
  147. return (
  148. <NSpace>
  149. <NButton
  150. text
  151. type="primary"
  152. onClick={() => gotoWorkDetail(row)}>
  153. 详情
  154. </NButton>
  155. </NSpace>
  156. );
  157. }
  158. }
  159. ];
  160. };
  161. const gotoWorkDetail = (row: any) => {
  162. console.log(row);
  163. router.push({
  164. path: '/afterWorkDetail',
  165. query: {
  166. ...route.query,
  167. teacherName: row.teacherName,
  168. trainingId: row.id,
  169. id: row.classGroupId,
  170. name: row.classGroupName
  171. }
  172. });
  173. };
  174. const getList = async () => {
  175. try {
  176. const res = await getTrainingStat({
  177. ...getTimes(
  178. currentTimer.value,
  179. ['startTime', 'endTime'],
  180. 'YYYY-MM-DD'
  181. )
  182. });
  183. totalDateRef.value = { ...res.data };
  184. payForm.dateList = res.data.trainingStatDetails.map((item: any) => {
  185. return item.date;
  186. });
  187. payForm.payInfoList = res.data.trainingStatDetails.map((item: any) => {
  188. return item.qualifiedStudentCount;
  189. });
  190. payForm.studentList = res.data.trainingStatDetails.map((item: any) => {
  191. return item.unqualifiedStudentCount;
  192. });
  193. setChart();
  194. } catch (e) {
  195. console.log(e);
  196. }
  197. try {
  198. const res = await getTrainingList({
  199. ...state.pagination,
  200. ...getTimes(
  201. currentTimer.value,
  202. ['startTime', 'endTime'],
  203. 'YYYY-MM-DD'
  204. )
  205. });
  206. state.tableList = res.data.rows;
  207. state.pagination.pageTotal = res.data.total;
  208. state.loading = false;
  209. } catch (e) {
  210. state.loading = false;
  211. console.log(e);
  212. }
  213. };
  214. expose({ getList });
  215. const setChart = () => {
  216. setOptions({
  217. tooltip: {
  218. trigger: 'axis',
  219. axisPointer: {
  220. lineStyle: {
  221. width: 2,
  222. color: '#A9C7FF'
  223. }
  224. }
  225. },
  226. legend: {
  227. show: false,
  228. selected: {
  229. //在这里设置默认展示就ok了
  230. 合格人数: qualifiedFlag.value,
  231. 不合格人数: unqualifiedFlag.value
  232. }
  233. },
  234. xAxis: {
  235. type: 'category',
  236. boundaryGap: true,
  237. axisLabel: {
  238. show: true,
  239. interval: 0
  240. },
  241. data: payForm.dateList
  242. // splitLine: {
  243. // show: true,
  244. // lineStyle: {
  245. // width: 2,
  246. // type: 'solid',
  247. // color: 'rgba(226,226,226,0.5)'
  248. // }
  249. // }
  250. // axisTick: {
  251. // show: false
  252. // }
  253. },
  254. yAxis: [
  255. {
  256. type: 'value',
  257. axisLabel: {
  258. formatter: '{value}人'
  259. },
  260. axisTick: {
  261. show: false
  262. },
  263. splitArea: {
  264. show: false,
  265. areaStyle: {
  266. color: ['rgba(255,255,255,0.2)']
  267. }
  268. },
  269. minInterval: 1,
  270. splitNumber: 5
  271. }
  272. ],
  273. grid: {
  274. left: '1%',
  275. right: '1%',
  276. top: '2 %',
  277. bottom: 0,
  278. containLabel: true
  279. },
  280. series: [
  281. {
  282. // smooth: true,
  283. data: payForm.studentList,
  284. symbolSize: 10,
  285. type: 'line',
  286. name: '不合格人数',
  287. symbol: 'circle',
  288. smooth: true,
  289. itemStyle: {
  290. color: '#FF7AA7',
  291. borderColor: '#fff',
  292. borderWidth: 3
  293. },
  294. lineStyle: {
  295. width: 3 //设置线条粗细
  296. },
  297. areaStyle: {
  298. color: {
  299. type: 'linear',
  300. x: 0,
  301. y: 0,
  302. x2: 0,
  303. y2: 1,
  304. colorStops: [
  305. {
  306. offset: 0,
  307. color: 'rgba(255, 243, 246, 1)'
  308. // 0% 处的颜色
  309. },
  310. {
  311. offset: 1,
  312. // 100% 处的颜色
  313. color: 'rgba(255, 246, 248, 0)'
  314. }
  315. ]
  316. }
  317. },
  318. emphasis: {
  319. disabled: true
  320. }
  321. },
  322. {
  323. data: payForm.payInfoList,
  324. type: 'line',
  325. name: '合格人数',
  326. symbolSize: 10,
  327. symbol: 'circle',
  328. smooth: true,
  329. itemStyle: {
  330. color: '#198CFE',
  331. borderColor: '#fff',
  332. borderWidth: 3
  333. },
  334. lineStyle: {
  335. width: 2 //设置线条粗细
  336. },
  337. areaStyle: {
  338. color: {
  339. type: 'linear',
  340. x: 0,
  341. y: 0,
  342. x2: 0,
  343. y2: 1,
  344. colorStops: [
  345. {
  346. offset: 0,
  347. color: 'rgba(212, 231, 255, 1)'
  348. // 0% 处的颜色
  349. },
  350. {
  351. offset: 1,
  352. color: 'rgba(221, 235, 254, 0)' // 100% 处的颜色
  353. }
  354. ]
  355. }
  356. },
  357. emphasis: {
  358. disabled: true
  359. }
  360. }
  361. ],
  362. formatter: (item: any) => {
  363. if (Array.isArray(item)) {
  364. return [
  365. item[0].axisValueLabel,
  366. ...item.map(
  367. (d: any) =>
  368. `<br/>${
  369. d.marker
  370. }<span style="margin-top:10px;margin-left:5px;font-size: 13px;font-weight: 500;
  371. color: #333333;
  372. line-height: 18px;">${d.seriesName}: ${
  373. d.value
  374. }${'人'} </span>`
  375. )
  376. ].join('');
  377. } else {
  378. return item;
  379. }
  380. }
  381. // dataZoom: [
  382. // {
  383. // type: 'slider',
  384. // start: 5,
  385. // end: 100,
  386. // filterMode: 'empty'
  387. // }
  388. // ]
  389. });
  390. };
  391. onMounted(() => {
  392. getList();
  393. });
  394. return () => (
  395. <>
  396. <div class={styles.homeTrainData}>
  397. <div class={styles.TrainDataTop}>
  398. <div class={styles.TrainDataTopLeft}>
  399. <div class={styles.TrainDataItem}>
  400. <p class={styles.TrainDataItemTitle}>
  401. <span>
  402. <NNumberAnimation
  403. from={0}
  404. to={totalDateRef.value.trainingCount}></NNumberAnimation>
  405. </span>
  406. </p>
  407. <p class={styles.TrainDataItemsubTitle}>学练次数</p>
  408. </div>
  409. <div class={styles.TrainDataItem}>
  410. <p class={styles.TrainDataItemTitle}>
  411. <span>
  412. <NNumberAnimation
  413. from={0}
  414. to={
  415. totalDateRef.value.totalStudentCount
  416. }></NNumberAnimation>
  417. </span>
  418. 人次
  419. </p>
  420. <p class={styles.TrainDataItemsubTitle}>应交总人次</p>
  421. </div>
  422. <div class={styles.TrainDataItem}>
  423. <p class={styles.TrainDataItemTitle}>
  424. <span>
  425. <NNumberAnimation
  426. from={0}
  427. to={
  428. totalDateRef.value.submitStudentCount
  429. }></NNumberAnimation>
  430. </span>
  431. 人次
  432. </p>
  433. <p class={styles.TrainDataItemsubTitle}>提交总人次</p>
  434. </div>
  435. <div class={styles.TrainDataItem}>
  436. <p class={styles.TrainDataItemTitle}>
  437. <span>
  438. {' '}
  439. <NNumberAnimation
  440. from={0}
  441. to={
  442. totalDateRef.value.qualifiedStudentCount
  443. }></NNumberAnimation>
  444. </span>
  445. 人次
  446. </p>
  447. <p class={styles.TrainDataItemsubTitle}>合格总人次</p>
  448. </div>
  449. <div class={styles.TrainDataItem}>
  450. <p class={styles.TrainDataItemTitle}>
  451. <span>
  452. <NNumberAnimation
  453. from={0}
  454. to={totalDateRef.value.trainingRate}></NNumberAnimation>
  455. %
  456. </span>
  457. </p>
  458. <p class={styles.TrainDataItemsubTitle}>作业提交率</p>
  459. </div>
  460. <div class={styles.TrainDataItem}>
  461. <p class={styles.TrainDataItemTitle}>
  462. <span>
  463. <NNumberAnimation
  464. from={0}
  465. to={totalDateRef.value.qualifiedRate}></NNumberAnimation>
  466. %
  467. </span>
  468. </p>
  469. <p class={styles.TrainDataItemsubTitle}>作业合格率</p>
  470. </div>
  471. </div>
  472. <div class={styles.TrainDataTopRight}>
  473. <div
  474. onClick={() => {
  475. qualifiedFlag.value = !qualifiedFlag.value;
  476. setChart();
  477. }}
  478. class={[
  479. styles.DataTopRightItem,
  480. qualifiedFlag.value ? '' : styles.DataTopRightItemDis
  481. ]}>
  482. <div class={styles.DataTopRightDot}></div>
  483. <p>合格人数</p>
  484. </div>
  485. <div
  486. onClick={() => {
  487. unqualifiedFlag.value = !unqualifiedFlag.value;
  488. setChart();
  489. }}
  490. class={[
  491. styles.DataTopRightItem,
  492. unqualifiedFlag.value ? '' : styles.DataTopRightItemDis
  493. ]}>
  494. <div class={[styles.DataTopRightDot, styles.red]}></div>
  495. <p>不合格人数</p>
  496. </div>
  497. </div>
  498. </div>
  499. <div class={styles.chatrs}>
  500. <div
  501. ref={chartRef}
  502. style={{ height: payForm.height, width: payForm.width }}></div>
  503. </div>
  504. <div class={styles.tableWrap}>
  505. <NDataTable
  506. v-slots={{
  507. empty: () => <TheEmpty></TheEmpty>
  508. }}
  509. class={styles.classTable}
  510. loading={state.loading}
  511. columns={columns()}
  512. data={state.tableList}></NDataTable>
  513. <Pagination
  514. v-model:page={state.pagination.page}
  515. v-model:pageSize={state.pagination.rows}
  516. v-model:pageTotal={state.pagination.pageTotal}
  517. onList={getList}
  518. sync
  519. />
  520. </div>
  521. </div>
  522. </>
  523. );
  524. }
  525. });