Merge pull request #22 from lbaf23/feat/export-document

feat: export evidence
This commit is contained in:
lhf 2021-08-28 15:59:54 +08:00 committed by GitHub
commit a454cedb18
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
32 changed files with 1074 additions and 357 deletions

View File

@ -562,3 +562,29 @@ func (p *ProjectController) ViewProject() {
}
p.ServeJSON()
}
// CloneProject
// @Title
// @Description create project
// @Success 200 {object} Response
// @Failure 401
// @Failure 400
// @Failure 403
// @router /:id/clone [post]
func (p *ProjectController) CloneProject() {
pid, err := p.GetInt64(":id")
uid := util.GetUserId(p.GetSessionUser())
err = models.CloneProject(uid, pid)
if err != nil {
p.Data["json"] = Response{
Code: 400,
Msg: err.Error(),
}
} else {
p.Data["json"] = Response{
Code: 200,
Msg: "复制成功,请到未发布项目中查看",
}
}
p.ServeJSON()
}

View File

@ -77,6 +77,7 @@ func (p *ProjectController) CreateChapterSection() {
SectionName: p.GetString("sectionName"),
SectionNumber: sectionNumber,
ChapterNumber: chapterNumber,
SectionMinute: 1,
}
if err != nil {
p.Data["json"] = Response{

View File

@ -33,9 +33,6 @@ func (p *Chapter) Delete() (err error) {
session := adapter.Engine.NewSession()
defer session.Close()
session.Begin()
fmt.Println(p.ProjectId, p.ChapterNumber)
_, err = session.
Exec("update chapter set chapter_number = chapter_number - 1 " +
"where project_id = ? and chapter_number > ?", p.ProjectId, p.ChapterNumber)
@ -76,3 +73,30 @@ func GetChaptersByPid(pid string, uid string) (outline []Outline, err error) {
}
return
}
func DeleteProjectChapters(pid int64) (err error) {
var chapters []Chapter
err = (&Chapter{}).GetEngine().Where("project_id = ?", pid).Find(&chapters)
for i:=0; i< len(chapters); i++ {
c := chapters[i]
cid := c.Id
_, err = (&Chapter{}).GetEngine().ID(cid).Delete(&Chapter{})
err = DeleteChapterSections(cid)
}
return
}
func CloneProjectChapters(pid int64, newPid int64) (err error) {
var chapters []Chapter
err = (&Chapter{}).GetEngine().Where("project_id = ?", pid).Find(&chapters)
for i:=0; i< len(chapters); i++ {
c := chapters[i]
cid := c.Id
c.Id = 0
c.ProjectId = newPid
_, err = (&Chapter{}).GetEngine().Insert(&c)
newCid := c.Id
err = CloneChapterSections(newPid, cid, newCid)
}
return
}

View File

@ -165,16 +165,9 @@ func (p *Project) UpdateInfo(subjects []*ProjectSubject, skills []*ProjectSkill)
}
func (p *Project) Delete() (err error) {
pid := p.Id
_, err = p.GetEngine().ID(p.Id).Delete(p)
return
}
func GetOutlineByPid(pid string) (c []Outline, err error) {
err = adapter.Engine.
SQL("select * from chapter left join section s on chapter.id = s.chapter_id where chapter.project_id = 1").
// Where("project_id = ?", pid).
// Asc("chapter_number").
Find(&c)
err = DeleteProjectChapters(pid)
return
}
@ -230,4 +223,21 @@ func ViewProject(pid string) (err error) {
_, err = adapter.Engine.
Exec("update project set read_num = read_num + 1 where id = ?", pid)
return
}
func CloneProject(uid string, pid int64) (err error) {
var project Project
_, err = (&Project{}).GetEngine().ID(pid).Get(&project)
project.TeacherId = uid
project.Id = 0
project.Closed = false
project.Published = false
project.CreateAt = time.Now()
project.ReadNum = 0
project.JoinNum = 0
project.ProjectTitle = project.ProjectTitle + "-副本"
_, err = (&Project{}).GetEngine().Insert(&project)
newPid := project.Id
err = CloneProjectChapters(pid, newPid)
return
}

View File

@ -33,4 +33,18 @@ func GetResourceById(id string) (r Resource, err error) {
ID(id).
Get(&r)
return
}
func DeleteSectionResource(sid int64) (err error) {
_, err = (&Resource{}).GetEngine().Where("section_id = ?", sid).Delete(&Resource{})
return
}
func CloneSectionResource(sid int64, newSid int64) (err error) {
var resource Resource
_, err = (&Resource{}).GetEngine().Where("section_id = ?", sid).Get(&resource)
resource.SectionId = newSid
resource.Id = 0
_, err = (&Resource{}).GetEngine().Insert(&resource)
return
}

View File

@ -12,7 +12,7 @@ type Section struct {
SectionNumber int `json:"sectionNumber" xorm:"index"`
ChapterNumber int `json:"chapterNumber" xorm:"index"`
SectionMinute int `json:"sectionMinute" xorm:"default 10"`
SectionMinute int `json:"sectionMinute" xorm:"default 1"`
}
type SectionMinute struct {
@ -91,4 +91,33 @@ func GetSectionDetailById(sid string) (s SectionDetail, err error) {
Join("INNER", "resource", "resource.section_id = section.id").
Get(&s)
return
}
func DeleteChapterSections(cid int64) (err error) {
var sections []Section
err = (&Section{}).GetEngine().Where("chapter_id = ?", cid).Find(&sections)
for i:=0; i< len(sections); i++ {
s := sections[i]
sid := s.Id
_, err = (&Section{}).GetEngine().ID(sid).Delete(&Section{})
err = DeleteSectionResource(sid)
err = DeleteTasks(sid)
}
return
}
func CloneChapterSections(newPid int64, cid int64, newCid int64) (err error) {
var sections []Section
err = (&Section{}).GetEngine().Where("chapter_id = ?", cid).Find(&sections)
for i:=0; i< len(sections); i++ {
s := sections[i]
sid := s.Id
s.Id = 0
s.ChapterId = newCid
_, err = (&Section{}).GetEngine().Insert(&s)
newSid := s.Id
err = CloneSectionResource(sid, newSid)
err = CloneTasks(newPid, sid, newSid)
}
return
}

View File

@ -73,4 +73,33 @@ func ExchangeQuestion(id1 string, id2 string) (err error) {
Exec("update question t1 join question t2 on (t1.id = ? and t2.id = ?) " +
"set t1.question_order = t2.question_order, t2.question_order = t1.question_order", id1, id2)
return
}
func DeleteSurvey(tid int64) (err error) {
var survey Survey
_, err = (&Survey{}).GetEngine().Where("task_id = ?", tid).Get(&survey)
suid := survey.Id
_, err = (&Survey{}).GetEngine().ID(suid).Delete(&Survey{})
_, err = (&Question{}).GetEngine().Where("survey_id = ?", suid).Delete(&Question{})
return
}
func CloneSurvey(tid int64, newTid int64) (err error) {
var survey Survey
_, err = (&Survey{}).GetEngine().Where("task_id = ?", tid).Get(&survey)
suid := survey.Id
survey.Id = 0
survey.TaskId = newTid
_, err = (&Survey{}).GetEngine().Insert(&survey)
newSuid := survey.Id
var questions []Question
err = (&Question{}).GetEngine().Where("survey_id = ?", suid).Find(&questions)
for i:=0; i< len(questions); i++ {
q := questions[i]
q.Id = 0
q.SurveyId = newSuid
q.QuestionCount = ""
_, err = (&Question{}).GetEngine().Insert(&q)
}
return
}

View File

@ -190,4 +190,36 @@ func GetProjectTasks(pid string) (t []TaskEvaluate, err error) {
Asc("task_order").
Find(&t)
return
}
func DeleteTasks(sid int64) (err error) {
var tasks []Task
err = (&Task{}).GetEngine().Where("section_id = ?", sid).Find(&tasks)
for i:=0; i<len(tasks); i++ {
t := tasks[i]
tid := t.Id
_, err = (&Task{}).GetEngine().ID(tid).Delete(&Task{})
if t.TaskType == "survey" {
err = DeleteSurvey(tid)
}
}
return
}
func CloneTasks(newPid int64, sid int64, newSid int64) (err error) {
var tasks []Task
err = (&Task{}).GetEngine().Where("section_id = ?", sid).Find(&tasks)
for i:=0; i<len(tasks); i++ {
t := tasks[i]
tid := t.Id
t.Id = 0
t.ProjectId = newPid
t.SectionId = newSid
_, err = (&Task{}).GetEngine().Insert(&t)
newTid := t.Id
if t.TaskType == "survey" {
err = CloneSurvey(tid, newTid)
}
}
return
}

View File

@ -18,6 +18,8 @@
"echarts": "^5.1.2",
"echarts-for-react": "^3.0.1",
"enquire-js": "^0.2.1",
"file-saver": "^2.0.5",
"html-docx-js": "^0.3.1",
"localStorage": "^1.0.4",
"lodash": "^4.17.21",
"prop-types": "^15.7.2",

View File

@ -1,14 +1,19 @@
import { Route, BrowserRouter, Switch } from "react-router-dom";
import {Route, BrowserRouter, Switch, Redirect} from "react-router-dom";
import './App.less';
import AuthCallback from "./pages/User/Auth/AuthCallback";
import HeaderLayout from "./pages/component/Layout/HeaderLayout";
import StudentEvidenceContent from "./pages/Project/Evidence/component/StudentEvidenceContent";
function App() {
return (
<div className="App">
<BrowserRouter>
<Route path="/" component={HeaderLayout} />
<Route exact path="/callback" component={AuthCallback} />
<Route exact path="/" render={() => (
<Redirect to="/home"/>
)}/>
<Route path="/home" component={HeaderLayout} />
<Route exact path="/export/project/:projectId/student/:studentId/evidence" component={StudentEvidenceContent} />
<Route exact path="/callback" component={AuthCallback} />
</BrowserRouter>
</div>
);

View File

@ -97,6 +97,12 @@ const ProjectApi = {
url: `/project/${pid}/view`,
method: 'post'
})
},
cloneProject(pid) {
return request({
url: `/project/${pid}/clone`,
method: 'post'
})
}
}

