课程断点答题修改

main
king 1 year ago
parent eeed36b787
commit 331e7dc95f
  1. 5
      config/index.js
  2. 27
      src/api/curriculum.ts
  3. 7
      src/api/user.ts
  4. 7
      src/app.config.ts
  5. 7
      src/components/video/video.tsx
  6. 3
      src/components/videoCover/videoCover.tsx
  7. 38
      src/hooks/unique_ident.ts
  8. 3
      src/pages/business/curHistory/curHistory.config.ts
  9. 55
      src/pages/business/curHistory/curHistory.module.scss
  10. 76
      src/pages/business/curHistory/curHistory.tsx
  11. 35
      src/pages/business/history/components/CategoryTabs.tsx
  12. 3
      src/pages/business/history/history.config.ts
  13. 50
      src/pages/business/history/history.module.scss
  14. 45
      src/pages/business/history/history.tsx
  15. 8
      src/pages/business/test/test.tsx
  16. 4
      src/pages/business/test/text.module.scss
  17. 7
      src/pages/business/userInfo/userInfo.module.scss
  18. 77
      src/pages/business/videoInfo/components/catalogue.tsx
  19. 38
      src/pages/business/videoInfo/components/course.tsx
  20. 4
      src/pages/business/videoInfo/components/hours.tsx
  21. 41
      src/pages/business/videoInfo/components/studentRecord.tsx
  22. 21
      src/pages/business/videoInfo/videoInfo.scss
  23. 17
      src/pages/business/videoInfo/videoInfo.tsx
  24. 49
      src/pages/index/components/videoList.tsx
  25. 4
      src/pages/index/index.tsx
  26. 6
      src/pages/manage/addStudent/addStudent.scss
  27. 7
      src/pages/manage/addStudent/addStudent.tsx
  28. 15
      src/pages/manage/depAdmin/depAdmin.scss
  29. 28
      src/pages/manage/depAdmin/depAdmin.tsx
  30. 4
      src/pages/my/components/header/service.tsx
  31. 27
      src/utils/time.ts
  32. 18
      types/curriculum.d.ts
  33. 17
      types/user.d.ts

