index.tsx 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507
  1. import OHeader from '@/components/o-header'
  2. import OSticky from '@/components/o-sticky'
  3. import OEmpty from '@/components/o-empty'
  4. import dayjs from 'dayjs'
  5. import isBetween from 'dayjs/plugin/isBetween'
  6. dayjs.extend(isBetween)
  7. import {
  8. Cell,
  9. Icon,
  10. List,
  11. showToast,
  12. Image,
  13. DropdownMenu,
  14. DropdownItem,
  15. Button,
  16. Calendar,
  17. CellGroup
  18. } from 'vant'
  19. import OFullRefresh from '@/components/o-full-refresh'
  20. import { defineComponent, reactive, ref, onMounted, nextTick } from 'vue'
  21. import { state as globalState } from '@/state'
  22. import { useRouter } from 'vue-router'
  23. import styles from './index.module.less'
  24. import request from '@/helpers/request'
  25. import iconTeacher from '@common/images/icon_teacher.png'
  26. import { courseEmnu } from '@/constant'
  27. export default defineComponent({
  28. name: 'exercise-record',
  29. setup() {
  30. const platformApi = ref(globalState.platformApi)
  31. const router = useRouter()
  32. const state = reactive({
  33. showSearchStatus: true,
  34. showPopoverTime: false,
  35. actions: [] as any,
  36. subjects: [] as any
  37. })
  38. const forms = reactive({
  39. startTime: dayjs().day(1).format('YYYY-MM-DD'),
  40. endTime: dayjs().day(7).format('YYYY-MM-DD'),
  41. orchestraId: '',
  42. orchestraName: '',
  43. courseType: '',
  44. courseTypeName: '',
  45. coursewareErr: null,
  46. coursewareErrName: '',
  47. keyword: '',
  48. page: 1,
  49. rows: 20
  50. })
  51. const refreshing = ref(false)
  52. const loading = ref(false)
  53. const finished = ref(false)
  54. const showContact = ref(true)
  55. const list = ref([])
  56. const getList = async () => {
  57. try {
  58. if (refreshing.value) {
  59. forms.page = 1
  60. list.value = []
  61. refreshing.value = false
  62. }
  63. const { endTime, startTime, ...re } = forms
  64. const res = await request.post(`${platformApi.value}/courseSchedule/coursewareErrPage`, {
  65. data: { ...re, endTime: endTime + ' 23:59:59', startTime: startTime + ' 00:00:00' }
  66. })
  67. if (list.value.length > 0 && res.data.pages === 1) {
  68. return
  69. }
  70. list.value = list.value.concat(res.data.rows || [])
  71. finished.value = res.data.current >= res.data.pages
  72. showContact.value = list.value.length > 0
  73. forms.page = res.data.current + 1
  74. // console.log(showContact.value, ' showContact.value ')
  75. loading.value = false
  76. } catch (e: any) {
  77. // console.log(e, 'e')
  78. const message = e.message
  79. showToast(message)
  80. showContact.value = false
  81. finished.value = true
  82. }
  83. }
  84. onMounted(() => {
  85. getList()
  86. getOrchestraList()
  87. // getSubjects()
  88. })
  89. const onBack = () => {
  90. router.go(-1)
  91. }
  92. const checkSort = (val: any) => {
  93. console.log(val, 'val')
  94. forms.coursewareErr = val.value
  95. forms.coursewareErrName = val.name
  96. }
  97. const checkOrchestra = (val: any) => {
  98. forms.orchestraId = val.value
  99. forms.orchestraName = val.name
  100. }
  101. const checkSubject = (val: any) => {
  102. forms.courseType = val.value
  103. forms.courseTypeName = val.name
  104. }
  105. const getOrchestraList = async () => {
  106. try {
  107. const res = await request.post(`${platformApi.value}/orchestra/page`, {
  108. data: { page: 1, rows: 9999, status: 'DONE' }
  109. })
  110. state.actions = res.data.rows.map((item: any) => {
  111. return {
  112. name: item.name,
  113. value: item.id as string
  114. }
  115. })
  116. } catch (e: any) {
  117. const message = e.message
  118. showToast(message)
  119. }
  120. }
  121. // const getSubjects = async () => {
  122. // try {
  123. // const res = await request.post(`${platformApi.value}/subjectBasicConfig/page`, {
  124. // data: { page: 1, rows: 9999 }
  125. // })
  126. // state.subjects = res.data.rows.map((item) => {
  127. // return {
  128. // name: item.courseTypeName,
  129. // value: item.courseType as string
  130. // }
  131. // })
  132. // } catch (e: any) {
  133. // const message = e.message
  134. // showToast(message)
  135. // }
  136. // }
  137. const onRefresh = () => {
  138. finished.value = false
  139. // 重新加载数据
  140. // 将 loading 设置为 true,表示处于加载状态
  141. loading.value = true
  142. getList()
  143. }
  144. // 名称
  145. const formatLength = (name: string) => {
  146. if (name.length > 11) {
  147. const fristName = name.substring(0, 6)
  148. const lastName = name.substring(name.length - 5, name.length)
  149. return fristName + '...' + lastName
  150. } else {
  151. return name
  152. }
  153. }
  154. const formatTime2Mins = (second: number) => {
  155. if (second) {
  156. const first = Math.floor(second / 60)
  157. const last = second % 60
  158. return (first >= 10 ? first : '0' + first) + ':' + (last >= 10 ? last : '0' + last)
  159. } else {
  160. return '00:00'
  161. }
  162. }
  163. const dropdownMenuRef = ref()
  164. const dropdownItemRef = ref()
  165. // 重置
  166. const onSearchReset = () => {
  167. forms.startTime = dayjs().day(1).format('YYYY-MM-DD')
  168. forms.endTime = dayjs().day(7).format('YYYY-MM-DD')
  169. forms.orchestraId = ''
  170. forms.orchestraName = ''
  171. forms.courseType = ''
  172. forms.courseTypeName = ''
  173. forms.coursewareErr = null
  174. forms.coursewareErrName = ''
  175. dropdownItemRef.value?.toggle()
  176. refreshing.value = true
  177. getList()
  178. }
  179. // 搜索
  180. const onSearchConfirm = () => {
  181. dropdownItemRef.value?.toggle()
  182. refreshing.value = true
  183. getList()
  184. }
  185. return () => (
  186. <div class={[!showContact.value ? 'emptyRootContainer' : '', styles.exerciseRecord]}>
  187. <OSticky
  188. position="top"
  189. background="#F8F8F8"
  190. onGetHeight={(height: any) => {
  191. document.documentElement.style.setProperty('--header-height', height + 'px')
  192. }}
  193. >
  194. <OHeader border={false}>
  195. {{
  196. right: () => (
  197. <DropdownMenu
  198. class={styles.searchMore}
  199. closeOnClickOverlay={false}
  200. closeOnClickOutside={false}
  201. ref={dropdownMenuRef}
  202. >
  203. <DropdownItem title="筛选" v-model={state.showSearchStatus} ref={dropdownItemRef}>
  204. <div class={styles.searchContainer}>
  205. {state.actions.length > 0 && (
  206. <>
  207. <div class={styles.searchTitle}>乐团</div>
  208. <div class={[styles.searchTypeGroup, styles.searchTypeFlex]}>
  209. {state.actions.map((item: any) => (
  210. <div
  211. class={[
  212. styles.searchTypeItem,
  213. item.value === forms.orchestraId && styles['is-active']
  214. ]}
  215. onClick={() => checkOrchestra(item)}
  216. >
  217. {formatLength(item.name)}
  218. </div>
  219. ))}
  220. </div>
  221. </>
  222. )}
  223. <div class={styles.searchTitle}>时间段</div>
  224. <div class={[styles.searchTypeGroup, styles.searchTypeFlex2]}>
  225. <div
  226. class={styles.searchTypeItem}
  227. onClick={() => (state.showPopoverTime = true)}
  228. >
  229. {forms.startTime}
  230. </div>
  231. <div
  232. class={styles.searchTypeItemLine}
  233. onClick={() => (state.showPopoverTime = true)}
  234. ></div>
  235. <div
  236. class={styles.searchTypeItem}
  237. onClick={() => (state.showPopoverTime = true)}
  238. >
  239. {forms.endTime}
  240. </div>
  241. </div>
  242. <div class={styles.searchTitle}>课程类型</div>
  243. <div class={[styles.searchTypeGroup, styles.searchTypeFlex1]}>
  244. {Object.keys(courseEmnu).map((subject: any) => (
  245. <div
  246. class={[
  247. styles.searchTypeItem,
  248. subject === forms.courseType && styles['is-active']
  249. ]}
  250. onClick={() => {
  251. checkSubject({
  252. name: courseEmnu[subject],
  253. value: subject
  254. })
  255. }}
  256. >
  257. {courseEmnu[subject]}
  258. </div>
  259. ))}
  260. </div>
  261. <div class={styles.searchTitle}>课件使用状态</div>
  262. <div class={[styles.searchTypeGroup, styles.searchTypeFlex]}>
  263. {[
  264. {
  265. name: '正常',
  266. value: false
  267. },
  268. {
  269. name: '异常',
  270. value: true
  271. }
  272. ].map((item: any) => (
  273. <div
  274. class={[
  275. styles.searchTypeItem,
  276. forms.coursewareErr === item.value && styles['is-active']
  277. ]}
  278. onClick={() => checkSort(item)}
  279. >
  280. {item.name}
  281. </div>
  282. ))}
  283. </div>
  284. </div>
  285. <div class={[styles.searchMoreGroup, 'van-hairline--top']}>
  286. <Button type="default" block round onClick={onSearchReset}>
  287. 重置
  288. </Button>
  289. <Button type="primary" block round onClick={onSearchConfirm}>
  290. 查询
  291. </Button>
  292. </div>
  293. </DropdownItem>
  294. </DropdownMenu>
  295. )
  296. }}
  297. </OHeader>
  298. <div
  299. style={{
  300. backgroundColor: '#fff'
  301. }}
  302. >
  303. <div class={styles.searchPreview}>
  304. <div
  305. class={styles.searchPreviewItem}
  306. onClick={() => {
  307. dropdownItemRef.value?.toggle()
  308. nextTick(() => {
  309. ;(document.querySelectorAll('.van-dropdown-item--down')[0] as any).style.top =
  310. 'var(--van-nav-bar-height)'
  311. })
  312. }}
  313. >
  314. {forms.startTime}~{forms.endTime}
  315. </div>
  316. {forms.orchestraId && (
  317. <div class={styles.searchPreviewItem}>
  318. {formatLength(forms.orchestraName)}
  319. <Icon
  320. name="cross"
  321. class={styles.cross}
  322. onClick={(e: any) => {
  323. forms.orchestraId = ''
  324. forms.orchestraName = ''
  325. e.stopPropagation()
  326. refreshing.value = true
  327. getList()
  328. }}
  329. />
  330. </div>
  331. )}
  332. {forms.courseType && (
  333. <div class={styles.searchPreviewItem}>
  334. {forms.courseTypeName}
  335. <Icon
  336. name="cross"
  337. class={styles.cross}
  338. onClick={(e: any) => {
  339. forms.courseType = ''
  340. forms.courseTypeName = ''
  341. e.stopPropagation()
  342. refreshing.value = true
  343. getList()
  344. }}
  345. />
  346. </div>
  347. )}
  348. {forms.coursewareErr != null && (
  349. <div class={styles.searchPreviewItem}>
  350. {forms.coursewareErrName}
  351. <Icon
  352. name="cross"
  353. class={styles.cross}
  354. onClick={(e: any) => {
  355. forms.coursewareErr = null
  356. forms.coursewareErrName = ''
  357. e.stopPropagation()
  358. refreshing.value = true
  359. getList()
  360. }}
  361. />
  362. </div>
  363. )}
  364. </div>
  365. </div>
  366. </OSticky>
  367. {showContact.value ? (
  368. <OFullRefresh
  369. v-model:modelValue={refreshing.value}
  370. onRefresh={onRefresh}
  371. style={{
  372. minHeight: `calc(100vh - var(--header-height))`
  373. }}
  374. >
  375. <List
  376. loading-text=" "
  377. // v-model:loading={loading.value}
  378. finished={finished.value}
  379. style={{
  380. paddingTop: '12px'
  381. }}
  382. finished-text=" "
  383. immediateCheck={false}
  384. onLoad={getList}
  385. >
  386. {list.value.map((item: any) => (
  387. <CellGroup inset class={styles.coursewareGroup}>
  388. <Cell class={styles.top1}>
  389. {{
  390. title: () => (
  391. <>
  392. {dayjs(item.startTime).format('YYYY-MM-DD HH:mm')}~
  393. {dayjs(item.endTime).format('HH:mm')}
  394. </>
  395. ),
  396. value: () => <>{formatLength(item.orchestraName)}</>
  397. }}
  398. </Cell>
  399. <Cell class={styles.top2} center>
  400. {{
  401. icon: () => (
  402. <Image
  403. class={styles.userLogo}
  404. src={item.teacherAvatar || iconTeacher}
  405. fit="cover"
  406. />
  407. ),
  408. title: () => (
  409. <div class={styles.userInfo}>
  410. <p class={[styles.userName, 'van-ellipsis']}>{item.teacherName}</p>
  411. <p class={[styles.courseName, 'van-ellipsis']}>
  412. {['INSTRUMENTAL_ENSEMBLE', 'EUPHONIUM_SINGLE'].includes(item.type)
  413. ? courseEmnu[item.type]
  414. : courseEmnu[item.type] + '单技课'}
  415. </p>
  416. </div>
  417. ),
  418. value: () => (
  419. <div class={styles.courseCount}>
  420. <p class={styles.courseRange}>
  421. <span
  422. style={{
  423. color:
  424. item.coursewarePlayTime >= item.adviseStudyTimeSecond
  425. ? '#333'
  426. : '#f44541'
  427. }}
  428. >
  429. {formatTime2Mins(item.coursewarePlayTime)}
  430. </span>
  431. /{formatTime2Mins(item.adviseStudyTimeSecond)}
  432. </p>
  433. <p class={styles.courseRangeTips}>使用时长/标准时长</p>
  434. </div>
  435. )
  436. }}
  437. </Cell>
  438. </CellGroup>
  439. ))}
  440. </List>
  441. </OFullRefresh>
  442. ) : (
  443. <OEmpty tips="暂无课件记录" />
  444. )}
  445. <Calendar
  446. v-model:show={state.showPopoverTime}
  447. firstDayOfWeek={1}
  448. showConfirm={false}
  449. type="range"
  450. maxRange={7}
  451. minDate={new Date('2023-02-27')}
  452. defaultDate={[dayjs(forms.startTime).toDate(), dayjs(forms.endTime).toDate()]}
  453. style={{
  454. height: '70%'
  455. }}
  456. onSelect={(item: any) => {
  457. forms.startTime = ''
  458. forms.endTime = ''
  459. // console.log(
  460. // item,
  461. // dayjs(item[0]).format('YYYY-MM-DD'),
  462. // 'item',
  463. // dayjs(item[0]).day(0).format('YYYY-MM-DD'),
  464. // dayjs(item[0]).day(),
  465. // dayjs(item[0]).subtract(6, 'day').format('YYYY-MM-DD')
  466. // )
  467. if (!dayjs(item[0]).isBetween(dayjs(forms.startTime), dayjs(forms.endTime))) {
  468. const week = dayjs(item[0]).day()
  469. if (week === 0) {
  470. // 星期天
  471. forms.startTime = dayjs(item[0]).subtract(6, 'day').format('YYYY-MM-DD')
  472. forms.endTime = dayjs(item[0]).format('YYYY-MM-DD')
  473. } else if (week === 1) {
  474. // 星期一
  475. forms.startTime = dayjs(item[0]).format('YYYY-MM-DD')
  476. forms.endTime = dayjs(item[0]).add(6, 'day').format('YYYY-MM-DD')
  477. } else {
  478. forms.startTime = dayjs(item[0])
  479. .subtract(week - 1, 'day')
  480. .format('YYYY-MM-DD')
  481. forms.endTime = dayjs(item[0])
  482. .add(7 - week, 'day')
  483. .format('YYYY-MM-DD')
  484. }
  485. }
  486. state.showPopoverTime = false
  487. }}
  488. />
  489. </div>
  490. )
  491. }
  492. })