View File

@ -40,7 +40,7 @@ function LatestProject(obj) {
{
projects.map((item, index) => (
<Col key={index.toString()} {...topColResponsiveProps}>
<Link to={`/project/${item.id}/info`}>
<Link to={`/home/project/${item.id}/info`}>
<Card
hoverable
bordered={false}

View File

@ -22,17 +22,17 @@ class Message extends React.Component {
<Layout.Sider breakpoint="lg" collapsedWidth="0">
<Menu theme="light" mode="inline" defaultSelectedKeys={['all-message']}>
<Menu.Item key="all-message">
<Link to="/message/all">
<Link to="/home/message/all">
全部消息
</Link>
</Menu.Item>
<Menu.Item key="unread-message">
<Link to="/message/unread">
<Link to="/home/message/unread">
未读消息
</Link>
</Menu.Item>
<Menu.Item key="read-message">
<Link to="/message/read">
<Link to="/home/message/read">
已读消息
</Link>
</Menu.Item>
@ -41,12 +41,12 @@ class Message extends React.Component {
</Affix>
<Layout.Content style={{backgroundColor: 'white', marginLeft: '10px'}}>
<Switch>
<Route exact path="/message" render={() => (
<Redirect to="/message/all"/>
<Route exact path="/home/message" render={() => (
<Redirect to="/home/message/all"/>
)}/>
<Route exact path="/message/all" component={AllMessage}/>
<Route exact path="/message/unread" component={UnreadMessage}/>
<Route exact path="/message/read" component={ReadMessage}/>
<Route exact path="/home/message/all" component={AllMessage}/>
<Route exact path="/home/message/unread" component={UnreadMessage}/>
<Route exact path="/home/message/read" component={ReadMessage}/>
</Switch>
</Layout.Content>
</Layout>

View File

@ -52,11 +52,11 @@ function InfoEditPage(obj) {
const loadSubjectsAndSkills = () => {
ProjectApi.getSubjectsAndSkills(pid)
.then(res=>{
.then(res => {
if (res.data.code === 200) {
if (res.data.subjects !== null) {
let s = res.data.subjects
for (let i=0; i<iniSubjects.length; i++) {
for (let i = 0; i < iniSubjects.length; i++) {
if (s.indexOf(iniSubjects[i]) < 0) {
s.push(iniSubjects[i])
}
@ -65,7 +65,7 @@ function InfoEditPage(obj) {
}
if (res.data.skills !== null) {
let s = res.data.skills
for (let i=0; i<iniSkills.length; i++) {
for (let i = 0; i < iniSkills.length; i++) {
if (s.indexOf(iniSkills[i]) < 0) {
s.push(iniSkills[i])
}
@ -74,7 +74,9 @@ function InfoEditPage(obj) {
}
}
})
.catch(e=>{console.log(e)})
.catch(e => {
console.log(e)
})
}
const changeTitle = value => {
@ -146,7 +148,7 @@ function InfoEditPage(obj) {
if (res.data.code === 200) {
message.success(res.data.msg)
setTimeout(() => {
window.location.href = `/project/${pid}/outline/edit`
window.location.href = `/home/project/${pid}/outline/edit`
}, 200)
}
})
@ -161,7 +163,7 @@ function InfoEditPage(obj) {
onFinish();
} else {
setNextPageLoading(false)
window.location.href = `/project/${pid}/outline/edit`
window.location.href = `/home/project/${pid}/outline/edit`
}
}
}

View File

@ -18,7 +18,7 @@ class EditInfo extends React.PureComponent {
}
back = e => {
window.location.href = `/project/${this.state.pid}/info`
window.location.href = `/home/project/${this.state.pid}/info`
}
render() {

View File

@ -27,6 +27,8 @@ function OutlineEditPage(obj) {
const [chapterName, setChapterName] = useState('')
const [sectionName, setSectionName] = useState('')
const [btLoading, setBtLoading] = useState(false)
useEffect(() => {
getChapters()
}, [])
@ -105,6 +107,7 @@ function OutlineEditPage(obj) {
return
}
if (opt === 'modify') {
setBtLoading(true)
let c = {
id: chapter.id,
projectId: chapter.projectId,
@ -113,6 +116,7 @@ function OutlineEditPage(obj) {
}
ChapterApi.updateProjectChapter(c)
.then((res) => {
setBtLoading(false)
if (res.data.code === 200) {
setChapterModalVisible(false)
message.success(res.data.msg)
@ -126,6 +130,7 @@ function OutlineEditPage(obj) {
console.log(e)
})
} else if (opt === 'add') {
setBtLoading(true)
let len = chapters.length
let l = 0
if (len > 0) {
@ -134,6 +139,7 @@ function OutlineEditPage(obj) {
let cp = {chapterName: chapterName, chapterNumber: l, projectId: pid}
ChapterApi.createProjectChapter(cp)
.then((res) => {
setBtLoading(false)
setChapterModalVisible(false)
setChapterName('')
if (res.data.code === 200) {
@ -153,6 +159,7 @@ function OutlineEditPage(obj) {
return
}
if (opt === 'modify') {
setBtLoading(true)
let s = {
id: section.id,
chapterId: section.chapterId,
@ -162,9 +169,9 @@ function OutlineEditPage(obj) {
}
SectionApi.updateChapterSection(pid, s)
.then((res) => {
setBtLoading(false)
if (res.data.code === 200) {
setSectionModalVisible(false)
chapters[index].sections[subIndex].sectionName = sectionName
setChapters([...chapters])
setSectionName('')
@ -174,6 +181,7 @@ function OutlineEditPage(obj) {
console.log(e)
})
} else if (opt === 'add') {
setBtLoading(true)
let l = 0
if (chapters[index].sections !== undefined && chapters[index].sections !== null) {
let len = chapters[index].sections.length
@ -189,6 +197,8 @@ function OutlineEditPage(obj) {
}
SectionApi.createChapterSection(pid, sec)
.then((res) => {
setBtLoading(false)
setSectionModalVisible(false)
setSectionName('')
if (res.data.code === 200) {
@ -291,7 +301,7 @@ function OutlineEditPage(obj) {
<Menu.Item key={index.toString() + subIndex.toString()}>
{util.FormatSectionName(subItem.sectionName, subItem.chapterNumber, subItem.sectionNumber)}
<span style={{float: 'right', marginRight: '20px'}}>
<Link to={`/project/${pid}/section/${subItem.id}/edit`}>
<Link to={`/home/project/${pid}/section/${subItem.id}/edit`}>
<Button type="text">编辑资源</Button>
</Link>
@ -321,15 +331,16 @@ function OutlineEditPage(obj) {
<Button type="round" style={{margin: '5px'}} onClick={addChapter}>添加章节</Button>
</div>
<div style={{textAlign: 'right', marginTop: '20px', marginRight: '20px'}}>
<Link to={`/project/${pid}/info`}>
<Link to={`/home/project/${pid}/info`}>
<Button type="primary" size="middle" style={{float: 'right'}}>完成</Button>
</Link>
<Link to={`/project/${pid}/info/edit`}>
<Link to={`/home/project/${pid}/info/edit`}>
<Button size="middle" style={{float: 'right', marginRight: '20px'}}>上一页</Button>
</Link>
</div>
<Modal title="" visible={chapterModalVisible} onOk={doChapter} onCancel={cancelDoChapter}>
<Modal title="" visible={chapterModalVisible} onOk={doChapter} confirmLoading={btLoading}
onCancel={cancelDoChapter}>
<br/>
<Row>
<Col span={3}>
@ -341,7 +352,7 @@ function OutlineEditPage(obj) {
</Row>
</Modal>
<Modal visible={sectionModalVisible} onOk={doSection} onCancel={cancelDoSection}>
<Modal visible={sectionModalVisible} onOk={doSection} confirmLoading={btLoading} onCancel={cancelDoSection}>
<br/>
<Row>
<Col span={3}>

View File

@ -19,7 +19,7 @@ class EditOutline extends React.PureComponent {
}
back = e => {
this.props.history.push(`/project/${this.state.pid}/info/`)
this.props.history.push(`/home/project/${this.state.pid}/info/`)
}
render() {

View File

@ -27,7 +27,7 @@ function SectionEditPage(obj) {
const back = e => {
window.location.href = `/project/${pid}/outline/edit`
window.location.href = `/home/project/${pid}/outline/edit`
}
@ -49,7 +49,7 @@ function SectionEditPage(obj) {
<StudentTask section={section} pid={pid}/>
</div>
<div style={{textAlign: 'center'}}>
<Link to={`/project/${pid}/section/${sid}/preview?back=/project/${pid}/section/${sid}/edit`}>
<Link to={`/home/project/${pid}/section/${sid}/preview?back=/project/${pid}/section/${sid}/edit`}>
<Button style={{marginBottom: '20px'}}>预览</Button>
</Link>
</div>

View File

@ -102,7 +102,7 @@ function StudentTask(obj) {
}
const gotoSurvey = (item, index) => {
saveContent(item, index)
window.location.href = `/project/${pid}/section/${obj.section.id}/task/${item.id}/survey/edit`
window.location.href = `/home/project/${pid}/section/${obj.section.id}/task/${item.id}/survey/edit`
}
return (

View File

@ -146,7 +146,7 @@ function SurveyEditPage(obj) {
setEditing([...editing])
}
const back = e => {
window.location.href = `/project/${pid}/section/${sid}/edit`
window.location.href = `/home/project/${pid}/section/${sid}/edit`
}
const getType = t => {

View File

@ -121,6 +121,7 @@ function StudentEvidence(obj) {
<List
size="large"
dataSource={item.sections}
locale={{emptyText: '--'}}
renderItem={
item => (
<List.Item>

View File

@ -0,0 +1,261 @@
import React, {useEffect, useState} from "react";
import {Button, Checkbox, Divider, List, Radio} from "antd";
import TaskApi from "../../../../api/TaskApi";
import ChapterApi from "../../../../api/ChapterApi";
import util from "../../../component/Util"
import htmlDocx from 'html-docx-js/dist/html-docx';
import saveAs from 'file-saver';
import Question from "../../CreateProject/Survey/component/Question";
const blank = Question.blank
function StudentEvidenceContent(obj) {
const pid = obj.match.params.projectId
const studentId = obj.match.params.studentId
const [tasks, setTasks] = useState([])
const [learning, setLearning] = useState(false)
const [editable, setEditable] = useState(false)
const [teacherScore, setTeacherScore] = useState(false)
const [showCount, setShowCount] = useState(false)
const [chapters, setChapters] = useState([])
const [showMinute, setShowMinute] = useState(false)
useEffect(() => {
getChapters()
getTasks()
setTimeout(() => {
saveFile()
}, 500)
}, []);
const getChapters = () => {
ChapterApi.getProjectChapters(pid, studentId)
.then((res) => {
if (res.data.chapters === null) {
setChapters([])
} else {
setChapters(res.data.chapters)
setShowMinute(res.data.showMinute)
}
})
.catch(e => {
console.log(e)
})
}
const getTasks = () => {
TaskApi.getProjectTasksDetail(pid, studentId)
.then(res => {
if (res.data.code === 200) {
if (res.data.tasks === null) {
setTasks([])
} else {
let t = res.data.tasks
for (let i = 0; i < t.length; i++) {
if (t[i].questions !== undefined && t[i].questions != null) {
for (let j = 0; j < t[i].questions.length; j++) {
t[i].questions[j].questionOptions = t[i].questions[j].questionOptions.split(",")
}
} else {
t[i].questions = []
}
if (t[i].choices !== undefined && t[i].choices != null) {
for (let j = 0; j < t[i].choices.length; j++) {
t[i].choices[j].choiceOptions = t[i].choices[j].choiceOptions.split(",")
}
} else {
t[i].choices = []
for (let j = 0; j < t[i].questions.length; j++) {
t[i].choices.push({
choiceOptions: [],
choiceOrder: j
})
}
}
}
setTasks(t)
setLearning(res.data.learning)
setEditable(res.data.editable)
setTeacherScore(res.data.teacherScore)
setShowCount(res.data.showCount)
}
}
})
.catch(e => {
console.log(e)
})
}
const getScore = (score, weight) => {
return (score * weight / 100).toFixed(2)
}
const getPercent = (item) => {
let p = ((item.learnMinute + item.learnSecond / 60) / item.sectionMinute * 100).toFixed(1)
if (p > 100) {
return 100
}
return p
}
const saveFile = () => {
let content = document.getElementById("evidence").innerHTML
let page = `<!DOCTYPE html><html><head><meta charset="UTF-8"></head><body>${content}</body></html>`
const converted = htmlDocx.asBlob(page)
saveAs(converted, `${studentId}.docx`)
}
return (
<div style={{padding: '40px'}}>
<div style={{float: 'right'}}><Button onClick={saveFile}>保存</Button></div>
<div id="evidence">
<h2>章节学习时长</h2>
{chapters.map((item, index) => (
<div key={index.toString()} style={{textAlign: 'left'}}>
<p style={{fontWeight: 'bold', fontSize: '16px'}}>
{util.FormatChapterName(item.chapterName, item.chapterNumber)}
</p>
{(item.sections === null || item.sections === undefined) ?
null
:
<>
<List
size="large"
dataSource={item.sections}
locale={{emptyText: '--'}}
renderItem={
item => (
<List.Item>
<p>{util.FormatSectionName(item.sectionName, item.chapterNumber, item.sectionNumber)}</p>
要求学习时长{item.sectionMinute}分钟,&nbsp;
学生学习时长{item.learnMinute}{item.learnSecond},&nbsp;
学习进度{getPercent(item)}%
</List.Item>
)
}
/><br/>
</>
}
</div>
))}
<h2>学生任务</h2>
<List
size="large"
dataSource={tasks}
locale={{emptyText: '--'}}
renderItem={
item => (
<List.Item>
<div style={{textAlign: 'left'}}>
<div>
<p
style={{fontWeight: 'bold'}}>{util.FormatSectionName(item.taskTitle, item.chapterNumber, item.sectionNumber)}</p>
{item.submitted ?
<span style={{color: 'green'}}>已提交</span>
:
<span>未提交</span>
},&nbsp;
{item.submit.scored ?
<span style={{color: 'green'}}>已打分</span>
:
<span>未打分</span>
}&nbsp;&nbsp;
任务权重{item.taskWeight}%,&nbsp;
学生得分{getScore(item.submit.score, item.taskWeight)}/{item.taskWeight}
</div>
<div>
标题
<p>{item.taskTitle}</p>
描述
<p>{item.taskIntroduce}</p>
{item.taskType === 'file' ?
<div>
<p>{item.submit.submitContent}</p>
</div>
: null
}
{item.taskType === 'comment' ?
<div>
学生提交<br/>{item.submit.submitContent}
</div>
: null
}
{item.taskType === 'survey' ?
<>学生提交<br/>
<h2 style={{textAlign: 'center'}}>{item.survey.surveyTitle}</h2>
<div style={{marginLeft: '20px', marginRight: '20px'}}>
{item.questions.map((subItem, subIndex) => (
<div key={subIndex.toString()}>
{subItem.questionType === 'singleChoice' || subItem.questionType === 'scale5' || subItem.questionType === 'scale7' ?
<div style={{textAlign: "left", marginTop: '10px'}}>
<p>{subItem.questionTitle}</p>
<Radio.Group value={item.choices[subIndex].choiceOptions[0]}>
{subItem.questionOptions.map((optItem, optIndex) => (
<div style={{marginTop: '10px'}} key={optIndex.toString()}>
<Radio value={optIndex.toString()}>
{optItem}
</Radio>
</div>
))}
</Radio.Group>
</div>
: null}
{subItem.questionType === 'multipleChoice' ?
<div style={{textAlign: "left", marginTop: '10px'}}>
<p>{subItem.questionTitle}</p>
<Checkbox.Group value={item.choices[subIndex].choiceOptions}>
{subItem.questionOptions.map((optItem, optIndex) => (
<div key={optIndex.toString()} style={{marginTop: '10px'}}>
<Checkbox value={optIndex.toString()}>
{optItem}
</Checkbox>
</div>
))}
</Checkbox.Group>
</div>
: null}
{subItem.questionType === 'blankFill' ?
<div style={{textAlign: "left", marginTop: '10px'}}>
<p>{subItem.questionTitle}</p>
{subItem.questionOptions.map((optItem, optIndex) => (
<div style={{marginTop: '10px'}} key={optIndex.toString()}>
{optItem === blank ?
<span style={{float: 'left', margin: '5px'}}>
<u>&nbsp;{item.choices[subIndex].choiceOptions[optIndex]}&nbsp;</u>
</span>
:
<span style={{float: 'left', margin: '5px'}}>{optItem}</span>
}
</div>
))}
</div>
: null}
{subItem.questionType === 'briefAnswer' ?
<div style={{textAlign: "left", marginTop: '10px'}}>
<p>{subItem.questionOptions[0]}</p>
{item.choices[subIndex].choiceOptions[0]}
</div>
: null}
<Divider/>
</div>
))}
</div>
</>
: null
}
</div>
</div>
</List.Item>
)
}
/>
</div>
</div>
)
}
export default StudentEvidenceContent

View File

@ -1,8 +1,9 @@
import React, {useEffect} from "react";
import {Card, PageHeader} from "antd";
import {Button, Card, PageHeader} from "antd";
import DocumentTitle from 'react-document-title';
import StudentEvidence from "./component/StudentEvidence";
function Evidence(obj) {
const pid = obj.match.params.pid
const sid = obj.match.params.sid
@ -11,9 +12,12 @@ function Evidence(obj) {
}, [])
const back = e => {
window.location.href = `/project/${pid}/info?menu=student-admin`
window.location.href = `/home/project/${pid}/info?menu=student-admin`
}
const exportFile = e => {
window.open(`/export/project/${pid}/student/${sid}/evidence`)
}
return (
<DocumentTitle title="Project">
<div style={{backgroundColor: '#f2f4f5', minHeight: '100vh'}}>
@ -41,12 +45,17 @@ function Evidence(obj) {
maxWidth: '1200px',
}}
>
<Card>
<StudentEvidence
pid={pid}
studentId={sid}
/>
</Card>
<div style={{padding: '10px', textAlign: 'center'}}>
<Button onClick={exportFile}>导出</Button>
</div>
<div>
<Card>
<StudentEvidence
pid={pid}
studentId={sid}
/>
</Card>
</div>
</div>
</div>
</div>

View File

@ -1,7 +1,14 @@
import React from 'react';
import DocumentTitle from 'react-document-title';
import {Link, Redirect, Route, Switch} from 'react-router-dom'
import {CheckCircleOutlined, CheckOutlined, CopyOutlined, HighlightOutlined, SyncOutlined, StarFilled} from "@ant-design/icons";
import {
CheckCircleOutlined,
CheckOutlined,
CopyOutlined,
HighlightOutlined,
StarFilled,
SyncOutlined
} from "@ant-design/icons";
import PublishedProject from "../PublishedProject";
import EditingProject from "../EditingProject";
@ -21,7 +28,7 @@ class MyProject extends React.PureComponent {
}
changeMenu = (e) => {
if (e !== undefined ) {
if (e !== undefined) {
this.setState({menu: e.key})
return
}
@ -43,7 +50,7 @@ class MyProject extends React.PureComponent {
ProjectApi.createProject()
.then((res) => {
if (res.data.code === 200) {
window.open(`/project/${res.data.data}/info/edit`)
window.open(`/home/project/${res.data.data}/info/edit`)
}
})
.catch((e) => {
@ -68,26 +75,26 @@ class MyProject extends React.PureComponent {
defaultSelectedKeys={['published']}
className="menu-bar"
selectedKeys={[menu]}
onClick={e=>this.changeMenu(e)}
onClick={e => this.changeMenu(e)}
mode="inline"
>
<Menu.Item key="published" icon={<CheckCircleOutlined/>}>
<Link to="/my-project/published">
<Link to="/home/my-project/published">
已发布
</Link>
</Menu.Item>
<Menu.Item key="editing" icon={<HighlightOutlined/>}>
<Link to="/my-project/editing">
<Link to="/home/my-project/editing">
未发布
</Link>
</Menu.Item>
<Menu.Item key="finished" icon={<CopyOutlined/>}>
<Link to="/my-project/finished">
<Link to="/home/my-project/finished">
已结束
</Link>
</Menu.Item>
<Menu.Item key="favourite" icon={<StarFilled />}>
<Link to="/my-project/favourite">
<Menu.Item key="favourite" icon={<StarFilled/>}>
<Link to="/home/my-project/favourite">
收藏夹
</Link>
</Menu.Item>
@ -97,21 +104,21 @@ class MyProject extends React.PureComponent {
defaultSelectedKeys={['learning']}
className="menu-bar"
selectedKeys={[menu]}
onClick={e=>this.changeMenu(e)}
onClick={e => this.changeMenu(e)}
mode="inline"
>
<Menu.Item key="learning" icon={<SyncOutlined/>}>
<Link to="/my-project/learning">
<Link to="/home/my-project/learning">
进行中
</Link>
</Menu.Item>
<Menu.Item key="finished" icon={<CheckOutlined/>}>
<Link to="/my-project/finished">
<Link to="/home/my-project/finished">
已完成
</Link>
</Menu.Item>
<Menu.Item key="favourite" icon={<StarFilled />}>
<Link to="/my-project/favourite">
<Menu.Item key="favourite" icon={<StarFilled/>}>
<Link to="/home/my-project/favourite">
收藏夹
</Link>
</Menu.Item>
@ -137,22 +144,22 @@ class MyProject extends React.PureComponent {
<Layout.Content style={{marginLeft: '10px'}}>
{this.props.account.tag === '老师' ?
<Switch>
<Route exact path="/my-project" render={() => (
<Redirect to="/my-project/published"/>
<Route exact path="/home/my-project" render={() => (
<Redirect to="/home/my-project/published"/>
)}/>
<Route exact path="/my-project/published" component={PublishedProject}/>
<Route exact path="/my-project/editing" component={EditingProject}/>
<Route exact path="/my-project/finished" component={FinishedProject}/>
<Route exact path="/my-project/favourite" component={FavouriteProject}/>
<Route exact path="/home/my-project/published" component={PublishedProject}/>
<Route exact path="/home/my-project/editing" component={EditingProject}/>
<Route exact path="/home/my-project/finished" component={FinishedProject}/>
<Route exact path="/home/my-project/favourite" component={FavouriteProject}/>
</Switch>
:
<Switch>
<Route exact path="/my-project" render={() => (
<Redirect to="/my-project/learning"/>
<Route exact path="/home/my-project" render={() => (
<Redirect to="/home/my-project/learning"/>
)}/>
<Route exact path="/my-project/learning" component={LearningProject}/>
<Route exact path="/my-project/finished" component={FinishedProject}/>
<Route exact path="/my-project/favourite" component={FavouriteProject}/>
<Route exact path="/home/my-project/learning" component={LearningProject}/>
<Route exact path="/home/my-project/finished" component={FinishedProject}/>
<Route exact path="/home/my-project/favourite" component={FavouriteProject}/>
</Switch>
}
</Layout.Content>

View File

@ -1,4 +1,4 @@
import React, {useEffect, useState} from "react";
import React from "react";
import {BackTop, Card, PageHeader} from "antd";
import DocumentTitle from 'react-document-title';
@ -10,64 +10,64 @@ import util from "../../component/Util"
import StudentApi from "../../../api/StudentApi";
import TaskCard from "./component/TaskCard";
function PreviewSection(obj) {
let url = new URLSearchParams(obj.location.search)
const backUrl = url.get('back')
const sid = obj.match.params.sid
const pid = obj.match.params.pid
const [section, setSection] = useState({resource: {}})
const [tasks, setTasks] = useState([])
const [learning, setLearning] = useState(false)
const [editable, setEditable] = useState(false)
const [showCount, setShowCount] = 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(() => {
if (learning) {
window.onbeforeunload = leave
class PreviewSection extends React.Component {
constructor(props) {
super(props);
let url = new URLSearchParams(this.props.location.search)
const backUrl = url.get('back')
this.state = {
backUrl: backUrl,
sid: this.props.match.params.sid,
pid: this.props.match.params.pid,
section: {resource: {}},
tasks: [],
learning: false,
editable: false,
showCount: false,
timer: null,
minute: 0,
second: 0,
}
}, [learning])
const leave = () => {
if (timer != null) {
clearTimeout(timer)
}
let data = {
learnMinute: m,
learnSecond: s
}
StudentApi.updateLearnSection(pid, sid, data)
.then(res => {
})
.catch(e => {
console.log(e)
})
window.removeEventListener("onbeforeunload", leave)
}
const getSectionDetail = () => {
SectionApi.getSectionDetail(sid, pid)
componentDidMount() {
this.getSectionDetail()
this.getTasks()
}
componentWillUnmount() {
if (this.state.learning) {
if (this.state.timer != null) {
clearTimeout(this.state.timer)
}
let data = {
learnMinute: this.state.minute,
learnSecond: this.state.second
}
StudentApi.updateLearnSection(this.state.pid, this.state.sid, data)
.then(res => {
})
.catch(e => {
console.log(e)
})
}
}
getSectionDetail = () => {
SectionApi.getSectionDetail(this.state.sid, this.state.pid)
.then(res => {
setSection(res.data.section)
this.setState({
section: res.data.section
})
})
.catch(e => {
console.log(e)
})
}
const getTasks = () => {
TaskApi.getSectionTasks(sid, pid)
getTasks = () => {
TaskApi.getSectionTasks(this.state.sid, this.state.pid)
.then(res => {
if (res.data.tasks === null) {
setTasks([])
} else {
let t = res.data.tasks
for (let i = 0; i < t.length; i++) {
@ -93,118 +93,133 @@ function PreviewSection(obj) {
}
}
}
setTasks(t)
this.setState({
tasks: t
})
}
setLearning(res.data.learning)
setEditable(res.data.editable)
setShowCount(res.data.showCount)
this.setState({
learning: res.data.learning,
editable: res.data.editable,
showCount: res.data.showCount
})
if (res.data.learning) {
getTimer()
if (res.data.learning && this.state.timer === null) {
this.setTimer()
}
})
.catch(e => {
console.log(e)
})
}
const getTimer = () => {
StudentApi.getLearnSection(pid, sid)
setTimer = () => {
StudentApi.getLearnSection(this.state.pid, this.state.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
let m = res.data.data.learnMinute
let s = res.data.data.learnSecond
this.setState({
minute: m,
second: s
})
if (this.state.timer !== null) {
clearTimeout(this.state.timer)
}
this.state.timer = setTimeout(this.count, 1000)
}
})
.catch(e => {
console.log(e)
})
setTimeout(count, 1000)
}
const count = () => {
if (s === 30) {
s = 0
m++
setMinute(m)
count = () => {
if (this.state.second >= 60) {
this.setState({
second: 0,
minute: ++this.state.minute
})
} else {
s++
this.setState({
second: ++this.state.second
})
}
setSecond(s)
setTimeout(count, 1000)
this.state.timer = setTimeout(this.count, 1000)
}
const back = e => {
if (backUrl === undefined || backUrl === null) {
window.location.href = `/project/${pid}/section/${sid}/edit`
back = e => {
if (this.state.backUrl === undefined || this.state.backUrl === null) {
this.props.history.push(`/project/${this.state.pid}/section/${this.state.sid}/edit`)
} else {
window.location.href = backUrl
this.props.history.push(this.state.backUrl)
}
}
const setTaskItem = (item, index) => {
tasks[index] = item
setTasks([...tasks])
setTaskItem = (item, index) => {
this.state.tasks[index] = item
this.setState({
tasks: [...this.state.tasks]
})
}
return (
<DocumentTitle title="Project">
<div style={{backgroundColor: '#f2f4f5', minHeight: '100vh'}}>
<BackTop/>
<PageHeader
className="site-page-header"
onBack={back}
title="返回"
subTitle="我的项目"
/>
<div style={{padding: '20px', margin: 'auto'}}>
<Card>
<h2 style={{fontWeight: 'bold'}}>
{util.FormatSectionName(section.sectionName, section.chapterNumber, section.sectionNumber)}
</h2>
{learning ?
<span style={{float: 'right'}}>{minute}&nbsp;:&nbsp;{second}</span>
: null}
</Card>
<Card className="resource-card">
<div dangerouslySetInnerHTML={{__html: section.resource.content}}/>
</Card>
<Card className="resource-card">
<p className="card-title">文件资源</p>
<p>{section.resource.fileTitle}</p>
<p>{section.resource.fileIntroduce}</p>
</Card>
{tasks.map((item, index) => (
<Card className="resource-card" key={index.toString()}>
<p className="card-title">学生任务
{item.submitted ?
<span className="submit-status" style={{color: 'green'}}>
权重占比&nbsp;{item.taskWeight}&nbsp;%&nbsp;&nbsp;已提交&nbsp;&nbsp;{util.FilterTime(item.submit.createAt)}
</span>
:
<span className="submit-status" style={{color: 'gray'}}>
权重占比&nbsp;{item.taskWeight}&nbsp;%&nbsp;&nbsp;未提交
</span>
}
</p>
<TaskCard
pid={pid}
item={item}
index={index}
learning={learning}
editable={editable}
showCount={showCount}
setTaskItem={setTaskItem}
getTasks={getTasks}
/>
render() {
const {section, tasks, learning, pid, editable, showCount, minute, second} = this.state
return (
<DocumentTitle title="Project">
<div style={{backgroundColor: '#f2f4f5', minHeight: '100vh'}}>
<BackTop/>
<PageHeader
className="site-page-header"
onBack={this.back}
title="返回"
subTitle="我的项目"
/>
<div style={{padding: '20px', margin: 'auto'}}>
<Card>
<h2 style={{fontWeight: 'bold'}}>
{util.FormatSectionName(section.sectionName, section.chapterNumber, section.sectionNumber)}
</h2>
{learning ?
<span style={{float: 'right'}}>{minute}&nbsp;:&nbsp;{second}</span>
: null}
</Card>
))
}
<Card className="resource-card">
<div dangerouslySetInnerHTML={{__html: section.resource.content}}/>
</Card>
<Card className="resource-card">
<p className="card-title">文件资源</p>
<p>{section.resource.fileTitle}</p>
<p>{section.resource.fileIntroduce}</p>
</Card>
{tasks.map((item, index) => (
<Card className="resource-card" key={index.toString()}>
<p className="card-title">学生任务
{item.submitted ?
<span className="submit-status" style={{color: 'green'}}>
权重占比&nbsp;{item.taskWeight}&nbsp;%&nbsp;&nbsp;已提交&nbsp;&nbsp;{util.FilterTime(item.submit.createAt)}
</span>
:
<span className="submit-status" style={{color: 'gray'}}>
权重占比&nbsp;{item.taskWeight}&nbsp;%&nbsp;&nbsp;未提交
</span>
}
</p>
<TaskCard
pid={pid}
item={item}
index={index}
learning={learning}
editable={editable}
showCount={showCount}
setTaskItem={this.setTaskItem}
getTasks={this.getTasks}
/>
</Card>
))
}
</div>
<br/>
</div>
<br/>
</div>
</DocumentTitle>
)
</DocumentTitle>
)
}
}
export default PreviewSection

View File

@ -1,10 +1,11 @@
import React, {useEffect, useState} from 'react';
import {Avatar, Button, Comment, Form, Input, Pagination, message, Tooltip, Popconfirm, Switch} from 'antd';
import {Avatar, Button, Comment, Form, Input, message, Pagination, Popconfirm, Switch, Tooltip} from 'antd';
import {DeleteOutlined} from "@ant-design/icons"
import QueueAnim from 'rc-queue-anim';
import CommentApi from "../../../../api/CommentApi"
import util from "../../../component/Util"
const {TextArea} = Input;
@ -21,13 +22,13 @@ function ProjectComment(obj) {
const updateCommentList = (p, size, isTeacher) => {
let query = {
from: (p-1)*size,
from: (p - 1) * size,
size: size,
text: '',
isTeacher: isTeacher,
}
CommentApi.getProjectComments(obj.project.id, query)
.then(res=>{
.then(res => {
if (res.data.code === 200) {
if (res.data.comments !== null) {
setCommentList(res.data.comments)
@ -35,7 +36,9 @@ function ProjectComment(obj) {
}
}
})
.catch(e=>{console.log(e)})
.catch(e => {
console.log(e)
})
setPage(p);
};
@ -50,11 +53,11 @@ function ProjectComment(obj) {
}
setSubmitting(true);
let c = {
content: value,
content: value,
projectId: obj.project.id,
}
CommentApi.createComment(obj.project.id, c)
.then(res=>{
.then(res => {
setSubmitting(false);
if (res.data.code === 200) {
message.success(res.data.msg)
@ -64,11 +67,13 @@ function ProjectComment(obj) {
message.error(res.data.msg)
}
})
.catch(e=>{console.log(e)})
.catch(e => {
console.log(e)
})
};
const deleteComment = (item) => {
CommentApi.deleteComment(obj.project.id, item.id)
.then(res=>{
.then(res => {
if (res.data.code === 200) {
message.success(res.data.msg)
updateCommentList(page, pageSize, isTeacher)
@ -76,7 +81,9 @@ function ProjectComment(obj) {
message.error(res.data.msg)
}
})
.catch(e=>{console.log(e)})
.catch(e => {
console.log(e)
})
}
const onChange = (v) => {
@ -120,8 +127,8 @@ function ProjectComment(obj) {
</>,
<>
{obj.account.name === item.userId ?
<Popconfirm title="确定删除留言?" onConfirm={e=>deleteComment(item)}>
<Button type="text" shape="circle" icon={<DeleteOutlined />} />
<Popconfirm title="确定删除留言?" onConfirm={e => deleteComment(item)}>
<Button type="text" shape="circle" icon={<DeleteOutlined/>}/>
</Popconfirm>
: null
}
@ -144,9 +151,8 @@ function ProjectComment(obj) {
/>
))
}<br/>
<div>
<div style={{textAlign: 'right', marginRight: '40px'}}>
<Pagination
hideOnSinglePage
total={total}
showTotal={t => `${total}项内容`}
current={page}

View File

@ -35,7 +35,7 @@ function ProjectOutline(obj) {
message.warn("请先加入学习")
return
}
window.open(`/project/${pid}/section/${subItem.id}/preview?back=/project/${pid}/info?menu=project-outline`)
window.open(`/home/project/${pid}/section/${subItem.id}/preview?back=/project/${pid}/info?menu=project-outline`)
}
return (

View File

@ -82,7 +82,7 @@ function StudentAdmin(obj) {
title: '',
dataIndex: 'showEvidence',
render: (text, item, index) => (
<Link to={`/project/${pid}/student/${item.studentId}/evidence`}>
<Link to={`/home/project/${pid}/student/${item.studentId}/evidence`}>
<Button type="text">查看学习证据</Button>
</Link>
)

View File

@ -13,7 +13,8 @@ import {
PageHeader,
Popconfirm,
Row,
Tag, Tooltip
Tag,
Tooltip
} from 'antd';
import QueueAnim from 'rc-queue-anim';
import {Link} from 'react-router-dom';
@ -49,7 +50,9 @@ class ProjectInfo extends React.PureComponent {
favBtLoading: false,
learnBtLoading: false,
exitBtLoading: false,
closeBtLoading: false
closeBtLoading: false,
cloneBtLoading: false,
deleteBtLoading: false
};
}
@ -108,9 +111,9 @@ class ProjectInfo extends React.PureComponent {
}
back = e => {
if (this.props.account.tag === '老师') {
window.location.href = '/my-project/published'
window.location.href = '/home/my-project/published'
} else {
window.location.href = '/my-project/learning'
window.location.href = '/home/my-project/learning'
}
}
learnProject = e => {
@ -183,9 +186,9 @@ class ProjectInfo extends React.PureComponent {
})
if (res.data.code === 200) {
if (this.props.account.tag === '老师') {
window.location.href = '/my-project/published'
window.location.href = '/home/my-project/published'
} else {
window.location.href = '/my-project/learning'
window.location.href = '/home/my-project/learning'
}
} else {
message.error(res.data.msg)
@ -196,13 +199,15 @@ class ProjectInfo extends React.PureComponent {
})
}
deleteProject = e => {
this.setState({deleteBtLoading: true})
ProjectApi.deleteProject(this.state.pid)
.then(res => {
this.setState({deleteBtLoading: false})
if (res.data.code === 200) {
if (this.props.account.tag === '老师') {
window.location.href = '/my-project/published'
window.location.href = '/home/my-project/published'
} else {
window.location.href = '/my-project/learning'
window.location.href = '/home/my-project/learning'
}
} else {
message.error(res.data.msg)
@ -217,7 +222,7 @@ class ProjectInfo extends React.PureComponent {
favBtLoading: true
})
ProjectApi.addFavourite(this.state.pid)
.then(res=>{
.then(res => {
this.setState({
favBtLoading: false
})
@ -228,14 +233,16 @@ class ProjectInfo extends React.PureComponent {
message.error(res.data.msg)
}
})
.catch(e=>{console.log(e)})
.catch(e => {
console.log(e)
})
}
removeFavourite = e => {
this.setState({
favBtLoading: true
})
ProjectApi.removeFavourite(this.state.pid)
.then(res=>{
.then(res => {
this.setState({
favBtLoading: false
})
@ -246,7 +253,9 @@ class ProjectInfo extends React.PureComponent {
message.error(res.data.msg)
}
})
.catch(e=>{console.log(e)})
.catch(e => {
console.log(e)
})
}
setProject = project => {
@ -265,30 +274,79 @@ class ProjectInfo extends React.PureComponent {
return "( 编辑中 )"
}
}
cloneProject = () => {
this.setState({
cloneBtLoading: true
})
ProjectApi.cloneProject(this.state.pid)
.then(res => {
this.setState({
cloneBtLoading: false
})
if (res.data.code === 200) {
message.success(res.data.msg)
} else {
message.error(res.data.msg)
}
})
.catch(e => {
console.log(e)
})
}
render() {
const {project, teacher, menu, pid, lastLearn, favBtLoading, learnBtLoading, exitBtLoading, closeBtLoading} = this.state;
const {
project,
teacher,
menu,
pid,
lastLearn,
favBtLoading,
learnBtLoading,
exitBtLoading,
closeBtLoading,
cloneBtLoading,
deleteBtLoading
} = this.state;
const teacherBt = (
<div style={{float: 'right'}}>
{project.published && !project.closed ?
<Popconfirm
title="确定结束项目?"
placement="topRight"
onConfirm={this.closeProject}
okText="确定"
cancelText="取消"
>
<Button
shape="round"
size="middle"
danger
style={{margin: '5px'}}
loading={closeBtLoading}
<>
<Popconfirm
title="确定结束项目?"
placement="topRight"
onConfirm={this.closeProject}
okText="确定"
cancelText="取消"
>
结束项目
</Button>
</Popconfirm>
<Button
shape="round"
size="middle"
danger
style={{margin: '5px'}}
loading={closeBtLoading}
>
结束项目
</Button>
</Popconfirm>
<Popconfirm
title="确定复制项目?"
placement="topRight"
onConfirm={this.cloneProject}
okText="确定"
cancelText="取消"
>
<Button
shape="round"
size="middle"
style={{margin: '5px'}}
loading={cloneBtLoading}
>
复制项目
</Button>
</Popconfirm>
</>
: null}
{!project.published ?
<div>
@ -307,7 +365,7 @@ class ProjectInfo extends React.PureComponent {
发布项目
</Button>
</Popconfirm>
<Link to={`/project/${pid}/info/edit`} target="_blank">
<Link to={`/home/project/${pid}/info/edit`} target="_blank">
<Button
shape="round"
size="middle"
@ -316,6 +374,22 @@ class ProjectInfo extends React.PureComponent {
编辑项目
</Button>
</Link>
<Popconfirm
title="确定复制项目?"
placement="topRight"
onConfirm={this.cloneProject}
okText="确定"
cancelText="取消"
>
<Button
shape="round"
size="middle"
style={{margin: '5px'}}
loading={cloneBtLoading}
>
复制项目
</Button>
</Popconfirm>
<Popconfirm
title="确定删除项目?删除后不能恢复"
onConfirm={this.deleteProject}
@ -325,6 +399,7 @@ class ProjectInfo extends React.PureComponent {
shape="circle"
size="middle"
type="danger"
loading={deleteBtLoading}
style={{marginTop: '5px', marginLeft: '20px', marginBottom: '5px', marginRight: '5px'}}
icon={<DeleteOutlined/>}
/>
@ -332,6 +407,22 @@ class ProjectInfo extends React.PureComponent {
</div> : null}
{project.closed ?
<div>
<Popconfirm
title="确定复制项目?"
placement="topRight"
onConfirm={this.cloneProject}
okText="确定"
cancelText="取消"
>
<Button
shape="round"
size="middle"
style={{margin: '5px'}}
loading={cloneBtLoading}
>
复制项目
</Button>
</Popconfirm>
<Popconfirm
title="确定删除项目?删除后不能恢复"
onConfirm={this.deleteProject}
@ -353,7 +444,7 @@ class ProjectInfo extends React.PureComponent {
<div style={{float: 'right'}}>
{project.learning ?
<>
<Link to={`/project/${project.id}/section/${lastLearn.id}/preview?back=/project/${project.id}/info`}>
<Link to={`/home/project/${project.id}/section/${lastLearn.id}/preview?back=/project/${project.id}/info`}>
{lastLearn.last ?
<span>
@ -447,11 +538,13 @@ class ProjectInfo extends React.PureComponent {
<span style={{float: 'right'}}>
{project.favourite ?
<Tooltip title="点击取消收藏">
<Button shape="circle" type="text" loading={favBtLoading} icon={<StarFilled/>} onClick={this.removeFavourite}/>
<Button shape="circle" type="text" loading={favBtLoading} icon={<StarFilled/>}
onClick={this.removeFavourite}/>
</Tooltip>
:
<Tooltip title="点击收藏">
<Button shape="circle" type="text" loading={favBtLoading} icon={<StarOutlined/>} onClick={this.addFavourite}/>
<Button shape="circle" type="text" loading={favBtLoading} icon={<StarOutlined/>}
onClick={this.addFavourite}/>
</Tooltip>
}
</span>

View File

@ -1,5 +1,5 @@
import React, {useEffect, useState} from 'react';
import {Card, Col, Divider, Empty, Image, Input, Pagination, Row, Select, Spin, Tag} from 'antd';
import {Card, Col, Divider, Image, Input, Pagination, Row, Select, Spin, Tag} from 'antd';
import {EyeOutlined, TeamOutlined} from '@ant-design/icons';
import './project-list.less';
@ -47,7 +47,7 @@ function ProjectList(obj) {
const updateProjectList = (p, size, subject, skill, text) => {
setLoadProjects(true)
let q = {
from: (p - 1)*size,
from: (p - 1) * size,
size: size,
orderBy: 'create_at',
orderType: 'desc',
@ -78,11 +78,11 @@ function ProjectList(obj) {
const loadSubjectsAndSkills = () => {
ProjectApi.getSubjectsAndSkills(obj.pid)
.then(res=>{
.then(res => {
if (res.data.code === 200) {
if (res.data.subjects !== null) {
let s = res.data.subjects
for (let i=0; i<iniSubjects.length; i++) {
for (let i = 0; i < iniSubjects.length; i++) {
if (s.indexOf(iniSubjects[i]) < 0) {
s.push(iniSubjects[i])
}
@ -91,7 +91,7 @@ function ProjectList(obj) {
}
if (res.data.skills !== null) {
let s = res.data.skills
for (let i=0; i<iniSkills.length; i++) {
for (let i = 0; i < iniSkills.length; i++) {
if (s.indexOf(iniSkills[i]) < 0) {
s.push(iniSkills[i])
}
@ -100,7 +100,9 @@ function ProjectList(obj) {
}
}
})
.catch(e=>{console.log(e)})
.catch(e => {
console.log(e)
})
}
const handleSubjectsChange = selected => {
@ -119,7 +121,7 @@ function ProjectList(obj) {
.catch(e => {
})
}
window.location.href = `/project/${item.id}/info`
window.location.href = `/home/project/${item.id}/info`
}
const onSearch = (v) => {
@ -180,75 +182,74 @@ function ProjectList(obj) {
</Card>
<div style={{marginTop: '10px', textAlign: 'center'}}>
<Spin spinning={loadProjects} />
<Spin spinning={loadProjects}/>
<Row gutter={[20, 20]} style={{textAlign: 'left'}}>
{
learningProjectList.map((item, index) => (
<Col key={index.toString()} {...topColResponsiveProps}>
<Card
hoverable
bordered={false}
onClick={e=>viewProject(item)}
style={{
borderRadius: '10px',
}}
cover={
<Image
src={item.image}
preview={false}
style={{
borderTopLeftRadius: '10px',
borderTopRightRadius: '10px',
}}
fallback={"https://cdn.open-ct.com/task-resources//openpbl-empty-project.png"}
/>
}
>
<Meta
title={
item.projectTitle === '' ? "无": item.projectTitle
}
description={
<div>
<span className="des-text">{item.subjects === '' ? '--' : item.subjects}</span><br/>
<span className="des-text">{item.skills === '' ? '--' : item.skills}</span>
{item.learning ?
<Tag color="geekblue" style={{zIndex: '999', float: 'right'}}>学习中</Tag> : null}
{item.teacherId === uid && type === 'teacher' ?
<Tag color="geekblue" style={{zIndex: '999', float: 'right'}}>我的项目</Tag> : null}
</div>
}
<Card
hoverable
bordered={false}
onClick={e => viewProject(item)}
style={{
borderRadius: '10px',
}}
cover={
<Image
src={item.image}
preview={false}
style={{
borderTopLeftRadius: '10px',
borderTopRightRadius: '10px',
}}
fallback={"https://cdn.open-ct.com/task-resources//openpbl-empty-project.png"}
/>
}
>
<Meta
title={
item.projectTitle === '' ? "无" : item.projectTitle
}
description={
<div>
<span className="des-text">{item.subjects === '' ? '--' : item.subjects}</span>
{item.learning ?
<Tag color="geekblue" style={{zIndex: '999', float: 'right'}}>学习中</Tag> : null}
{item.teacherId === uid && type === 'teacher' ?
<Tag color="geekblue" style={{zIndex: '999', float: 'right'}}>我的项目</Tag> : null}
</div>
}
/>
<span
style={{
color: 'gray',
fontSize: 'small',
}}
>
<span
style={{
color: 'gray',
fontSize: 'small',
}}
>
<EyeOutlined/>&nbsp;
{item.readNum}
{item.readNum}
</span>
<Divider type="vertical"/>
<span
style={{
color: 'gray',
fontSize: 'small',
}}
>
<Divider type="vertical"/>
<span
style={{
color: 'gray',
fontSize: 'small',
}}
>
<TeamOutlined/>&nbsp;
{item.joinNum}
{item.joinNum}
</span>
<span
style={{
color: 'gray',
fontSize: 'small',
float: 'right',
}}
>
<span
style={{
color: 'gray',
fontSize: 'small',
float: 'right',
}}
>
{util.FilterMoment(item.createAt)}
</span>
</Card>
</Card>
</Col>
))
}

View File

@ -1,5 +1,5 @@
import React from "react";
import {Avatar, Badge, Button, Col, Dropdown, Layout, Menu, Row, Tag, Image, message} from "antd";
import {Avatar, Badge, Button, Col, Dropdown, Image, Layout, Menu, message, Row, Tag} from "antd";
import {Link, Redirect, Route, Switch} from "react-router-dom";
import {BellOutlined, LogoutOutlined, SettingOutlined} from '@ant-design/icons';
@ -35,23 +35,25 @@ class HeaderLayout extends React.Component {
account: res.data.data
})
})
.catch((e) => {console.log(e)})
.catch((e) => {
console.log(e)
})
this.changeMenu()
}
changeMenu = (e) => {
if (e !== undefined ) {
if (e !== undefined) {
this.setState({menu: e.key})
return
}
const p = this.props.location.pathname
if (p.startsWith('/home')) {
this.setState({menu: 'home'})
} else if (p.startsWith("/my-project")) {
if (p.startsWith("/home/my-project")) {
this.setState({menu: 'my-project'})
} else if (p.startsWith("/public-project")) {
} else if (p.startsWith("/home/public-project")) {
this.setState({menu: 'public-project'})
} else {
this.setState({menu: 'home'})
}
}
@ -60,7 +62,7 @@ class HeaderLayout extends React.Component {
if (this.state.account === null) {
message.warn('请先登录')
return <Redirect to={'/home'} />
return <Redirect to={'/home'}/>
} else if (this.state.account === undefined) {
return null
} else {
@ -82,7 +84,9 @@ class HeaderLayout extends React.Component {
window.location.href = '/'
}
})
.catch(e => {console.log(e)})
.catch(e => {
console.log(e)
})
}
}
@ -146,7 +150,7 @@ class HeaderLayout extends React.Component {
style={{border: 0}}
defaultSelectedKeys={['home']}
selectedKeys={[menu]}
onClick={e=>this.changeMenu(e)}
onClick={e => this.changeMenu(e)}
>
<Menu.Item key="home">
<Link to="/home">
@ -154,27 +158,22 @@ class HeaderLayout extends React.Component {
</Link>
</Menu.Item>
<Menu.Item key="public-project">
<Link to="/public-project">
<Link to="/home/public-project">
公共项目
</Link>
</Menu.Item>
<Menu.Item key="my-project">
<Link to="/my-project">
<Link to="/home/my-project">
我的空间
</Link>
</Menu.Item>
<Menu.Item key="bbs">
<a href="https://bbs.open-ct.com">
在线论坛
</a>
</Menu.Item>
</Menu>
</Col>
<Col xxl={4} xl={4} lg={6} md={8} sm={8} xs={11}>
{
<>
<span style={{float: 'left', marginRight: '20px'}}>
<Link to="/message">
<Link to="/home/message">
<Button
shape="circle"
type="text"
@ -195,24 +194,34 @@ class HeaderLayout extends React.Component {
</Layout.Header>
<Layout.Content>
<Switch>
<Route exact path="/" render={() => (
<Redirect to="/home"/>
)}/>
<Route exact path="/home" render={(props) => <Home account={this.state.account} {...props} />}/>
<Route exact path="/home/public-project"
render={(props) => this.renderHomeIfLoggedIn(<Project account={this.state.account} {...props} />)}/>
<Route path="/home/my-project" render={(props) => this.renderHomeIfLoggedIn(<MyProject
account={this.state.account} {...props} />)}/>
<Route path="/home/message"
render={(props) => this.renderHomeIfLoggedIn(<Message account={this.state.account} {...props} />)}/>
<Route exact path="/home" render={(props)=><Home account={this.state.account} {...props} />}/>
<Route exact path="/public-project" render={(props)=>this.renderHomeIfLoggedIn(<Project account={this.state.account} {...props} />)} />
<Route path="/my-project" render={(props)=>this.renderHomeIfLoggedIn(<MyProject account={this.state.account} {...props} />)} />
<Route path="/message" render={(props)=>this.renderHomeIfLoggedIn(<Message account={this.state.account} {...props} />)}/>
<Route exact path="/home/project/:id/info" render={(props) => this.renderHomeIfLoggedIn(<ProjectInfo
account={this.state.account} {...props} />)}/>
<Route exact path="/home/project/:id/info/edit"
render={(props) => this.renderHomeIfLoggedIn(<EditInfo account={this.state.account} {...props} />)}/>
<Route exact path="/home/project/:id/outline/edit"
render={(props) => this.renderHomeIfLoggedIn(<EditOutlined
account={this.state.account} {...props} />)}/>
<Route exact path="/project/:id/info" render={(props)=>this.renderHomeIfLoggedIn(<ProjectInfo account={this.state.account} {...props} />)}/>
<Route exact path="/project/:id/info/edit" render={(props)=>this.renderHomeIfLoggedIn(<EditInfo account={this.state.account} {...props} />)}/>
<Route exact path="/project/:id/outline/edit" render={(props)=>this.renderHomeIfLoggedIn(<EditOutlined account={this.state.account} {...props} />)}/>
<Route exact path="/home/project/:pid/student/:sid/evidence"
render={(props) => this.renderHomeIfLoggedIn(<Evidence account={this.state.account} {...props} />)}/>
<Route exact path="/project/:pid/student/:sid/evidence" render={(props)=>this.renderHomeIfLoggedIn(<Evidence account={this.state.account} {...props} />)}/>
<Route exact path="/project/:pid/section/:sid/edit" render={(props)=>this.renderHomeIfLoggedIn(<SectionEditPage account={this.state.account} {...props} />)}/>
<Route exact path="/project/:pid/section/:sid/task/:tid/survey/edit" render={(props)=>this.renderHomeIfLoggedIn(<SurveyEditPage account={this.state.account} {...props} />)}/>
<Route exact path="/project/:pid/section/:sid/preview" render={(props)=>this.renderHomeIfLoggedIn(<PreviewSection account={this.state.account} {...props} />)}/>
<Route exact path="/home/project/:pid/section/:sid/edit"
render={(props) => this.renderHomeIfLoggedIn(<SectionEditPage
account={this.state.account} {...props} />)}/>
<Route exact path="/home/project/:pid/section/:sid/task/:tid/survey/edit"
render={(props) => this.renderHomeIfLoggedIn(<SurveyEditPage
account={this.state.account} {...props} />)}/>
<Route exact path="/home/project/:pid/section/:sid/preview"
render={(props) => this.renderHomeIfLoggedIn(<PreviewSection
account={this.state.account} {...props} />)}/>
</Switch>
</Layout.Content>
<Layout.Footer style={{textAlign: 'center'}}>OpenPBL</Layout.Footer>

View File

@ -5668,6 +5668,11 @@ file-loader@^6.0.0:
loader-utils "^2.0.0"
schema-utils "^3.0.0"
file-saver@^2.0.5:
version "2.0.5"
resolved "https://registry.yarnpkg.com/file-saver/-/file-saver-2.0.5.tgz#d61cfe2ce059f414d899e9dd6d4107ee25670c38"
integrity sha512-P9bmyZ3h/PRG+Nzga+rbdI4OEpNDzAVyy74uVO9ATgzLK6VtAsYybF/+TOCvrc0MO793d6+42lLyZTw7/ArVzA==
file-uri-to-path@1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz#553a7b8446ff6f684359c445f1e37a05dacc33dd"
@ -6297,6 +6302,15 @@ html-comment-regex@^1.1.0:
resolved "https://registry.yarnpkg.com/html-comment-regex/-/html-comment-regex-1.1.2.tgz#97d4688aeb5c81886a364faa0cad1dda14d433a7"
integrity sha512-P+M65QY2JQ5Y0G9KKdlDpo0zK+/OHptU5AaBwUfAIDJZk1MYf32Frm84EcOytfJE0t5JvkAnKlmjsXDnWzCJmQ==
html-docx-js@^0.3.1:
version "0.3.1"
resolved "https://registry.yarnpkg.com/html-docx-js/-/html-docx-js-0.3.1.tgz#9713f6587a08d1f0c87a801fe7649a4d0ab07d76"
integrity sha1-lxP2WHoI0fDIeoAf52SaTQqwfXY=
dependencies:
jszip "^2.3.0"
lodash.escape "^3.0.0"
lodash.merge "^3.2.0"
html-encoding-sniffer@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/html-encoding-sniffer/-/html-encoding-sniffer-2.0.1.tgz#42a6dc4fd33f00281176e8b23759ca4e4fa185f3"
@ -7678,6 +7692,13 @@ jstoxml@^0.2.3:
array-includes "^3.1.2"
object.assign "^4.1.2"
jszip@^2.3.0:
version "2.6.1"
resolved "https://registry.yarnpkg.com/jszip/-/jszip-2.6.1.tgz#b88f3a7b2e67a2a048152982c7a3756d9c4828f0"
integrity sha1-uI86ey5noqBIFSmCx6N1bZxIKPA=
dependencies:
pako "~1.0.2"
killable@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/killable/-/killable-1.0.1.tgz#4c8ce441187a061c7474fb87ca08e2a638194892"
@ -7866,21 +7887,72 @@ locate-path@^5.0.0:
dependencies:
p-locate "^4.1.0"
lodash._arraycopy@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/lodash._arraycopy/-/lodash._arraycopy-3.0.0.tgz#76e7b7c1f1fb92547374878a562ed06a3e50f6e1"
integrity sha1-due3wfH7klRzdIeKVi7Qaj5Q9uE=
lodash._arrayeach@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/lodash._arrayeach/-/lodash._arrayeach-3.0.0.tgz#bab156b2a90d3f1bbd5c653403349e5e5933ef9e"
integrity sha1-urFWsqkNPxu9XGU0AzSeXlkz754=
lodash._basecopy@^3.0.0:
version "3.0.1"
resolved "https://registry.yarnpkg.com/lodash._basecopy/-/lodash._basecopy-3.0.1.tgz#8da0e6a876cf344c0ad8a54882111dd3c5c7ca36"
integrity sha1-jaDmqHbPNEwK2KVIghEd08XHyjY=
lodash._basefor@^3.0.0:
version "3.0.3"
resolved "https://registry.yarnpkg.com/lodash._basefor/-/lodash._basefor-3.0.3.tgz#7550b4e9218ef09fad24343b612021c79b4c20c2"
integrity sha1-dVC06SGO8J+tJDQ7YSAhx5tMIMI=
lodash._bindcallback@^3.0.0:
version "3.0.1"
resolved "https://registry.yarnpkg.com/lodash._bindcallback/-/lodash._bindcallback-3.0.1.tgz#e531c27644cf8b57a99e17ed95b35c748789392e"
integrity sha1-5THCdkTPi1epnhftlbNcdIeJOS4=
lodash._createassigner@^3.0.0:
version "3.1.1"
resolved "https://registry.yarnpkg.com/lodash._createassigner/-/lodash._createassigner-3.1.1.tgz#838a5bae2fdaca63ac22dee8e19fa4e6d6970b11"
integrity sha1-g4pbri/aymOsIt7o4Z+k5taXCxE=
dependencies:
lodash._bindcallback "^3.0.0"
lodash._isiterateecall "^3.0.0"
lodash.restparam "^3.0.0"
lodash._getnative@^3.0.0:
version "3.9.1"
resolved "https://registry.yarnpkg.com/lodash._getnative/-/lodash._getnative-3.9.1.tgz#570bc7dede46d61cdcde687d65d3eecbaa3aaff5"
integrity sha1-VwvH3t5G1hzc3mh9ZdPuy6o6r/U=
lodash._isiterateecall@^3.0.0:
version "3.0.9"
resolved "https://registry.yarnpkg.com/lodash._isiterateecall/-/lodash._isiterateecall-3.0.9.tgz#5203ad7ba425fae842460e696db9cf3e6aac057c"
integrity sha1-UgOte6Ql+uhCRg5pbbnPPmqsBXw=
lodash._reinterpolate@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz#0ccf2d89166af03b3663c796538b75ac6e114d9d"
integrity sha1-DM8tiRZq8Ds2Y8eWU4t1rG4RTZ0=
lodash._root@^3.0.0:
version "3.0.1"
resolved "https://registry.yarnpkg.com/lodash._root/-/lodash._root-3.0.1.tgz#fba1c4524c19ee9a5f8136b4609f017cf4ded692"
integrity sha1-+6HEUkwZ7ppfgTa0YJ8BfPTe1pI=
lodash.debounce@^4.0.0, lodash.debounce@^4.0.8:
version "4.0.8"
resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af"
integrity sha1-gteb/zCmfEAF/9XiUVMArZyk168=
lodash.escape@^3.0.0:
version "3.2.0"
resolved "https://registry.yarnpkg.com/lodash.escape/-/lodash.escape-3.2.0.tgz#995ee0dc18c1b48cc92effae71a10aab5b487698"
integrity sha1-mV7g3BjBtIzJLv+ucaEKq1tIdpg=
dependencies:
lodash._root "^3.0.0"
lodash.get@^4.4.2:
version "4.4.2"
resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99"
@ -7896,7 +7968,21 @@ lodash.isarray@^3.0.0:
resolved "https://registry.yarnpkg.com/lodash.isarray/-/lodash.isarray-3.0.4.tgz#79e4eb88c36a8122af86f844aa9bcd851b5fbb55"
integrity sha1-eeTriMNqgSKvhvhEqpvNhRtfu1U=
lodash.keys@^3.1.2:
lodash.isplainobject@^3.0.0:
version "3.2.0"
resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-3.2.0.tgz#9a8238ae16b200432960cd7346512d0123fbf4c5"
integrity sha1-moI4rhayAEMpYM1zRlEtASP79MU=
dependencies:
lodash._basefor "^3.0.0"
lodash.isarguments "^3.0.0"
lodash.keysin "^3.0.0"
lodash.istypedarray@^3.0.0:
version "3.0.6"
resolved "https://registry.yarnpkg.com/lodash.istypedarray/-/lodash.istypedarray-3.0.6.tgz#c9a477498607501d8e8494d283b87c39281cef62"
integrity sha1-yaR3SYYHUB2OhJTSg7h8OSgc72I=
lodash.keys@^3.0.0, lodash.keys@^3.1.2:
version "3.1.2"
resolved "https://registry.yarnpkg.com/lodash.keys/-/lodash.keys-3.1.2.tgz#4dbc0472b156be50a0b286855d1bd0b0c656098a"
integrity sha1-TbwEcrFWvlCgsoaFXRvQsMZWCYo=
@ -7905,11 +7991,41 @@ lodash.keys@^3.1.2:
lodash.isarguments "^3.0.0"
lodash.isarray "^3.0.0"
lodash.keysin@^3.0.0:
version "3.0.8"
resolved "https://registry.yarnpkg.com/lodash.keysin/-/lodash.keysin-3.0.8.tgz#22c4493ebbedb1427962a54b445b2c8a767fb47f"
integrity sha1-IsRJPrvtsUJ5YqVLRFssinZ/tH8=
dependencies:
lodash.isarguments "^3.0.0"
lodash.isarray "^3.0.0"
lodash.memoize@^4.1.2:
version "4.1.2"
resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe"
integrity sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4=
lodash.merge@^3.2.0:
version "3.3.2"
resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-3.3.2.tgz#0d90d93ed637b1878437bb3e21601260d7afe994"
integrity sha1-DZDZPtY3sYeEN7s+IWASYNev6ZQ=
dependencies:
lodash._arraycopy "^3.0.0"
lodash._arrayeach "^3.0.0"
lodash._createassigner "^3.0.0"
lodash._getnative "^3.0.0"
lodash.isarguments "^3.0.0"
lodash.isarray "^3.0.0"
lodash.isplainobject "^3.0.0"
lodash.istypedarray "^3.0.0"
lodash.keys "^3.0.0"
lodash.keysin "^3.0.0"
lodash.toplainobject "^3.0.0"
lodash.restparam@^3.0.0:
version "3.6.1"
resolved "https://registry.yarnpkg.com/lodash.restparam/-/lodash.restparam-3.6.1.tgz#936a4e309ef330a7645ed4145986c85ae5b20805"
integrity sha1-k2pOMJ7zMKdkXtQUWYbIWuWyCAU=
lodash.sortby@^4.7.0:
version "4.7.0"
resolved "https://registry.yarnpkg.com/lodash.sortby/-/lodash.sortby-4.7.0.tgz#edd14c824e2cc9c1e0b0a1b42bb5210516a42438"
@ -7935,6 +8051,14 @@ lodash.throttle@^4.0.0:
resolved "https://registry.yarnpkg.com/lodash.throttle/-/lodash.throttle-4.1.1.tgz#c23e91b710242ac70c37f1e1cda9274cc39bf2f4"
integrity sha1-wj6RtxAkKscMN/HhzaknTMOb8vQ=
lodash.toplainobject@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/lodash.toplainobject/-/lodash.toplainobject-3.0.0.tgz#28790ad942d293d78aa663a07ecf7f52ca04198d"
integrity sha1-KHkK2ULSk9eKpmOgfs9/UsoEGY0=
dependencies:
lodash._basecopy "^3.0.0"
lodash.keysin "^3.0.0"
lodash.uniq@^4.5.0:
version "4.5.0"
resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773"
@ -8928,7 +9052,7 @@ pac-resolver@^4.1.0:
ip "^1.1.5"
netmask "^2.0.1"
pako@~1.0.5:
pako@~1.0.2, pako@~1.0.5:
version "1.0.11"
resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.11.tgz#6c9599d340d54dfd3946380252a35705a6b992bf"
integrity sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==