课时记录

main
king 1 year ago
parent ed9599ac8b
commit 33eaa1adcb
  1. 4
      .env
  2. 14
      src/api/course.ts
  3. 1
      src/api/index.ts
  4. 7
      src/api/manage.ts
  5. 4
      src/api/user.ts
  6. 6
      src/components/custom-page-container/custom-page-container.tsx
  7. 71
      src/pages/business/curHistory/curHistory.tsx
  8. 2
      src/pages/business/history/history.tsx
  9. 57
      src/pages/business/hourHistory/hourHistory.module.scss
  10. 90
      src/pages/business/hourHistory/hourHistory.tsx
  11. 18
      src/pages/business/videoInfo/components/catalogue.tsx
  12. 3
      src/pages/business/videoInfo/videoInfo.tsx
  13. 8
      src/pages/manage/addStudent/addStudent.tsx
  14. 4
      src/pages/manage/depAdmin/depAdmin.tsx
  15. 4
      types/user.d.ts

@ -1,4 +1,4 @@
#TARO_APP_API=https://yjx.dev.yaojiankang.top TARO_APP_API=https://yjx.dev.yaojiankang.top
TARO_APP_API=https://playedu.yaojiankang.top #TARO_APP_API=https://playedu.yaojiankang.top
TARO_APP_LGOIN=true TARO_APP_LGOIN=true

@ -0,0 +1,14 @@
import {request} from "@/api/request";
interface HourHistorys {
course: Curriculum
data: Record<number, HourHistory[]>
hour: Hour
}
export const courseApi = {
/** 课时学习记录 */
hourHistory(courseId: string, hourId: string) {
return request<HourHistorys>(`/api/v1/course/${courseId}/hour/${hourId}/info`, "GET")
}
}

@ -4,3 +4,4 @@ export * from './login'
export * from './meeting' export * from './meeting'
export * from './public' export * from './public'
export * from './manage' export * from './manage'
export * from './course'

@ -117,11 +117,4 @@ export const ManageApi = {
buy(data_list: number[]) { buy(data_list: number[]) {
return request(`/api/v1/course/buy?data_list=${data_list}`, "POST") return request(`/api/v1/course/buy?data_list=${data_list}`, "POST")
}, },
/** 学员学习记录 */
userRecord(curId: number) {
return request<CurLearningRecord>(`/api/v1/course/${curId}/user/index?page=1&size=10000`, "GET")
},
offline(data: Offline) {
return request('/wechat/link', "GET", data)
}
} }

