index.tsx 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501
  1. import { computed, defineComponent, onMounted, reactive, ref } from 'vue';
  2. import styles from './index.module.less';
  3. import {
  4. NButton,
  5. NDataTable,
  6. NForm,
  7. NFormItem,
  8. NImage,
  9. NModal,
  10. NProgress,
  11. NSpace
  12. } from 'naive-ui';
  13. import SearchInput from '@/components/searchInput';
  14. import CSelect from '@/components/CSelect';
  15. import Pagination from '@/components/pagination';
  16. import { api_trainingDetail, api_trainingStudentList } from '../api';
  17. import { useRoute } from 'vue-router';
  18. import CBreadcrumb from '/src/components/CBreadcrumb';
  19. import defultHeade from '@/components/layout/images/teacherIcon.png';
  20. import { trainingStatusArray } from '@/utils/searchArray';
  21. import dayjs from 'dayjs';
  22. import TheEmpty from '/src/components/TheEmpty';
  23. import TrainingDetails from '../../classList/modals/TrainingDetails';
  24. import { evaluateDifficult } from '/src/utils/contants';
  25. import { modalClickMask } from '/src/state';
  26. export default defineComponent({
  27. name: 'homewrok-record-detail',
  28. setup() {
  29. const route = useRoute();
  30. const state = reactive({
  31. searchForm: {
  32. keyword: '',
  33. vipFlag: null as any,
  34. trainingStatus: '' as any,
  35. classGroupId: '' as any
  36. },
  37. loading: false,
  38. pagination: {
  39. page: 1,
  40. rows: 10,
  41. pageTotal: 4
  42. },
  43. studentClassList: [] as any,
  44. tableList: [] as any,
  45. workInfo: {} as any,
  46. detailVisiable: false,
  47. activeRow: null as any,
  48. index: 0
  49. });
  50. const TrainingDetailsRef = ref();
  51. const routerList = ref([
  52. { name: '作业', path: '/homework-record' },
  53. { name: route.query.name, path: '/homework-record-detail' }
  54. ] as any);
  55. const search = () => {
  56. state.pagination.page = 1;
  57. getList();
  58. };
  59. const onReset = () => {
  60. state.searchForm = {
  61. keyword: '',
  62. vipFlag: null,
  63. trainingStatus: '' as any,
  64. classGroupId: '' as any
  65. };
  66. search();
  67. };
  68. const getList = async (type?: string, page?: number) => {
  69. state.loading = true;
  70. try {
  71. const res = await api_trainingStudentList({
  72. trainingId: route.query.id,
  73. ...state.searchForm,
  74. ...state.pagination,
  75. page: page || state.pagination.page
  76. });
  77. state.tableList = res.data.rows;
  78. state.pagination.pageTotal = res.data.total;
  79. state.pagination.page = res.data.current;
  80. state.loading = false;
  81. if (type === 'next') {
  82. state.index = 0;
  83. goToNext();
  84. } else if (type === 'prev') {
  85. state.index = state.tableList.length + 1;
  86. gotoPre();
  87. }
  88. } catch (e) {
  89. state.loading = false;
  90. console.log(e);
  91. }
  92. };
  93. const getWorkInfo = async () => {
  94. try {
  95. const res = await api_trainingDetail({ id: route.query.id });
  96. const result = res.data || {};
  97. // state.workInfo
  98. let pTitle = '';
  99. let eTitle = '';
  100. if (
  101. result.studentLessonTrainingDetails &&
  102. result.studentLessonTrainingDetails.length > 0
  103. ) {
  104. result.studentLessonTrainingDetails.forEach((child: any) => {
  105. // if (child.trainingType === 'PRACTICE' && child.musicName) {
  106. // pTitle += pTitle ? '、' + child.musicName : child.musicName;
  107. // }
  108. // if (child.trainingType === 'EVALUATION' && child.musicName) {
  109. // eTitle += eTitle ? '、' + child.musicName : child.musicName;
  110. // }
  111. const trainingContent = child.trainingContent
  112. ? JSON.parse(child.trainingContent)
  113. : null;
  114. if (child.trainingType === 'PRACTICE' && child.musicName) {
  115. pTitle += '《' + child.musicName + '》';
  116. if (trainingContent) {
  117. const tempList = [
  118. `${trainingContent.practiceChapterBegin}-${trainingContent.practiceChapterEnd}小节`,
  119. `速度${trainingContent.practiceSpeed || 0}`,
  120. `${trainingContent.trainingTimes}分钟`
  121. ];
  122. pTitle += tempList.join(' | ') + ';';
  123. }
  124. }
  125. if (child.trainingType === 'EVALUATION' && child.musicName) {
  126. eTitle += '《' + child.musicName + '》';
  127. if (trainingContent) {
  128. const tempList = [
  129. `${evaluateDifficult[trainingContent.evaluateDifficult]}`,
  130. `${trainingContent.practiceChapterBegin || 0}-${trainingContent.practiceChapterEnd || 0}小节`,
  131. `速度${trainingContent.evaluateSpeed || 0}`,
  132. `${trainingContent.trainingTimes}分达标`
  133. ];
  134. eTitle += tempList.join(' | ') + ';';
  135. }
  136. }
  137. });
  138. }
  139. result.pTitle = pTitle;
  140. result.eTitle = eTitle;
  141. state.workInfo = result;
  142. // 班级列表
  143. const classList = result.studentClassGroup || [];
  144. classList.forEach((item: any) => {
  145. state.studentClassList.push({
  146. label: item.name,
  147. value: item.id
  148. });
  149. });
  150. } catch (e) {
  151. console.log(e);
  152. }
  153. };
  154. const lookDetail = (row: any, index: number) => {
  155. console.log(index, 'index');
  156. state.index = index + 1;
  157. state.activeRow = row;
  158. state.detailVisiable = true;
  159. };
  160. onMounted(() => {
  161. getWorkInfo();
  162. getList();
  163. });
  164. const columns = () => {
  165. return [
  166. {
  167. title: '学生姓名',
  168. key: 'studentName'
  169. },
  170. {
  171. title: '最后提交时间',
  172. key: 'submitTime',
  173. render(row: any) {
  174. return row.submitTime
  175. ? dayjs(row.submitTime).format('YYYY-MM-DD')
  176. : '--';
  177. }
  178. },
  179. {
  180. title: '所属班级',
  181. key: 'classGroupName'
  182. },
  183. {
  184. title: '作业状态',
  185. key: 'trainingStatus',
  186. render(row: any) {
  187. return (
  188. <div>
  189. {row.trainingStatus == 'UNSUBMITTED' ? (
  190. <p class={styles.nosub}>未提交</p>
  191. ) : null}
  192. {row.trainingStatus == 'SUBMITTED' ? (
  193. <p class={styles.ison}>未达标</p>
  194. ) : null}
  195. {row.trainingStatus == 'TARGET' ? (
  196. <p class={styles.isok}>达标</p>
  197. ) : null}
  198. </div>
  199. );
  200. }
  201. },
  202. {
  203. title: '是否会员',
  204. key: 'vipFlag',
  205. render(row: any) {
  206. return row.vipFlag ? '是' : '否';
  207. }
  208. },
  209. {
  210. title: '操作',
  211. key: 'id',
  212. render(row: any, index: number) {
  213. return (
  214. <NButton
  215. text
  216. type="primary"
  217. onClick={() => {
  218. lookDetail(row, index);
  219. }}>
  220. 详情
  221. </NButton>
  222. );
  223. }
  224. }
  225. ];
  226. };
  227. const goToNext = () => {
  228. if (state.index >= state.tableList.length) {
  229. getList('next', state.pagination.page + 1);
  230. } else {
  231. ++state.index;
  232. state.activeRow = state.tableList[state.index - 1];
  233. TrainingDetailsRef.value.getTrainingDetail(
  234. state.activeRow.studentLessonTrainingId
  235. );
  236. }
  237. };
  238. const gotoPre = () => {
  239. if (state.index === 1 && state.pagination.page !== 1) {
  240. getList('prev', state.pagination.page - 1);
  241. } else {
  242. --state.index;
  243. state.activeRow = state.tableList[state.index - 1];
  244. TrainingDetailsRef.value.getTrainingDetail(
  245. state.activeRow.studentLessonTrainingId
  246. );
  247. }
  248. };
  249. const currentStudentIndex = computed(() => {
  250. return state.index + (state.pagination.page - 1) * state.pagination.rows;
  251. });
  252. return () => (
  253. <div>
  254. <CBreadcrumb list={routerList.value}></CBreadcrumb>
  255. <div class={styles.listWrap}>
  256. <div class={styles.teacherSection}>
  257. <div class={styles.teacherList}>
  258. <div class={styles.tTemp}>
  259. <div class={styles.teacherHeader}>
  260. <div class={styles.teacherHeaderBorder}>
  261. <NImage
  262. class={styles.teacherHeaderImg}
  263. src={state.workInfo.teacherAvatar || defultHeade}
  264. previewDisabled></NImage>
  265. </div>
  266. </div>
  267. <div class={styles.workafterInfo}>
  268. <h4>{state.workInfo.teacherName}</h4>
  269. {state.workInfo.createTime && (
  270. <p>
  271. 布置时间:
  272. {state.workInfo.createTime &&
  273. dayjs(state.workInfo.createTime).format(
  274. 'YYYY-MM-DD HH:mm'
  275. )}{' '}
  276. |{' '}
  277. <span>
  278. 截止时间:
  279. {state.workInfo.expireDate &&
  280. dayjs(state.workInfo.expireDate).format(
  281. 'YYYY-MM-DD HH:mm'
  282. )}
  283. </span>
  284. </p>
  285. )}
  286. </div>
  287. </div>
  288. <div class={styles.infos}>
  289. <div class={styles.homeTitle}>{state.workInfo.name}</div>
  290. <div class={[styles.homeContent, styles.homeworkText]}>
  291. <div class={styles.pSection}>
  292. {state.workInfo.pTitle && (
  293. <p class={[styles.text, styles.p1]}>
  294. <div>
  295. <span>练习曲目:</span>
  296. <p>{state.workInfo.pTitle}</p>
  297. </div>
  298. </p>
  299. )}
  300. {state.workInfo.eTitle && (
  301. <p class={[styles.text, styles.p2]}>
  302. <div>
  303. <span>评测曲目:</span>
  304. <p>{state.workInfo.eTitle}</p>
  305. </div>
  306. </p>
  307. )}
  308. </div>
  309. </div>
  310. </div>
  311. </div>
  312. <div>
  313. <div class={styles.stitcTitle}>作业完成情况</div>
  314. <div class={styles.stitcConent}>
  315. <NSpace size={[38, 0]}>
  316. <NProgress
  317. percentage={state.workInfo.trainingRate || 0}
  318. // percentage={20}
  319. offset-degree={180}
  320. type="circle"
  321. strokeWidth={6}
  322. rail-color={'EDEFFA'}
  323. color={'#64A5FF'}>
  324. <div class={styles.contentRect}>
  325. <div class={styles.nums}>
  326. {state.workInfo.trainingNum || 0}
  327. <i>/</i>
  328. {state.workInfo.expectNum || 0}
  329. <span>人</span>
  330. </div>
  331. <div class={styles.text}>已提交</div>
  332. </div>
  333. </NProgress>
  334. <NProgress
  335. percentage={state.workInfo.trainingRate || 0}
  336. offset-degree={180}
  337. strokeWidth={6}
  338. type="circle"
  339. rail-color={'EDEFFA'}
  340. color={'#64A5FF'}>
  341. <div class={styles.contentRect}>
  342. <div class={styles.nums}>
  343. {state.workInfo.trainingRate || 0}%
  344. </div>
  345. <div class={styles.text}>提交率</div>
  346. </div>
  347. </NProgress>
  348. <NProgress
  349. percentage={state.workInfo.qualifiedRate || 0}
  350. offset-degree={180}
  351. strokeWidth={6}
  352. type="circle"
  353. rail-color={'EDEFFA'}
  354. color={'#40CEAE'}>
  355. <div class={styles.contentRect}>
  356. <div class={styles.nums}>
  357. {state.workInfo.standardNum || 0}
  358. <span>人</span>
  359. </div>
  360. <div class={styles.text}>达标人数</div>
  361. </div>
  362. </NProgress>
  363. <NProgress
  364. percentage={state.workInfo.qualifiedRate || 0}
  365. offset-degree={180}
  366. strokeWidth={6}
  367. type="circle"
  368. rail-color={'EDEFFA'}
  369. color={'#40CEAE'}>
  370. <div class={styles.contentRect}>
  371. <div class={styles.nums}>
  372. {state.workInfo.qualifiedRate || 0}%
  373. </div>
  374. <div class={styles.text}>达标率</div>
  375. </div>
  376. </NProgress>
  377. </NSpace>
  378. </div>
  379. </div>
  380. </div>
  381. <div class={styles.searchList}>
  382. <NForm label-placement="left" inline>
  383. <NFormItem>
  384. <SearchInput
  385. {...{ placeholder: '请输入学生姓名' }}
  386. class={styles.searchInput}
  387. searchWord={state.searchForm.keyword}
  388. onChangeValue={(val: string) =>
  389. (state.searchForm.keyword = val)
  390. }></SearchInput>
  391. </NFormItem>
  392. <NFormItem>
  393. <CSelect
  394. {...({
  395. options: [
  396. {
  397. label: '全部班级',
  398. value: ''
  399. },
  400. ...state.studentClassList
  401. ],
  402. placeholder: '全部班级',
  403. clearable: true,
  404. inline: true
  405. } as any)}
  406. v-model:value={state.searchForm.classGroupId}></CSelect>
  407. </NFormItem>
  408. <NFormItem>
  409. <CSelect
  410. {...({
  411. options: [
  412. {
  413. label: '全部状态',
  414. value: ''
  415. },
  416. ...trainingStatusArray
  417. ],
  418. placeholder: '作业状态',
  419. clearable: true,
  420. inline: true
  421. } as any)}
  422. v-model:value={state.searchForm.trainingStatus}></CSelect>
  423. </NFormItem>
  424. <NFormItem>
  425. <CSelect
  426. {...({
  427. options: [
  428. { label: '是', value: true },
  429. { label: '否', value: false }
  430. ],
  431. placeholder: '是否会员',
  432. clearable: true,
  433. inline: true
  434. } as any)}
  435. v-model:value={state.searchForm.vipFlag}></CSelect>
  436. </NFormItem>
  437. <NFormItem>
  438. <NSpace justify="end">
  439. <NButton type="primary" class="searchBtn" onClick={search}>
  440. 搜索
  441. </NButton>
  442. <NButton
  443. type="primary"
  444. ghost
  445. class="resetBtn"
  446. onClick={onReset}>
  447. 重置
  448. </NButton>
  449. </NSpace>
  450. </NFormItem>
  451. </NForm>
  452. </div>
  453. <div class={styles.tableWrap}>
  454. <NDataTable
  455. v-slots={{
  456. empty: () => <TheEmpty></TheEmpty>
  457. }}
  458. class={styles.classTable}
  459. loading={state.loading}
  460. columns={columns()}
  461. data={state.tableList}></NDataTable>
  462. <Pagination
  463. v-model:page={state.pagination.page}
  464. v-model:pageSize={state.pagination.rows}
  465. v-model:pageTotal={state.pagination.pageTotal}
  466. onList={getList}
  467. // sync
  468. />
  469. </div>
  470. </div>
  471. <NModal
  472. maskClosable={modalClickMask}
  473. v-model:show={state.detailVisiable}
  474. preset="card"
  475. class={['modalTitle background', styles.wordDetailModel]}
  476. title={'作业详情'}>
  477. <TrainingDetails
  478. onNext={() => goToNext()}
  479. onPre={() => gotoPre()}
  480. ref={TrainingDetailsRef}
  481. onClose={() => (state.detailVisiable = false)}
  482. total={state.pagination.pageTotal}
  483. current={currentStudentIndex.value}
  484. activeRow={state.activeRow}></TrainingDetails>
  485. </NModal>
  486. </div>
  487. );
  488. }
  489. });