video-class-detail.tsx 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376
  1. import CourseVideoItem from '@/business-components/course-video-item'
  2. import SectionDetail from '@/business-components/section-detail'
  3. import ColHeader from '@/components/col-header'
  4. import ColVideo from '@/components/col-video'
  5. import request from '@/helpers/request'
  6. import {
  7. Cell,
  8. Icon,
  9. Rate,
  10. Tab,
  11. Tabs,
  12. Image,
  13. Button,
  14. Sticky,
  15. Field,
  16. List,
  17. Toast
  18. } from 'vant'
  19. import { defineComponent } from 'vue'
  20. import styles from './video-class-detail.module.less'
  21. import { state } from '@/state'
  22. import iconTeacher from '@common/images/icon_teacher.png'
  23. import ColResult from '@/components/col-result'
  24. import dayjs from 'dayjs'
  25. export default defineComponent({
  26. name: 'VideoClassDetail',
  27. data() {
  28. const query = this.$route.query
  29. return {
  30. groupId: query.groupId,
  31. classId: query.classId,
  32. tabIndex: 1,
  33. title: '',
  34. lessonPrice: 0,
  35. useRelationType: '',
  36. alreadyBuy: false,
  37. detailList: [],
  38. posterUrl: '',
  39. srcUrl: '',
  40. message: '',
  41. navHeight: 0 as any,
  42. currentClassIndex: 1,
  43. reload: false,
  44. videoContent: '',
  45. list: [],
  46. dataShow: true, // 判断是否有数据
  47. loading: false,
  48. finished: false,
  49. params: {
  50. page: 1,
  51. rows: 20
  52. },
  53. freeRate: 0, // 试看百分比
  54. trySee: false, // 是否试看
  55. videoHeight: '212px'
  56. }
  57. },
  58. computed: {
  59. users() {
  60. return state.user.data
  61. },
  62. offsetTop() {
  63. const navHeight: number = this.navHeight
  64. const top = Number(navHeight) + 44
  65. return top + 'px'
  66. }
  67. },
  68. async mounted() {
  69. // 处理视频显示
  70. const width = document.body.clientWidth || document.body.offsetWidth
  71. this.videoHeight = (width / 16) * 9 + 'px'
  72. this.navHeight = sessionStorage.getItem('navHeight') || 0
  73. try {
  74. //
  75. this.reload = true
  76. const res = await request.get(
  77. '/api-student/videoLesson/selectVideoLesson',
  78. {
  79. params: {
  80. groupId: this.groupId
  81. }
  82. }
  83. )
  84. const result = res.data || {}
  85. this.title = result.lessonGroup.lessonName
  86. this.lessonPrice = result.lessonGroup.lessonPrice
  87. this.useRelationType = result.lessonGroup.relationType
  88. this.alreadyBuy = result.alreadyBuy
  89. this.detailList = result.detailList || []
  90. this.trySee = !result.alreadyBuy
  91. this.detailList.forEach((item: any, index: number) => {
  92. if (item.id === Number(this.classId)) {
  93. this.posterUrl = item.coverUrl
  94. this.srcUrl = item.videoUrl
  95. this.title = item.videoTitle
  96. this.currentClassIndex = index + 1
  97. this.videoContent = item.videoContent
  98. }
  99. })
  100. this.reload = false
  101. const config = await request.get(
  102. '/api-student/sysConfig/queryByParamNameList',
  103. {
  104. params: {
  105. paramNames: 'video_lesson_free_rate'
  106. }
  107. }
  108. )
  109. this.freeRate = config.data[0]?.paramValue || 0
  110. } catch {}
  111. this.getList()
  112. },
  113. methods: {
  114. onSearch() {
  115. this.params.page = 1
  116. this.list = []
  117. this.dataShow = true // 判断是否有数据
  118. this.loading = false
  119. this.finished = false
  120. this.getList()
  121. },
  122. async getList() {
  123. try {
  124. let params = this.params
  125. const res = await request.post('/api-student/videoLesson/page', {
  126. data: {
  127. ...params,
  128. videoId: this.classId
  129. }
  130. })
  131. this.loading = false
  132. const result = res.data || {}
  133. // 处理重复请求数据
  134. if (this.list.length > 0 && result.pageNo === 1) {
  135. return
  136. }
  137. this.list = this.list.concat(result.rows || [])
  138. this.finished = result.pageNo >= result.totalPage
  139. this.params.page = result.pageNo + 1
  140. this.dataShow = this.list.length > 0
  141. } catch {
  142. this.dataShow = false
  143. this.finished = true
  144. }
  145. },
  146. onPlay(item: any) {
  147. // 判断是否点击的是当前播放的视频
  148. if (item.id === Number(this.classId)) {
  149. return
  150. }
  151. this.reload = true
  152. this.posterUrl = item.imgUrl
  153. this.srcUrl = item.videoUrl
  154. this.title = item.title
  155. this.currentClassIndex = item.index
  156. this.videoContent = item.content
  157. this.classId = item.id
  158. this.onSearch()
  159. setTimeout(() => {
  160. this.reload = false
  161. }, 0)
  162. },
  163. async onSubmit() {
  164. try {
  165. await request.post('/api-student/videoLesson/evaluate', {
  166. data: {
  167. isTeacher: 0,
  168. videoId: this.classId,
  169. content: this.message,
  170. studentId: state.user.data.userId
  171. }
  172. })
  173. Toast('评论成功')
  174. this.message = ''
  175. setTimeout(() => {
  176. this.onSearch()
  177. }, 500)
  178. } catch {}
  179. }
  180. },
  181. render() {
  182. return (
  183. <div class={styles['video-class-detail']}>
  184. {/* <Sticky offsetTop={0} position="top"> */}
  185. <ColHeader
  186. v-slots={{
  187. default: () =>
  188. !this.reload && (
  189. <ColVideo
  190. freeRate={Number(this.freeRate)}
  191. freeTitleStatus={this.lessonPrice > 0 ? true : false}
  192. trySee={this.trySee}
  193. src={this.srcUrl}
  194. poster={this.posterUrl}
  195. height={this.videoHeight}
  196. />
  197. )
  198. }}
  199. />
  200. {/* </Sticky> */}
  201. <Cell
  202. border={false}
  203. class={styles.cell}
  204. title={this.title}
  205. titleClass={[styles.titleInfo, 'van-ellipsis']}
  206. v-slots={{
  207. icon: () => (
  208. <Icon
  209. name="video"
  210. size={18}
  211. color="var(--van-primary)"
  212. style={{ display: 'flex', alignItems: 'center' }}
  213. />
  214. ),
  215. value: () => (
  216. <div class={styles.label}>
  217. <span>{this.currentClassIndex}</span>/{this.detailList.length}
  218. 课时
  219. </div>
  220. )
  221. }}
  222. ></Cell>
  223. <div class={styles.videoDesc}>{this.videoContent}</div>
  224. <Tabs
  225. v-model:active={this.tabIndex}
  226. class={styles.infoField}
  227. color="var(--van-primary)"
  228. sticky
  229. offsetTop={this.offsetTop}
  230. lineWidth={20}
  231. >
  232. <Tab title="目录" name={1}>
  233. <div
  234. style={{
  235. // height: `calc(100vh - 320px - ${this.navHeight}px)`,
  236. overflowY: 'auto'
  237. }}
  238. >
  239. <SectionDetail title="课程列表" icon="courseList" border>
  240. {this.detailList.map((item: any, index: number) => {
  241. const musicAlbumInfos = item.musicAlbumInfos || []
  242. const temp = musicAlbumInfos.map((info: any) => {
  243. return {
  244. relationMusicAlbum: info.relationType,
  245. musicAlbumName: info.name,
  246. musicAlbumId: info.musicAlbumId,
  247. status: info.status,
  248. useRelationType: this.useRelationType
  249. }
  250. })
  251. return (
  252. <CourseVideoItem
  253. musicAlbumInfos={temp}
  254. playId={Number(this.classId)}
  255. detail={{
  256. id: item.id,
  257. title: item.videoTitle,
  258. content: item.videoContent,
  259. imgUrl: item.coverUrl,
  260. videoUrl: item.videoUrl,
  261. index: index + 1
  262. }}
  263. onPlay={this.onPlay}
  264. onMusicAlbumDetail={(item: any) => {
  265. if (!this.alreadyBuy && !item.status) {
  266. Toast('数据正在维护中,请稍后在试')
  267. return
  268. }
  269. if (item.relationMusicAlbum === 'MUSIC') {
  270. this.$router.push({
  271. path: '/music-detail',
  272. query: {
  273. id: item.musicAlbumId
  274. }
  275. })
  276. } else if (item.relationMusicAlbum === 'ALBUM') {
  277. this.$router.push({
  278. path: '/music-album-detail/' + item.musicAlbumId
  279. })
  280. }
  281. }}
  282. />
  283. )
  284. })}
  285. </SectionDetail>
  286. </div>
  287. </Tab>
  288. <Tab title="讨论" name={3}>
  289. <div
  290. style={{
  291. // height: `calc(100vh - 380px - ${this.navHeight}px)`,
  292. overflowY: 'auto',
  293. marginBottom:
  294. 'calc(var(--van-cell-vertical-padding) * 2 + var( --van-cell-line-height))'
  295. }}
  296. >
  297. {this.dataShow ? (
  298. <List
  299. v-model:loading={this.loading}
  300. finished={this.finished}
  301. finishedText=" "
  302. immediateCheck={false}
  303. onLoad={this.getList}
  304. >
  305. {this.list.map((item: any) => (
  306. <Cell
  307. class={[
  308. styles['message-list'],
  309. item.isTeacher === 1 && styles['message-active']
  310. ]}
  311. valueClass={styles['message-time']}
  312. v-slots={{
  313. icon: () => (
  314. <Image
  315. class={styles.userLogo}
  316. src={item.avatar || iconTeacher}
  317. fit="cover"
  318. />
  319. ),
  320. title: () => (
  321. <div class={styles.title}>
  322. <div class={styles['message-name']}>
  323. {item.userName}
  324. </div>
  325. <div class={styles['message-time']}>
  326. {dayjs(item.evaluateTime).format(
  327. 'YYYY年MM月DD日'
  328. )}
  329. </div>
  330. </div>
  331. ),
  332. label: () => (
  333. <div class={styles.label}>{item.content}</div>
  334. )
  335. }}
  336. />
  337. ))}
  338. </List>
  339. ) : (
  340. <ColResult btnStatus={false} tips="暂无讨论" />
  341. )}
  342. </div>
  343. {/* <Sticky offsetBottom={0} position="bottom"> */}
  344. <div class={styles.messageContainer}>
  345. <Field
  346. placeholder="快来讨论吧~"
  347. v-model={this.message}
  348. v-slots={{
  349. button: () => (
  350. <Button
  351. type="primary"
  352. disabled={!this.message}
  353. style={{ padding: '0 20px' }}
  354. size="small"
  355. round
  356. onClick={this.onSubmit}
  357. >
  358. 发布
  359. </Button>
  360. )
  361. }}
  362. />
  363. </div>
  364. {/* </Sticky> */}
  365. </Tab>
  366. </Tabs>
  367. </div>
  368. )
  369. }
  370. })