@ -37,7 +37,7 @@ interface LearningRecord {
interface CourseRecord { interface CourseRecord {
course: Curriculum course: Curriculum
data: Record<number, userRecord> data: Record<number, HourHistory>
} }
interface HourCourse { interface HourCourse {
@ -71,7 +71,7 @@ export const userApi = {
}, },
/** 学习记录 */ /** 学习记录 */
record(category_id: number) { record(category_id: number) {
return request<Record<number, userRecord>>(`/api/v1/course/${category_id}/record`, "GET") return request<Record<number, HourHistory>>(`/api/v1/course/${category_id}/record`, "GET")
}, },
courseRecord(course_id: string) { courseRecord(course_id: string) {
return request<CourseRecord>(`/api/v1/course/${course_id}/info`, "GET") return request<CourseRecord>(`/api/v1/course/${course_id}/info`, "GET")

@ -1,6 +1,7 @@
import {PageContainer, PageContainerProps, View} from "@tarojs/components"; import {PageContainer, PageContainerProps, View} from "@tarojs/components";
import {CSSProperties, FC, useCallback, useEffect, useState} from "react"; import {CSSProperties, FC, useCallback, useEffect, useState} from "react";
import styles from './custom-page-container.module.scss' import styles from './custom-page-container.module.scss'
import Taro from "@tarojs/taro";
const PageContainerInner: FC<PageContainerProps> = (props) => { const PageContainerInner: FC<PageContainerProps> = (props) => {
const [visible, setVisible] = useState(props.show) const [visible, setVisible] = useState(props.show)
@ -55,6 +56,11 @@ const PageContainerInner: FC<PageContainerProps> = (props) => {
} }
}, [props.show]) }, [props.show])
Taro.useDidHide(() => {
props.onClickOverlay?.(null as any)
setVisible(false)
})
return ( return (
<View <View
className={styles.customPageContainer} className={styles.customPageContainer}

@ -5,30 +5,35 @@ import {useState} from "react";
import {userApi} from "@/api"; import {userApi} from "@/api";
import {formatDateTime, formatMinute} from "@/utils/time"; import {formatDateTime, formatMinute} from "@/utils/time";
import CustomPageContainer from "@/components/custom-page-container/custom-page-container"; import CustomPageContainer from "@/components/custom-page-container/custom-page-container";
import Empty from "@/components/empty/empty";
const CurHistory = () => { const CurHistory = () => {
const [show, setShow] = useState(false) const [show, setShow] = useState(false)
const {course_id} = Taro.getCurrentInstance().router?.params as unknown as { course_id: string } const {course_id} = Taro.getCurrentInstance().router?.params as unknown as { course_id: string }
const [data, setData] = useState<userRecord[]>([]) const [data, setData] = useState<HourHistory[]>([])
const [course, setCourse] = useState<Curriculum | null>(null) const [course, setCourse] = useState<Curriculum | null>(null)
const [hours, setHours] = useState<Hour[] | null>(null) const [hours, setHours] = useState<Hour[] | null>(null)
const [durations, setDuration] = useState<Record<number, number> | null>(null) const [durations, setDuration] = useState<Record<number, number> | null>(null)
const [time, setTime] = useState<Record<number, { start: number, end: number }> | null>(null) const [time, setTime] = useState<Record<number, { start: number, end: number }> | null>(null)
Taro.useLoad(() => { Taro.useLoad(() => {
// Taro.setNavigationBarTitle({title: name})
userApi.courseRecord(course_id).then(res => { userApi.courseRecord(course_id).then(res => {
Taro.setNavigationBarTitle({title: res.course.title})
setData(Object.values(res.data)) setData(Object.values(res.data))
setCourse(res.course) setCourse(res.course)
}) })
}) })
async function setHour(unique_ident: number) { async function setHour(unique_ident: number) {
const res = await userApi.hourCourse(course_id, unique_ident) try {
setHours(Object.values(res.courseHour)) const res = await userApi.hourCourse(course_id, unique_ident)
setDuration(res.duration) setHours(Object.values(res.courseHour))
setTime(res.date) setDuration(res.duration)
setShow(true) setTime(res.date)
setShow(true)
} catch (e) {
}
} }
function percent(duration: number): number { function percent(duration: number): number {
@ -64,24 +69,33 @@ const CurHistory = () => {
</View> </View>
<View className={styles.record}> {
{data.map((d, index) => <View key={d.id} onClick={() => setHour(d.unique_ident)} className={styles.recordItem}> data.length ? <View className={styles.record}>
<View className='text-hover-dark'>{index + 1}</View> {
<View>{getCurTime(d.start_at, d.end_at)} </View> data.map((d, index) => <View
<View>{formatMinute(d.duration)}</View> key={d.id}
<Progress onClick={() => setHour(d.unique_ident)}
className={styles.progress} className={styles.recordItem}>
percent={percent(d.duration)} <View className='text-hover-dark'>{index + 1}</View>
showInfo <View>{getCurTime(d.start_at, d.end_at)} </View>
borderRadius={10} <View>{formatMinute(d.duration)}</View>
active <Progress
duration={10} className={styles.progress}
strokeWidth={10} percent={percent(d.duration)}
color='#45D4A8' showInfo
activeColor='#45D4A8' borderRadius={10}
/> active
</View>)} duration={10}
</View> strokeWidth={10}
color='#45D4A8'
activeColor='#45D4A8'
/>
</View>)
}
</View>
: <Empty name='暂无学习记录'/>
}
<CustomPageContainer <CustomPageContainer
show={show} show={show}
@ -89,7 +103,8 @@ const CurHistory = () => {
position='bottom' position='bottom'
onClickOverlay={() => setShow(false)}> onClickOverlay={() => setShow(false)}>
<View className={styles.hourRecord}> <View className={styles.hourRecord}>
{hours?.map(d => <View key={d.id}> {hours?.length ?
hours?.map(d => <View key={d.id}>
<View className={styles.recordItem}> <View className={styles.recordItem}>
<View>{d.title}</View> <View>{d.title}</View>
<View>{getTime(d.id)}</View> <View>{getTime(d.id)}</View>
@ -104,8 +119,8 @@ const CurHistory = () => {
activeColor={'#45D4A8'} activeColor={'#45D4A8'}
/> />
</View> </View>
</View> </View>)
)} : <Empty name='暂无学习记录'/>}
</View> </View>
</CustomPageContainer> </CustomPageContainer>
</View> </View>

@ -8,7 +8,7 @@ import {formatMinute} from "@/utils/time";
import Empty from "@/components/empty/empty"; import Empty from "@/components/empty/empty";
const History = () => { const History = () => {
const [data, setData] = useState<userRecord[]>([]) const [data, setData] = useState<HourHistory[]>([])
async function getData(id: number) { async function getData(id: number) {
const res = await userApi.record(id) const res = await userApi.record(id)

@ -0,0 +1,57 @@
.page {
padding: 30px !important;
box-sizing: border-box;
}
.cur {
background: #fff;
overflow: hidden;
border-radius: 16px;
font-weight: 500;
position: relative;
}
.classHour {
background: rgba(#000, .6);
position: absolute;
top: 250rpx;
width: 100%;
font-size: 25rpx;
line-height: 50rpx;
text-align: center;
color: #fff;
font-weight: 100;
left: 0;
}
.title {
padding: 10rpx;
height: 100rpx;
}
.image {
width: 100%;
height: 300rpx;
display: block;
background: #ddd;
}
.progressBox {
background: #fff;
padding: 20rpx;
margin-top: 30rpx;
border-radius: 30rpx;
}
.recordItem {
padding: 24px 0 38rpx;
border-bottom: 1px solid #F5F8F7;
font-size: 26rpx;
color: #606563;
line-height: 1.75;
}
.progress {
color: #45D4A8;
}

@ -1,9 +1,91 @@
import {View} from "@tarojs/components"; import {Image, Progress, View} from "@tarojs/components";
import {FC} from "react"; import {FC, useEffect, useState} from "react";
import Taro from "@tarojs/taro";
import {courseApi} from "@/api";
import styles from './hourHistory.module.scss'
import Empty from "@/components/empty/empty";
import {formatDateTime} from "@/utils/time";
const HourHistory: FC = () => {
const {courseId, hourId} = Taro.getCurrentInstance().router?.params as { courseId: string, hourId: string }
const [cur, setCur] = useState<Curriculum | null>(null)
const [hour, setHour] = useState<Hour | null>(null)
const [times, setTimes] = useState<HourHistory[]>([])
useEffect(() => {
courseApi.hourHistory(courseId, hourId).then(res => {
if (res) {
setHour(res.hour)
setCur(res.course)
const data = Object.values(res.data).map(d => {
if (d.length === 1) {
return d[0]
}
const duration = d.reduce((pre, cur) => {
return pre + cur.duration
}, 0)
return ({
...d[0],
duration,
end_at: d[d.length - 1].end_at
})
})
setTimes(data)
}
})
}, [])
const HourHistory:FC = () => {
return ( return (
<View>sd</View> <View className={styles.page}>
<View className={styles.cur}>
<Image className={styles.image} src={cur?.thumb || ''} mode='center'/>
<View className={styles.classHour}>{cur?.class_hour}/{cur?.finished_hour_count || 0}</View>
<View className={styles.title}>
<View>{cur?.title}</View>
<View>{hour?.title}</View>
</View>
</View>
{
times.length ?
<View className={styles.progressBox}>
{
times.map(d => <View key={d.id} className={styles.recordItem}>
<View>{formatDateTime(new Date(d.start_at), 'MM-dd')}</View>
<Progress
className={styles.progress}
percent={d.duration / (hour?.duration || 0)}
showInfo
borderRadius={10}
active
duration={10}
strokeWidth={10}
color='#45D4A8'
activeColor='#45D4A8'
/>
</View>)
}
</View>
: <Empty name='暂无数据'/>
}
{/*{times.length ? times.map((d, index) =>*/}
{/* <View key={index} className={styles.category}>*/}
{/* <View className={styles.thumb}>*/}
{/* <Image src={d.course.thumb} className={styles.image}/>*/}
{/* <View className={styles.count}>共{d.total_hour_count}节/已学{d.finished_count}节</View>*/}
{/* </View>*/}
{/* <View className={styles.text}>*/}
{/* <View>{d.course.title}</View>*/}
{/* <View className={styles.describe}>*/}
{/* <Text className='mr-4'>观看{formatMinute(d.duration)}</Text>*/}
{/* <Text>学习进度:{(d.finished_count / d.total_hour_count * 100).toFixed(0)}%</Text>*/}
{/* </View>*/}
{/* </View>*/}
{/* </View>) : <Empty name='无历史记录'/>}*/}
</View>
) )
} }

@ -13,7 +13,8 @@ import hourRecord from '@/static/img/hourRecord.png'
interface Props { interface Props {
data: CourseDepData | null data: CourseDepData | null
setHors: (is_complete: boolean, id: number) => void setHors: (is_complete: boolean, id: number) => void
id: number id: number //课程
playId: number | null
} }
const tabList = [ const tabList = [
@ -23,12 +24,12 @@ const tabList = [
] ]
const Catalogue: FC<Props> = ({data, setHors, id}) => { const Catalogue: FC<Props> = ({data, setHors, id, playId}) => {
const [current, setCurrent] = useState(1) const [current, setCurrent] = useState(1)
const [show, setShow] = useState(false) const [show, setShow] = useState(false)
function jumCurHistory() { function jumCurHistory() {
// Taro.navigateTo({url: `/pages/business/curHistory/curHistory`}) Taro.navigateTo({url: `/pages/business/hourHistory/hourHistory?courseId=${id}&hourId=${playId}`})
} }
function tabChange({tab}: OnChangOpt) { function tabChange({tab}: OnChangOpt) {
@ -133,10 +134,13 @@ const Catalogue: FC<Props> = ({data, setHors, id}) => {
<Image src={curRecord} className='image'/> <Image src={curRecord} className='image'/>
</View> </View>
<View onClick={jumCurHistory}> {
<Image src={hourRecord} className='image'/> playId != null && <View onClick={jumCurHistory}>
<Image src={hourRecord} className='image'/>
</View>
</View>
}
</View> </View>
<MyButton onClick={() => setShow(false)} type='default' fillet></MyButton> <MyButton onClick={() => setShow(false)} type='default' fillet></MyButton>

@ -109,9 +109,8 @@ const VideoInfo: FC = () => {
<Text>{data?.learn_hour_records.length || 0}/{data?.course.class_hour}</Text> <Text>{data?.learn_hour_records.length || 0}/{data?.course.class_hour}</Text>
</View> </View>
</View> </View>
<Catalogue data={data} setHors={setHors} id={id}/> <Catalogue data={data} setHors={setHors} id={id} playId={playId}/>
</View> </View>
</> </>
) )
} }

@ -19,7 +19,6 @@ const AddStudent = () => {
const [department, setDepartment] = useState<Department[]>([]) const [department, setDepartment] = useState<Department[]>([])
const [depIds, setDepIds] = useState<number[]>([]) const [depIds, setDepIds] = useState<number[]>([])
const params = getCurrentInstance()?.router?.params as { id?: number } const params = getCurrentInstance()?.router?.params as { id?: number }
const [disable, setDisable] = useState(false)
const {company} = Profile.useContainer() const {company} = Profile.useContainer()
@ -34,12 +33,11 @@ const AddStudent = () => {
const value: Student = event.detail.value const value: Student = event.detail.value
for (const [key, value1] of Object.entries(value)) { for (const [key, value1] of Object.entries(value)) {
if (!value1 && !['id_card', 'password'].includes(key)) { if (!value1 && !['id_card', 'password'].includes(key)) {
Taro.showToast({title: "请填写完整", icon: 'error'}) Taro.showToast({title: "请认真填写", icon: 'error'})
return return
} }
} }
Taro.showLoading() Taro.showLoading()
setDisable(true)
try { try {
if (params.id) { if (params.id) {
await ManageApi.putUser(params.id, {...value, dep_ids: depIds, company_id: company?.id || 0}) await ManageApi.putUser(params.id, {...value, dep_ids: depIds, company_id: company?.id || 0})
@ -54,12 +52,10 @@ const AddStudent = () => {
} catch (e) { } catch (e) {
} }
Taro.hideLoading() Taro.hideLoading()
setDisable(true)
} }
const formatDep = useCallback(() => { const formatDep = useCallback(() => {
console.log(depIds, 12)
const selected = department.filter(d => depIds.includes(d.id)).map(d => d.title) const selected = department.filter(d => depIds.includes(d.id)).map(d => d.title)
const top4 = selected.splice(0, 3).join('、') const top4 = selected.splice(0, 3).join('、')
return top4 + (selected.length ? "+" + selected.length : '') return top4 + (selected.length ? "+" + selected.length : '')
@ -117,7 +113,7 @@ const AddStudent = () => {
</View> </View>
</View> </View>
<Button className='add button' formType='submit' disabled={disable}></Button> <Button className='add button' formType='submit'></Button>
</Form> </Form>
</View> </View>
) )

@ -80,6 +80,8 @@ const DepAdmin: FC = () => {
del(item.name, item.id) del(item.name, item.id)
break break
} }
},
fail() {
} }
}) })
} }
@ -126,6 +128,8 @@ const DepAdmin: FC = () => {
setRoleType(user) setRoleType(user)
break break
} }
},
fail() {
} }
}) })
} }

4
types/user.d.ts vendored

@ -34,6 +34,7 @@ interface Department {
users: null users: null
} }
/** 公司 */
interface Company { interface Company {
id: number id: number
company_name: string company_name: string
@ -76,7 +77,8 @@ interface CueStats {
} }
interface userRecord { /** 课时学习记录 */
interface HourHistory {
id: number; id: number;
duration: number; duration: number;
course: Curriculum; course: Curriculum;

Loading…
Cancel
Save