index.tsx 16 KB

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