index.tsx 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765
  1. import { defineComponent, reactive, ref, shallowReactive } from 'vue'
  2. import styles from './index.module.less'
  3. import iconArrow1 from '../images/icon-arrow1.png'
  4. import iconArrow11 from '../images/icon-arrow1-1.png'
  5. import icon1 from '../images/icon-1.png'
  6. import icon2 from '../images/icon-2.png'
  7. import ArrowUp from '../images/arrow-up.png'
  8. import ArrowUpActive from '../images/arrow-up-active.png'
  9. import iconDownload from '../images/icon-download.png'
  10. import { Button, DatetimePicker, Popup, Toast } from 'vant'
  11. import Echats from './echats'
  12. import ColHeader from '@/components/col-header'
  13. import TheSticky from '@/components/the-sticky'
  14. import { formatterDatePicker } from '@/helpers/utils'
  15. import dayjs from 'dayjs'
  16. import request from '@/helpers/request'
  17. import { getTimeRange, TIME_TYPE } from '../home-statistics'
  18. import ColResult from '@/components/col-result'
  19. import { promisefiyPostMessage } from '@/helpers/native-message'
  20. import { useRouter } from 'vue-router'
  21. import { state } from '@/state'
  22. /** 秒转分 */
  23. export const formatSecToMin = (second: number) => {
  24. if (isNaN(second)) {
  25. return '0'
  26. }
  27. const mm = (Math.floor(second / 60) + Math.floor(second % 60) / 60).toFixed(2)
  28. return mm
  29. }
  30. /** 秒转时分秒 */
  31. export const formatSecToHMS = second => {
  32. const hours = Math.floor(second / 3600)
  33. .toString()
  34. .padStart(2, '0')
  35. const minutes = Math.floor((second % 3600) / 60)
  36. .toString()
  37. .padStart(2, '0')
  38. const seconds = Math.round(second % 60)
  39. .toString()
  40. .padStart(2, '0')
  41. return {
  42. all: hours + '时' + minutes + '分' + seconds + '秒',
  43. hours,
  44. minutes,
  45. seconds
  46. }
  47. }
  48. const catchKey = 'practice-statistics-detail-search'
  49. export default defineComponent({
  50. name: 'PracticeDetail',
  51. setup() {
  52. const router = useRouter()
  53. let catchSearch: any = sessionStorage.getItem(catchKey)
  54. catchSearch = catchSearch ? JSON.parse(catchSearch) : {}
  55. sessionStorage.removeItem(catchKey)
  56. const searchStatus = ref(false)
  57. const currentType = ref<TIME_TYPE>(
  58. catchSearch.currentType !== undefined ? catchSearch.currentType : 'MONTH'
  59. )
  60. const searchObj = reactive({
  61. tempSubjectId: catchSearch.subjectId || ('' as any),
  62. type:
  63. catchSearch.currentType !== undefined
  64. ? catchSearch.currentType
  65. : ('MONTH' as TIME_TYPE)
  66. })
  67. const timeRange =
  68. catchSearch.startTime && catchSearch.endTime
  69. ? {
  70. startTime: catchSearch.startTime,
  71. endTime: catchSearch.endTime
  72. }
  73. : getTimeRange(currentType.value)
  74. const forms = reactive({
  75. loading: false,
  76. dataShow: true,
  77. isScrollLeft: false,
  78. subjectId: catchSearch.subjectId || ('' as any), // 选择的声部
  79. subjectList: [] as any,
  80. startTimeStatus: false,
  81. startTimeClosedStatus: false,
  82. endTimeMinDate: new Date(timeRange?.startTime || ''),
  83. endTimeMaxDate: dayjs(new Date(timeRange?.startTime || ''))
  84. .add(1, 'year')
  85. .toDate(),
  86. endTimeStatus: false,
  87. endTimeClosedStatus: false,
  88. startTime: new Date(timeRange?.startTime || ''),
  89. startTimeStr: timeRange?.startTime || '',
  90. endTime: new Date(timeRange?.endTime || ''),
  91. endTimeStr: timeRange?.endTime || '',
  92. sortField: '' as 'totalPracticeTime' | 'averagePracticeTime' | '', // 排序字段
  93. sortType: '' as 'ASC' | 'DESC' | '' // 排序方式 ,ASC升序,DESC降序
  94. })
  95. // 练习统计
  96. const practiceSummary = shallowReactive({
  97. averagePracticeTime: '0',
  98. practiceCount: '0',
  99. totalPracticeTime: '0',
  100. totalTimes: {
  101. hours: '00',
  102. minutes: '00',
  103. seconds: '00'
  104. }
  105. })
  106. const obj = ref({
  107. students: [] as any,
  108. xAxisDataTime: [] as any,
  109. yAxisDataTime: [] as any,
  110. timeMaxCount: 5,
  111. timeCount: 0 as any,
  112. timeStr: '',
  113. xAxisDataCount: [] as any,
  114. yAxisDataCount: [] as any,
  115. countMaxCount: 5, // 默认横线
  116. countCount: 0,
  117. countStr: ''
  118. })
  119. // const searchText = computed(() => {
  120. // const template = {
  121. // MONTH: '本月',
  122. // THREE_MONTH: '近三个月',
  123. // HALF_YEAR: '近年半',
  124. // YEAR: '近一年'
  125. // }
  126. // return template[currentType.value]
  127. // })
  128. // 导出学生练习时长数据
  129. const onExport = async () => {
  130. try {
  131. const { data } = await request.post(
  132. '/api-teacher/home/exportStudentPractice',
  133. {
  134. data: {
  135. startTime: forms.startTimeStr,
  136. endTime: forms.endTimeStr,
  137. subjectId: forms.subjectId,
  138. sortField: forms.sortField,
  139. sortType: forms.sortType // 排序方式 ,ASC升序,DESC降序
  140. }
  141. }
  142. )
  143. const pathname = data || ''
  144. if (pathname) {
  145. const fileName = `练习详情${forms.startTimeStr}~${forms.endTimeStr}_${state.user.data?.userId}`
  146. // 发送消息通知移动端下载文件
  147. promisefiyPostMessage({
  148. api: 'downloadFile',
  149. content: {
  150. downloadUrl: pathname,
  151. fileName
  152. }
  153. })
  154. }
  155. } catch {
  156. //
  157. }
  158. }
  159. const getDetail = async () => {
  160. forms.loading = true
  161. try {
  162. const { data } = await request.post('/api-teacher/home/practice', {
  163. data: {
  164. startTime: forms.startTimeStr,
  165. endTime: forms.endTimeStr,
  166. subjectId: forms.subjectId
  167. }
  168. })
  169. const summary = data.practiceSummary || {}
  170. practiceSummary.averagePracticeTime = formatSecToMin(
  171. summary.averagePracticeTime || 0
  172. )
  173. practiceSummary.practiceCount = summary.practiceCount || 0
  174. practiceSummary.totalPracticeTime = summary.totalPracticeTime || 0
  175. practiceSummary.totalTimes = formatSecToHMS(
  176. summary.totalPracticeTime || 0
  177. )
  178. // 练习时长
  179. const practiceTimes = data.practiceTimes || []
  180. const xAxisDataTimes: string[] = []
  181. const practiceTimeList: any[] = []
  182. practiceTimes.forEach((item: any, index: number) => {
  183. xAxisDataTimes.push(item.date)
  184. practiceTimeList.push([
  185. index,
  186. formatSecToMin(item.practiceTime),
  187. item.practiceTime
  188. ])
  189. if (practiceTimes.length - 1 === index) {
  190. obj.value.timeCount = item.practiceTime
  191. obj.value.timeStr = item.date
  192. }
  193. })
  194. // 练习人数
  195. const practiceCounts = data.practiceCounts || []
  196. const xAxisDataCounts: string[] = []
  197. const countList: number[] = []
  198. let maxCount = 0 // 最大人数 - 用记设置练习人数分割线
  199. practiceCounts.forEach((item: any, index: number) => {
  200. xAxisDataCounts.push(item.date)
  201. countList.push(item.practiceTime)
  202. if (maxCount < (item.practiceTime || 0)) {
  203. maxCount = item.practiceTime
  204. }
  205. if (practiceCounts.length - 1 === index) {
  206. obj.value.countCount = item.practiceTime
  207. obj.value.countStr = item.date
  208. }
  209. })
  210. obj.value.xAxisDataTime = xAxisDataTimes
  211. obj.value.yAxisDataTime = practiceTimeList
  212. obj.value.xAxisDataCount = xAxisDataCounts
  213. obj.value.yAxisDataCount = countList
  214. // 最小数量为1
  215. obj.value.countMaxCount = maxCount >= 5 ? 5 : Math.max(maxCount, 1)
  216. } catch {
  217. //
  218. }
  219. forms.loading = false
  220. }
  221. // 用户列表数据
  222. const getStudentDetail = async () => {
  223. try {
  224. const { data } = await request.post(
  225. '/api-teacher/home/studentPractice',
  226. {
  227. data: {
  228. startTime: forms.startTimeStr,
  229. endTime: forms.endTimeStr,
  230. subjectId: forms.subjectId,
  231. sortField: forms.sortField,
  232. sortType: forms.sortType
  233. }
  234. }
  235. )
  236. // 学员练习时长
  237. const studentPracticeSummary = data || []
  238. const tempStudents: any = []
  239. studentPracticeSummary.forEach((item: any) => {
  240. const student = {
  241. avatar: item.avatar,
  242. averagePracticeTime: formatSecToHMS(item.averagePracticeTime || 0),
  243. practiceDays: item.practiceDays || 0,
  244. studentName: item.studentName,
  245. subjectName: item.subjectName,
  246. totalPracticeTime: formatSecToHMS(item.totalPracticeTime || 0),
  247. userId: item.userId
  248. }
  249. tempStudents.push(student)
  250. })
  251. obj.value.students = tempStudents
  252. forms.dataShow = tempStudents.length > 0 ? true : false
  253. } catch {
  254. //
  255. }
  256. }
  257. const getSubjectList = async () => {
  258. const { data } = await request.get(
  259. `/api-teacher/subject/subSubjectSelect?type=MUSIC`
  260. )
  261. if (Array.isArray(data)) {
  262. forms.subjectList = data
  263. }
  264. }
  265. getSubjectList()
  266. getDetail()
  267. getStudentDetail()
  268. const onChangeTime = (type: TIME_TYPE) => {
  269. if (searchObj.type === type) return
  270. searchObj.type = type
  271. resetTime(type)
  272. }
  273. // 格式化
  274. const resetTime = (type: TIME_TYPE) => {
  275. const timeRang = getTimeRange(type)
  276. forms.startTime = new Date(timeRang?.startTime || '')
  277. forms.startTimeStr = timeRang?.startTime || ''
  278. forms.endTimeMinDate = dayjs(timeRang?.startTime || '').toDate()
  279. forms.endTimeMaxDate = dayjs(timeRang?.startTime || '')
  280. .add(1, 'year')
  281. .toDate()
  282. forms.endTime = new Date(timeRang?.endTime || '')
  283. forms.endTimeStr = timeRang?.endTime || ''
  284. }
  285. // 重置
  286. const onConfirm = () => {
  287. if (!forms.startTimeStr || !forms.endTimeStr) {
  288. Toast('请选择时间范围')
  289. return
  290. }
  291. // timeRange.value = getTimeRange(currentType.value)
  292. searchStatus.value = false
  293. forms.subjectId = searchObj.tempSubjectId
  294. currentType.value = searchObj.type
  295. getDetail()
  296. getStudentDetail()
  297. }
  298. /** 排序 */
  299. const onSort = (
  300. field: 'totalPracticeTime' | 'averagePracticeTime' | ''
  301. ) => {
  302. if (!field) return
  303. if (forms.sortField !== field) {
  304. forms.sortType = ''
  305. }
  306. forms.sortField = field
  307. if (forms.sortType === 'ASC') {
  308. forms.sortType = ''
  309. forms.sortField = ''
  310. } else if (forms.sortType === 'DESC') {
  311. forms.sortType = 'ASC'
  312. } else {
  313. forms.sortType = 'DESC'
  314. }
  315. getStudentDetail()
  316. }
  317. /** 跳转详情 */
  318. const toDetail = (item: any) => {
  319. sessionStorage.setItem(
  320. catchKey,
  321. JSON.stringify({
  322. startTime: forms.startTimeStr,
  323. endTime: forms.endTimeStr,
  324. currentType: currentType.value,
  325. subjectId: forms.subjectId
  326. })
  327. )
  328. router.push({
  329. path: '/exercise-detail',
  330. query: {
  331. studentId: item.userId || ''
  332. }
  333. })
  334. }
  335. return () => (
  336. <div class={styles.practiceDetail}>
  337. <TheSticky position="top">
  338. <ColHeader background="transparent" border={false} />
  339. </TheSticky>
  340. <div class={styles.groupContainer}>
  341. <div class={styles.section}>
  342. <div
  343. class={[styles.filter, searchStatus.value && styles.active]}
  344. onClick={() => (searchStatus.value = true)}
  345. >
  346. <span>筛选</span>
  347. <img src={searchStatus.value ? iconArrow11 : iconArrow1} />
  348. </div>
  349. <div class={styles.title}>
  350. <span>总练习时长</span>
  351. </div>
  352. <div class={styles.sList}>
  353. <div class={styles.sItem}>
  354. <div class={styles.sTop}>
  355. <img src={icon1} />
  356. <span>总练习时长</span>
  357. </div>
  358. <div class={styles.sBottom}>
  359. <div class={styles.leaveTime}>
  360. <span class={styles.num}>
  361. {practiceSummary.totalTimes.hours}
  362. </span>
  363. <span class={styles.text}>小时</span>
  364. <span class={styles.num}>
  365. {practiceSummary.totalTimes.minutes}
  366. </span>
  367. <span class={styles.text}>分</span>
  368. <span class={styles.num}>
  369. {practiceSummary.totalTimes.seconds}
  370. </span>
  371. <span class={styles.text}>秒</span>
  372. </div>
  373. </div>
  374. </div>
  375. <div class={styles.sItem}>
  376. <div class={styles.sTop}>
  377. <img src={icon2} />
  378. <span>练习人数</span>
  379. </div>
  380. <div class={styles.sBottom}>
  381. <span class={styles.num}>
  382. {practiceSummary.practiceCount}
  383. </span>
  384. <span class={styles.text}>人</span>
  385. </div>
  386. </div>
  387. {/* <div class={styles.sItem}>
  388. <div class={styles.sTop}>
  389. <img src={icon1} />
  390. <span>平均练习时长</span>
  391. </div>
  392. <div class={styles.sBottom}>
  393. <span class={styles.num}>
  394. {practiceSummary.averagePracticeTime}
  395. </span>
  396. <span class={styles.text}>分钟</span>
  397. </div>
  398. </div> */}
  399. </div>
  400. </div>
  401. <div class={styles.section}>
  402. <div class={styles.title}>
  403. <span>练习时长</span>
  404. </div>
  405. <Echats
  406. obj={{
  407. xAxisData: obj.value.xAxisDataTime,
  408. yAxisData: obj.value.yAxisDataTime,
  409. count: obj.value.timeCount,
  410. time: obj.value.timeStr
  411. }}
  412. />
  413. </div>
  414. <div class={styles.section}>
  415. <div class={styles.title}>
  416. <span>练习人数</span>
  417. </div>
  418. <Echats
  419. type="NUM"
  420. obj={{
  421. xAxisData: obj.value.xAxisDataCount,
  422. yAxisData: obj.value.yAxisDataCount,
  423. count: obj.value.countCount,
  424. time: obj.value.countStr,
  425. countMaxCount: obj.value.countMaxCount
  426. }}
  427. />
  428. </div>
  429. <div class={styles.section}>
  430. <div class={styles.title}>
  431. <span>学员练习详情</span>
  432. <div class={styles.download} onClick={onExport}>
  433. <div>导出</div>
  434. <img src={iconDownload} />
  435. </div>
  436. </div>
  437. <div class={styles.scroll}>
  438. {forms.dataShow ? (
  439. <table class={[styles.dataTable]} style={{ width: '486px' }}>
  440. <colgroup>
  441. <col style="width: 88px;" />
  442. <col style="width: 105px;" />
  443. <col style="width: 106px;" />
  444. <col style="width: 72px;" />
  445. <col style="width: 106px;" />
  446. </colgroup>
  447. <thead>
  448. <tr>
  449. <th class={[styles.tdFixedLeft]}>学员</th>
  450. <th>乐器</th>
  451. <th>
  452. <div
  453. class={styles.filterSection}
  454. onClick={() => onSort('totalPracticeTime')}
  455. >
  456. 练习总时长
  457. <div class={styles.filters}>
  458. <img
  459. src={
  460. forms.sortField === 'totalPracticeTime' &&
  461. forms.sortType === 'ASC'
  462. ? ArrowUpActive
  463. : ArrowUp
  464. }
  465. class={styles.upArrow}
  466. />
  467. <img
  468. src={
  469. forms.sortField === 'totalPracticeTime' &&
  470. forms.sortType === 'DESC'
  471. ? ArrowUpActive
  472. : ArrowUp
  473. }
  474. class={styles.downArrow}
  475. />
  476. </div>
  477. </div>
  478. </th>
  479. <th>练习天数</th>
  480. <th>
  481. <div
  482. class={styles.filterSection}
  483. onClick={() => onSort('averagePracticeTime')}
  484. >
  485. 平均练习时长
  486. <div class={styles.filters}>
  487. <img
  488. src={
  489. forms.sortField === 'averagePracticeTime' &&
  490. forms.sortType === 'ASC'
  491. ? ArrowUpActive
  492. : ArrowUp
  493. }
  494. class={styles.upArrow}
  495. />
  496. <img
  497. src={
  498. forms.sortField === 'averagePracticeTime' &&
  499. forms.sortType === 'DESC'
  500. ? ArrowUpActive
  501. : ArrowUp
  502. }
  503. class={styles.downArrow}
  504. />
  505. <img />
  506. </div>
  507. </div>
  508. </th>
  509. </tr>
  510. </thead>
  511. <tbody>
  512. {obj.value.students.map((item: any) => (
  513. <tr onClick={() => toDetail(item)}>
  514. <td class={[styles.tdFixedLeft]}>
  515. <img class={styles.userImg} src={item.avatar} />
  516. <span>{item.studentName}</span>
  517. </td>
  518. <td>{item.subjectName}</td>
  519. <td>
  520. {item.totalPracticeTime.hours}小时
  521. {item.totalPracticeTime.minutes}分
  522. {item.totalPracticeTime.seconds}秒
  523. </td>
  524. <td>{item.practiceDays}</td>
  525. <td>
  526. {item.averagePracticeTime.hours}小时
  527. {item.averagePracticeTime.minutes}分
  528. {item.averagePracticeTime.seconds}秒
  529. </td>
  530. </tr>
  531. ))}
  532. </tbody>
  533. </table>
  534. ) : (
  535. <ColResult
  536. classImgSize="SMALL"
  537. btnStatus={false}
  538. tips="暂无数据"
  539. />
  540. )}
  541. </div>
  542. </div>
  543. </div>
  544. <Popup
  545. v-model:show={searchStatus.value}
  546. closeable
  547. round
  548. position="bottom"
  549. class={styles.searchPopup}
  550. >
  551. <div class={styles.popupContainer}>
  552. <div class={styles.popupTitle}>筛选</div>
  553. <div class={styles.popupSearchList}>
  554. <div class={styles.popupSection}>
  555. <div class={styles.title}>
  556. <span>时间</span>
  557. </div>
  558. <div class={styles.timeCount}>
  559. <p
  560. onClick={() => onChangeTime('MONTH')}
  561. class={searchObj.type === 'MONTH' ? styles.active : ''}
  562. >
  563. 本月
  564. </p>
  565. <p
  566. onClick={() => onChangeTime('THREE_MONTH')}
  567. class={
  568. searchObj.type === 'THREE_MONTH' ? styles.active : ''
  569. }
  570. >
  571. 近三个月
  572. </p>
  573. <p
  574. onClick={() => onChangeTime('HALF_YEAR')}
  575. class={searchObj.type === 'HALF_YEAR' ? styles.active : ''}
  576. >
  577. 近半年
  578. </p>
  579. <p
  580. onClick={() => onChangeTime('YEAR')}
  581. class={searchObj.type === 'YEAR' ? styles.active : ''}
  582. >
  583. 近一年
  584. </p>
  585. </div>
  586. <div class={styles.timeRang}>
  587. <p
  588. class={[
  589. styles.timeInput,
  590. forms.startTimeStr && styles.hasValue
  591. ]}
  592. onClick={() => {
  593. forms.startTimeStatus = true
  594. forms.startTimeClosedStatus = true
  595. }}
  596. >
  597. {forms.startTimeStr || '起始时间'}
  598. </p>
  599. <p class={styles.timeUnit}></p>
  600. <p
  601. class={[
  602. styles.timeInput,
  603. forms.endTimeStr && styles.hasValue
  604. ]}
  605. onClick={() => {
  606. forms.endTimeStatus = true
  607. forms.endTimeClosedStatus = true
  608. }}
  609. >
  610. {forms.endTimeStr || '终止时间'}
  611. </p>
  612. </div>
  613. </div>
  614. <div class={styles.popupSection}>
  615. <div class={styles.title}>
  616. <span>声部</span>
  617. </div>
  618. <div class={[styles.timeCount, styles.timeSubject]}>
  619. <p
  620. class={searchObj.tempSubjectId === '' ? styles.active : ''}
  621. onClick={() => (searchObj.tempSubjectId = '')}
  622. >
  623. 全部
  624. </p>
  625. {forms.subjectList.map((item: any) => (
  626. <p
  627. class={
  628. searchObj.tempSubjectId === item.id ? styles.active : ''
  629. }
  630. onClick={() => {
  631. searchObj.tempSubjectId = item.id
  632. }}
  633. >
  634. {item.name}
  635. </p>
  636. ))}
  637. </div>
  638. </div>
  639. </div>
  640. <div class={styles.popupBottom}>
  641. <Button
  642. round
  643. block
  644. type="default"
  645. onClick={() => {
  646. searchObj.tempSubjectId = ''
  647. searchObj.type = 'MONTH'
  648. resetTime('MONTH')
  649. }}
  650. >
  651. 重置
  652. </Button>
  653. <Button round block type="primary" onClick={onConfirm}>
  654. 确认
  655. </Button>
  656. </div>
  657. </div>
  658. </Popup>
  659. {/* 开始日期 */}
  660. <Popup
  661. v-model:show={forms.startTimeStatus}
  662. position="bottom"
  663. round
  664. class={'popupBottomSearch'}
  665. onClosed={() => {
  666. forms.startTimeClosedStatus = false
  667. }}
  668. >
  669. {forms.startTimeClosedStatus && (
  670. <DatetimePicker
  671. v-model={forms.startTime}
  672. type="date"
  673. formatter={formatterDatePicker}
  674. onCancel={() => (forms.startTimeStatus = false)}
  675. onConfirm={(val: any) => {
  676. forms.startTime = val
  677. forms.startTimeStr = dayjs(val).format('YYYY-MM-DD')
  678. forms.startTimeStatus = false
  679. forms.endTimeMinDate = dayjs(val || new Date()).toDate()
  680. forms.endTimeMaxDate = dayjs(val || new Date())
  681. .add(1, 'year')
  682. .toDate()
  683. forms.endTime = val
  684. forms.endTimeStr = ''
  685. searchObj.type = '' as any
  686. }}
  687. />
  688. )}
  689. </Popup>
  690. {/* 结束日期 */}
  691. <Popup
  692. v-model:show={forms.endTimeStatus}
  693. position="bottom"
  694. round
  695. class={'popupBottomSearch'}
  696. onClosed={() => {
  697. forms.endTimeClosedStatus = false
  698. }}
  699. >
  700. {forms.endTimeClosedStatus && (
  701. <DatetimePicker
  702. v-model={forms.endTime}
  703. type="date"
  704. minDate={forms.endTimeMinDate}
  705. maxDate={forms.endTimeMaxDate}
  706. formatter={formatterDatePicker}
  707. onCancel={() => (forms.endTimeStatus = false)}
  708. onConfirm={(val: any) => {
  709. forms.endTime = val
  710. forms.endTimeStatus = false
  711. forms.endTimeStr = dayjs(val).format('YYYY-MM-DD')
  712. searchObj.type = '' as any
  713. }}
  714. />
  715. )}
  716. </Popup>
  717. </div>
  718. )
  719. }
  720. })