index.tsx 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635
  1. import { defineComponent, reactive, onMounted, ref } from 'vue';
  2. import styles from './index.module.less';
  3. import {
  4. NAvatar,
  5. NButton,
  6. NDivider,
  7. NForm,
  8. NFormItem,
  9. NImage,
  10. NModal,
  11. NSpace,
  12. NSpin,
  13. useMessage
  14. } from 'naive-ui';
  15. import SearchInput from '@/components/searchInput';
  16. import CDatePicker from '/src/components/CDatePicker';
  17. import CSelect from '@/components/CSelect';
  18. import add from '@/views/studentList/images/add.png';
  19. import { useRoute, useRouter } from 'vue-router';
  20. import { getGradeLevelList, getGradeYearList } from '../home/api';
  21. import { initCache, setCache } from '/src/hooks/use-async';
  22. import { classArray, getgradeNumList } from '../classList/contants';
  23. import teacherIcon from '@components/layout/images/teacherIcon.png';
  24. import Pagination from '/src/components/pagination';
  25. import { api_trainingList, api_withdrawTraining } from './api';
  26. import TheEmpty from '/src/components/TheEmpty';
  27. import { fscreen, getTimes } from '/src/utils';
  28. import dayjs from 'dayjs';
  29. import Train from '../prepare-lessons/components/lesson-main/train';
  30. import ResourceMain from '../prepare-lessons/components/resource-main';
  31. import { useResizeObserver } from '@vueuse/core';
  32. import { nextTick } from 'process';
  33. import PreviewWindow from '../preview-window';
  34. import { state as baseState } from '@/state';
  35. export const getCurrentMonth = () => {
  36. return [dayjs().startOf('month').valueOf(), dayjs().endOf('month').valueOf()];
  37. };
  38. export default defineComponent({
  39. name: 'homework-record',
  40. setup() {
  41. const state = reactive({
  42. workVisiable: false,
  43. resetVisiable: false,
  44. resetItem: {} as any,
  45. searchForm: {
  46. keyword: null as any,
  47. currentClass: '',
  48. homeworkType: '',
  49. currentGradeNum: '',
  50. subjectId: '',
  51. gradeYear: '',
  52. gradeLevel: '',
  53. homeworkObj: '',
  54. status: '',
  55. timer: getCurrentMonth() as any,
  56. selfFlag: true
  57. },
  58. loading: false,
  59. pagination: {
  60. page: 1,
  61. rows: 10,
  62. pageTotal: 6
  63. },
  64. gradeNumList: [] as any,
  65. tableList: [] as any,
  66. studentVisible: false,
  67. activeRow: null as any,
  68. showaddClass: false,
  69. popSelectYearList: [] as any,
  70. popSelectLevelList: [] as any,
  71. previewModal: false,
  72. previewParams: {
  73. type: '',
  74. courseId: '',
  75. subjectId: '',
  76. detailId: ''
  77. } as any
  78. });
  79. const formRef = ref();
  80. const message = useMessage();
  81. const router = useRouter();
  82. const route = useRoute();
  83. const search = () => {
  84. state.pagination.page = 1;
  85. getList();
  86. setCache({ current: state.searchForm, saveKey: route.path });
  87. };
  88. state.gradeNumList = getgradeNumList();
  89. const onReset = () => {
  90. state.searchForm = {
  91. keyword: null as any,
  92. currentClass: '' as any,
  93. currentGradeNum: '' as any,
  94. subjectId: '' as any,
  95. gradeYear: '' as any,
  96. gradeLevel: '',
  97. homeworkObj: '',
  98. status: '',
  99. timer: getCurrentMonth() as any,
  100. selfFlag: true
  101. };
  102. if (state.popSelectYearList.length > 0) {
  103. state.searchForm.gradeYear = state.popSelectYearList[1].id;
  104. }
  105. //
  106. setCache({ current: state.searchForm, saveKey: route.path });
  107. getList();
  108. };
  109. const getModalHeight = () => {
  110. useResizeObserver(
  111. document.querySelector('#model-homework-height') as HTMLElement,
  112. (entries: any) => {
  113. const entry = entries[0];
  114. const { height } = entry.contentRect;
  115. document.documentElement.style.setProperty(
  116. '--window-page-lesson-height',
  117. height + 'px'
  118. );
  119. }
  120. );
  121. };
  122. const getList = async () => {
  123. // // classGroupList
  124. state.loading = true;
  125. try {
  126. const res = await api_trainingList({
  127. ...state.searchForm,
  128. ...state.pagination,
  129. ...getTimes(
  130. state.searchForm.timer,
  131. ['startTime', 'endTime'],
  132. 'YYYY-MM-DD'
  133. )
  134. });
  135. const result = res.data.rows || [];
  136. result.forEach((item: any) => {
  137. let pTitle = '';
  138. let eTitle = '';
  139. if (
  140. item.studentLessonTrainingDetails &&
  141. item.studentLessonTrainingDetails.length > 0
  142. ) {
  143. item.studentLessonTrainingDetails.forEach((child: any) => {
  144. if (child.trainingType === 'PRACTICE' && child.musicName) {
  145. pTitle += pTitle
  146. ? '、《' + child.musicName + '》'
  147. : '练习曲目《' + child.musicName + '》';
  148. }
  149. if (child.trainingType === 'EVALUATION' && child.musicName) {
  150. eTitle += eTitle
  151. ? '、《' + child.musicName + '》'
  152. : '评测曲目《' + child.musicName + '》';
  153. }
  154. });
  155. }
  156. item.pTitle = pTitle;
  157. item.eTitle = eTitle;
  158. let studentName = item.homeworkObjName;
  159. if (item.homeworkObjName) {
  160. const tempObjName = item.homeworkObjName.split(',');
  161. if (tempObjName.length > 5) {
  162. studentName =
  163. tempObjName.slice(0, 5).join('、') +
  164. `...等${tempObjName.length}${
  165. item.homeworkObj === 'CLASS' ? '个班级' : '人'
  166. }`;
  167. } else {
  168. studentName = tempObjName.join('、');
  169. }
  170. }
  171. item.studentName = studentName;
  172. });
  173. state.tableList = res.data.rows;
  174. state.pagination.pageTotal = res.data.total;
  175. state.loading = false;
  176. } catch (e) {
  177. state.loading = false;
  178. console.log(e);
  179. }
  180. };
  181. // 获取学年
  182. const getYearList = async () => {
  183. try {
  184. const { data } = await getGradeYearList();
  185. const temp = data || [];
  186. temp.forEach((i: any) => {
  187. i.name = i.name + '学年';
  188. });
  189. temp.unshift({
  190. id: '',
  191. name: '全部学年'
  192. });
  193. state.popSelectYearList = temp || [];
  194. if (temp.length > 0 && !state.searchForm.gradeYear) {
  195. state.searchForm.gradeYear = temp[1].id;
  196. }
  197. } catch {
  198. //
  199. }
  200. };
  201. // 获取学级
  202. const getLevelList = async () => {
  203. try {
  204. const { data } = await getGradeLevelList();
  205. const temp = data || [];
  206. temp.forEach((i: any) => {
  207. i.name = i.name + '级';
  208. });
  209. temp.unshift({
  210. id: '',
  211. name: '全部学级'
  212. });
  213. state.popSelectLevelList = temp || [];
  214. if (temp.length > 0 && !state.searchForm.gradeLevel) {
  215. state.searchForm.gradeLevel = temp[0].id;
  216. }
  217. } catch {
  218. //
  219. }
  220. };
  221. initCache({
  222. current: state.searchForm,
  223. callBack: (active: any) => {
  224. state.searchForm = active;
  225. }
  226. });
  227. const onResetRecord = async () => {
  228. try {
  229. await api_withdrawTraining({ lessonTrainingId: state.resetItem.id });
  230. message.success('撤回成功');
  231. state.resetVisiable = false;
  232. search();
  233. } catch {
  234. //
  235. }
  236. };
  237. // 预览上课
  238. // courseId: '' as any, // 课件编号
  239. // subjectId: '' as any, // 声部编号
  240. // lessonCourseId: '' as any, // 教材编号
  241. // lessonCoursewareDetailId: '' as any, // 章节
  242. // detailId: '' as any, // 编号 - 课程编号
  243. // classGroupId: '' as any, // 上课时需要 班级编号
  244. const onPreviewAttend = (item: any) => {
  245. // 判断是否在应用里面
  246. if (window.matchMedia('(display-mode: standalone)').matches) {
  247. baseState.application = window.matchMedia(
  248. '(display-mode: standalone)'
  249. ).matches;
  250. state.previewModal = true;
  251. fscreen();
  252. state.previewParams = {
  253. type: 'preview',
  254. courseId: item.chapterLessonCoursewareId,
  255. subjectId: null,
  256. detailId: item.lessonCoursewareKnowledgeDetailId,
  257. lessonCourseId: item.lessonCoursewareId
  258. };
  259. } else {
  260. const { href } = router.resolve({
  261. path: '/attend-class',
  262. query: {
  263. type: 'preview',
  264. courseId: item.chapterLessonCoursewareId,
  265. subjectId: null,
  266. detailId: item.lessonCoursewareKnowledgeDetailId,
  267. lessonCourseId: item.lessonCoursewareId
  268. }
  269. });
  270. window.open(href, +new Date() + '');
  271. }
  272. };
  273. onMounted(async () => {
  274. state.loading = true;
  275. await getYearList();
  276. await getLevelList();
  277. await getList();
  278. state.loading = false;
  279. });
  280. return () => (
  281. <div class={styles.listWrap}>
  282. <div class={styles.searchList}>
  283. <NForm label-placement="left" inline ref={formRef}>
  284. <NFormItem>
  285. <SearchInput
  286. {...{ placeholder: '请输入作业标题关键词' }}
  287. class={styles.searchInput}
  288. searchWord={state.searchForm.keyword}
  289. onChangeValue={(val: string) =>
  290. (state.searchForm.keyword = val)
  291. }></SearchInput>
  292. </NFormItem>
  293. <NFormItem>
  294. <CSelect
  295. {...({
  296. options: [
  297. { id: '', name: '全部类型' },
  298. { id: 'CLASSWORK', name: '课外作业' },
  299. { id: 'HOMEWORK', name: '课后作业' }
  300. ],
  301. placeholder: '选择类型',
  302. clearable: true,
  303. inline: true,
  304. labelField: 'name',
  305. valueField: 'id'
  306. } as any)}
  307. v-model:value={state.searchForm.homeworkType}></CSelect>
  308. </NFormItem>
  309. <NFormItem>
  310. <CSelect
  311. {...({
  312. options: [
  313. { id: '', name: '作业对象' },
  314. { id: 'PERSON', name: '个人' },
  315. { id: 'CLASS', name: '班级' }
  316. ],
  317. placeholder: '选择作业对象',
  318. clearable: true,
  319. inline: true,
  320. labelField: 'name',
  321. valueField: 'id'
  322. } as any)}
  323. v-model:value={state.searchForm.homeworkObj}></CSelect>
  324. </NFormItem>
  325. <NFormItem>
  326. <CSelect
  327. {...({
  328. options: state.popSelectYearList,
  329. placeholder: '选择学年',
  330. clearable: true,
  331. inline: true,
  332. labelField: 'name',
  333. valueField: 'id'
  334. } as any)}
  335. v-model:value={state.searchForm.gradeYear}></CSelect>
  336. </NFormItem>
  337. <NFormItem>
  338. <CSelect
  339. {...({
  340. options: state.popSelectLevelList,
  341. placeholder: '选择学级',
  342. clearable: true,
  343. inline: true,
  344. labelField: 'name',
  345. valueField: 'id'
  346. } as any)}
  347. v-model:value={state.searchForm.gradeLevel}></CSelect>
  348. </NFormItem>
  349. <NFormItem>
  350. <CSelect
  351. {...({
  352. options: state.gradeNumList,
  353. placeholder: '选择年级',
  354. clearable: true,
  355. inline: true
  356. } as any)}
  357. v-model:value={state.searchForm.currentGradeNum}></CSelect>
  358. </NFormItem>
  359. <NFormItem>
  360. <CSelect
  361. {...({
  362. options: classArray,
  363. placeholder: '选择班级',
  364. clearable: true,
  365. inline: true
  366. } as any)}
  367. v-model:value={state.searchForm.currentClass}></CSelect>
  368. </NFormItem>
  369. <NFormItem>
  370. <CSelect
  371. {...({
  372. options: [
  373. { value: '', label: '全部状态' },
  374. { value: 0, label: '进行中' },
  375. { value: 1, label: '已结束' }
  376. ],
  377. placeholder: '选择状态',
  378. clearable: true,
  379. inline: true
  380. } as any)}
  381. v-model:value={state.searchForm.status}></CSelect>
  382. </NFormItem>
  383. <NFormItem>
  384. <CDatePicker
  385. class={styles.CDatePickerItem}
  386. separator={'-'}
  387. type="daterange"
  388. v-model:value={state.searchForm.timer}
  389. timerValue={state.searchForm.timer}></CDatePicker>
  390. </NFormItem>
  391. <NFormItem>
  392. <NSpace justify="end">
  393. <NButton type="primary" class="searchBtn" onClick={search}>
  394. 搜索
  395. </NButton>
  396. <NButton
  397. type="primary"
  398. ghost
  399. class="resetBtn"
  400. onClick={onReset}>
  401. 重置
  402. </NButton>
  403. </NSpace>
  404. </NFormItem>
  405. </NForm>
  406. </div>
  407. <NButton
  408. class={styles.addBtn}
  409. type="primary"
  410. onClick={() => {
  411. state.workVisiable = true;
  412. nextTick(() => {
  413. getModalHeight();
  414. });
  415. }}
  416. v-slots={{
  417. icon: () => (
  418. <>
  419. <NImage
  420. class={styles.addBtnIcon}
  421. previewDisabled
  422. src={add}></NImage>
  423. </>
  424. )
  425. }}>
  426. 布置作业
  427. </NButton>
  428. <div class={styles.tableWrap}>
  429. <NSpin show={state.loading}>
  430. <div style={{ minHeight: '40vh' }}>
  431. <div class={styles.listSection}>
  432. {state.tableList.map((item: any) => (
  433. <div
  434. class={styles.item}
  435. onClick={() => {
  436. router.push({
  437. path: '/homework-record-detail',
  438. query: {
  439. id: item.id,
  440. name: item.name
  441. }
  442. });
  443. }}>
  444. <div class={styles.header}>
  445. <NAvatar
  446. class={styles.navatar}
  447. round
  448. src={item.teacherAvatar || teacherIcon}
  449. />
  450. <div class={styles.userInfo}>
  451. <h2>{item.teacherName}</h2>
  452. <p>
  453. 布置时间:
  454. {dayjs(item.createTime).format('YYYY-MM-DD HH:mm')}
  455. <span> | </span>
  456. <span>
  457. 截止时间:
  458. {dayjs(item.expireDate).format('YYYY-MM-DD HH:mm')}
  459. </span>
  460. </p>
  461. </div>
  462. <div class={item.status ? styles.over : styles.ing}>
  463. {/* {item.status ? '已结束' : '进行中'} */}
  464. </div>
  465. </div>
  466. <div class={styles.content}>
  467. {/* <div> */}
  468. <div class={styles.homeTitle}>
  469. <p>
  470. <span class={styles[item.homeworkType]}>
  471. {item.homeworkType === 'CLASSWORK'
  472. ? '课外'
  473. : '课后'}
  474. </span>
  475. {item.name}
  476. </p>
  477. <NSpace>
  478. {item.chapterLessonCoursewareId && (
  479. <NButton
  480. class={styles.errorBtn}
  481. text
  482. color="#1677FF"
  483. onClick={(e: any) => {
  484. e.stopPropagation();
  485. // state.resetVisiable = true;
  486. // state.resetItem = item;
  487. onPreviewAttend(item);
  488. }}>
  489. 查看课件
  490. </NButton>
  491. )}
  492. {!item.status && (
  493. <NButton
  494. class={styles.errorBtn}
  495. text
  496. color="#1677FF"
  497. onClick={(e: any) => {
  498. e.stopPropagation();
  499. state.resetVisiable = true;
  500. state.resetItem = item;
  501. }}>
  502. 撤回
  503. </NButton>
  504. )}
  505. </NSpace>
  506. </div>
  507. <div class={styles.homeContent}>
  508. <span class={styles.title}>作业对象:</span>
  509. <span class={styles.text}>{item.studentName}</span>
  510. </div>
  511. <div class={[styles.homeContent, styles.homeworkText]}>
  512. <span class={styles.title}>作业内容:</span>
  513. <div class={styles.pSection}>
  514. {item.pTitle && (
  515. <p class={[styles.text, styles.p1]}>
  516. {item.pTitle}
  517. </p>
  518. )}
  519. {item.eTitle && (
  520. <p class={[styles.text, styles.p2]}>
  521. {item.eTitle}
  522. </p>
  523. )}
  524. </div>
  525. </div>
  526. <div class={styles.homeSubmit}>
  527. <span class={styles.title}>已提交:</span>
  528. <span class={styles.text}>
  529. {item.trainingNum || 0}/{item.expectNum || 0}人
  530. </span>
  531. <NDivider vertical />
  532. <span class={styles.title}>提交率:</span>
  533. <span class={styles.text}>
  534. {item.trainingRate || 0}%
  535. </span>
  536. <NDivider vertical />
  537. <span class={styles.title}>合格人数:</span>
  538. <span class={styles.text}>
  539. {item.standardNum || 0}人
  540. </span>
  541. <NDivider vertical />
  542. <span class={styles.title}>合格率:</span>
  543. <span class={styles.text}>
  544. {item.qualifiedRate || 0}%
  545. </span>
  546. </div>
  547. {/* </div> */}
  548. </div>
  549. </div>
  550. ))}
  551. </div>
  552. {state.tableList.length <= 0 && !state.loading && (
  553. <TheEmpty class={styles.nowEmpty} />
  554. )}
  555. </div>
  556. </NSpin>
  557. {state.tableList.length > 0 && (
  558. <Pagination
  559. v-model:page={state.pagination.page}
  560. v-model:pageSize={state.pagination.rows}
  561. v-model:pageTotal={state.pagination.pageTotal}
  562. onList={getList}
  563. sync
  564. />
  565. )}
  566. </div>
  567. <NModal
  568. v-model:show={state.resetVisiable}
  569. preset="card"
  570. class={['modalTitle', styles.removeVisiable]}
  571. title={'撤回作业'}>
  572. <div class={styles.studentRemove}>
  573. <p>
  574. 撤回作业后,此条作业将被删除,是否确认撤回【{state.resetItem.name}
  575. 】?
  576. </p>
  577. <NSpace class={styles.btnGroup} justify="center">
  578. <NButton round onClick={() => (state.resetVisiable = false)}>
  579. 取消
  580. </NButton>
  581. <NButton round type="primary" onClick={onResetRecord}>
  582. 确定
  583. </NButton>
  584. </NSpace>
  585. </div>
  586. </NModal>
  587. <NModal
  588. v-model:show={state.workVisiable}
  589. preset="card"
  590. class={['modalTitle background', styles.workVisiable]}
  591. title={'作业详情'}>
  592. <div id="model-homework-height" class={styles.workContainer}>
  593. <div class={styles.workTrain}>
  594. <Train
  595. lessonPreTraining={{
  596. title: dayjs().format('YYYY年MM月DD日') + '-课外作业'
  597. }}
  598. cardType={'homeworkRecord'}
  599. onChange={(val: any) => {
  600. state.workVisiable = val.status;
  601. getList();
  602. }}
  603. />
  604. </div>
  605. <div class={styles.resourceMain}>
  606. <ResourceMain cardType="homerowk-record" />
  607. </div>
  608. </div>
  609. </NModal>
  610. {/* 应用内预览或上课 */}
  611. <PreviewWindow
  612. v-model:show={state.previewModal}
  613. type="attend"
  614. params={state.previewParams}
  615. />
  616. </div>
  617. );
  618. }
  619. });