video-class-detail.tsx 11 KB

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