information.tsx 12 KB

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