@ -47,7 +47,10 @@ const config = {
generateScopedName: '[name]__[local]___[hash:base64:5]'
}
}
}
},
optimizeMainPackage: {
enable: true,
},
},
h5: {
publicPath: '/',

@ -17,16 +17,18 @@ export interface CourseDepData {
}
export interface HourPlayData {
/** 独特的识别 */
unique_ident: number
/** 时间 */
duration: number
/** 格式 */
extension: string
/** 地址 */
url: string
hourExamQuestions: {
count: Record<number, number>
data: Record<number, (ShareSubject | Multi)[]>
}
/** 断点题 */
hourExamQuestions: Record<number, (ShareSubject | Multi)[]>
/** 次数 */
count: Record<number, number>
/** 断点 */
timeList: number[]
hour_test?: { id: number }
@ -56,9 +58,16 @@ interface RecordTextParam {
user_id: number;
}
export interface CurEndParam {
duration: number
unique_ident: number
/** 开始时间时间戳 */
start_date: number
}
export const curriculum = {
department() {
return request<{data: Manage[] }>('/api/v1/department/index', 'GET')
return request<{ data: Manage[] }>('/api/v1/department/index', 'GET')
},
/** 学习记录 */
record(id: number) {
@ -81,8 +90,8 @@ export const curriculum = {
return request<Course>(`/api/v1/user/courses`, "GET")
},
/** 课程结束 */
curEnd(courseId: number, id: number, duration: number) {
return request(`/api/v1/course/${courseId}/hour/${id}/record`, "POST", {duration})
curEnd(courseId: number, id: number, data: CurEndParam) {
return request(`/api/v1/course/${courseId}/hour/${id}/record`, "POST", data)
},
answerRecord(hourId: number, data: AnswerRecord) {
return request<{ time: number }>(`/api/v1/test/video/record/${hourId}`, "POST", data)
@ -97,5 +106,9 @@ export const curriculum = {
},
putRecordText(hour_id: number, is_pass: boolean) {
return request(`/api/v1/test/record/${hour_id}`, "PUT", {is_pass})
},
/** 学习记录 */
hourCache(courseId: number, data: HourCacheParam) {
return request(`/api/v1/course/${courseId}/hour/cache`, "PUT", data)
}
}

@ -58,5 +58,12 @@ export const userApi = {
},
learningRecord(courseId?: number) {
return request<LearningRecord>(`/api/v1/user/record/course`, "GET", courseId ? {courseId} : {})
},
/** 学习记录 */
record(category_id: number) {
return request<Record<number, userRecord>>(`/api/v1/course/${category_id}/record`, "GET")
},
courseRecord(course_id: number) {
return request<Record<number, userRecord>>(`/api/v1/course/${course_id}/info`, "GET")
}
}

@ -11,6 +11,8 @@ export default defineAppConfig({
navigationBarTextStyle: 'black'
},
tabBar: {
color: '#909795',
selectedColor: '#45D4A8',
list: [
{
text: '课题',
@ -43,6 +45,8 @@ export default defineAppConfig({
'userInfo/userInfo',
'videoInfo/videoInfo',
'test/test',
'history/history',
'curHistory/curHistory',
]
},
{
@ -56,5 +60,6 @@ export default defineAppConfig({
'addCur/addCur',
]
}
]
],
lazyCodeLoading: "requiredComponents"
})

@ -2,12 +2,12 @@ import {BaseEventOrig, Video, VideoProps} from "@tarojs/components";
import {HVideoOptions} from "@/components/video/type";
import Taro from "@tarojs/taro";
import {FC, useState} from "react";
import {Profile} from '@/store'
// import {Profile} from '@/store'
const deviation: number = 0.5
const HVideo: FC<HVideoOptions> = (opt: HVideoOptions) => {
const {user} = Profile.useContainer()
// const {user} = Profile.useContainer()
const video = Taro.createVideoContext('myVideo')
const [currentTime, setCurrentTime] = useState(0)
@ -25,6 +25,7 @@ const HVideo: FC<HVideoOptions> = (opt: HVideoOptions) => {
/** 判断是否进入断点 */
opt.breakpoint.forEach(d => {
if (time < d + deviation && time > d - deviation) {
video.exitFullScreen()
video.pause()
video.seek(d - deviation)
opt.onBreakpoint(d)
@ -52,7 +53,7 @@ const HVideo: FC<HVideoOptions> = (opt: HVideoOptions) => {
return (
<Video
id={'myVideo'}
style={'width:100%'}
style={{width:'100%',height:'100%'}}
poster={opt?.poster || ''}
src={opt.src}
enableProgressGesture={false}

@ -20,12 +20,13 @@ interface VideoCoverProps {
const VideoCover: FC<VideoCoverProps> = (opt: VideoCoverProps) => {
function jump() {
Taro.preload({id: opt.id, depId: opt.depId || ''})
Taro.navigateTo({url: `/pages/business/videoInfo/videoInfo?id=${opt.id}&depId=${opt.depId || ''}`})
}
return (
<View className='videoBox'>
<View className='video' >
<View className='video'>
<View onClick={jump}>
<Image src={opt.thumb} mode='scaleToFill'/>
{opt.content && <View className='content'>{opt.content}</View>}

@ -0,0 +1,38 @@
import Taro from "@tarojs/taro";
import {curriculum} from "@/api";
const KEY = 'unique_ident'
/** 设置学习记录缓存 */
function set(data: HourCacheParam) {
Taro.removeStorageSync(KEY)
Taro.setStorage({
key: KEY,
data
})
}
/**
*
* @param duration
* @param start_date
* @param upload
*/
function put(duration?: number, start_date?: number, upload = true) {
const data: HourCacheParam | undefined = Taro.getStorageSync(KEY)
if (data) {
data.duration = duration || data.duration
data.start_date = start_date || data.start_date
Taro.setStorageSync(KEY, data)
if (upload) {
curriculum.hourCache(data.courseId, data)
}
}
}
export default {
set,
put
}

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

@ -0,0 +1,55 @@
page {
padding: 30px;
box-sizing: border-box;
}
.cur {
background: #fff;
overflow: hidden;
border-radius: 16px;
font-weight: bold;
View {
color: #323635;
padding: 24px;
}
Image {
width: 100%;
height: 288rpx;
display: block;
background: #ddd;
}
}
.record {
background: #fff;
overflow: hidden;
border-radius: 16px;
font-weight: bold;
padding: 0 24px;
margin-top: 30px;
font-weight: 400;
}
.recordItem {
padding: 24px 0 38rpx;
border-bottom: 1px solid #F5F8F7;
Progress {
margin-top: 16px;
color: #45D4A8;
}
}
.hourRecord {
padding: 30px;
max-height: 70vh;
overflow: auto;
& > View {
padding-bottom: env(safe-area-inset-bottom);
}
}

@ -0,0 +1,76 @@
import {Image, PageContainer, Progress, View} from "@tarojs/components";
import Taro from "@tarojs/taro";
import styles from './curHistory.module.scss'
import {useState} from "react";
import {userApi} from "@/api";
import {formatDateTime, formatMinute} from "@/utils/time";
const CurHistory = () => {
const [show, setShow] = useState(false)
const {course_id, name} = Taro.getCurrentInstance().preloadData as { course_id: number, name: string }
const [data, setData] = useState<userRecord[]>([])
Taro.useLoad(() => {
Taro.setNavigationBarTitle({title: name})
userApi.courseRecord(course_id).then(res => {
setData(Object.values(res))
})
})
function setHour() {
setShow(true)
}
return (
<View>
<View className={styles.cur}>
<Image src={''} mode='aspectFit'/>
<View>{name}</View>
</View>
<View className={styles.record}>
{
data.map(d => <View onClick={() => setHour()} 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>
<View>{formatMinute(d.duration)}</View>
<Progress
percent={d.duration}
showInfo
borderRadius={10}
active
strokeWidth={10}
color={'#45D4A8'}
/>
</View>)
}
</View>
<PageContainer
show={show}
round
onAfterLeave={() => setShow(false)}>
{show && <View className={styles.hourRecord}>
<View>
<View onClick={() => setHour()} className={styles.recordItem}>
<View>2023-6-19</View>
<Progress
percent={10}
showInfo
borderRadius={10}
active
strokeWidth={10}
color={'#45D4A8'}
/>
</View>
</View>
</View>}
</PageContainer>
</View>
)
}
export default CurHistory

@ -0,0 +1,35 @@
import {FC, useState} from "react";
import {View} from "@tarojs/components";
import {publicApi} from "@/api/public";
import Taro from "@tarojs/taro";
import Tabs, {OnChangOpt, TabList} from "@/components/tabs/tabs";
interface Props {
changeTabs: (id: number) => void
}
const CategoryTabs: FC<Props> = ({changeTabs}) => {
const [tabs, setTabs] = useState<TabList[]>([])
async function getData() {
const res = await publicApi.category()
const data = Object.values(res.categories).reduce((pre, cur) => {
pre.push(...cur)
return pre
}, []).map<TabList>(d => ({title: d.name, value: d.id}))
setTabs(data)
changeTabs(data[0].value as number)
}
function onChange(data: OnChangOpt) {
changeTabs(data.tab?.value as number)
}
Taro.useLoad(getData)
return (
<View className='bg-white'>
<Tabs tabList={tabs} onChange={onChange}/>
</View>
)
}
export default CategoryTabs

@ -0,0 +1,3 @@
export default definePageConfig({
navigationBarTitleText: '历史记录'
})

@ -0,0 +1,50 @@
.category {
padding: 30px;
border-bottom: 1px solid #F5F8F7;
display: flex;
justify-content: space-between;
background: #fff;
}
.thumb {
background: #ddd;
width: 300rpx;
height: 188rpx;
border-radius: 8px;
position: relative;
overflow: hidden;
Image {
width: 100%;
height: 100%;
display: block;
}
View {
width: 100%;
text-align: center;
height: 48rpx;
background: rgba(#000, .5);
color: #fff;
position: absolute;
left: 0;
bottom: 0;
}
}
.text {
height: 188px;
flex: 1;
display: flex;
flex-direction: column;
margin-left: 20px;
justify-content: space-between;
font-weight: bold;
word-break: break-all;
& > View:last-child {
font-weight: 100;
color: #909795;
}
}

@ -0,0 +1,45 @@
import {Image, View} from "@tarojs/components";
import CategoryTabs from "@/pages/business/history/components/CategoryTabs";
// import {useState} from "react";
import styles from './history.module.scss'
import Taro from "@tarojs/taro";
import {userApi} from "@/api";
import {useState} from "react";
import {formatMinute} from "@/utils/time";
const History = () => {
const [data, setData] = useState<userRecord[]>([])
async function getData(id: number) {
const res = await userApi.record(id)
setData(Object.values(res))
}
function jump(course_id: number, name: string) {
Taro.preload({course_id, name})
Taro.navigateTo({url: `/pages/business/curHistory/curHistory`})
}
return (
<View>
<CategoryTabs changeTabs={getData}/>
<View className='mt-3'>
{data.map(d => <View className={styles.category} onClick={() => jump(d.course_id, d.course.title)}>
<View className={styles.thumb}>
<Image src={d.course.thumb} />
<View>{d.total_hour_count}/{d.finished_count}</View>
</View>
<View className={styles.text}>
<View>{d.course.title}</View>
<View>{formatMinute(d.duration)}</View>
</View>
</View>)}
<View className='text-center text-muted mt-3'>- -</View>
</View>
</View>
)
}
export default History

@ -7,6 +7,8 @@ import Judge from "@/components/topic/judge";
import Multi from "@/components/topic/multi";
import {Profile} from '@/store'
import Taro from "@tarojs/taro";
import styles from './text.module.scss'
import unique_ident from "@/hooks/unique_ident";
const record: boolean[] = []
const Test = () => {
@ -37,6 +39,8 @@ const Test = () => {
useEffect(() => {
if (answers.length) {
unique_ident.put(0, Date.now())
const isPass = answers.every(d => d)
curriculum.recordText(hourId, {
is_pass: isPass,
@ -45,7 +49,7 @@ const Test = () => {
user_id: user?.id!,
test_score: 0
}).then()
curriculum.putRecordText(hourId,isPass).then()
curriculum.putRecordText(hourId, isPass).then()
if (isPass) {
Taro.showModal({
title: '考卷已完成',
@ -77,7 +81,7 @@ const Test = () => {
return (
<View>
<View className={styles.content}>
{data?.fill.map((d, index) => <ShortAnswer
data={d}
index={index}

@ -0,0 +1,4 @@
.content{
padding-bottom: env(safe-area-inset-bottom);
margin-bottom: 20px;
}

@ -9,11 +9,8 @@
padding: 10px 0;
}
.buttonFixed{
.buttonFixed {
position: fixed;
bottom:100px;
bottom: 100px;
left: 30rpx;
}

@ -1,12 +1,10 @@
import {FC, useEffect, useState} from "react";
import Tabs, {OnChangOpt, TabList} from "@/components/tabs/tabs";
import {View} from "@tarojs/components";
import {Profile} from '@/store'
import {FC, useState} from "react";
import Tabs, {OnChangOpt} from "@/components/tabs/tabs";
import {Button, View} from "@tarojs/components";
import {CourseDepData} from "@/api";
import Collapse from "@/components/collapse/collapse";
import Hours from "@/pages/business/videoInfo/components/hours";
import Taro from "@tarojs/taro";
import StudentRecord from "@/pages/business/videoInfo/components/studentRecord";
interface Props {
data: CourseDepData | null
@ -14,23 +12,19 @@ interface Props {
id: number
}
const tabList = [
{title: '介绍', value: 0},
{title: '目录', value: 1},
// {title: '评价', value: 2},
]
const Catalogue: FC<Props> = ({data, setHors, id}) => {
const {user} = Profile.useContainer()
const [current, setCurrent] = useState(1)
const [tabList, setTabList] = useState<TabList[]>([
{title: '介绍', value: 0},
{title: '目录', value: 1},
// {title: '评价', value: 2},
])
useEffect(() => {
if (user?.role_type && user?.role_type > 0) {
setTabList([
...tabList,
{title: '学员进度', value: 3}
])
}
}, [])
function jumCurHistory() {
Taro.navigateTo({url: `/pages/business/curHistory/curHistory?id=${id}&name=${data?.course.title}`})
}
function tabChange({tab}: OnChangOpt) {
setCurrent(tab?.value as number)
@ -73,27 +67,32 @@ const Catalogue: FC<Props> = ({data, setHors, id}) => {
}
return (
<View className='catalogue'>
<Tabs tabList={tabList} onChange={tabChange} current={current}/>
<View className='py-2'>
{current === 0 && <View className='short_desc'>{data?.course.short_desc || '无'}</View>}
{current === 1 && <View>
<View className='font-weight mb-2'></View>
{data?.chapters.length ? Object.values(data?.chapters || {}).map((d, index) => <View>
<Collapse title={`${index + 1}.${d.name}`}>
<Hours
click={(is_complete, id) => upChaptersOver(is_complete, id, index)}
learn_hour_records={data.learn_hour_records}
data={getHors(d.id)}
/>
</Collapse>
</View>)
: <Hours data={data?.hours?.[0]} click={setHors} learn_hour_records={data?.learn_hour_records}/>}
</View>}
{current === 2 && <View className='text-center'></View>}
{current === 3 && <StudentRecord id={id}/>}
<>
<View className='catalogue'>
<Tabs tabList={tabList} onChange={tabChange} current={current}/>
<View className='py-2 hours'>
{current === 0 && <View className='short_desc'>{data?.course.short_desc || '无'}</View>}
{current === 1 && <View>
<View className='font-weight mb-2'></View>
{data?.chapters.length ? Object.values(data?.chapters || {}).map((d, index) => <View>
<Collapse title={`${index + 1}.${d.name}`}>
<Hours
click={(is_complete, id) => upChaptersOver(is_complete, id, index)}
learn_hour_records={data.learn_hour_records}
data={getHors(d.id)}
/>
</Collapse>
</View>)
: <Hours data={data?.hours?.[0]} click={setHors} learn_hour_records={data?.learn_hour_records}/>}
</View>}
{current === 2 && <View className='text-center'></View>}
</View>
</View>
<View className='Videobutton'>
<Button className='button' onClick={jumCurHistory}></Button>
</View>
</View>
</>
)
}

@ -1,12 +1,13 @@
import {Button, PageContainer, ScrollView, Swiper, SwiperItem, View} from "@tarojs/components";
import {FC, useEffect, useState} from "react";
import HVideo from "@/components/video/video";
import {curriculum, HourPlayData} from "@/api";
import {CurEndParam, curriculum, HourPlayData} from "@/api";
import {Profile} from '@/store'
import Taro from "@tarojs/taro";
import Multi from "@/components/topic/multi";
import Judge from "@/components/topic/judge";
import ShortAnswer from "@/components/topic/shortAnswer";
import unique_ident from "@/hooks/unique_ident";
interface Props {
id: number,
@ -15,6 +16,7 @@ interface Props {
curEnd: () => void
}
let seek: (time: number) => void
let record: boolean[] = [] //答案记录
const Course: FC<Props> = ({id, courseId, preview, curEnd}: Props) => {
@ -29,10 +31,14 @@ const Course: FC<Props> = ({id, courseId, preview, curEnd}: Props) => {
const [count, setCount] = useState<Record<number, number> | null>(null)
const [frequency, setFrequency] = useState<number>(1) // 次数
const [testId, setTestId] = useState<number | null>(null)
const [startRecording, setStartRecording] = useState<CurEndParam | null>(null) // 开始记录
const {user} = Profile.useContainer()
async function onEnded() {
await curriculum.curEnd(courseId, id, data?.duration!)
unique_ident.put(data?.duration, Date.now()) // 记录
startRecording && curriculum.curEnd(courseId, id, {...startRecording, duration: data?.duration!}) //结束
if (testId) {
Taro.navigateTo({
url: `/pages/business/test/test?testId=${testId}`
@ -49,6 +55,7 @@ const Course: FC<Props> = ({id, courseId, preview, curEnd}: Props) => {
/** 进入断点 */
function onBreakpoint(breakpoint: number) {
console.log(breakpoint)
if (breakpoint !== time) {
setFrequency(count?.[breakpoint] || 1)
}
@ -56,22 +63,43 @@ const Course: FC<Props> = ({id, courseId, preview, curEnd}: Props) => {
setShow(true)
}
async function getData() {
unique_ident.put()
const res = await curriculum.hourPlay(courseId, id)
if (res) {
setData(res)
setBreakpoint(res.timeList)
setExamAll(res.hourExamQuestions.data)
setCount(res.hourExamQuestions.count)
setExamAll(res.hourExamQuestions || [])
setCount(res.count)
if (res.hour_test) {
setTestId(res.hour_test.id)
}
setStartRecording({
duration: 0,
unique_ident: res.unique_ident,
start_date: Date.now()
})
unique_ident.set({
courseId: Number(courseId),
user_id: user?.id!,
duration: 0,
start_date: Date.now(),
end_date: Date.now(),
hour_id: id,
unique_ident: res.unique_ident
})
}
}
useEffect(() => {
init()
getData()
}, [])
}, [id])
/** 统计答案 */
function onAnswer(isAnswer: boolean) {

@ -56,7 +56,9 @@ const Hours: FC<Props> = ({data, click, learn_hour_records}) => {
Taro.showModal({title: '请完成上一个视频'})
return
}
click(!!is_complete, id)
console.log(id)
click(is_complete !== undefined, id)
}
return (

@ -1,41 +0,0 @@
import {View} from "@tarojs/components";
import {FC, useEffect, useState} from "react";
import {CurLearningRecord, ManageApi} from "@/api/manage";
import PopPut from "@/components/popPut/popPut";
interface Props {
id: number
}
const StudentRecord: FC<Props> = ({id}) => {
const [data, setData] = useState<CurLearningRecord | null>(null)
async function getDate() {
try {
const res = await ManageApi.userRecord(id)
setData(res)
} catch (e) {
}
}
function record(id: number): string {
return `${data?.user_course_records?.[id]?.finished_count || 0}/${data?.course.class_hour || 0}`
}
useEffect(() => {
getDate()
}, [id])
return (
<View>
{data?.data?.map(d => <PopPut
leftImage={d.avatar}
title={d.name}
content={record(d.id)}
chevron
/>)}
</View>
)
}
export default StudentRecord

@ -1,11 +1,12 @@
.content {
&-video{
&-video {
height: 480rpx;
}
.image {
width: 100%;
height: 100%;
display: block;
}
@ -20,9 +21,14 @@
.catalogue {
background: #fff;
border-radius: 40rpx;
padding: 24rpx;
padding: 24px;
padding-bottom: env(safe-area-inset-bottom);
margin-top: 20rpx;
.hours {
padding-bottom: 80px;
}
.short_desc {
color: #606563;
line-height: 1.75;
@ -48,7 +54,7 @@
View {
width: 630rpx;
margin-bottom: 20px;
word-wrap:break-word;
word-wrap: break-word;
}
}
}
@ -57,3 +63,12 @@
color: #45D4A8;
}
.Videobutton {
background: #fff;
position: fixed;
width: 100%;
left: 0;
bottom: 0;
padding-bottom: env(safe-area-inset-bottom);
}

@ -1,6 +1,5 @@
import {Image, Text, View} from "@tarojs/components";
import {FC, useState} from "react";
import {getCurrentInstance} from "@tarojs/runtime";
import {CourseDepData, curriculum} from "@/api";
import './videoInfo.scss'
import {Profile} from '@/store'
@ -9,7 +8,7 @@ import Course from "./components/course";
import Taro from "@tarojs/taro";
const VideoInfo: FC = () => {
const {id, depId} = getCurrentInstance()?.router?.params as { id: number, depId: number | null }
const {id, depId} = Taro.getCurrentInstance().preloadData as { id: number, depId: number | null }
const [data, setData] = useState<CourseDepData | null>(null)
const [playId, setPlayId] = useState<number | null>(null)
const [preview, setPreview] = useState(false)
@ -21,12 +20,20 @@ const VideoInfo: FC = () => {
}
}
function setHors(is_complete: boolean, id: number) {
function setHors(is_complete: boolean, play_id: number) {
setPreview(is_complete)
setPlayId(id)
setPlayId(play_id)
}
Taro.useDidShow(getData)
Taro.useLoad(() => {
getData()
})
Taro.useDidShow(() => {
if (data) {
getData()
}
})
return (
<Profile.Provider>

@ -12,6 +12,7 @@ interface Props {
}
export const VideoList: FC<Props> = ({categoryKey}) => {
const [init, setInit] = useState(false)
const [data, setData] = useState<Courses>({
is_finished: [],
is_required: [],
@ -25,10 +26,46 @@ export const VideoList: FC<Props> = ({categoryKey}) => {
try {
const res = await publicApi.course({page: page, pageSize: 10})
const oldData: Courses = JSON.parse(JSON.stringify(data))
oldData.is_finished.push(...res.is_finished)
oldData.is_required.push(...res.is_required)
oldData.is_not_finished.push(...res.is_not_finished)
oldData.is_not_required.push(...res.is_not_required)
oldData.is_finished = res.is_finished.reduce((pre, cur) => {
const index = pre.findIndex(d => d.id === cur.id)
if (index === -1) {
pre.push(cur)
} else {
pre.splice(index, 1, cur)
}
return pre
}, oldData.is_finished)
oldData.is_required = res.is_required.reduce((pre, cur) => {
const index = pre.findIndex(d => d.id === cur.id)
if (index === -1) {
pre.push(cur)
} else {
pre.splice(index, 1, cur)
}
return pre
}, oldData.is_required)
oldData.is_not_finished = res.is_not_finished.reduce((pre, cur) => {
const index = pre.findIndex(d => d.id === cur.id)
if (index === -1) {
pre.push(cur)
} else {
pre.splice(index, 1, cur)
}
return pre
}, oldData.is_not_finished)
oldData.is_not_required = res.is_not_required.reduce((pre, cur) => {
const index = pre.findIndex(d => d.id === cur.id)
if (index === -1) {
pre.push(cur)
} else {
pre.splice(index, 1, cur)
}
return pre
}, oldData.is_not_required)
setData(oldData)
} catch (e) {
}
@ -65,10 +102,14 @@ export const VideoList: FC<Props> = ({categoryKey}) => {
})
Taro.useDidShow(() => {
if (init) {
getData()
}
getRecords()
})
useEffect(() => {
setInit(true)
getData()
}, [page])
return (

@ -3,7 +3,7 @@ import {View} from "@tarojs/components";
import Taro from "@tarojs/taro";
import styles from './index.module.scss'
import {Profile} from '@/store'
import {Search} from "@/pages/index/components/search";
// import {Search} from "@/pages/index/components/search";
import {VideoList} from "@/pages/index/components/videoList";
import Tabs, {OnChangOpt, TabList} from "@/components/tabs/tabs";
import {CoursesKey} from "@/api/public";
@ -27,7 +27,7 @@ const Index: FC = () => {
<Profile.Provider>
<View className={styles.content} style={`paddingTop:${globalData.statusBarHeight}px`}>
<View className='text-center font-weight font-34 mt-3'></View>
<Search/>
{/*<Search/>*/}
<Tabs tabList={category} onChange={tabChange} current={categoryKey}/>
<VideoList categoryKey={categoryKey}/>
<View className='text-center text-muted my-3'>- -</View>

@ -10,10 +10,8 @@
}
.add {
border-radius: 10px;
background: linear-gradient(to right, #8284f7, #5a93f9);
color: #fff;
position: fixed;
width: 710rpx;
bottom: 20px;
bottom: env(safe-area-inset-bottom);
margin-bottom: 30px;
}

@ -1,4 +1,4 @@
import {Button, CustomWrapper, Form, Input, PageContainer, View} from "@tarojs/components";
import {Button, Form, Input, PageContainer, View} from "@tarojs/components";
import {FC, useEffect, useState} from "react";
import {ManageApi, Student} from "@/api/manage";
import Icon from "@/components/icon";
@ -124,8 +124,7 @@ const AddStudent = () => {
</View>
</View>
<Button className='add' formType='submit' disabled={disable}></Button>
<Button className='add button' formType='submit' disabled={disable}></Button>
</Form>
<PageContainer show={show} round>
@ -147,11 +146,9 @@ const AddStudent = () => {
const AddPage: FC = () => {
return (
<CustomWrapper>
<Profile.Provider>
<AddStudent/>
</Profile.Provider>
</CustomWrapper>
)
}

@ -1,12 +1,19 @@
.operation {
display: flex;
width: 100%;
justify-content: space-around;
background: #F5F8F7;
position: fixed;
bottom: 0;
padding-bottom: env(safe-area-inset-bottom);
padding-top: 30px;
padding: 30px 0;
color: #45D4A8;
left: 0;
.safeAreaInsetBottom {
justify-content: space-around;
padding-bottom: env(safe-area-inset-bottom);
display: flex;
}
}
.depForm {
padding: 20px 20px env(safe-area-inset-bottom);
}

@ -17,7 +17,6 @@ interface ChangeDataProps {
const ChangeData: FC<ChangeDataProps> = ({putCompany, getDeps, parent_id}: ChangeDataProps) => {
const {company} = Profile.useContainer()
const [name, setName] = useState<string>('')
const [sort, setSort] = useState<number>(putCompany?.sort || 0)
const [disable, setDisable] = useState(false)
const company_id = putCompany?.company_id || company?.id || 0
@ -40,7 +39,7 @@ const ChangeData: FC<ChangeDataProps> = ({putCompany, getDeps, parent_id}: Chang
name,
parent_id: putCompany?.parent_id || parent_id || 0,
company_id: company_id,
sort: sort
sort: putCompany?.sort || 0
}
if (putCompany) {
await ManageApi.putDep(data)
@ -56,20 +55,19 @@ const ChangeData: FC<ChangeDataProps> = ({putCompany, getDeps, parent_id}: Chang
}
return (
<View className='p-2'>
<View className='depForm'>
<View className='mt-2 text-center font-weight font-34'></View>
<Form className='form mt-3'>
<View className='item'>
<View></View>
<Input placeholder='请输入部门名称' value={name} onInput={(event) => setName(event.detail.value)}/>
</View>
<View className='item'>
<View></View>
<Input
type="number"
focus
confirmHold
holdKeyboard
cursorSpacing={151}
placeholder='请输入部门名称'
value={String(sort)}
onInput={(event) => setSort(Number(event.detail.value))}/>
value={name}
onInput={(event) => setName(event.detail.value)}/>
</View>
<Button className='mt-3 button' formType='submit' onClick={submit} disabled={disable}></Button>
@ -188,7 +186,7 @@ const DepAdmin: FC = () => {
Taro.navigateTo({url: '/pages/manage/addStudent/addStudent'})
}
function setRoleType(user:User) {
function setRoleType(user: User) {
if (user.role_type === 2) {
Taro.showModal({title: "禁止修改超级管理员"})
return
@ -243,13 +241,15 @@ const DepAdmin: FC = () => {
<View className='text-center text-muted mt-3'>- -</View>
<View className='operation'>
<View onClick={jumpAddStudent}></View>
<View onClick={() => showPop(null)}></View>
<View className='safeAreaInsetBottom'>
<View onClick={jumpAddStudent}></View>
<View onClick={() => showPop(null)}></View>
</View>
</View>
</View>
<PageContainer show={show} round onAfterLeave={() => setShow(false)}>
<View className='h-4'>
<View>
{show && <ChangeData getDeps={getData} putCompany={putCompany} parent_id={Number(params.id)}/>}
</View>
</PageContainer>

@ -5,6 +5,7 @@ import {Profile} from '@/store/profile'
import styles from '../../my.module.scss'
import dep from '@/static/img/dep.png'
import buy from '@/static/img/buy.png'
import cur from '@/static/img/cur.png'
interface List {
title: string;
@ -14,7 +15,8 @@ interface List {
const Service = () => {
const [list, setList] = useState<List[]>([
{title: '设置', src: dep, router: '/pages/business/userInfo/userInfo'}
{title: '学习记录', src: cur, router: '/pages/business/history/history'},
{title: '设置', src: dep, router: '/pages/business/userInfo/userInfo'},
])
const {user} = Profile.useContainer()

@ -16,3 +16,30 @@ export function formatMinute(s: number | string): string {
const formatSecond = sec > 59 ? 59 : sec
return `${hours > 0 ? `${hours}:` : ''}${min < 10 ? '0' + min : min}:${formatSecond < 10 ? '0' + formatSecond : formatSecond}`
}
export function formatDateTime(date, format) {
const o = {
'M+': date.getMonth() + 1, // 月份
'd+': date.getDate(), // 日
'h+': date.getHours() % 12 === 0 ? 12 : date.getHours() % 12, // 小时
'H+': date.getHours(), // 小时
'm+': date.getMinutes(), // 分
's+': date.getSeconds(), // 秒
'q+': Math.floor((date.getMonth() + 3) / 3), // 季度
S: date.getMilliseconds(), // 毫秒
a: date.getHours() < 12 ? '上午' : '下午', // 上午/下午
A: date.getHours() < 12 ? 'AM' : 'PM', // AM/PM
};
if (/(y+)/.test(format)) {
format = format.replace(RegExp.$1, (date.getFullYear() + '').substr(4 - RegExp.$1.length));
}
for (let k in o) {
if (new RegExp('(' + k + ')').test(format)) {
format = format.replace(
RegExp.$1,
RegExp.$1.length === 1 ? o[k] : ('00' + o[k]).substr(('' + o[k]).length)
);
}
}
return format;
}

@ -71,8 +71,8 @@ interface LearnHourRecord {
user_id: 17
}
interface LearnHourRecords extends Hour{
courseHourRecords:LearnHourRecord
interface LearnHourRecords extends Hour {
courseHourRecords: LearnHourRecord
}
@ -86,3 +86,17 @@ interface LearnRecord {
progress: number
user_id: number
}
/** 学习记录 */
interface HourCacheParam {
courseId:number
/** 视频学习时长 */
duration: number;
/** 课时结束学习时间 */
end_date: number;
hour_id: number;
/** 课时开始学习时间 */
start_date: number;
user_id: number;
unique_ident:number
}

17
types/user.d.ts vendored

@ -74,3 +74,20 @@ interface CueStats {
total_course_count: number
}
interface userRecord{
id: number;
duration: number;
course: Curriculum;
user_id: number;
created_date: string;
start_at: string;
end_at: string;
course_id: number;
hour_id: number;
unique_ident: number;
total_hour_count: number;
finished_count: number;
}

Loading…
Cancel
Save