首页数据 & 查看更多 & 视频播放 & 学员管理

main
king 1 year ago
parent 618d28cd62
commit 5b9c976e78
  1. 3
      src/api/curriculum.ts
  2. 4
      src/api/public.ts
  3. 5
      src/app.config.ts
  4. 5
      src/components/video/video.tsx
  5. 3
      src/components/videoCover/videoCover.scss
  6. 2
      src/components/videoCover/videoCover.tsx
  7. 3
      src/pages/business/categoryCur/categoryCur.config.ts
  8. 50
      src/pages/business/categoryCur/categoryCur.tsx
  9. 3
      src/pages/business/course/course.config.ts
  10. 17
      src/pages/business/videoInfo/components/catalogue.tsx
  11. 29
      src/pages/business/videoInfo/components/course.tsx
  12. 6
      src/pages/business/videoInfo/components/hours.tsx
  13. 7
      src/pages/business/videoInfo/videoInfo.scss
  14. 24
      src/pages/business/videoInfo/videoInfo.tsx
  15. 61
      src/pages/index/components/videoList.tsx
  16. 3
      src/pages/index/index.config.ts
  17. 5
      src/pages/index/index.module.scss
  18. 6
      src/pages/index/index.tsx
  19. 5
      src/pages/manage/depAdmin/depAdmin.tsx
  20. 4
      src/pages/manage/studentAdmin/studentAdmin.tsx
  21. 4
      src/pages/my/components/header/service.tsx

@ -59,5 +59,8 @@ export const curriculum = {
/** 课程结束 */ /** 课程结束 */
curEnd(courseId: number, id: number, duration: number) { curEnd(courseId: number, id: number, duration: number) {
return request(`/api/v1/course/${courseId}/hour/${id}/record`, "POST", {duration}) return request(`/api/v1/course/${courseId}/hour/${id}/record`, "POST", {duration})
},
categoryCur(categoryId:number,companyId:number){
return request<Curriculum[]>(`/api/v1/category/${categoryId}/${companyId}`,"GET")
} }
} }

