考卷 & 部门

main
king 1 year ago
parent bfbf611931
commit eeed36b787
  1. 1
      .env
  2. 36
      src/api/curriculum.ts
  3. 10
      src/api/manage.ts
  4. 12
      src/api/public.ts
  5. 4
      src/app.config.ts
  6. 30
      src/components/topic/judge.tsx
  7. 29
      src/components/topic/multi.tsx
  8. 30
      src/components/topic/shortAnswer.tsx
  9. 25
      src/components/topic/topic.scss
  10. 6
      src/components/video/video.tsx
  11. 3
      src/pages/business/categoryCur/categoryCur.config.ts
  12. 50
      src/pages/business/categoryCur/categoryCur.tsx
  13. 3
      src/pages/business/test/test.config.ts
  14. 116
      src/pages/business/test/test.tsx
  15. 7
      src/pages/business/videoInfo/components/catalogue.tsx
  16. 54
      src/pages/business/videoInfo/components/course.tsx
  17. 69
      src/pages/business/videoInfo/components/hours.tsx
  18. 25
      src/pages/business/videoInfo/videoInfo.scss
  19. 7
      src/pages/business/videoInfo/videoInfo.tsx
  20. 105
      src/pages/index/components/videoList.tsx
  21. 10
      src/pages/index/index.tsx
  22. 6
      src/pages/manage/addStudent/addStudent.tsx
  23. 3
      src/pages/manage/curAdmin/curAdmin.config.ts
  24. 10
      src/pages/manage/curAdmin/curAdmin.tsx
  25. 118
      src/pages/manage/depAdmin/depAdmin.tsx
  26. 48
      src/pages/manage/studentAdmin/student.scss
  27. 3
      src/pages/manage/studentAdmin/studentAdmin.config.ts
  28. 150
      src/pages/manage/studentAdmin/studentAdmin.tsx
  29. 4
      src/pages/my/components/header/service.tsx
  30. 8
      types/curriculum.d.ts
  31. 1
      types/topic.d.ts

@ -1,2 +1,3 @@
#TARO_APP_API=http://192.168.1.19:9898
TARO_APP_API=https://yjx.dev.yaojiankang.top
#TARO_APP_API=https://playedu.yaojiankang.top

