判断视频是否完成,自动播放下一个视频

main
king 1 year ago
parent 331e7dc95f
commit 0054b7741d
  1. 2
      src/api/curriculum.ts
  2. 21
      src/api/request.ts
  3. 15
      src/api/user.ts
  4. 5
      src/app.tsx
  5. 2
      src/components/video/video.tsx
  6. 7
      src/hooks/unique_ident.ts
  7. 94
      src/pages/business/curHistory/curHistory.tsx
  8. 4
      src/pages/business/userInfo/userInfo.tsx
  9. 3
      src/pages/business/videoInfo/components/catalogue.tsx
  10. 4
      src/pages/business/videoInfo/components/course.tsx
  11. 58
      src/pages/business/videoInfo/videoInfo.tsx
  12. 23
      src/pages/manage/depAdmin/depAdmin.tsx

@ -85,7 +85,7 @@ export const curriculum = {
delCur(dep_id: number, course_id: number) { delCur(dep_id: number, course_id: number) {
return request(`/api/v1/department/assign/${dep_id}?course_id=${course_id}`, "DELETE") return request(`/api/v1/department/assign/${dep_id}?course_id=${course_id}`, "DELETE")
}, },
/** 部门课程进度 */ /** 课程进度 */
course() { course() {
return request<Course>(`/api/v1/user/courses`, "GET") return request<Course>(`/api/v1/user/courses`, "GET")
}, },

@ -43,6 +43,7 @@ export const ERROR_STATUS: Record<number | string, string> = {
'INVALID_DATA': '服务器响应异常~', 'INVALID_DATA': '服务器响应异常~',
'OVERSTEP': '请求越界~' 'OVERSTEP': '请求越界~'
} }
let isOverdue = false
export function request<T = unknown>( export function request<T = unknown>(
url: string, url: string,
@ -70,6 +71,9 @@ export function request<T = unknown>(
}) })
option.url += parameter option.url += parameter
} }
if (isOverdue) {
isOverdue = false
}
data && (option.data = data) data && (option.data = data)
return new Promise<T>((resolve, reject) => { return new Promise<T>((resolve, reject) => {
Taro.request<T>({ Taro.request<T>({
@ -80,13 +84,16 @@ export function request<T = unknown>(
if (data?.code === 0 && res.statusCode === 200) { if (data?.code === 0 && res.statusCode === 200) {
resolve(data.data || []) resolve(data.data || [])
} else if (res.statusCode === 401) { } else if (res.statusCode === 401) {
Taro.showModal({ if (!isOverdue) {
title: "登录过期,需重新登陆", isOverdue = true
showCancel: false, Taro.showModal({
success({confirm}) { title: "登录过期,需重新登陆",
confirm && Taro.reLaunch({url: '/pages/login/login'}) showCancel: false,
} success({confirm}) {
}) confirm && Taro.reLaunch({url: '/pages/login/login'})
}
})
}
} else { } else {
Taro.showToast({title: data.msg || ERROR_STATUS[res.statusCode] || '请求错误~', icon: 'error'}) Taro.showToast({title: data.msg || ERROR_STATUS[res.statusCode] || '请求错误~', icon: 'error'})
reject(null) reject(null)

@ -35,6 +35,16 @@ interface LearningRecord {
user_course_records: LearnRecord[] user_course_records: LearnRecord[]
} }
interface CourseRecord {
course: Curriculum
data: Record<number, userRecord>
}
interface HourCourse{
courseHour:Record<number, Hour>,
date:Record<number, { start:number,end:number} >
duration:Record<number, number>
}
export const userApi = { export const userApi = {
login(code: string) { login(code: string) {
@ -64,6 +74,9 @@ export const userApi = {
return request<Record<number, userRecord>>(`/api/v1/course/${category_id}/record`, "GET") return request<Record<number, userRecord>>(`/api/v1/course/${category_id}/record`, "GET")
}, },
courseRecord(course_id: number) { courseRecord(course_id: number) {
return request<Record<number, userRecord>>(`/api/v1/course/${course_id}/info`, "GET") return request<CourseRecord>(`/api/v1/course/${course_id}/info`, "GET")
},
hourCourse(course_id: number, unique_ident: number) {
return request<HourCourse>(`/api/v1/course/${course_id}/info/${unique_ident}`, "GET")
} }
} }

@ -1,6 +1,7 @@
import Taro, {useDidShow, useDidHide} from '@tarojs/taro' import Taro, {useDidShow, useDidHide} from '@tarojs/taro'
import './app.scss' import './app.scss'
import {CustomWrapper} from "@tarojs/components"; import {CustomWrapper} from "@tarojs/components";
import unique_ident from "@/hooks/unique_ident";
function updateApp() { function updateApp() {
if (Taro.canIUse('getUpdateManager')) { if (Taro.canIUse('getUpdateManager')) {
@ -32,12 +33,14 @@ function updateApp() {
function App(props) { function App(props) {
// 可以使用所有的 React Hooks // 可以使用所有的 React Hooks
Taro.useLaunch(()=>{ Taro.useLaunch(() => {
updateApp() updateApp()
const token = JSON.parse(Taro.getStorageSync('profile') || '{}')?.token const token = JSON.parse(Taro.getStorageSync('profile') || '{}')?.token
if (!token) { if (!token) {
Taro.reLaunch({url: '/pages/login/login'}) Taro.reLaunch({url: '/pages/login/login'})
} }
unique_ident.put()
unique_ident.del()
}) })

@ -35,8 +35,8 @@ const HVideo: FC<HVideoOptions> = (opt: HVideoOptions) => {
} }
opt.setTime((time: number) => { opt.setTime((time: number) => {
video.stop()
video.seek(time) video.seek(time)
video.play()
}) })
function onEnded() { function onEnded() {

@ -31,8 +31,13 @@ function put(duration?: number, start_date?: number, upload = true) {
} }
} }
function del() {
Taro.removeStorageSync(KEY)
}
export default { export default {
set, set,
put put,
del
} }

@ -9,44 +9,66 @@ const CurHistory = () => {
const [show, setShow] = useState(false) const [show, setShow] = useState(false)
const {course_id, name} = Taro.getCurrentInstance().preloadData as { course_id: number, name: string } const {course_id, name} = Taro.getCurrentInstance().preloadData as { course_id: number, name: string }
const [data, setData] = useState<userRecord[]>([]) const [data, setData] = useState<userRecord[]>([])
const [course, setCourse] = useState<Curriculum | null>(null)
const [hours, setHours] = useState<Hour[] | null>(null)
const [durations, setDuration] = useState<Record<number, number> | null>(null)
const [time, setTime] = useState<Record<number, { start: number, end: number }> | null>(null)
Taro.useLoad(() => { Taro.useLoad(() => {
Taro.setNavigationBarTitle({title: name}) Taro.setNavigationBarTitle({title: name})
userApi.courseRecord(course_id).then(res => { userApi.courseRecord(course_id).then(res => {
setData(Object.values(res)) setData(Object.values(res.data))
setCourse(res.course)
}) })
}) })
function setHour() { async function setHour(unique_ident: number) {
const res = await userApi.hourCourse(course_id, unique_ident)
setHours(Object.values(res.courseHour))
setDuration(res.duration)
setTime(res.date)
setShow(true) setShow(true)
} }
function percent(duration: number): number {
if (!course?.course_duration) return 0
return Number((duration / course.course_duration * 100).toFixed(2))
}
function getTime(id: number): string {
const start = formatDateTime(new Date(time?.[id].start || 0), 'MM/dd HH:mm')
const end = formatDateTime(new Date(time?.[id].end || 0), 'MM/dd HH:mm')
return `${start} - ${end}`
}
function hourPercent(id: number, duration: number): number {
const viewingTime = durations?.[id] || 0
console.log(viewingTime,duration)
return Number((viewingTime / duration * 100).toFixed(2))
}
return ( return (
<View> <View>
<View className={styles.cur}> <View className={styles.cur}>
<Image src={''} mode='aspectFit'/> <Image src={course?.thumb || ''} mode='widthFix'/>
<View>{name}</View> <View>{name}</View>
</View> </View>
<View className={styles.record}> <View className={styles.record}>
{data.map(d => <View onClick={() => setHour(d.unique_ident)} className={styles.recordItem}>
{ <View>{formatDateTime(new Date(d.start_at), "MM/dd HH:ss")} - {formatDateTime(new Date(d.end_at), "MM/dd HH:ss")}</View>
data.map(d => <View onClick={() => setHour()} className={styles.recordItem}> <View>{formatMinute(d.duration)}</View>
<View>{formatDateTime(new Date(d.start_at), "MM-dd HH:ss")} - {formatDateTime(new Date(d.end_at), "MM-dd HH:ss")}</View> <Progress
<View>{formatMinute(d.duration)}</View> percent={percent(d.duration)}
<Progress showInfo
percent={d.duration} borderRadius={10}
showInfo active
borderRadius={10} duration={10}
active strokeWidth={10}
strokeWidth={10} color={'#45D4A8'}
color={'#45D4A8'} />
/> </View>)}
</View>)
}
</View> </View>
<PageContainer <PageContainer
@ -54,19 +76,25 @@ const CurHistory = () => {
round round
onAfterLeave={() => setShow(false)}> onAfterLeave={() => setShow(false)}>
{show && <View className={styles.hourRecord}> {show && <View className={styles.hourRecord}>
<View>
<View onClick={() => setHour()} className={styles.recordItem}> {
<View>2023-6-19</View> hours?.map(d => <View>
<Progress <View className={styles.recordItem}>
percent={10} <View>{d.title}</View>
showInfo <View>{getTime(d.id)}</View>
borderRadius={10} <Progress
active percent={hourPercent(d.id, d.duration)}
strokeWidth={10} showInfo
color={'#45D4A8'} duration={10}
/> borderRadius={10}
</View> active
</View> strokeWidth={10}
color={'#45D4A8'}
/>
</View>
</View>
)
}
</View>} </View>}
</PageContainer> </PageContainer>
</View> </View>

@ -2,7 +2,7 @@ import {FC, useState} from "react";
import {Profile} from '@/store' import {Profile} from '@/store'
import avatar from "@/static/img/avatar.png" import avatar from "@/static/img/avatar.png"
import PopPut from "@/components/popPut/popPut"; import PopPut from "@/components/popPut/popPut";
import {Button, CustomWrapper, Input, View} from "@tarojs/components"; import {Button, Input, View} from "@tarojs/components";
import Taro from "@tarojs/taro"; import Taro from "@tarojs/taro";
import {userApi} from "@/api"; import {userApi} from "@/api";
import styles from './userInfo.module.scss' import styles from './userInfo.module.scss'
@ -70,11 +70,9 @@ const List = () => {
const userInfo: FC = () => { const userInfo: FC = () => {
return ( return (
<CustomWrapper>
<Profile.Provider> <Profile.Provider>
<List/> <List/>
</Profile.Provider> </Profile.Provider>
</CustomWrapper>
) )
} }

@ -23,7 +23,8 @@ const Catalogue: FC<Props> = ({data, setHors, id}) => {
const [current, setCurrent] = useState(1) const [current, setCurrent] = useState(1)
function jumCurHistory() { function jumCurHistory() {
Taro.navigateTo({url: `/pages/business/curHistory/curHistory?id=${id}&name=${data?.course.title}`}) Taro.preload({course_id: id, name: data?.course.title})
Taro.navigateTo({url: `/pages/business/curHistory/curHistory`})
} }
function tabChange({tab}: OnChangOpt) { function tabChange({tab}: OnChangOpt) {

@ -55,7 +55,6 @@ const Course: FC<Props> = ({id, courseId, preview, curEnd}: Props) => {
/** 进入断点 */ /** 进入断点 */
function onBreakpoint(breakpoint: number) { function onBreakpoint(breakpoint: number) {
console.log(breakpoint)
if (breakpoint !== time) { if (breakpoint !== time) {
setFrequency(count?.[breakpoint] || 1) setFrequency(count?.[breakpoint] || 1)
} }
@ -162,7 +161,8 @@ const Course: FC<Props> = ({id, courseId, preview, curEnd}: Props) => {
const index = old.indexOf(time) const index = old.indexOf(time)
old.splice(index, 1) old.splice(index, 1)
setBreakpoint(old) setBreakpoint(old)
Taro.showToast({title: '全部通过'}) Taro.showToast({title: '答题正确', duration: 1000})
seek(time)
init() init()
} else { } else {
Taro.showToast({title: '错误', icon: 'error'}) Taro.showToast({title: '错误', icon: 'error'})

@ -6,6 +6,8 @@ import {Profile} from '@/store'
import Catalogue from "./components/catalogue"; import Catalogue from "./components/catalogue";
import Course from "./components/course"; import Course from "./components/course";
import Taro from "@tarojs/taro"; import Taro from "@tarojs/taro";
import unique_ident from "@/hooks/unique_ident";
const VideoInfo: FC = () => { const VideoInfo: FC = () => {
const {id, depId} = Taro.getCurrentInstance().preloadData as { id: number, depId: number | null } const {id, depId} = Taro.getCurrentInstance().preloadData as { id: number, depId: number | null }
@ -18,6 +20,10 @@ const VideoInfo: FC = () => {
if (res) { if (res) {
setData(res) setData(res)
} }
/** 用于自动播放 判断当前课程是否完成 */
if (playId) {
currentVideo()
}
} }
function setHors(is_complete: boolean, play_id: number) { function setHors(is_complete: boolean, play_id: number) {
@ -29,12 +35,62 @@ const VideoInfo: FC = () => {
getData() getData()
}) })
/** 播放下一个视频 */
function playNext() {
const flats: Hour[] = Object.values(data?.hours || {}).flat(Infinity) as Hour[]
if (playId === flats.at(-1)?.id) {
Taro.showModal({title: '当前课程结束'})
return;
}
for (const [index, flat] of flats.entries()) {
if (flat.id === playId) {
const next = flats[index + 1]
if (next) {
Taro.showModal({
title: '是否播放下一个视频',
success({confirm}) {
if (confirm) {
setPlayId(next.id)
setPreview(false)
}
}
})
}
return
}
}
}
/**
*
*/
function currentVideo() {
const courseHourRecordsFinish = data?.learn_hour_records.find(d => d.id === playId)?.courseHourRecordsFinish
if (typeof courseHourRecordsFinish === 'number') {
if (courseHourRecordsFinish === 1) {
playNext()
} else {
Taro.showModal({
title: '有考卷还未完成',
content: '未完成考卷不能播放下一个视频',
confirmText: '考试',
success({confirm}) {
confirm && Taro.navigateTo({url: `/pages/business/test/test?testId=${playId}`})
}
})
}
}
}
Taro.useDidShow(() => { Taro.useDidShow(() => {
if (data) { if (data) {
getData() getData().then()
} }
}) })
Taro.useUnload(unique_ident.put)
return ( return (
<Profile.Provider> <Profile.Provider>
<View className='content'> <View className='content'>

@ -156,13 +156,19 @@ const DepAdmin: FC = () => {
} }
function userManagesSheet(user: User) { function userManagesSheet(user: User) {
const itemList = [
'修改',
'删除',
"日学习记录",
]
if (user.role_type === 1) {
itemList.push('取消管理员')
} else if (user.role_type === 0) {
itemList.push('设置为管理员')
}
Taro.showActionSheet({ Taro.showActionSheet({
itemList: [ itemList,
'修改',
'删除',
'设置为管理员',
"日学习记录"
],
success({tapIndex}) { success({tapIndex}) {
switch (tapIndex) { switch (tapIndex) {
case 0: case 0:
@ -172,10 +178,10 @@ const DepAdmin: FC = () => {
delUser(user.id) delUser(user.id)
break break
case 2: case 2:
setRoleType(user) Taro.navigateTo({url: `/pages/manage/college/college?id=${user.id}&name=${user.name}`})
break break
case 3: case 3:
Taro.navigateTo({url: `/pages/manage/college/college?id=${user.id}&name=${user.name}`}) setRoleType(user)
break break
} }
} }
@ -235,6 +241,7 @@ const DepAdmin: FC = () => {
leftImage={d.avatar} leftImage={d.avatar}
title={d.name} title={d.name}
onClick={() => userManagesSheet(d)} onClick={() => userManagesSheet(d)}
content={['学员', '管理员', '超级管理员'][d.role_type]}
/>) />)
} }

Loading…
Cancel
Save