information.tsx 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421
  1. import OSticky from '@/components/o-sticky'
  2. import {
  3. ActionSheet,
  4. Button,
  5. closeToast,
  6. DatePicker,
  7. Grid,
  8. GridItem,
  9. Icon,
  10. Image,
  11. List,
  12. Picker,
  13. Popover,
  14. Popup,
  15. showFailToast,
  16. showLoadingToast,
  17. showSuccessToast,
  18. showToast
  19. } from 'vant'
  20. import { computed, defineComponent, nextTick, onMounted, reactive, Teleport } from 'vue'
  21. import styles from './information.module.less'
  22. import iconSaveImage from '../images/icon-save-image.png'
  23. import iconWechat from '../images/icon-wechat.png'
  24. import OQrcode from '@/components/o-qrcode'
  25. import request from '@/helpers/request'
  26. import { useRoute, useRouter } from 'vue-router'
  27. import { CountUp } from 'countup.js'
  28. import OEmpty from '@/components/o-empty'
  29. import dayjs from 'dayjs'
  30. import isSameOrBefore from 'dayjs/plugin/isSameOrBefore'
  31. import isSameOrAfter from 'dayjs/plugin/isSameOrAfter'
  32. dayjs.extend(isSameOrBefore, isSameOrAfter)
  33. import { promisefiyPostMessage, postMessage } from '@/helpers/native-message'
  34. import html2canvas from 'html2canvas'
  35. import OActionSheet from '@/components/o-action-sheet'
  36. import { formatterDatePicker } from '@/helpers/utils'
  37. export default defineComponent({
  38. name: 'detail-information',
  39. props: {
  40. termTimes: {
  41. type: Object,
  42. default: {}
  43. }
  44. },
  45. setup(props) {
  46. const startTime = computed(() => props.termTimes.start)
  47. const endTime = computed(() => props.termTimes.end)
  48. const route = useRoute()
  49. const router = useRouter()
  50. const state = reactive({
  51. timeShow: false,
  52. currentData: [dayjs().year() + ''],
  53. actionText: '上学期',
  54. actionType: 'up',
  55. actionTerm: [
  56. { name: '上学期', selected: true, value: 'up' },
  57. { name: '下学期', value: 'down' }
  58. ],
  59. oPopover: false,
  60. check: [],
  61. checkboxRefs: [] as any,
  62. isLoading: false,
  63. list: [] as any,
  64. listState: {
  65. dataShow: true, // 判断是否有数据
  66. loading: false,
  67. finished: false
  68. },
  69. bottomHeight: 0,
  70. params: {
  71. startTime: dayjs(dayjs().year() + startTime.value).format('YYYY-MM-DD HH:mm:ss'),
  72. endTime: dayjs(dayjs().year() + endTime.value)
  73. .add(1, 'year')
  74. .subtract(1, 'day')
  75. .format('YYYY-MM-DD HH:mm:ss'),
  76. page: 1,
  77. rows: 20
  78. },
  79. statistics: {} as any,
  80. orchestraInfo: {} as any // 乐团详情
  81. })
  82. // 选择学期
  83. const onSelect = (val: any) => {
  84. state.actionTerm.forEach((item: any) => {
  85. item.selected = false
  86. })
  87. val.selected = true
  88. state.actionText = val.name
  89. state.actionType = val.value
  90. if (val.value === 'up') {
  91. state.params.startTime = dayjs(Number(state.currentData[0]) + startTime.value).format(
  92. 'YYYY-MM-DD HH:mm:ss'
  93. )
  94. state.params.endTime = dayjs(Number(state.currentData[0]) + endTime.value)
  95. .add(1, 'year')
  96. .subtract(1, 'day')
  97. .format('YYYY-MM-DD HH:mm:ss')
  98. } else if (val.value === 'down') {
  99. state.params.startTime = dayjs(Number(state.currentData[0]) + endTime.value)
  100. .add(1, 'year')
  101. .format('YYYY-MM-DD HH:mm:ss')
  102. state.params.endTime = dayjs(Number(state.currentData[0]) + startTime.value)
  103. .add(1, 'year')
  104. .subtract(1, 'day')
  105. .format('YYYY-MM-DD HH:mm:ss')
  106. }
  107. state.oPopover = false
  108. onSearch()
  109. }
  110. const onConfirmDate = (date: any) => {
  111. state.currentData = date.selectedValues
  112. const year = Number(state.currentData[0]) + 1
  113. if (state.actionType === 'up') {
  114. state.params.startTime = dayjs(year + startTime.value).format('YYYY-MM-DD HH:mm:ss')
  115. state.params.endTime = dayjs(year + endTime.value)
  116. .add(1, 'year')
  117. .subtract(1, 'day')
  118. .format('YYYY-MM-DD HH:mm:ss')
  119. } else if (state.actionType === 'down') {
  120. state.params.startTime = dayjs(year + endTime.value).format('YYYY-MM-DD HH:mm:ss')
  121. state.params.endTime = dayjs(year + startTime.value)
  122. .subtract(1, 'day')
  123. .format('YYYY-MM-DD HH:mm:ss')
  124. }
  125. state.timeShow = false
  126. onSearch()
  127. }
  128. const getDetails = async () => {
  129. try {
  130. const { data } = await request.get('/api-school/orchestra/detail/' + route.query.id)
  131. state.orchestraInfo = data || {}
  132. } catch {
  133. //
  134. }
  135. }
  136. const getStatistics = async () => {
  137. try {
  138. const { data } = await request.post('/api-school/school/schoolSummaryStat', {
  139. data: {
  140. orchestraId: route.query.id
  141. }
  142. })
  143. state.statistics = data || {}
  144. initNumCountUp()
  145. } catch {
  146. //
  147. }
  148. }
  149. // 班级列表
  150. const getList = async () => {
  151. try {
  152. if (state.isLoading) return
  153. state.isLoading = true
  154. const res = await request.post('/api-school/classGroup/page', {
  155. data: {
  156. ...state.params,
  157. orchestraId: route.query.id
  158. }
  159. })
  160. state.listState.loading = false
  161. const result = res.data || {}
  162. // 处理重复请求数据
  163. if (state.list.length > 0 && result.current === 1) {
  164. return
  165. }
  166. const rows = result.rows || []
  167. state.list = state.list.concat(rows)
  168. state.listState.finished = result.current >= result.pages
  169. state.params.page = result.current + 1
  170. state.listState.dataShow = state.list.length > 0
  171. state.isLoading = false
  172. } catch {
  173. state.listState.dataShow = false
  174. state.listState.finished = true
  175. state.isLoading = false
  176. }
  177. }
  178. const onSearch = () => {
  179. state.params.page = 1
  180. state.list = []
  181. state.listState.dataShow = true // 判断是否有数据
  182. state.listState.loading = false
  183. state.listState.finished = false
  184. getList()
  185. }
  186. const initNumCountUp = () => {
  187. nextTick(() => {
  188. // 在读学生
  189. const statistics = state.statistics
  190. new CountUp('currentStudentNum', statistics.currentStudent || 0).start()
  191. new CountUp('time1', statistics.attendanceRate * 100 || 0).start()
  192. new CountUp('time2', statistics.homeworkSubmissionRate * 100 || 0).start()
  193. new CountUp('time3', statistics.practicePassRate * 100 || 0).start()
  194. })
  195. }
  196. onMounted(async () => {
  197. const sysStartTime = dayjs(dayjs().year() + startTime.value).format('YYYY-MM-DD')
  198. const sysEndTime = dayjs(dayjs().year() + endTime.value).format('YYYY-MM-DD')
  199. const nowTime = dayjs().format('YYYY-MM-DD')
  200. console.log(nowTime, sysStartTime)
  201. const before = dayjs(nowTime).isBefore(dayjs(sysStartTime))
  202. const after = dayjs(nowTime).isBefore(dayjs(sysEndTime))
  203. const year = dayjs().year()
  204. // console.log(before, after, year)
  205. if (before && after) {
  206. state.currentData = [year - 1 + '']
  207. state.params.startTime = dayjs(year - 1 + startTime.value).format('YYYY-MM-DD HH:mm:ss')
  208. state.params.endTime = dayjs(dayjs().year() + endTime.value)
  209. .subtract(1, 'day')
  210. .format('YYYY-MM-DD HH:mm:ss')
  211. // 上学期
  212. }
  213. if (!before && !after) {
  214. state.params.startTime = dayjs(dayjs().year() + startTime.value).format(
  215. 'YYYY-MM-DD HH:mm:ss'
  216. )
  217. state.params.endTime = dayjs(dayjs().year() + endTime.value)
  218. .add(1, 'year')
  219. .subtract(1, 'day')
  220. .format('YYYY-MM-DD HH:mm:ss')
  221. // 下一年的上学期
  222. }
  223. if (before && !after) {
  224. state.params.startTime = dayjs(year + endTime.value).format('YYYY-MM-DD HH:mm:ss')
  225. state.params.endTime = dayjs(year + startTime.value)
  226. .subtract(1, 'day')
  227. .format('YYYY-MM-DD HH:mm:ss')
  228. // 下学期
  229. state.actionTerm.forEach((item: any) => {
  230. if (item.value === 'down') {
  231. item.color = 'var(--van-primary-color)'
  232. state.actionText = item.text
  233. state.actionType = item.value
  234. } else {
  235. item.color = ''
  236. }
  237. })
  238. state.currentData = [year - 1 + '']
  239. state.actionText = '下学期'
  240. state.actionType = 'up'
  241. state.actionTerm.forEach((item: any) => {
  242. if (item.value === 'up') {
  243. item.selected = true
  244. } else {
  245. item.selected = false
  246. }
  247. })
  248. }
  249. await getDetails()
  250. await getStatistics()
  251. await getList()
  252. })
  253. return () => (
  254. <>
  255. <div class={'searchGroup'}>
  256. <div
  257. class={['searchItem', state.timeShow ? 'searchItem-active' : '']}
  258. onClick={() => (state.timeShow = true)}
  259. >
  260. {state.currentData[0]}年 <i class={'arrow'}></i>
  261. </div>
  262. <div
  263. class={['searchItem', state.oPopover ? 'searchItem-active' : '']}
  264. onClick={() => (state.oPopover = true)}
  265. >
  266. {state.actionText} <i class={'arrow'}></i>
  267. </div>
  268. </div>
  269. <div
  270. style={{
  271. height: `calc(100vh - var(--header-height) - var(--van-tabs-line-height) - 1.17333rem)`,
  272. overflow: 'hidden',
  273. overflowY: 'auto'
  274. }}
  275. >
  276. <Grid border={false} class={styles.gridContainer}>
  277. <GridItem>
  278. <p class={[styles.title, styles.red]}>
  279. <span id="currentStudentNum">{state.statistics.studentNum || 0}</span>
  280. <i> 名</i>
  281. </p>
  282. <p class={styles.name}>在读学生</p>
  283. </GridItem>
  284. <GridItem>
  285. <p class={[styles.title, styles.red]}>
  286. <span id="time1">{state.statistics.attendanceRate || 0}</span>%
  287. </p>
  288. <p class={styles.name}>到课率</p>
  289. </GridItem>
  290. <GridItem>
  291. <p class={[styles.title, styles.red]}>
  292. <span id="time2">{state.statistics.homeworkRate || 0}</span>%
  293. </p>
  294. <p class={styles.name}>作业提交率</p>
  295. </GridItem>
  296. <GridItem>
  297. <p class={[styles.title, styles.red]}>
  298. <span id="time3">{state.statistics.homeworkQualifiedRate || 0}</span>%
  299. </p>
  300. <p class={styles.name}>练习合格率</p>
  301. </GridItem>
  302. </Grid>
  303. {state.listState.dataShow ? (
  304. <List
  305. // v-model:loading={state.listState.loading}
  306. finished={state.listState.finished}
  307. finishedText=" "
  308. class={[styles.liveList]}
  309. onLoad={getList}
  310. immediateCheck={false}
  311. >
  312. {state.list.map((item: any) => (
  313. <div class={[styles.gridContainer, styles.gridClass]}>
  314. <div class={styles.className}>
  315. <i class={styles.line}></i>
  316. {item.name}
  317. </div>
  318. <Grid border={false} columnNum={3}>
  319. <GridItem>
  320. <p class={styles.title}>{item.preStudentNum || 0}</p>
  321. <p class={styles.name}>在读学生</p>
  322. </GridItem>
  323. <GridItem>
  324. <p class={[styles.title, styles.teacher, 'van-ellipsis']}>
  325. {item.teacherName || '-'}
  326. </p>
  327. <p class={styles.name}>伴学指导</p>
  328. </GridItem>
  329. <GridItem>
  330. <p class={styles.title}>
  331. {item.completeCourseScheduleNum || 0}/{item.courseScheduleNum || 0}
  332. </p>
  333. <p class={styles.name}>课时</p>
  334. </GridItem>
  335. </Grid>
  336. </div>
  337. ))}
  338. </List>
  339. ) : (
  340. <OEmpty btnStatus={false} tips="暂无班级" />
  341. )}
  342. {/* */}
  343. {state.orchestraInfo.canSignUp && (
  344. // <Teleport to={'body'}>
  345. <OSticky
  346. position="bottom"
  347. class={styles.informationBottom}
  348. onGetHeight={(height: any) => {
  349. state.bottomHeight = height
  350. }}
  351. >
  352. <div class={'btnGroup'}>
  353. <Button
  354. round
  355. block
  356. type="primary"
  357. onClick={() => {
  358. router.push({
  359. path: 'save-share-image',
  360. query: {
  361. type: 'orchestra',
  362. id: route.query.id
  363. }
  364. })
  365. }}
  366. >
  367. 报名二维码
  368. </Button>
  369. </div>
  370. </OSticky>
  371. // </Teleport>
  372. )}
  373. </div>
  374. <OActionSheet
  375. v-model:show={state.oPopover}
  376. actions={state.actionTerm}
  377. onSelect={onSelect}
  378. teleport={'body'}
  379. />
  380. <Popup
  381. v-model:show={state.timeShow}
  382. position="bottom"
  383. round
  384. class={'popupBottomSearch'}
  385. teleport={'body'}
  386. >
  387. <DatePicker
  388. v-model={state.currentData}
  389. columnsType={['year']}
  390. formatter={formatterDatePicker}
  391. onConfirm={onConfirmDate}
  392. onCancel={() => (state.timeShow = false)}
  393. />
  394. </Popup>
  395. </>
  396. )
  397. }
  398. })