feat: record learning progress

This commit is contained in:
lbaf23 2021-08-02 15:53:03 +08:00
parent c86b20cc04
commit caabfd7f92
13 changed files with 346 additions and 104 deletions

View File

@ -5,6 +5,12 @@ import (
"strconv"
)
type ChaptersResponse struct {
Code int `json:"code"`
Msg string `json:"msg"`
Chapters []models.Outline `json:"chapters"`
}
// GetProjectChapters
// @Title
// @Description
@ -13,12 +19,32 @@ import (
// @Failure 403 body is empty
// @router /:id/chapters [get]
func (p *ProjectController) GetProjectChapters() {
user := p.GetSessionUser()
if user == nil {
p.Data["json"] = ChaptersResponse{
Code: 401,
Msg: "请先登录",
}
p.ServeJSON()
return
}
uid := ""
if user.Tag == "student" {
uid = user.Username
}
pid := p.GetString(":id")
outline, err := models.GetChaptersByPid(pid)
outline, err := models.GetChaptersByPid(pid, uid)
if err != nil {
p.Data["json"] = map[string][]models.Outline{"chapters": nil}
p.Data["json"] = ChaptersResponse{
Code: 400,
Msg: err.Error(),
Chapters: make([]models.Outline, 0),
}
} else {
p.Data["json"] = map[string][]models.Outline{"chapters": outline}
p.Data["json"] = ChaptersResponse{
Code: 200,
Chapters: outline,
}
}
p.ServeJSON()
}

View File

@ -2,6 +2,7 @@ package controllers
import (
"OpenPBL/models"
"encoding/json"
"strconv"
)
@ -175,5 +176,29 @@ func (p *ProjectController) ExchangeChapterSection() {
p.ServeJSON()
}
// UpdateSectionsMinute
// @Title
// @Description
// @Param body body []models.Section true ""
// @Success 200 {object}
// @Failure 401
// @router /:projectId/sections-minute [post]
func (p *ProjectController) UpdateSectionsMinute() {
sections := make([]models.Section, 0)
err := json.Unmarshal([]byte(p.GetString("sections")), &sections)
err = models.UpdateSectionsMinute(sections)
if err != nil {
p.Data["json"] = Response{
Code: 400,
Msg: err.Error(),
}
} else {
p.Data["json"] = Response{
Code: 200,
Msg: "更新成功",
Data: true,
}
}
p.ServeJSON()
}

View File

@ -157,16 +157,14 @@ func (u *StudentController) FinishedProject() {
u.ServeJSON()
}
var TimeFormatStr = "2006-01-02 15:04:05"
// CreateLearnSection
// GetLearnSection
// @Title
// @Description
// @Param body body models.LearnSection true ""
// @Success 200 {object} Response
// @Failure 400
// @router /section/:sectionId [post]
func (u *StudentController) CreateLearnSection() {
// @router /section/:sectionId [get]
func (u *StudentController) GetLearnSection() {
var resp Response
user := u.GetSessionUser()
if user == nil {
@ -189,11 +187,7 @@ func (u *StudentController) CreateLearnSection() {
}
uid := user.Username
sid, err := u.GetInt64(":sectionId")
l := models.LearnSection{
StudentId: uid,
SectionId: sid,
}
err = l.Create()
l, err := models.GetLearnSection(sid, uid)
if err != nil {
u.Data["json"] = Response{
Code: 400,
@ -202,7 +196,7 @@ func (u *StudentController) CreateLearnSection() {
} else {
u.Data["json"] = Response{
Code: 200,
Msg: "",
Data: l,
}
}
u.ServeJSON()
@ -238,10 +232,13 @@ func (u *StudentController) UpdateLearnSection() {
}
uid := user.Username
sid, err := u.GetInt64(":sectionId")
m, err := u.GetInt("learnMinute")
s, err := u.GetInt("learnSecond")
l := models.LearnSection{
StudentId: uid,
SectionId: sid,
LearnMinute: m,
LearnSecond: s,
}
err = l.Update()
if err != nil {
@ -252,7 +249,6 @@ func (u *StudentController) UpdateLearnSection() {
} else {
u.Data["json"] = Response{
Code: 200,
Msg: "",
}
}
u.ServeJSON()

View File

@ -239,29 +239,3 @@ func (p *ProjectController) ExchangeTask() {
}
p.ServeJSON()
}
// SaveTaskWeight
// @Title
// @Description
// @Param cid path string true ""
// @Success 200 {object} Response
// @Failure 401
// @router /:projectId/tasks/exchange [post]
func (p *ProjectController) SaveTaskWeight() {
tid1 := p.GetString(":taskId1")
tid2 := p.GetString(":taskId2")
err := models.ExchangeTasks(tid1, tid2)
if err != nil {
p.Data["json"] = Response{
Code: 400,
}
} else {
p.Data["json"] = Response{
Code: 200,
Data: true,
}
}
p.ServeJSON()
}

View File

@ -1,6 +1,8 @@
package models
import "xorm.io/xorm"
import (
"xorm.io/xorm"
)
type Chapter struct {
Id int64 `json:"id" xorm:"not null pk autoincr"`
@ -10,8 +12,8 @@ type Chapter struct {
}
type Outline struct {
Chapter `xorm:"extends"`
Sections []Section `json:"sections" xorm:"extends"`
Chapter `xorm:"extends"`
Sections []SectionMinute `json:"sections" xorm:"extends"`
}
func (p *Chapter) GetEngine() *xorm.Session {
@ -40,7 +42,7 @@ func ExchangeChapters(cid1 string, cid2 string) (err error) {
return
}
func GetChaptersByPid(pid string) (outline []Outline, err error) {
func GetChaptersByPid(pid string, uid string) (outline []Outline, err error) {
var c []Chapter
err = (&Chapter{}).GetEngine().
Where("project_id = ?", pid).
@ -48,11 +50,17 @@ func GetChaptersByPid(pid string) (outline []Outline, err error) {
Find(&c)
outline = make([]Outline, len(c))
for i:=0; i< len(c); i++ {
sections := make([]Section, 0)
sections := make([]SectionMinute, 0)
outline[i].Chapter = c[i]
err = (&Section{}).GetEngine().
Where("chapter_id = ?", c[i].Id).
Find(&sections)
if uid == "" {
err = (&Section{}).GetEngine().
Where("chapter_id = ?", c[i].Id).
Find(&sections)
} else {
err = adapter.Engine.
SQL("select * from (select * from section where chapter_id = ?) s LEFT JOIN learn_section ls on s.id = ls.section_id and ls.student_id = ?", c[i].Id, uid).
Find(&sections)
}
outline[i].Sections = sections
}
return

View File

@ -1,6 +1,7 @@
package models
import (
"fmt"
"xorm.io/xorm"
)
@ -11,7 +12,12 @@ type Section struct {
SectionNumber int `json:"sectionNumber" xorm:"index"`
ChapterNumber int `json:"chapterNumber" xorm:"index"`
SectionMinute int `json:"sectionMinute"`
SectionMinute int `json:"sectionMinute" xorm:"default 10"`
}
type SectionMinute struct {
Section `xorm:"extends"`
LearnSection `xorm:"extends"`
}
type SectionDetail struct {
@ -43,6 +49,15 @@ func (p *Section) Update() (err error) {
_, err = p.GetEngine().ID(p.Id).Update(p)
return
}
func UpdateSectionsMinute(sections []Section) (err error) {
for i:=0; i< len(sections); i++ {
fmt.Println(sections[i])
err = (&sections[i]).Update()
}
return
}
func (p *Section) Delete() (err error) {
_, err = p.GetEngine().
Exec("update section set section_number = section_number - 1 where section_number > ", p.SectionNumber)

View File

@ -18,7 +18,8 @@ type LearnSection struct {
StudentId string `json:"studentId" xorm:"not null index pk"`
SectionId int64 `json:"sectionId" xorm:"not null index pk"`
LearnMinute int `json:"learnTime" xorm:"default 0"`
LearnMinute int `json:"learnMinute" xorm:"default 0"`
LearnSecond int `json:"learnSecond" xorm:"default 0"`
}
func (l *LearnProject) GetEngine() *xorm.Session {
@ -27,6 +28,7 @@ func (l *LearnProject) GetEngine() *xorm.Session {
func (l *LearnSection) GetEngine() *xorm.Session {
return adapter.Engine.Table(l)
}
func (l *LearnProject) Create() (err error) {
_, err = (&LearnProject{}).GetEngine().Insert(l)
_, err = adapter.Engine.
@ -78,6 +80,19 @@ func GetProjectStudents(pid string, from int, size int) (s []LearnProject, rows
return
}
func GetLearnSection(sectionId int64, studentId string) (l LearnSection, err error) {
var b bool
b, err = (&LearnSection{}).GetEngine().
Where("section_id = ?", sectionId).
Where("student_id = ?", studentId).
Get(&l)
if !b {
l.SectionId = sectionId
l.StudentId = studentId
(&l).Create()
}
return
}
func (l *LearnSection) Create() (err error) {
_, err = (&LearnSection{}).GetEngine().Insert(l)
return

View File

@ -23,6 +23,13 @@ const SectionApi = {
data: qs.stringify(section)
})
},
updateSectionsMinute(pid, sections) {
return request({
url: `/project/${pid}/sections-minute`,
method: 'post',
data: qs.stringify(sections)
})
},
deleteChapterSection(pid, s) {
return request({
url: `/project/${pid}/chapter/${s.chapterId}/section/${s.id}/delete`,

View File

@ -24,6 +24,19 @@ const StudentApi = {
learning: false
})
})
},
getLearnSection(sid) {
return request({
url: `/student/section/${sid}`,
method: 'get'
})
},
updateLearnSection(sid, data) {
return request({
url: `/student/section/${sid}`,
method: 'post',
data: qs.stringify(data)
})
}
}

View File

@ -10,11 +10,12 @@ import TaskApi from "../../../api/TaskApi";
import FillSurvey from "./component/FillSurvey";
import SubmitApi from "../../../api/SubmitApi";
import util from "../component/Util"
import StudentApi from "../../../api/StudentApi";
function PreviewSection(obj) {
let s = new URLSearchParams(obj.location.search)
const backUrl = s.get('back')
let url = new URLSearchParams(obj.location.search)
const backUrl = url.get('back')
const sid = obj.match.params.sid
const pid = obj.match.params.pid
@ -22,10 +23,33 @@ function PreviewSection(obj) {
const [tasks, setTasks] = useState([])
const [learning, setLearning] = useState(false)
const [minute, setMinute] = useState(0)
const [second, setSecond] = useState(0)
const [timer, setTimer] = useState(null)
let s = 0
let m = 0
useEffect(()=>{
getSectionDetail()
getTasks()
}, [])
useEffect(()=>{
window.onbeforeunload = leave
}, [])
const leave = () => {
if (timer != null) {
clearTimeout(timer)
}
let data = {
learnMinute: m,
learnSecond: s
}
StudentApi.updateLearnSection(sid, data)
.then(res=>{
})
.catch(e=>{console.log(e)})
}
const getSectionDetail = () => {
SectionApi.getSectionDetail(sid, pid)
.then(res=>{
@ -64,10 +88,38 @@ function PreviewSection(obj) {
}
setTasks(t)
setLearning(res.data.learning)
if (res.data.learning) {
getTimer()
}
}
})
.catch(e=>{console.log(e)})
}
const getTimer = () => {
StudentApi.getLearnSection(sid)
.then(res=>{
if (res.data.code === 200) {
setSecond(res.data.data.learnSecond)
setMinute(res.data.data.learnMinute)
s = res.data.data.learnSecond
m = res.data.data.learnMinute
}
})
.catch(e=>{console.log(e)})
setTimeout(count, 1000)
}
const count = () => {
if (s === 30) {
s = 0
m ++
setMinute(m)
} else {
s++
}
setSecond(s)
setTimeout(count, 1000)
}
const back = e => {
if (backUrl === undefined || backUrl === null) {
window.location.href = `/project/${pid}/section/${sid}/edit`
@ -141,6 +193,9 @@ function PreviewSection(obj) {
<div style={{ padding: '20px', margin: 'auto'}}>
<Card>
<h2 style={{ fontWeight: 'bold' }}>{section.sectionName}</h2>
{learning ?
<span style={{float: 'right'}}>{minute}&nbsp;:&nbsp;{second}</span>
: null}
</Card>
<Card className="resource-card">
<div dangerouslySetInnerHTML={{__html: section.resource.content}}/>

View File

@ -6,16 +6,22 @@ import 'echarts/lib/component/legend';
import 'echarts/lib/component/markPoint';
import ReactEcharts from 'echarts-for-react';
import QueueAnim from 'rc-queue-anim';
import {Button, Card, Col, Divider, InputNumber, Row, Table, message} from "antd";
import {Button, Card, Col, Divider, InputNumber, Row, Table, message, Menu, Input} from "antd";
import TaskApi from "../../../../api/TaskApi";
import ProjectApi from "../../../../api/ProjectApi";
import ChapterApi from "../../../../api/ChapterApi";
import SectionApi from "../../../../api/SectionApi";
const {SubMenu} = Menu
function ProjectEvaluation(obj) {
const pid = obj.project.id
const published = obj.project.published
const type = localStorage.getItem("type")
const [chapters, setChapters] = useState([])
const [defaultOpenedKeys, setDefaultOpenedKeys] = useState([])
const [data, setData] = useState([])
const [tasks, setTasks] = useState([])
@ -23,8 +29,10 @@ function ProjectEvaluation(obj) {
const [learnMinuteWeight, setLearnMinuteWeight] = useState(obj.project.learnMinuteWeight)
const [editWeight, setEditWeight] = useState(false)
const [editMinute, setEditMinute] = useState(false)
useEffect(() => {
getChapters()
getTasks()
}, []);
const getTasks = () => {
@ -41,7 +49,26 @@ function ProjectEvaluation(obj) {
})
.catch(e=>{console.log(e)})
}
const getChapters = () => {
ChapterApi.getProjectChapters(pid)
.then((res) => {
if (res.data.chapters === null) {
setChapters([])
setDefaultOpenedKeys(0)
} else {
setChapters(res.data.chapters)
setOpenAllKeys(res.data.chapters.length)
}
})
.catch(e=>{console.log(e)})
}
const setOpenAllKeys = n => {
let keys = []
for (let i=0; i<n; i++) {
keys.push(i.toString())
}
setDefaultOpenedKeys(keys)
}
const checkWeight = () => {
let total = learnMinuteWeight
for (let i=0; i<tasks.length; i++) {
@ -57,12 +84,29 @@ function ProjectEvaluation(obj) {
}
return false
}
const edit = () => {
const edit1 = () => {
setEditMinute(true)
}
const edit2 = () => {
setEditWeight(true)
}
const save = () => {
const saveMinute = () => {
let sections = []
for (let i=0; i<chapters.length; i++) {
sections = sections.concat(chapters[i].sections)
}
let data = JSON.stringify(sections)
SectionApi.updateSectionsMinute(pid, {sections: data})
.then(res=>{
setEditMinute(false)
if (res.data.code === 200) {
getChapters()
}
})
.catch(e=>{console.log(e)})
}
const saveWeight = () => {
if (checkWeight()) {
setEditWeight(false)
let data = {
learnMinute: learnMinute,
learnMinuteWeight: learnMinuteWeight,
@ -70,20 +114,24 @@ function ProjectEvaluation(obj) {
}
ProjectApi.updateWeight(pid, data)
.then(res=>{
setEditWeight(false)
if (res.data.code === 200) {
getTasks()
obj.loadProjectDetail()
}
})
.catch(e=>{console.log(e)})
}
}
const cancel = () => {
const cancel1 = () => {
setEditMinute(false)
}
const cancel2 = () => {
setEditWeight(false)
}
const changeLearnMinute = (v) => {
setLearnMinute(v)
const changeLearnMinute = (v, index, subIndex) => {
chapters[index].sections[subIndex].sectionMinute = v
setChapters([...chapters])
}
const changeLearnMinuteWeight = (v) => {
setLearnMinuteWeight(v)
@ -193,34 +241,76 @@ function ProjectEvaluation(obj) {
<div style={{textAlign: 'left', marginBottom: '30px'}} key="1">
<ReactEcharts option={getOptions()}/>
{!published ?
<div style={{textAlign: 'right'}}>
{editWeight ?
<>
<Button onClick={cancel} style={{marginRight: '20px'}}>取消</Button>
<Button onClick={save} type="primary">保存</Button>
</>
:
<Button onClick={edit}>编辑权重</Button>
<div>
<p style={{textAlign: 'center', fontWeight: 'bold', fontSize: '20px', marginTop: '20px'}}>
章节学习时长</p>
{!published ?
<div style={{float: 'right'}}>
{editMinute ?
<>
<Button onClick={cancel1} style={{marginRight: '20px'}}>取消</Button>
<Button onClick={saveMinute} type="primary">保存</Button>
</>
:
<Button onClick={edit1}>编辑时长</Button>
}
</div>
: null
}
</div>
<Menu
style={{width: '100%'}}
defaultSelectedKeys={[]}
openKeys={defaultOpenedKeys}
mode="inline"
>{chapters.map((item, index) => (
<SubMenu style={{fontSize: '2.7vh'}} key={index.toString()} title={item.chapterName}>
{(item.sections === null || item.sections === undefined) ? null :
item.sections.map((subItem, subIndex) => (
<Menu.Item key={index.toString() + subIndex.toString()} >
{subItem.sectionName}
{editMinute ?
<div style={{float: 'right'}}>
学习时长不少于&nbsp;&nbsp;
<InputNumber value={subItem.sectionMinute} onChange={v=>changeLearnMinute(v, index, subIndex)} min={0}/>
&nbsp;&nbsp;分钟
</div>
:
<div style={{float: 'right'}}>
学习时长不少于&nbsp;&nbsp;{subItem.sectionMinute}&nbsp;&nbsp;分钟
</div>
}
</Menu.Item>
))
}
</div>
: null
}
<p style={{textAlign: 'center', fontWeight: 'bold', fontSize: '20px', marginTop: '20px'}}>权重占比</p>
</SubMenu>
))}
</Menu>
<div>
<p style={{textAlign: 'center', fontWeight: 'bold', fontSize: '20px', marginTop: '20px'}}>
权重占比
</p>
{!published ?
<div style={{float: 'right'}}>
{editWeight ?
<>
<Button onClick={cancel2} style={{marginRight: '20px'}}>取消</Button>
<Button onClick={saveWeight} type="primary">保存</Button>
</>
:
<Button onClick={edit2}>编辑权重</Button>
}
</div>
: null
}
</div>
<Divider />
<Row style={{padding: '15px'}} gutter={[10, 10]}>
<Col span={8}>
<span style={{fontWeight: 'bold'}}>章节学习时长</span></Col>
<Col span={10}>
<span>每小节学习时长不少于&nbsp;&nbsp;</span>
{editWeight ?
<span><InputNumber value={learnMinute} onChange={changeLearnMinute} min={0}/></span>
:
<span>{obj.project.learnMinute}</span>
}
<span>&nbsp;&nbsp;分钟</span>
<span style={{fontWeight: 'bold'}}>学习时长</span></Col>
<Col span={8}>
</Col>
<Col span={6}>
<Col span={8}>
<span>权重&nbsp;&nbsp;</span>
{editWeight ?
<span><InputNumber value={learnMinuteWeight} onChange={changeLearnMinuteWeight} min={0}/></span>

View File

@ -1,5 +1,5 @@
import React, {useEffect, useState} from "react";
import {Menu} from 'antd'
import {Menu, Progress} from 'antd'
import QueueAnim from 'rc-queue-anim';
import ChapterApi from "../../../../api/ChapterApi";
@ -40,6 +40,24 @@ function ProjectOutline(obj) {
item.sections.map((subItem, subIndex) => (
<Menu.Item key={index.toString() + subIndex.toString()} onClick={e => gotoLearning(item, subItem)}>
{subItem.sectionName}
{obj.project.learning ?
<>
<span style={{float: 'right'}}>
<Progress
trailColor="lightgray"
width={35}
strokeWidth={10}
type="circle"
percent={((subItem.learnMinute+subItem.learnSecond/60)/subItem.sectionMinute*100).toFixed(1)}
/>
</span>
<span style={{float: 'right', marginRight: '20px'}}>
学习进度
{subItem.learnMinute}&nbsp;:&nbsp;{subItem.learnSecond}&nbsp;/&nbsp;
{subItem.sectionMinute}
</span>
</>
: null }
</Menu.Item>
))
}

View File

@ -45,18 +45,18 @@ func init() {
beego.GlobalControllerRouter["OpenPBL/controllers:ProjectController"] = append(beego.GlobalControllerRouter["OpenPBL/controllers:ProjectController"],
beego.ControllerComments{
Method: "UpdateProject",
Method: "GetProjectDetail",
Router: "/:id",
AllowHTTPMethods: []string{"post"},
AllowHTTPMethods: []string{"get"},
MethodParams: param.Make(),
Filters: nil,
Params: nil})
beego.GlobalControllerRouter["OpenPBL/controllers:ProjectController"] = append(beego.GlobalControllerRouter["OpenPBL/controllers:ProjectController"],
beego.ControllerComments{
Method: "GetProjectDetail",
Method: "UpdateProject",
Router: "/:id",
AllowHTTPMethods: []string{"get"},
AllowHTTPMethods: []string{"post"},
MethodParams: param.Make(),
Filters: nil,
Params: nil})
@ -214,6 +214,15 @@ func init() {
Filters: nil,
Params: nil})
beego.GlobalControllerRouter["OpenPBL/controllers:ProjectController"] = append(beego.GlobalControllerRouter["OpenPBL/controllers:ProjectController"],
beego.ControllerComments{
Method: "UpdateSectionsMinute",
Router: "/:projectId/sections-minute",
AllowHTTPMethods: []string{"post"},
MethodParams: param.Make(),
Filters: nil,
Params: nil})
beego.GlobalControllerRouter["OpenPBL/controllers:ProjectController"] = append(beego.GlobalControllerRouter["OpenPBL/controllers:ProjectController"],
beego.ControllerComments{
Method: "GetProjectStudents",
@ -340,15 +349,6 @@ func init() {
Filters: nil,
Params: nil})
beego.GlobalControllerRouter["OpenPBL/controllers:ProjectController"] = append(beego.GlobalControllerRouter["OpenPBL/controllers:ProjectController"],
beego.ControllerComments{
Method: "SaveTaskWeight",
Router: "/:projectId/tasks/exchange",
AllowHTTPMethods: []string{"post"},
MethodParams: param.Make(),
Filters: nil,
Params: nil})
beego.GlobalControllerRouter["OpenPBL/controllers:ProjectController"] = append(beego.GlobalControllerRouter["OpenPBL/controllers:ProjectController"],
beego.ControllerComments{
Method: "ExchangeTask",
@ -432,9 +432,9 @@ func init() {
beego.GlobalControllerRouter["OpenPBL/controllers:StudentController"] = append(beego.GlobalControllerRouter["OpenPBL/controllers:StudentController"],
beego.ControllerComments{
Method: "CreateLearnSection",
Method: "GetLearnSection",
Router: "/section/:sectionId",
AllowHTTPMethods: []string{"post"},
AllowHTTPMethods: []string{"get"},
MethodParams: param.Make(),
Filters: nil,
Params: nil})