@ -21,9 +21,11 @@ export interface Courses {
is_not_required: Curriculum[] is_not_required: Curriculum[]
/** 必修 */ /** 必修 */
is_required: Curriculum[] is_required: Curriculum[]
/** 总时长 */
total_course_duration: number
} }
export type CoursesKey = keyof Courses export type CoursesKey = keyof Omit<Courses, 'total_course_duration'>
export type Cur = Category & { export type Cur = Category & {
courses: Courses courses: Courses

@ -30,9 +30,10 @@ export default defineAppConfig({
{ {
root: 'pages/business', root: 'pages/business',
pages: [ pages: [
'course/course', // 'course/course',
'userInfo/userInfo', 'userInfo/userInfo',
'videoInfo/videoInfo' 'videoInfo/videoInfo',
'categoryCur/categoryCur'
] ]
}, },
{ {

@ -13,7 +13,7 @@ const HVideo: FC<HVideoOptions> = (opt: HVideoOptions) => {
function onTimeUpdate(event: BaseEventOrig<VideoProps.onTimeUpdateEventDetail>) { function onTimeUpdate(event: BaseEventOrig<VideoProps.onTimeUpdateEventDetail>) {
if (opt.preview) return; if (opt.preview) return;
if (user?.role_type !== 0) return; if (user?.role_type === 2) return;
const time = event.detail.currentTime const time = event.detail.currentTime
/** 前进回退 */ /** 前进回退 */
if (currentTime + deviation < time) { if (currentTime + deviation < time) {
@ -34,8 +34,7 @@ const HVideo: FC<HVideoOptions> = (opt: HVideoOptions) => {
} }
function onEnded() { function onEnded() {
console.log(user) if (user?.role_type === 2) return;
if (user?.role_type !== 0) return;
if (currentTime + 1 > opt.duration) { if (currentTime + 1 > opt.duration) {
opt.onEnded() opt.onEnded()
} else { } else {

@ -32,10 +32,7 @@
.title{ .title{
width: 100%; width: 100%;
//word-break: break-word;
//white-space: pre-line;
font-size: 28rpx; font-size: 28rpx;
word-break: break-all; word-break: break-all;
overflow: hidden; overflow: hidden;
display: -webkit-box; display: -webkit-box;

@ -34,7 +34,7 @@ const VideoCover: FC<VideoCoverProps> = (opt: VideoCoverProps) => {
<View className='box'> <View className='box'>
<View className='title'>{opt.title}</View> <View className='title'>{opt.title}</View>
<View className='flex justify-between videoButton'> <View className='flex justify-between videoButton'>
<View>{opt?.time}</View> {opt.time && <View>:{opt.time}</View>}
<View>{opt?.schedule}</View> <View>{opt?.schedule}</View>
</View> </View>
</View> </View>

@ -0,0 +1,3 @@
export default definePageConfig({
navigationBarTitleText: '更多课程'
})

@ -0,0 +1,50 @@
import {View} from "@tarojs/components";
import {Profile} from '@/store'
import {FC, useEffect, useState} from "react";
import {getCurrentInstance} from "@tarojs/runtime";
import {curriculum} from "@/api";
import VideoCover from "@/components/videoCover/videoCover";
const Index: FC = () => {
const {company} = Profile.useContainer()
const {categoryId} = getCurrentInstance()?.router?.params as { categoryId: number }
const [data, setData] = useState<Curriculum[]>([])
async function getData() {
try {
const res = await curriculum.categoryCur(categoryId!, company?.id!)
setData(res)
} catch (e) {
}
}
useEffect(() => {
getData()
}, [])
return (
<>
<View className='flex flex-wrap px-1'>
{data.map(d => <VideoCover
thumb={d.thumb}
title={d.title}
id={d.id}
depId={d.id}
/>)
}
</View>
<View className='text-center text-muted'>- -</View>
</>
)
}
const CategoryCur = () => {
return (
<Profile.Provider>
<Index/>
</Profile.Provider>
)
}
export default CategoryCur

@ -1,3 +0,0 @@
export default definePageConfig({
navigationBarTitleText: '课程'
})

@ -5,13 +5,13 @@ import {Profile} from '@/store'
import {CourseDepData} from "@/api"; import {CourseDepData} from "@/api";
import Collapse from "@/components/collapse/collapse"; import Collapse from "@/components/collapse/collapse";
import Hours from "@/pages/business/videoInfo/components/hours"; import Hours from "@/pages/business/videoInfo/components/hours";
import Taro from "@tarojs/taro";
interface Props { interface Props {
data: CourseDepData | null data: CourseDepData | null
setPlayId: (id: number) => void
} }
const Catalogue: FC<Props> = ({data}: Props) => { const Catalogue: FC<Props> = ({data, setPlayId}: Props) => {
const {user} = Profile.useContainer() const {user} = Profile.useContainer()
const [current, setCurrent] = useState(0) const [current, setCurrent] = useState(0)
const [tabList, setTabList] = useState<TabList[]>([ const [tabList, setTabList] = useState<TabList[]>([
@ -42,22 +42,15 @@ const Catalogue: FC<Props> = ({data}: Props) => {
return null return null
} }
function complete(id: number): boolean { function complete(id: number): boolean {
return !!data?.learn_hour_records?.[id]?.is_finished return !!data?.learn_hour_records?.[id]?.is_finished
} }
function jump(id) {
Taro.navigateTo({url: `/pages/business/course/course?courseId=${data?.course.id}&id=${id}`})
}
return ( return (
<View className='catalogue'> <View className='catalogue'>
<Tabs tabList={tabList} onChange={tabChange} current={current}/> <Tabs tabList={tabList} onChange={tabChange} current={current}/>
<View className='py-2'> <View className='py-2'>
{current === 0 && <View className='short_desc'>{data?.course.short_desc}</View>} {current === 0 && <View className='short_desc'>{data?.course.short_desc || '无'}</View>}
{current === 1 && <View> {current === 1 && <View>
<View className='font-weight mb-2'></View> <View className='font-weight mb-2'></View>
{data?.chapters.length ? Object.values(data?.chapters || {}).map((d, index) => <View> {data?.chapters.length ? Object.values(data?.chapters || {}).map((d, index) => <View>
@ -69,7 +62,7 @@ const Catalogue: FC<Props> = ({data}: Props) => {
title={hor.title} title={hor.title}
duration={hor.duration} duration={hor.duration}
complete={complete} complete={complete}
click={jump} click={() => setPlayId(d.id)}
/> />
)} )}
</> </>
@ -81,7 +74,7 @@ const Catalogue: FC<Props> = ({data}: Props) => {
title={hor.title} title={hor.title}
duration={hor.duration} duration={hor.duration}
complete={complete} complete={complete}
click={jump} click={() => setPlayId(hor.id)}
/> />
)} )}
</View>} </View>}

@ -1,25 +1,31 @@
import {CustomWrapper, PageContainer, ScrollView} from "@tarojs/components"; import {CustomWrapper, PageContainer, ScrollView} from "@tarojs/components";
import {useEffect, useState} from "react"; import {FC, useEffect, useState} from "react";
import HVideo from "@/components/video/video"; import HVideo from "@/components/video/video";
import {getCurrentInstance} from "@tarojs/runtime";
import {curriculum, HourPlayData} from "@/api"; import {curriculum, HourPlayData} from "@/api";
import {Profile} from '@/store' import {Profile} from '@/store'
import Taro from "@tarojs/taro"; import Taro from "@tarojs/taro";
const Course = () => { interface Props {
const {courseId, id, preview} = getCurrentInstance()?.router?.params as { id: number,
courseId: number, courseId: number
id: number, preview?: boolean
preview: string | null curEnd: () => void
} }
const Course: FC<Props> = ({id, courseId, preview, curEnd}: Props) => {
const [breakpoint, setBreakpoint] = useState<any[]>([]) const [breakpoint, setBreakpoint] = useState<any[]>([])
const [show, setShow] = useState(false) const [show, setShow] = useState(false)
const [data, setData] = useState<HourPlayData | null>(null) const [data, setData] = useState<HourPlayData | null>(null)
async function onEnded() { async function onEnded() {
try { try {
await curriculum.curEnd(courseId, id, data?.duration || 0) await curriculum.curEnd(courseId, id, data?.duration!)
Taro.showModal({title: "学习完成"}) Taro.showModal({
title: "学习完成",
success() {
curEnd()
}
})
} catch (e) { } catch (e) {
} }
} }
@ -38,7 +44,7 @@ const Course = () => {
useEffect(() => { useEffect(() => {
getData() getData()
}, []) }, [id])
return ( return (
<CustomWrapper> <CustomWrapper>
@ -52,7 +58,6 @@ const Course = () => {
onBreakpoint={onBreakpoint} onBreakpoint={onBreakpoint}
/>} />}
<view> <view>
<PageContainer <PageContainer
show={show} show={show}

@ -17,9 +17,9 @@ interface Props {
const Hours: FC<Props> = (opt: Props) => { const Hours: FC<Props> = (opt: Props) => {
return ( return (
<> <>
<View className={'hor' + ` ${opt.complete(opt.id) ? 'complete' : null}`} <View
onClick={() => opt.click(opt.id)} className={'hor' + ` ${opt.complete(opt.id) ? 'complete' : null}`}
> onClick={() => opt.click(opt.id)}>
<Image src={opt.complete(opt.id) ? playOk : play} mode='aspectFit'/> <Image src={opt.complete(opt.id) ? playOk : play} mode='aspectFit'/>
<View> <View>
<View>{opt.index + 1}.{opt.title}</View> <View>{opt.index + 1}.{opt.title}</View>

@ -1,4 +1,9 @@
.content { .content {
&-video{
height: 480rpx;
}
.image { .image {
width: 100%; width: 100%;
display: block; display: block;
@ -41,7 +46,9 @@
border-bottom: 1px solid #ddd; border-bottom: 1px solid #ddd;
View { View {
width: 630rpx;
margin-bottom: 20px; margin-bottom: 20px;
word-wrap:break-word;
} }
} }
} }

@ -1,15 +1,16 @@
import {Image, Text, View} from "@tarojs/components"; import {Image, Text, View} from "@tarojs/components";
import {FC, useState} from "react"; import {FC, useEffect, useState} from "react";
import {getCurrentInstance} from "@tarojs/runtime"; import {getCurrentInstance} from "@tarojs/runtime";
import {CourseDepData, curriculum} from "@/api"; import {CourseDepData, curriculum} from "@/api";
import './videoInfo.scss' import './videoInfo.scss'
import Taro from "@tarojs/taro";
import {Profile} from '@/store' import {Profile} from '@/store'
import Catalogue from "@/pages/business/videoInfo/components/catalogue"; import Catalogue from "./components/catalogue";
import Course from "./components/course";
const VideoInfo: FC = () => { const VideoInfo: FC = () => {
const {id, depId} = getCurrentInstance()?.router?.params as { id: number, depId: number | null } const {id, depId} = getCurrentInstance()?.router?.params as { id: number, depId: number | null }
const [data, setData] = useState<CourseDepData | null>(null) const [data, setData] = useState<CourseDepData | null>(null)
const [playId, setPlayId] = useState<number | null>(null)
async function getData() { async function getData() {
const res = await curriculum.courseDep(id, depId) const res = await curriculum.courseDep(id, depId)
@ -18,13 +19,20 @@ const VideoInfo: FC = () => {
} }
} }
Taro.useDidShow(getData) useEffect(() => {
getData()
}, [])
return ( return (
<Profile.Provider> <Profile.Provider>
<View className='content'> <View className='content'>
<Image src={data?.course.thumb || ''} className='image' mode='scaleToFill'/> <View className='content-video'>
{
playId ?
<Course id={playId} courseId={id} curEnd={getData}/>
: <Image src={data?.course.thumb || ''} className='image' mode='scaleToFill'/>
}
</View>
<View className='header'> <View className='header'>
<View className='flex justify-between text-muted'> <View className='flex justify-between text-muted'>
@ -34,11 +42,11 @@ const VideoInfo: FC = () => {
<View className='font-weight font-40 my-4'>{data?.course.title}</View> <View className='font-weight font-40 my-4'>{data?.course.title}</View>
<View className='text-muted font-26'> <View className='text-muted font-26'>
{/*<Text className='mr-3'>时长:32:10</Text>*/} {/*<Text className='mr-3'>时长:32:10</Text>*/}
<Text>{data?.learn_record?.finished_count || 0}/{data?.learn_record?.hour_count || Object.keys(data?.hours || {}).length}</Text> <Text>{data?.learn_record?.finished_count || 0}/{data?.course.class_hour}</Text>
</View> </View>
</View> </View>
<Catalogue data={data}/> <Catalogue data={data} setPlayId={(id) => setPlayId(id)}/>
</View> </View>
</Profile.Provider> </Profile.Provider>
) )

@ -1,9 +1,10 @@
import {FC, useState} from "react"; import {FC, useState} from "react";
import Taro from "@tarojs/taro"; import Taro, {useReachBottom} from "@tarojs/taro";
import {View} from "@tarojs/components"; import {Text, View} from "@tarojs/components";
import {CoursesKey, Cur, publicApi} from "@/api/public"; import {CoursesKey, Cur, publicApi} from "@/api/public";
import VideoCover from "@/components/videoCover/videoCover"; import VideoCover from "@/components/videoCover/videoCover";
import styles from '../index.module.scss' import styles from '../index.module.scss'
import {formatMinute} from "@/utils/time";
interface Props { interface Props {
categoryId: CoursesKey categoryId: CoursesKey
@ -11,36 +12,64 @@ interface Props {
export const VideoList: FC<Props> = ({categoryId}: Props) => { export const VideoList: FC<Props> = ({categoryId}: Props) => {
const [data, setDta] = useState<Cur[] | null>(null) const [data, setDta] = useState<Cur[] | null>(null)
const [index, setIndex] = useState(3)
async function getData() { async function getData() {
const res = await publicApi.curs() try {
setDta(res) Taro.showLoading()
} const res = await publicApi.curs()
const flatData = res.reduce((put, cur) => {
put.push(cur)
if (cur.resourceCategory?.length) {
put.push(...cur.resourceCategory.map(d => {
d.name = cur.name + '/' + d.name
return d
}))
}
return put
}, [] as Cur[])
setDta(flatData)
} catch (e) {
}
Taro.hideLoading()
}
Taro.useDidShow(() => {
getData()
})
function rateOfLearning(id: number, class_hour: number): JSX.Element { function rateOfLearning(id: number, class_hour: number): JSX.Element {
console.log(id) console.log(id)
return (<View>{`${class_hour}节/已学${0}`}</View>) return (<View>{`${class_hour}节/已学${0}`}</View>)
} }
function jumpCategoryCur(id: number) {
Taro.navigateTo({url: '/pages/business/categoryCur/categoryCur?categoryId=' + id})
}
useReachBottom(() => {
if (data && data?.length > index) {
setIndex(index + 1)
}
})
Taro.useDidShow(getData)
return ( return (
<> <>
{data?.map(d => ( {data?.slice(0, index).map(d => (
<>{ <>{
d.courses?.[categoryId].length ? <View> d.courses?.[categoryId].length ? <View>
<View className='font-weight'>{d.name}</View> <View className='flex justify-between align-center my-1' onClick={() => jumpCategoryCur(d.id)}>
<Text className='font-weight'>{d.name}</Text>
<Text className='font-20'></Text>
</View>
<View className={'py-2 flex justify-between flex-wrap ' + styles.videoListBox}> <View className={'py-2 flex justify-between flex-wrap ' + styles.videoListBox}>
{d.courses[categoryId].map(d => ( {d.courses[categoryId].map(c => (
<VideoCover <VideoCover
thumb={d.thumb} thumb={c.thumb}
title={d.title} title={c.title}
id={d.id} id={c.id}
depId={d.id} depId={c.id}
content={rateOfLearning(d.id, d.class_hour)} time={formatMinute(d.courses.total_course_duration)}
content={rateOfLearning(c.id, c.class_hour)}
/> />
))} ))}
</View> </View>

@ -1,3 +1,4 @@
export default definePageConfig({ export default definePageConfig({
navigationStyle: 'custom' navigationStyle: 'custom',
onReachBottomDistance: 30
}) })

@ -1,7 +1,10 @@
.content { .content {
position: relative; position: relative;
min-height: 100vh;
padding: 0 20px; padding: 0 20px;
min-height: 90vh;
box-sizing: border-box;
width: 750rpx;
overflow: hidden;
&:after { &:after {
min-height: 100vh; min-height: 100vh;

@ -23,12 +23,6 @@ const Index: FC = () => {
setCategoryId(data.tab?.value as CoursesKey) setCategoryId(data.tab?.value as CoursesKey)
} }
// Taro.showActionSheet({
// alertText: '212',
// itemColor:'red',
// itemList:['删除']
// })
return ( return (
<Profile.Provider> <Profile.Provider>
<View className={styles.content} style={`paddingTop:${globalData.statusBarHeight}px`}> <View className={styles.content} style={`paddingTop:${globalData.statusBarHeight}px`}>

@ -118,7 +118,7 @@ const DepAdmin: FC = () => {
function managesSheet(item: Manage) { function managesSheet(item: Manage) {
Taro.showActionSheet({ Taro.showActionSheet({
itemList: ['查看部门课程', '修改', '删除'], itemList: ['查看部门课程', '修改', '详情', '删除'],
success({tapIndex}) { success({tapIndex}) {
switch (tapIndex) { switch (tapIndex) {
case 0: case 0:
@ -128,6 +128,9 @@ const DepAdmin: FC = () => {
showPop(item) showPop(item)
break break
case 2: case 2:
Taro.navigateTo({url: `/pages/manage/depAdmin/depAdmin?id=${item.id}&name=${item.name}`})
break
case 3:
del(item.name, item.id) del(item.name, item.id)
break break
} }

@ -114,10 +114,6 @@ const studentAdmin = () => {
<View style='width:60px' className='text-muted'></View> <View style='width:60px' className='text-muted'></View>
<View>{d.phone_number}</View> <View>{d.phone_number}</View>
</View> </View>
<View className='flex mb-3'>
<View style='width:60px' className='text-muted'></View>
<View>{d.email}</View>
</View>
</View> </View>
<Image src={d.avatar} mode='widthFix'/> <Image src={d.avatar} mode='widthFix'/>
</View> </View>

@ -4,7 +4,7 @@ import Taro from "@tarojs/taro";
import {Profile} from '@/store/profile' import {Profile} from '@/store/profile'
import styles from '../../my.module.scss' import styles from '../../my.module.scss'
import dep from '@/static/img/dep.png' import dep from '@/static/img/dep.png'
import cur from '@/static/img/cur.png' // import cur from '@/static/img/cur.png'
import student from '@/static/img/student.png' import student from '@/static/img/student.png'
import buy from '@/static/img/buy.png' import buy from '@/static/img/buy.png'
@ -27,7 +27,7 @@ const Service = () => {
oldList.unshift(...[ oldList.unshift(...[
{title: '部门管理', src: dep, router: '/pages/manage/depAdmin/depAdmin'}, {title: '部门管理', src: dep, router: '/pages/manage/depAdmin/depAdmin'},
{title: '学员管理', src: student, router: '/pages/manage/studentAdmin/studentAdmin'}, {title: '学员管理', src: student, router: '/pages/manage/studentAdmin/studentAdmin'},
{title: '课程管理', src: cur, router: ''}, // {title: '课程管理', src: cur, router: ''},
{title: '课程购买', src: buy, router: '/pages/manage/curriculum/curriculum'}, {title: '课程购买', src: buy, router: '/pages/manage/curriculum/curriculum'},
]) ])
setList(oldList) setList(oldList)

Loading…
Cancel
Save