@ -12,7 +12,7 @@ export interface CourseDepData {
hours: Hours
/** 是否必修 */
is_required: boolean
learn_hour_records: LearnHourRecords
learn_hour_records: LearnHourRecords[]
learn_record: LearnRecord | null
}
@ -29,6 +29,7 @@ export interface HourPlayData {
}
/** 断点 */
timeList: number[]
hour_test?: { id: number }
}
export interface Course {
@ -39,14 +40,25 @@ export interface Course {
}
interface AnswerRecord {
question_id: number
question_type: number
is_pass: boolean
user_id: number
question_position: number
time: number
}
interface RecordTextParam {
is_pass: boolean;
test_id: number;
/** true true false 拼成的string */
test_info: string;
test_score: number;
user_id: number;
}
export const curriculum = {
use() {
return request<Manage[]>('/api/v1/user/all', 'GET')
department() {
return request<{data: Manage[] }>('/api/v1/department/index', 'GET')
},
/** 学习记录 */
record(id: number) {
@ -72,10 +84,18 @@ export const curriculum = {
curEnd(courseId: number, id: number, duration: number) {
return request(`/api/v1/course/${courseId}/hour/${id}/record`, "POST", {duration})
},
categoryCur(categoryId: number, companyId: number, type: string) {
return request<Curriculum[]>(`/api/v1/category/${categoryId}/${companyId}/${type}`, "GET")
answerRecord(hourId: number, data: AnswerRecord) {
return request<{ time: number }>(`/api/v1/test/video/record/${hourId}`, "POST", data)
},
/** 获取考卷 */
getText(testId: string) {
return request<Record<string, Multi[]>>(`/api/v1/test/bank/create/${testId}`, "GET")
},
/** 考卷记录 */
recordText(hourId: number, data: RecordTextParam) {
return request(`/api/v1/test/record/${hourId}`, 'POST', data)
},
answerRecord(id: number, data: AnswerRecord) {
return request<{ time: number }>(`/api/v1/test/video/record/${id}`, "POST", data)
putRecordText(hour_id: number, is_pass: boolean) {
return request(`/api/v1/test/record/${hour_id}`, "PUT", {is_pass})
}
}

@ -23,7 +23,6 @@ interface setRoleTypeData {
role_type: number
}
type DepList = Record<number, Manage[]>
export interface AddDepProps {
id?: number | null
@ -64,6 +63,11 @@ export interface CurLearningRecord {
total: number
}
interface DepListData {
data: User[]
department: Manage[]
}
export const ManageApi = {
/** 添加学员 */
@ -84,8 +88,8 @@ export const ManageApi = {
return request(`/api/v1/user/${id}`, "PUT", data)
},
/** 部门 */
depList() {
return request<DepList>('/api/v1/department/index', "GET")
depList(dep_id: number | string) {
return request<DepListData>('/api/v1/department/index?dep_id=' + dep_id, "GET")
},
addDep(data: AddDepProps) {
return request('/api/v1/department/save', "POST", data)

@ -22,17 +22,11 @@ export interface Courses {
/** 必修 */
is_required: Curriculum[]
/** 总时长 */
total_course_duration: number
total_course_duration?: number
}
export type CoursesKey = keyof Omit<Courses, 'total_course_duration'>
export type Cur = Category & {
courses: Courses
name: string
resourceCategory?: Cur[]
}
export const publicApi = {
/** 分类 */
@ -40,7 +34,7 @@ export const publicApi = {
return request<CategoryList>('/api/v1/category/all', "GET")
},
/** 课程 */
curs() {
return request<Cur[]>('/api/v1/category/index', "GET")
course(data: { page: number, pageSize: number }) {
return request<Courses>('/api/v1/category/course/index', "GET", data)
}
}

@ -40,17 +40,15 @@ export default defineAppConfig({
{
root: 'pages/business',
pages: [
// 'course/course',
'userInfo/userInfo',
'videoInfo/videoInfo',
'categoryCur/categoryCur'
'test/test',
]
},
{
root: 'pages/manage',
pages: [
'depAdmin/depAdmin',
'studentAdmin/studentAdmin',
'college/college',
'curriculum/curriculum',
'addStudent/addStudent',

@ -1,16 +1,17 @@
import {FC, useEffect, useState} from "react";
import {Button, Radio, RadioGroup, Text, View} from "@tarojs/components";
import './topic.scss'
interface Props {
data: ShareSubject
onAnswer: (isAnswer: boolean) => void
onUpAndDown: (index: number) => void
onUpAndDown?: (index: number) => void
index: number
validate: boolean
frequency:number
frequency?: number
}
const Judge: FC<Props> = ({data, onAnswer, onUpAndDown, index, validate,frequency}) => {
const Judge: FC<Props> = ({data, onAnswer, onUpAndDown, index, validate, frequency}) => {
const [rightAnswer, setRightAnswer] = useState<string | null>(null) //答案
const rightKey = data.right_answer ? 'correct' : 'error' // 正确答案数组
const [error, setError] = useState(false)
@ -31,7 +32,19 @@ const Judge: FC<Props> = ({data, onAnswer, onUpAndDown, index, validate,frequenc
return (
<View className='topic'>
<View className='mb-3'>
<Text style={{
display: 'inline-block',
verticalAlign: 'middle',
fontSize: '20rpx',
height: '34rpx',
background: '#45D4A8',
borderRadius: '8rpx',
color: '#fff',
padding: '0 2px',
marginRight: '8px'
}}></Text>
<Text>{data.question}</Text>
</View>
@ -39,26 +52,27 @@ const Judge: FC<Props> = ({data, onAnswer, onUpAndDown, index, validate,frequenc
<Radio
value={'correct'}
className='option'
color={validate ? 'correct' !== rightKey ? 'red' : undefined : undefined}
color={validate ? 'correct' !== rightKey ? 'red' : '#45d4a8' : '#45d4a8'}
disabled={validate ? 'correct' !== rightAnswer : false}
>
</Radio>
<Radio
value={'error'}
color={validate ? 'error' !== rightKey ? 'red' : undefined : undefined}
color={validate ? 'error' !== rightKey ? 'red' : '#45d4a8' : '#45d4a8'}
disabled={validate ? 'error' !== rightAnswer : false}
className='option'>
</Radio>
</RadioGroup>
<View className='upAndDown'>
{onUpAndDown && <View className='upAndDown'>
{index > 0 && <Button className='button' onClick={() => onUpAndDown(index - 1)}></Button>}
<Button className='button' onClick={() => onUpAndDown(index + 1)}></Button>
</View>
</View>}
{error && frequency === 0 && <View className='mt-3'>
{error && frequency == 0 && <View className='mt-3'>
<View className='font-weight mb-3'></View>
<View>{data.analysis}</View>
</View>}

@ -1,16 +1,17 @@
import {FC, useEffect, useState} from "react";
import {Button, Checkbox, CheckboxGroup, Radio, RadioGroup, Text, View} from "@tarojs/components";
import './topic.scss'
interface Props {
data: Multi
onAnswer: (isAnswer: boolean) => void
onUpAndDown: (index: number) => void
onUpAndDown?: (index: number) => void
index: number
validate: boolean
frequency:number
frequency?: number
}
const Multi: FC<Props> = ({data, onAnswer, onUpAndDown, index, validate,frequency}) => {
const Multi: FC<Props> = ({data, onAnswer, onUpAndDown, index, validate, frequency}) => {
const [rightAnswer, setRightAnswer] = useState<string[]>([]) //答案
const rightKey = data?.right_answer?.split(',') || [] // 正确答案数组
@ -43,11 +44,22 @@ const Multi: FC<Props> = ({data, onAnswer, onUpAndDown, index, validate,frequenc
return (
<View className='topic'>
<View className='mb-3'>
<Text style={{
display: 'inline-block',
verticalAlign: 'middle',
fontSize: '20rpx',
height: '34rpx',
background: '#45D4A8',
borderRadius: '8rpx',
color: '#fff',
padding: '0 2px',
marginRight: '8px'
}}>{data.type ? "多选题" : '单选题'}</Text>
<Text>{data.question}</Text>
</View>
<View>
{!data.type ? <CheckboxGroup onChange={onChange}>
{data.type ? <CheckboxGroup onChange={onChange}>
{answers.map(d => <Checkbox
value={d.value}
className='option'
@ -69,11 +81,10 @@ const Multi: FC<Props> = ({data, onAnswer, onUpAndDown, index, validate,frequenc
}
</View>
<View className='upAndDown'>
{index > 0 && <Button className='button' onClick={() => onUpAndDown(index - 1)}></Button>}
<Button onClick={() => onUpAndDown(index + 1)} className='button'></Button>
</View>
{onUpAndDown && <View className='upAndDown'>
{index > 0 && <Button className='button' onClick={() => onUpAndDown?.(index - 1)}></Button>}
<Button onClick={() => onUpAndDown?.(index + 1)} className='button'></Button>
</View>}
{error && frequency == 0 && <View className='mt-3'>

@ -1,14 +1,15 @@
import {Button, Text, Textarea, View} from "@tarojs/components";
import {FC, useEffect, useState} from "react";
import Taro from "@tarojs/taro";
import './topic.scss'
interface Props {
data: ShareSubject
onAnswer: (isAnswer: boolean) => void
onUpAndDown: (index: number) => void
onUpAndDown?: (index: number) => void
index: number
validate: boolean
frequency: number
frequency?: number
}
const ShortAnswer: FC<Props> = ({data, onAnswer, onUpAndDown, index, validate, frequency}) => {
@ -33,6 +34,17 @@ const ShortAnswer: FC<Props> = ({data, onAnswer, onUpAndDown, index, validate, f
return (
<View className='topic'>
<View className='mb-3'>
<Text style={{
display: 'inline-block',
verticalAlign: 'middle',
fontSize: '20rpx',
height: '34rpx',
background: '#45D4A8',
borderRadius: '8rpx',
color: '#fff',
padding: '0 2px',
marginRight: '8px'
}}></Text>
<Text>{data.question}</Text>
</View>
<Textarea
@ -41,14 +53,16 @@ const ShortAnswer: FC<Props> = ({data, onAnswer, onUpAndDown, index, validate, f
className='Textarea'
onBlur={onBlur}
onInput={(e) => setValue((e.target as HTMLTextAreaElement).value)}/>
<View className='upAndDown'>
{index > 0 && <Button className='button' onClick={() => onUpAndDown(index - 1)}></Button>}
<Button className='button' onClick={() => onUpAndDown(index + 1)}></Button>
</View>
<View>3</View>
{frequency === 0 && <View className='mt-3'>
{onUpAndDown && <View className='upAndDown'>
{index > 0 && <Button className='button' onClick={() => onUpAndDown?.(index - 1)}></Button>}
<Button className='button' onClick={() => onUpAndDown?.(index + 1)}></Button>
</View>}
{frequency == 0 && <View className='mt-3'>
<View className='font-weight mb-3'></View>
<View>{data.analysis}</View>
</View>}

@ -0,0 +1,25 @@
.topic {
padding: 40rpx;
box-sizing: border-box;
.option {
display: block;
margin-bottom: 10px;
}
}
.statistics {
padding: 40px 20px;
display: flex;
justify-content: space-around;
}
.upAndDown {
display: flex;
justify-content: space-around;
margin-top: 30px;
button {
width: 40%;
}
}

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

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

@ -1,50 +0,0 @@
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,type} = getCurrentInstance()?.router?.params as { categoryId: number,type:string }
const [data, setData] = useState<Curriculum[]>([])
async function getData() {
try {
const res = await curriculum.categoryCur(categoryId!, company?.id!,type)
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

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

@ -0,0 +1,116 @@
import {Button, View} from "@tarojs/components";
import {getCurrentInstance} from "@tarojs/runtime";
import {curriculum} from "@/api";
import {FC, useEffect, useState} from "react";
import ShortAnswer from "@/components/topic/shortAnswer";
import Judge from "@/components/topic/judge";
import Multi from "@/components/topic/multi";
import {Profile} from '@/store'
import Taro from "@tarojs/taro";
const record: boolean[] = []
const Test = () => {
const {testId} = getCurrentInstance()?.router?.params as { testId: string }
const [validate, setValidate] = useState(false)
const [answers, setAnswers] = useState<boolean[]>([])
const [data, setData] = useState<Record<string, Multi[]> | null>(null)
const [hourId, setHourId] = useState<number>(0)
const {user} = Profile.useContainer()
async function getData() {
const res = await curriculum.getText(testId)
setHourId((res.hour_test as any).hour_id)
setData(res)
}
function init() {
record.splice(0, record.length)
setValidate(false)
setAnswers([])
}
function onAnswer(answer: boolean) {
record.push(answer)
setAnswers(record)
}
useEffect(() => {
if (answers.length) {
const isPass = answers.every(d => d)
curriculum.recordText(hourId, {
is_pass: isPass,
test_id: Number(testId),
test_info: answers.join(','),
user_id: user?.id!,
test_score: 0
}).then()
curriculum.putRecordText(hourId,isPass).then()
if (isPass) {
Taro.showModal({
title: '考卷已完成',
showCancel: false,
success() {
Taro.navigateBack()
}
})
} else {
Taro.showModal({
title: '未及格,请再次作答',
showCancel: false,
content: '加油!',
success() {
init()
}
})
}
}
}, [answers])
function onSubmit() {
setValidate(true)
}
useEffect(() => {
getData()
}, [])
return (
<View>
{data?.fill.map((d, index) => <ShortAnswer
data={d}
index={index}
onAnswer={onAnswer}
validate={validate}
/>)}
{data?.judge.map((d, index) => <Judge
data={d}
index={index}
onAnswer={onAnswer}
validate={validate}
/>)}
{
data?.multi.map((d, index) => <Multi
data={d}
onAnswer={onAnswer}
index={index}
validate={validate}/>)
}
<Button className='button' onClick={onSubmit}></Button>
</View>
)
}
const TestProfile: FC = () => {
return (
<Profile.Provider>
<Test/>
</Profile.Provider>
)
}
export default TestProfile

@ -45,11 +45,12 @@ const Catalogue: FC<Props> = ({data, setHors, id}) => {
return null
}
function chapters_complete(chapter: Chapters): Boolean {
function chapters_complete(chapter: Chapters): boolean {
const hors = getHors(chapter.id)
if (hors) {
for (const hor of hors) {
if (!data?.learn_hour_records?.[hor.id]?.is_finished) {
const courseHourRecordsFinish = data?.learn_hour_records?.find(d => d.id === hor.id)?.courseHourRecordsFinish
if (courseHourRecordsFinish === 0 || !courseHourRecordsFinish) {
return false
}
}
@ -64,7 +65,7 @@ const Catalogue: FC<Props> = ({data, setHors, id}) => {
} else {
const ok = chapters_complete((Object.values(data?.chapters || {})?.[index - 1]))
if (!ok) {
Taro.showModal({title: '上一章节未完成'})
Taro.showModal({title: '上一章节未完成'})
return
}
setHors(is_complete, id)

@ -4,9 +4,9 @@ import HVideo from "@/components/video/video";
import {curriculum, HourPlayData} from "@/api";
import {Profile} from '@/store'
import Taro from "@tarojs/taro";
import Multi from "@/pages/business/videoInfo/components/multi";
import Judge from "@/pages/business/videoInfo/components/judge";
import ShortAnswer from "@/pages/business/videoInfo/components/shortAnswer";
import Multi from "@/components/topic/multi";
import Judge from "@/components/topic/judge";
import ShortAnswer from "@/components/topic/shortAnswer";
interface Props {
id: number,
@ -21,25 +21,29 @@ const Course: FC<Props> = ({id, courseId, preview, curEnd}: Props) => {
const [breakpoint, setBreakpoint] = useState<number[]>([]) // 断点
const [show, setShow] = useState(false) // 题
const [data, setData] = useState<HourPlayData | null>(null)
const [examAll, setExamAll] = useState<Record<number, (ShareSubject | Multi)[]>>([])
const [examAll, setExamAll] = useState<Record<number, (ShareSubject | Multi)[]>>([]) // 题库
const [index, setIndex] = useState(0)
const [time, setTime] = useState<number>(0) // 进入断点的时间
const [validate, setValidate] = useState(false) // 开启验证
const [newRecord, setNewRecord] = useState<boolean[]>([])
const [count, setCount] = useState<Record<number, number> | null>(null)
const [frequency, setFrequency] = useState<number>(1) // 次数
const [testId, setTestId] = useState<number | null>(null)
const {user} = Profile.useContainer()
async function onEnded() {
try {
await curriculum.curEnd(courseId, id, data?.duration!)
await curriculum.curEnd(courseId, id, data?.duration!)
if (testId) {
Taro.navigateTo({
url: `/pages/business/test/test?testId=${testId}`
})
} else {
Taro.showModal({
title: "学习完成",
success() {
curEnd()
}
})
} catch (e) {
}
}
@ -59,12 +63,15 @@ const Course: FC<Props> = ({id, courseId, preview, curEnd}: Props) => {
setBreakpoint(res.timeList)
setExamAll(res.hourExamQuestions.data)
setCount(res.hourExamQuestions.count)
if (res.hour_test) {
setTestId(res.hour_test.id)
}
}
}
useEffect(() => {
getData()
}, [id])
}, [])
/** 统计答案 */
function onAnswer(isAnswer: boolean) {
@ -93,16 +100,21 @@ const Course: FC<Props> = ({id, courseId, preview, curEnd}: Props) => {
/** 重新看 */
function again() {
if (frequency === 0) {
init()
if (frequency === 0 && show) {
const {id: question_id, question_type} = examAll?.[time]?.[0]
curriculum.answerRecord(id, {
is_pass: false,
user_id: user?.id!,
question_position: time
time: time,
question_id,
question_type
}).then(res => {
seek(res.time)
})
init()
}
setShow(false)
setIndex(0)
}
useEffect(() => {
@ -110,17 +122,19 @@ const Course: FC<Props> = ({id, courseId, preview, curEnd}: Props) => {
setFrequency(frequency - 1)
const pass = newRecord.every(d => d)
if (pass) {
const {id: question_id, question_type} = examAll?.[time]?.[0]
curriculum.answerRecord(id, {
is_pass: pass,
user_id: user?.id!,
question_position: time
}).then(() => {
const old: number[] = JSON.parse(JSON.stringify(breakpoint))
const index = old.indexOf(time)
old.splice(index, 1)
setBreakpoint(old)
Taro.showToast({title: '全部通过'})
})
time: time,
question_type,
question_id
}).then()
const old: number[] = JSON.parse(JSON.stringify(breakpoint))
const index = old.indexOf(time)
old.splice(index, 1)
setBreakpoint(old)
Taro.showToast({title: '全部通过'})
init()
} else {
Taro.showToast({title: '错误', icon: 'error'})
@ -191,7 +205,7 @@ const Course: FC<Props> = ({id, courseId, preview, curEnd}: Props) => {
<View className='mt-10 ml-4'>
<View className='mt-3'>{newRecord.filter(d => d).length}</View>
<View className='mt-3'>{newRecord.filter(d => !d).length}</View>
<View className='mt-3'>{frequency}</View>
<View className='mt-3'>{frequency}</View>
</View>
{!validate && <Button className='button mt-10' onClick={() => setValidate(true)}></Button>}

@ -5,36 +5,73 @@ import playOk from "@/static/img/play-ok.png";
import play from "@/static/img/play.png";
import {formatMinute} from "@/utils/time";
import Taro from "@tarojs/taro";
import {curriculum} from "@/api";
interface Props {
data?: Hour[] | null
learn_hour_records?: LearnHourRecords
learn_hour_records?: LearnHourRecords[]
click: (is_complete: boolean, id: number) => void
}
const Hours: FC<Props> = ({data, click, learn_hour_records}: Props) => {
const complete = (id: number) => !!learn_hour_records?.[id]?.is_finished
async function jumTest(hour: Hour) {
const {hour_test} = await curriculum.hourPlay(hour.course_id, hour.id)
Taro.navigateTo({
url: `/pages/business/test/test?testId=${hour_test?.id}`
})
}
const Hours: FC<Props> = ({data, click, learn_hour_records}) => {
const complete = (id: number): number | undefined => {
const find = learn_hour_records?.find(d => d.id === id)
if (find) {
return find.courseHourRecordsFinish
} else {
return undefined
}
}
function onClick(id: number, is_complete: number | undefined, hour: Hour, upId?: number,) {
if (is_complete === 0) {
Taro.showModal({
title: '考卷未完成,是否前往',
content: '考卷未完成不能播放下一个视频',
confirmText: '前往考试',
cancelText: '观看视频',
success({confirm}) {
if (confirm) {
jumTest(hour)
} else {
click(true, id)
}
}
})
return;
} else if (is_complete === 1) {
click(true, id)
return;
}
function onClick(id: number, is_complete: boolean, upId?: number) {
if (upId && !complete(upId)) {
Taro.showModal({title: '请播放上一个课程'})
if (upId && complete(upId) !== 1) {
Taro.showModal({title: '请完成上一个视频'})
return
}
click(is_complete, id)
click(!!is_complete, id)
}
return (
<>
{
data?.map((d, index) => <View
className={'hor' + ` ${complete(d.id) ? 'complete' : null}`}
onClick={() => onClick(d.id, complete(d.id), data?.[index - 1]?.id)}>
<Image src={complete(d.id) ? playOk : play} mode='aspectFit'/>
<View>
<View>{index + 1}.{d.title}</View>
<View className='font-26'>{formatMinute(d.duration)}</View>
</View>
</View>)
data?.map((d, index) =>
<View className={'hor' + ` ${complete(d.id) ? 'complete' : null}`}
onClick={() => onClick(d.id, complete(d.id), d, data?.[index - 1]?.id,)}>
<Image src={complete(d.id) ? playOk : play} mode='aspectFit'/>
<View>
<View>{index + 1}.{d.title}</View>
<View className='font-26'>{formatMinute(d.duration)}</View>
{complete(d.id) === 0 && <View className='font-26 text-danger'></View>}
</View>
</View>)
}
</>
)

@ -57,28 +57,3 @@
color: #45D4A8;
}
.topic{
padding: 20px;
box-sizing: border-box;
.option{
display: block;
margin-bottom: 10px;
}
}
.statistics{
padding: 40px 20px;
display: flex;
justify-content: space-around;
}
.upAndDown{
display: flex;
justify-content: space-around;
margin-top: 30px;
button{
width: 40%;
}
}

@ -1,11 +1,12 @@
import {Image, Text, View} from "@tarojs/components";
import {FC, useEffect, useState} from "react";
import {FC, useState} from "react";
import {getCurrentInstance} from "@tarojs/runtime";
import {CourseDepData, curriculum} from "@/api";
import './videoInfo.scss'
import {Profile} from '@/store'
import Catalogue from "./components/catalogue";
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 }
@ -25,9 +26,7 @@ const VideoInfo: FC = () => {
setPlayId(id)
}
useEffect(() => {
getData()
}, [])
Taro.useDidShow(getData)
return (
<Profile.Provider>

@ -1,40 +1,37 @@
import {FC, useState} from "react";
import {FC, useEffect, useState} from "react";
import Taro, {useReachBottom} from "@tarojs/taro";
import {Text, View} from "@tarojs/components";
import {CoursesKey, Cur, publicApi} from "@/api/public";
import {View} from "@tarojs/components";
import {Courses, CoursesKey, publicApi} from "@/api/public";
import VideoCover from "@/components/videoCover/videoCover";
import styles from '../index.module.scss'
import {formatMinute} from "@/utils/time";
import {userApi} from "@/api";
interface Props {
categoryId: CoursesKey
categoryKey: CoursesKey
}
export const VideoList: FC<Props> = ({categoryId}: Props) => {
const [data, setDta] = useState<Cur[] | null>(null)
const [index, setIndex] = useState(3)
export const VideoList: FC<Props> = ({categoryKey}) => {
const [data, setData] = useState<Courses>({
is_finished: [],
is_required: [],
is_not_finished: [],
is_not_required: []
})
const [page, setPage] = useState(1)
const [records, setRecords] = useState<LearnRecord[]>([])
async function getData() {
try {
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)
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)
setData(oldData)
} catch (e) {
}
Taro.hideLoading()
}
async function getRecords() {
@ -47,7 +44,7 @@ export const VideoList: FC<Props> = ({categoryId}: Props) => {
function rateOfLearning(id: number, class_hour: number): JSX.Element {
switch (categoryId) {
switch (categoryKey) {
case "is_required":
case "is_not_required":
const find = records.find(d => d?.course_id === id)
@ -60,58 +57,32 @@ export const VideoList: FC<Props> = ({categoryId}: Props) => {
case "is_finished":
return (<View>{`${class_hour}节/已学${class_hour}`}</View>)
}
}
function jumpCategoryCur(id: number) {
let type = 1
switch (categoryId) {
case "is_not_required":
type = 2
break
case "is_not_finished":
type = 4
break
case "is_finished":
type = 3
break
}
Taro.navigateTo({url: `/pages/business/categoryCur/categoryCur?categoryId=${id}&type=${type}`})
return (<View>sd</View>)
}
useReachBottom(() => {
if (data && data?.length > index) {
setIndex(index + 1)
}
setPage(page + 1)
})
Taro.useDidShow(() => {
getData()
getRecords()
})
useEffect(() => {
getData()
}, [page])
return (
<>
{data?.slice(0, index).map(d => (
<>{
d.courses?.[categoryId].length ? <View>
<View className='flex justify-between align-center my-1 mx-2' 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}>
{d.courses[categoryId].map(c => (
<VideoCover
thumb={c.thumb}
title={c.title}
id={c.id}
depId={c.id}
time={formatMinute(d.courses.total_course_duration)}
content={rateOfLearning(c.id, c.class_hour)}
/>
))}
</View>
</View> : null
}</>
))}
</>
<View className={'py-2 flex justify-between flex-wrap ' + styles.videoListBox}>
{data?.[categoryKey]?.map(c =>
<VideoCover
thumb={c.thumb}
title={c.title}
id={c.id}
depId={c.id}
time={formatMinute(c.course_duration)}
content={rateOfLearning(c.id, c.class_hour)}
/>)}
</View>
)
}

@ -2,10 +2,10 @@ import {FC, useState} from "react";
import {View} from "@tarojs/components";
import Taro from "@tarojs/taro";
import styles from './index.module.scss'
import Tabs, {OnChangOpt, TabList} from "@/components/tabs/tabs";
import {Profile} from '@/store'
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";
@ -17,10 +17,10 @@ const Index: FC = () => {
{title: "已完成", value: 'is_finished'},
{title: "未完成", value: 'is_not_finished'},
]
const [categoryId, setCategoryId] = useState<CoursesKey>('is_required')
const [categoryKey, setCategoryKey] = useState<CoursesKey>('is_required')
function tabChange(data: OnChangOpt) {
setCategoryId(data.tab?.value as CoursesKey)
setCategoryKey(data.tab?.value as CoursesKey)
}
return (
@ -28,8 +28,8 @@ const Index: FC = () => {
<View className={styles.content} style={`paddingTop:${globalData.statusBarHeight}px`}>
<View className='text-center font-weight font-34 mt-3'></View>
<Search/>
<Tabs tabList={category} onChange={tabChange} current={categoryId}/>
<VideoList categoryId={categoryId}/>
<Tabs tabList={category} onChange={tabChange} current={categoryKey}/>
<VideoList categoryKey={categoryKey}/>
<View className='text-center text-muted my-3'>- -</View>
</View>
</Profile.Provider>

@ -25,12 +25,10 @@ const AddStudent = () => {
async function getDepartment() {
Taro.showLoading()
const res = await curriculum.use()
const res = await curriculum.department()
if (res) {
setDepartment(res.map(d => ({title: d.name, id: d.id})))
setDepartment(res.data.map(d => ({title: d.name, id: d.id})))
}
Taro.hideLoading()
}
async function submit(event) {

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

@ -1,10 +0,0 @@
import {FC} from "react";
import {View} from "@tarojs/components";
const CurAdmin: FC = () => {
return (
<View>sd</View>
)
}
export default CurAdmin

@ -6,13 +6,15 @@ import {Profile} from '@/store'
import './depAdmin.scss'
import PopPut from "@/components/popPut/popPut";
import folder from '@/static/img/folder.png'
import {getCurrentInstance} from "@tarojs/runtime";
interface ChangeDataProps {
putCompany: Manage | null
parent_id: number
getDeps: () => Promise<void>
}
const ChangeData: FC<ChangeDataProps> = ({putCompany, getDeps}: 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)
@ -36,7 +38,7 @@ const ChangeData: FC<ChangeDataProps> = ({putCompany, getDeps}: ChangeDataProps)
const data: AddDepProps = {
id: putCompany?.id || null,
name,
parent_id: putCompany?.parent_id || 0,
parent_id: putCompany?.parent_id || parent_id || 0,
company_id: company_id,
sort: sort
}
@ -77,19 +79,18 @@ const ChangeData: FC<ChangeDataProps> = ({putCompany, getDeps}: ChangeDataProps)
}
const DepAdmin: FC = () => {
const params = getCurrentInstance()?.router?.params as { dep_id: string, name: string, id: string }
const [manages, setManages] = useState<Manage[]>([])
const [show, setShow] = useState(false)
const [users, setUsers] = useState<User[]>([])
const [putCompany, setPutCompany] = useState<Manage | null>(null)
async function getData() {
show && setShow(false)
const res = await ManageApi.depList()
const res = await ManageApi.depList(params.dep_id || 0)
if (res) {
const formatData: Manage[] = []
Object.values(res)?.forEach(d => {
formatData.push(...d)
})
setManages(formatData)
setManages(res.department)
setUsers(res.data)
}
}
@ -120,8 +121,8 @@ const DepAdmin: FC = () => {
Taro.showActionSheet({
itemList: [
'查看部门课程',
'查看子部门',
'修改',
// '详情',
'删除'
],
success({tapIndex}) {
@ -129,13 +130,13 @@ const DepAdmin: FC = () => {
case 0:
Taro.navigateTo({url: `/pages/manage/depCur/depCur?id=${item.id}`})
break
case 1:
case 2:
showPop(item)
break
// case 2:
// Taro.navigateTo({url: `/pages/manage/depAdmin/depAdmin?id=${item.id}&name=${item.name}`})
// break
case 2:
case 1:
Taro.navigateTo({url: `/pages/manage/depAdmin/depAdmin?dep_id=${item.id}&name=${item.name}&id=${item.id}`})
break
case 3:
del(item.name, item.id)
break
}
@ -143,25 +144,104 @@ const DepAdmin: FC = () => {
})
}
function delUser(id: number) {
Taro.showModal({
title: '是否确认删除',
async success({confirm}) {
if (confirm) {
await ManageApi.del(id)
Taro.showToast({title: '删除成功'})
await getData()
}
}
})
}
function userManagesSheet(user: User) {
Taro.showActionSheet({
itemList: [
'修改',
'删除',
'设置为管理员',
"日学习记录"
],
success({tapIndex}) {
switch (tapIndex) {
case 0:
Taro.navigateTo({url: "/pages/manage/addStudent/addStudent" + (user.id ? `?id=${user.id}` : '')})
break
case 1:
delUser(user.id)
break
case 2:
setRoleType(user)
break
case 3:
Taro.navigateTo({url: `/pages/manage/college/college?id=${user.id}&name=${user.name}`})
break
}
}
})
}
function jumpAddStudent() {
Taro.navigateTo({url: '/pages/manage/addStudent/addStudent'})
}
function setRoleType(user:User) {
if (user.role_type === 2) {
Taro.showModal({title: "禁止修改超级管理员"})
return
}
const type = user.role_type === 0 ? 1 : 0
Taro.showModal({
title: "设置为" + ['学员', '管理员'][type],
async success({confirm}) {
if (confirm) {
try {
Taro.showLoading()
await ManageApi.setRoleType(user.id, {auth_id: user?.id!, role_type: type})
Taro.hideLoading()
Taro.showToast({title: "设置成功"})
await getData()
} catch (e) {
}
}
}
})
}
Taro.useDidShow(getData)
useEffect(() => {
Taro.setNavigationBarTitle({
title: params.name ?? '部门管理'
})
})
return (
<Profile.Provider>
<View>
{manages.map(d => <PopPut
key={d.id}
title={d.name}
onClick={() => managesSheet(d)}
chevron
leftImage={folder}
/>)}
{
manages.map(d => <PopPut
users.map(d => <PopPut
key={d.id}
leftImage={d.avatar}
title={d.name}
chevron
onClick={() => managesSheet(d)}
leftImage={folder}
onClick={() => userManagesSheet(d)}
/>)
}
<View className='text-center text-muted mt-3'>- -</View>
<View className='operation'>
<View onClick={jumpAddStudent}></View>
<View onClick={() => showPop(null)}></View>
@ -170,7 +250,7 @@ const DepAdmin: FC = () => {
<PageContainer show={show} round onAfterLeave={() => setShow(false)}>
<View className='h-4'>
{show && <ChangeData getDeps={getData} putCompany={putCompany}/>}
{show && <ChangeData getDeps={getData} putCompany={putCompany} parent_id={Number(params.id)}/>}
</View>
</PageContainer>
</Profile.Provider>

@ -1,48 +0,0 @@
.user {
.header {
border-bottom: 1px solid #ddd;
.lock {
padding: 4px 20px;
border-radius: 5px;
color: #fff;
margin-right: 20px;
}
Text {
color: #6e6e6e;
}
.del {
color: red;
}
}
.info {
Image {
width: 150px;
height: 150px;
background: #ddd;
border-radius: 50%;
}
}
}
.add {
margin: 20px;
border-radius: 10px;
background: linear-gradient(to right, #8284f7, #5a93f9);
color: #fff;
position: fixed;
width: 710rpx;
bottom: 20px;
}
.operation {
border-top: 1px solid #ddd;
View {
width: 50%;
text-align: center;
}
}

@ -1,3 +0,0 @@
export default definePageConfig({
navigationBarTitleText: '学员管理',
})

@ -1,150 +0,0 @@
import {Button, CustomWrapper, Image, Text, View} from "@tarojs/components";
import {curriculum} from "@/api";
import {FC, useState} from "react";
import Taro, {useDidShow} from "@tarojs/taro";
import Tabs, {OnChangOpt, TabList} from "@/components/tabs/tabs";
import './student.scss'
import {Profile} from '@/store'
import {ManageApi} from "@/api/manage";
interface RoleTypeProps {
id: number
role_type: number
getData: () => Promise<void>
}
interface DelProps {
onClick: () => void
id: number
}
const Del: FC<DelProps> = ({onClick, id}: DelProps) => {
const {user} = Profile.useContainer()
return (
<>
{user?.id !== id && <View className='del' onClick={onClick}></View>}
</>
)
}
const RoleType: FC<RoleTypeProps> = ({id, role_type, getData}: RoleTypeProps) => {
const {user} = Profile.useContainer()
function setRoleType() {
if (role_type === 2) {
Taro.showModal({title: "禁止修改超级管理员"})
return
}
const type = role_type === 0 ? 1 : 0
Taro.showModal({
title: "设置为" + ['学员', '管理员'][type],
async success({confirm}) {
if (confirm) {
try {
Taro.showLoading()
await ManageApi.setRoleType(id, {auth_id: user?.id!, role_type: type})
Taro.hideLoading()
Taro.showToast({title: "设置成功"})
await getData()
} catch (e) {
}
}
}
})
}
return (user?.role_type === 2 ?
<View onClick={setRoleType}>{['设置管理员', '设置学员', '超级管理员'][role_type]}</View>
: null)
}
const studentAdmin = () => {
const [list, setList] = useState<TabList[]>([])
const [user, setUser] = useState<ManageUsers[]>([])
async function getData() {
Taro.showLoading()
const res = await curriculum.use()
if (res) {
setList(res.map(d => ({title: d.name, value: d})))
setUser(res[0].users)
}
Taro.hideLoading()
}
useDidShow(getData)
function listClick(data: OnChangOpt) {
setUser((data.tab?.value as Manage).users)
}
function jumCollege(id: number, name: string) {
Taro.navigateTo({url: `/pages/manage/college/college?id=${id}&name=${name}`})
}
function changeStudent(id?: number) {
Taro.navigateTo({url: "/pages/manage/addStudent/addStudent" + (id ? `?id=${id}` : '')})
}
function del(id: number) {
Taro.showModal({
title: '是否确认删除',
async success({confirm}) {
if (confirm) {
await ManageApi.del(id)
Taro.showToast({title: '删除成功'})
await getData()
}
}
})
}
return (
<CustomWrapper>
<Profile.Provider>
<View className='bg-white mb-3'>
<Tabs tabList={list} onChange={listClick}/>
</View>
{user.length ? user.map((d) => (
<View className='bg-white user'>
<View className='flex mt-3 header p-2 justify-between'>
<View className='flex'>
<View className='lock'
style={`background:${['#73c057', '#c94f4f'][d.is_lock]}`}>{['正常', '警用'][d.is_lock]}</View>
<Text> {d.id}</Text>
</View>
{d.role_type === 0 && <Del id={d.id} onClick={() => del(d.id)}/>}
</View>
<View className='p-2 flex info justify-between'>
<View>
<View className='font-weight my-3'>{d.name}</View>
<View className='flex mb-3'>
<View style='width:60px' className='text-muted'></View>
<View>{['学员', '管理员', '超级管理员'][d.role_type]}</View>
</View>
<View className='flex mb-3'>
<View style='width:60px' className='text-muted'></View>
<View>{d.phone_number}</View>
</View>
</View>
<Image src={d.avatar} mode='widthFix'/>
</View>
<View className='flex justify-between p-2 operation'>
<View onClick={() => changeStudent(d.id)}></View>
<View onClick={() => jumCollege(d.id, d.name)}></View>
<RoleType id={d.id} role_type={d.role_type} getData={getData}/>
</View>
</View>
))
: <View className='text-center'></View>}
<View className='py-8'/>
<Button className='add' onClick={() => changeStudent()}></Button>
</Profile.Provider>
</CustomWrapper>
)
}
export default studentAdmin

@ -4,8 +4,6 @@ import Taro from "@tarojs/taro";
import {Profile} from '@/store/profile'
import styles from '../../my.module.scss'
import dep from '@/static/img/dep.png'
// import cur from '@/static/img/cur.png'
import student from '@/static/img/student.png'
import buy from '@/static/img/buy.png'
interface List {
@ -26,8 +24,6 @@ const Service = () => {
if ([1, 2].includes(user?.role_type || 0)) {
oldList.unshift(...[
{title: '部门管理', src: dep, router: '/pages/manage/depAdmin/depAdmin'},
{title: '学员管理', src: student, router: '/pages/manage/studentAdmin/studentAdmin'},
// {title: '课程管理', src: cur, router: ''},
{title: '课程购买', src: buy, router: '/pages/manage/curriculum/curriculum'},
])
setList(oldList)

@ -11,6 +11,8 @@ interface Curriculum {
short_desc: string;
/** 课程封面 */
thumb: string;
/** 时间 */
course_duration: number
}
/** 课程信息 */
@ -40,6 +42,8 @@ interface Hour {
course_id: number;
/** 章节id */
chapter_id: number
/** 0未完成 1已完成 */
courseHourRecordsFinish: 0 | 1
}
type Hours = Record<number, Hour[]>
@ -67,7 +71,9 @@ interface LearnHourRecord {
user_id: 17
}
type LearnHourRecords = Record<number, LearnHourRecord>
interface LearnHourRecords extends Hour{
courseHourRecords:LearnHourRecord
}
/** 课程学习情况 */

1
types/topic.d.ts vendored

@ -1,5 +1,6 @@
/** 判断题 & 简答题 */
interface ShareSubject {
id:number
question_type: 1 | 2 | 3
question: string //课题题目
score: number //课题分数

Loading…
Cancel
Save