Merge pull request #10 from lbaf23/feature/student-evidence

feat: student evidence
This commit is contained in:
lhf 2021-08-07 19:21:40 +08:00 committed by GitHub
commit 6a470bb880
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
167 changed files with 28763 additions and 0 deletions

3
.dockerignore Normal file
View File

@ -0,0 +1,3 @@
.dockerignore
Dockerfile
docker-compose.yml

17
.gitignore vendored
View File

@ -1,3 +1,7 @@
# Dependency directories (remove the comment below to include it)
# vendor/
### Go template
# Binaries for programs and plugins
*.exe
*.exe~
@ -13,3 +17,16 @@
# Dependency directories (remove the comment below to include it)
# vendor/
*.zip
*.tmp
.idea
.vscode
tmp
### Config file
conf/app-dev.conf
conf/app-prod.conf

30
Dockerfile Normal file
View File

@ -0,0 +1,30 @@
FROM golang:1.16-rc-buster
WORKDIR /openpbl
RUN go env -w CGO_ENABLED=0 GOPROXY=https://goproxy.io,direct GOOS=linux GOARCH=amd64 \
&& apt update && apt install sudo \
&& wget https://nodejs.org/dist/v12.22.0/node-v12.22.0-linux-x64.tar.gz \
&& sudo tar xf node-v12.22.0-linux-x64.tar.gz \
&& sudo apt install wait-for-it
ENV PATH=$PATH:/openpbl/node-v12.22.0-linux-x64/bin
RUN npm install -g yarn
COPY openpbl-landing/package.json /openpbl/openpbl-landing/package.json
RUN cd openpbl-landing && yarn install
COPY openpbl-landing /openpbl/openpbl-landing
RUN cd openpbl-landing && yarn build && rm -rf node_modules
COPY ./ /openpbl
RUN cd /openpbl && go build main.go
FROM alpine:3.7
COPY --from=0 /openpbl /
COPY --from=0 /usr/bin/wait-for-it /
RUN set -eux \
&& sed -i 's/dl-cdn.alpinelinux.org/mirrors.ustc.edu.cn/g' /etc/apk/repositories \
&& apk update \
&& apk upgrade \
&& apk add bash
ENV RUNMODE=prod
CMD ./wait-for-it openpbl-db:3308 && ./main

View File

@ -1,2 +1,77 @@
# OpenPBL
System of PBL.
## 开发
### 后端
新建开发配置文件
`vim conf/app-dev.conf`
配置文件内容参考
`conf/app.conf`
### 前端
新建开发配置文件
`vim openpbl-landing/.env.development`
配置文件内容参考
`openpbl-landing/.env`
## 部署
### 后端 beego
新建部署配置文件
`vim conf/app-prod.conf`
配置文件内容参考
`conf/app.conf`
```
appname = OpenPBL
httpaddr = 127.0.0.1
autorender = false
copyrequestbody = true
EnableDocs = true
SessionOn = true
copyrequestbody = true
httpport = 5000
driverName = mysql
dataSourceName = root:root@tcp(db:3306)/ # docker-compose 部署环境下 localhost 改为 db
dbName = openpbl_db
casdoorEndpoint = http://localhost:8000
clientId = # casdoor 应用 id
clientSecret = # casdoor 应用 secret
jwtSecret = # jwt 加密密钥
casdoorOrganization = "openct" # casdoor 应用所属组织
```
### 前端 react
新建部署配置文件
`vim openpbl-landing/.env.production`
配置文件内容参考
`openpbl-landing/.env`
```dotenv
REACT_APP_BASE_URL='http://localhost:5000/api'
REACT_APP_OSS_REGION: 'oss-cn-hangzhou' # 阿里云 oss region
REACT_APP_OSS_ACCESSKEYID: '123' # oss accesskeyId
REACT_APP_OSS_ACCESSKEYSECRET: '123' # oss accessKeySecret
REACT_APP_OSS_BUCKET: 'bucket' # oss bucket
```

21
conf/app.conf Normal file
View File

@ -0,0 +1,21 @@
appname = OpenPBL
httpaddr = 127.0.0.1
autorender = false
copyrequestbody = true
EnableDocs = true
SessionOn = true
copyrequestbody = true
runmode = dev
httpport = 5000
driverName = mysql
dataSourceName = root:root@tcp(localhost:3306)/
dbName = openpbl_db
jwtSecret = CasdoorSecret
casdoorEndpoint = http://localhost:8000
clientId=
clientSecret=
casdoorOrganization="openct"

140
controllers/auth.go Normal file
View File

@ -0,0 +1,140 @@
package controllers
import (
"OpenPBL/util"
"fmt"
"github.com/astaxie/beego"
"github.com/casdoor/casdoor-go-sdk/auth"
)
type AuthController struct {
beego.Controller
}
func InitCasdoor() {
var CasdoorEndpoint = beego.AppConfig.String("casdoorEndpoint")
var ClientId = beego.AppConfig.String("clientId")
var ClientSecret = beego.AppConfig.String("clientSecret")
var JwtSecret = beego.AppConfig.String("jwtSecret")
var CasdoorOrganization = beego.AppConfig.String("casdoorOrganization")
auth.InitConfig(CasdoorEndpoint, ClientId, ClientSecret, JwtSecret, CasdoorOrganization)
}
func (c *AuthController) GetSessionUser() *auth.Claims {
s := c.GetSession("user")
if s == nil {
return nil
}
claims := &auth.Claims{}
err := util.JsonToStruct(s.(string), claims)
if err != nil {
panic(err)
}
return claims
}
func (c *AuthController) SetSessionUser(claims *auth.Claims) {
if claims == nil {
c.DelSession("user")
return
}
s := util.StructToJson(claims)
c.SetSession("user", s)
}
type Response struct {
Code int `json:"code"`
Msg string `json:"msg"`
Data interface{} `json:"data"`
}
// Login
// @Title
// @Description
// @Param code body string true "code"
// @Param state body string true "state"
// @Success 200 {int} models.Teacher.Id
// @Failure 403 body is empty
// @router /login [post]
func (c *AuthController) Login() {
code := c.Input().Get("code")
state := c.Input().Get("state")
token, err := auth.GetOAuthToken(code, state)
if err != nil {
c.Data["json"] = Response{
Code: 403,
Msg: err.Error(),
}
c.ServeJSON()
return
}
claims, err := auth.ParseJwtToken(token.AccessToken)
if err != nil {
c.Data["json"] = Response{
Code: 403,
Msg: err.Error(),
}
c.ServeJSON()
return
}
fmt.Println(claims.StandardClaims)
claims.AccessToken = token.AccessToken
c.SetSessionUser(claims)
resp := &Response{
Code: 200,
Msg: "登录成功",
Data: claims,
}
c.Data["json"] = resp
c.ServeJSON()
}
// Logout
// @Title
// @Description
// @Success 200 {object} Response
// @router /logout [post]
func (c *AuthController) Logout() {
var resp Response
c.SetSessionUser(nil)
resp = Response{
Code: 200,
Msg: "",
}
c.Data["json"] = resp
c.ServeJSON()
}
// GetAccount
// @Title
// @Description
// @Success 200 {object} Response
// @Failure 401 {object} Response
// @router /account [get]
func (c *AuthController) GetAccount() {
var resp Response
user := c.GetSessionUser()
if user == nil {
resp = Response{
Code: 404,
Msg: "账号不存在",
}
c.Data["json"] = resp
c.ServeJSON()
return
}
resp = Response{
Code: 200,
Msg: "",
Data: user,
}
c.Data["json"] = resp
c.ServeJSON()
}

188
controllers/chapter.go Normal file
View File

@ -0,0 +1,188 @@
package controllers
import (
"OpenPBL/models"
"strconv"
)
type ChaptersResponse struct {
Code int `json:"code"`
Msg string `json:"msg"`
Chapters []models.Outline `json:"chapters"`
ShowMinute bool `json:"showMinute"`
}
// GetProjectChapters
// @Title
// @Description
// @Param pid path string true "project id"
// @Success 200 {object} []models.Outline
// @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 := ""
show := false
if user.Tag == "student" {
uid = user.Username
show = true
}
if user.Tag == "teacher" {
uid = p.GetString("studentId")
if uid != "" {
show = true
}
}
pid := p.GetString(":id")
outline, err := models.GetChaptersByPid(pid, uid)
if err != nil {
p.Data["json"] = ChaptersResponse{
Code: 400,
Msg: err.Error(),
Chapters: make([]models.Outline, 0),
}
} else {
p.Data["json"] = ChaptersResponse{
Code: 200,
Chapters: outline,
ShowMinute: show,
}
}
p.ServeJSON()
}
// CreateProjectChapter
// @Title
// @Description
// @Param body body models.Chapter true ""
// @Success 200 {object} Response
// @Failure 401
// @router /:id/chapter [post]
func (p *ProjectController) CreateProjectChapter() {
pid, err := p.GetInt64(":id")
num, err := p.GetInt("chapterNumber")
chapter := &models.Chapter{
ProjectId: pid,
ChapterName: p.GetString("chapterName"),
ChapterNumber: num,
}
if err != nil {
p.Data["json"] = Response{
Code: 400,
Msg: err.Error(),
}
}
err = chapter.Create()
if err != nil {
p.Data["json"] = Response{
Code: 400,
Msg: err.Error(),
}
} else {
p.Data["json"] = Response{
Code: 200,
Msg: "添加成功",
Data: strconv.FormatInt(chapter.Id, 10),
}
}
p.ServeJSON()
}
// UpdateProjectChapter
// @Title
// @Description
// @Param body body models.Chapter true ""
// @Success 200 {object} Response
// @Failure 401
// @router /:projectId/chapter/:chapterId [post]
func (p *ProjectController) UpdateProjectChapter() {
cid, err := p.GetInt64(":chapterId")
pid, err := p.GetInt64(":projectId")
num, err := p.GetInt("chapterNumber")
chapter := &models.Chapter{
Id: cid,
ProjectId: pid,
ChapterName: p.GetString("chapterName"),
ChapterNumber: num,
}
err = chapter.Update()
if err != nil {
p.Data["json"] = Response{
Code: 400,
Msg: "更新失败",
}
} else {
p.Data["json"] = Response{
Code: 200,
Msg: "更新成功",
Data: true,
}
}
p.ServeJSON()
}
// DeleteProjectChapter
// @Title
// @Description
// @Param body body models.Chapter true ""
// @Success 200 {object} Response
// @Failure 401
// @router /:projectId/chapter/:chapterId/delete [post]
func (p *ProjectController) DeleteProjectChapter() {
cid, err := p.GetInt64(":chapterId")
pid, err := p.GetInt64(":projectId")
num, err := p.GetInt("chapterNumber")
chapter := &models.Chapter{
Id: cid,
ProjectId: pid,
ChapterName: p.GetString("chapterName"),
ChapterNumber: num,
}
err = chapter.Delete()
if err != nil {
p.Data["json"] = Response{
Code: 400,
Msg: err.Error(),
}
} else {
p.Data["json"] = Response{
Code: 200,
Msg: "删除成功",
Data: true,
}
}
p.ServeJSON()
}
// ExchangeProjectChapter
// @Title
// @Description
// @Param cid path string true ""
// @Success 200 {object} Response
// @Failure 401
// @router /:projectId/chapters/exchange [post]
func (p *ProjectController) ExchangeProjectChapter() {
cid1 := p.GetString("chapterId1")
cid2 := p.GetString("chapterId2")
err := models.ExchangeChapters(cid1, cid2)
if err != nil {
p.Data["json"] = Response{
Code: 400,
Msg: err.Error(),
}
} else {
p.Data["json"] = Response{
Code: 200,
Data: true,
}
}
p.ServeJSON()
}

2
controllers/evaluate.go Normal file
View File

@ -0,0 +1,2 @@
package controllers

538
controllers/project.go Normal file
View File

@ -0,0 +1,538 @@
package controllers
import (
"OpenPBL/models"
"OpenPBL/util"
"encoding/json"
"fmt"
"github.com/astaxie/beego"
"github.com/casdoor/casdoor-go-sdk/auth"
"strings"
"time"
)
// ProjectController
// Operations about Projects
type ProjectController struct {
beego.Controller
}
func (p *ProjectController) GetSessionUser() *auth.Claims {
s := p.GetSession("user")
if s == nil {
return nil
}
claims := &auth.Claims{}
err := util.JsonToStruct(s.(string), claims)
if err != nil {
panic(err)
}
return claims
}
type ProjectResponse struct {
Code int `json:"code"`
Msg string `json:"msg"`
Project models.ProjectDetail `json:"project"`
}
// GetProjectDetail
// @Title
// @Description
// @Param id path string true "project id"
// @Success 200 {object} models.TeacherProject
// @Failure 400
// @router /:id [get]
func (p *ProjectController) GetProjectDetail() {
pid := p.GetString(":id")
user := p.GetSessionUser()
var resp ProjectResponse
if user == nil {
resp = ProjectResponse{
Code: 401,
Msg: "请先登录",
}
p.Data["json"] = resp
p.ServeJSON()
return
}
uid := user.Username
var err error
var project models.ProjectDetail
if user.Tag == "student" {
project, err = models.GetProjectByPidForStudent(pid, uid)
} else if user.Tag == "teacher" {
project, err = models.GetProjectByPidForTeacher(pid)
}
if err != nil {
resp = ProjectResponse{
Code: 400,
Msg: err.Error(),
}
} else {
resp = ProjectResponse{
Code: 200,
Project: project,
}
}
p.Data["json"] = resp
p.ServeJSON()
}
// CreateProject
// @Title
// @Description create project
// @Success 200 {object} Response
// @Failure 401
// @Failure 400
// @Failure 403
// @router / [post]
func (p *ProjectController) CreateProject() {
user := p.GetSessionUser()
var resp Response
if user == nil {
resp = Response{
Code: 401,
Msg: "请先登录",
}
p.Data["json"] = resp
p.ServeJSON()
return
}
if user.Tag != "teacher" {
resp = Response{
Code: 403,
Msg: "非法用户",
}
p.Data["json"] = resp
p.ServeJSON()
return
}
uid := user.Username
project := &models.Project{
TeacherId: uid,
}
fmt.Println(project)
err := project.Create()
if err != nil {
resp = Response{
Code: 400,
Msg: err.Error(),
}
} else {
resp = Response{
Code: 200,
Msg: "创建成功",
Data: project.Id,
}
}
p.Data["json"] = resp
p.ServeJSON()
}
// UpdateProject
// @Title
// @Description create project
// @Param body body models.Project true ""
// @Success 200 {int} models.Project.Id
// @Failure 403 body is empty
// @router /:id [post]
func (p *ProjectController) UpdateProject() {
user := p.GetSessionUser()
var resp Response
if user == nil {
resp = Response{
Code: 401,
Msg: "请先登录",
}
p.Data["json"] = resp
p.ServeJSON()
return
}
if user.Tag != "teacher" {
resp = Response{
Code: 403,
Msg: "非法用户",
}
p.Data["json"] = resp
p.ServeJSON()
return
}
uid := user.Username
pid, err := p.GetInt64(":id")
if err != nil {
resp = Response{
Code: 400,
Msg: err.Error(),
}
p.Data["json"] = resp
p.ServeJSON()
return
}
project := &models.Project{
Id: pid,
Image: p.GetString("image"),
ProjectTitle: p.GetString("projectTitle"),
ProjectIntroduce: p.GetString("projectIntroduce"),
ProjectGoal: p.GetString("projectGoal"),
TeacherId: uid,
Subjects: p.GetString("subjects"),
Skills: p.GetString("skills"),
}
projectSubjects, projectSkills, err := getProjectSubjectsAndSkills(pid, project.Subjects, project.Skills)
err = project.UpdateInfo(projectSubjects, projectSkills)
if err != nil {
resp = Response{
Code: 400,
Msg: err.Error(),
Data: true,
}
} else {
resp = Response{
Code: 200,
Msg: "更新成功",
}
}
p.Data["json"] = resp
p.ServeJSON()
}
// UpdateProjectWeight
// @Title
// @Description create project
// @Param body body models.Project true ""
// @Success 200 {int} models.Project.Id
// @Failure 403 body is empty
// @router /:id/weight [post]
func (p *ProjectController) UpdateProjectWeight() {
user := p.GetSessionUser()
var resp Response
if user == nil {
resp = Response{
Code: 401,
Msg: "请先登录",
}
p.Data["json"] = resp
p.ServeJSON()
return
}
if user.Tag != "teacher" {
resp = Response{
Code: 403,
Msg: "非法用户",
}
p.Data["json"] = resp
p.ServeJSON()
return
}
pid, err := p.GetInt64(":id")
learnMinuteWeight, err := p.GetInt("learnMinuteWeight")
if err != nil {
resp = Response{
Code: 400,
Msg: err.Error(),
}
p.Data["json"] = resp
p.ServeJSON()
return
}
project := models.Project{
Id: pid,
LearnMinuteWeight: learnMinuteWeight,
}
tasks := make([]models.Task, 0)
err = json.Unmarshal([]byte(p.GetString("tasks")), &tasks)
err = models.UpdateWeight(project, tasks)
if err != nil {
resp = Response{
Code: 400,
Msg: err.Error(),
Data: true,
}
} else {
resp = Response{
Code: 200,
Msg: "更新成功",
}
}
p.Data["json"] = resp
p.ServeJSON()
}
// PublishProject
// @Title
// @Description
// @Param pid path int true ""
// @Success 200 {Response}
// @Failure 400
// @Failure 401
// @Failure 403
// @router /:id/publish [post]
func (u *ProjectController) PublishProject() {
pid, err := u.GetInt64(":id")
var resp Response
user := u.GetSessionUser()
if user == nil {
resp = Response{
Code: 401,
Msg: "请先登录",
}
u.Data["json"] = resp
u.ServeJSON()
return
}
if user.Tag != "teacher" {
resp = Response{
Code: 403,
Msg: "非法的用户",
}
u.Data["json"] = resp
u.ServeJSON()
return
}
p := models.Project{
Id: pid,
PublishedAt: time.Now(),
Published: true,
}
err = models.UpdatePublished(p)
if err != nil {
resp = Response{
Code: 400,
Msg: err.Error(),
}
} else {
resp = Response{
Code: 200,
Msg: "发布成功",
}
}
u.Data["json"] = resp
u.ServeJSON()
}
// CloseProject
// @Title
// @Description
// @Param pid path int true ""
// @Success 200 {object} Response
// @Failure 400
// @Failure 401
// @Failure 403
// @router /:id/close [post]
func (u *ProjectController) CloseProject() {
pid, err := u.GetInt64(":id")
var resp Response
user := u.GetSessionUser()
if user == nil {
resp = Response{
Code: 401,
Msg: "请先登录",
}
u.Data["json"] = resp
u.ServeJSON()
return
}
if user.Tag != "teacher" {
resp = Response{
Code: 403,
Msg: "非法的用户",
}
u.Data["json"] = resp
u.ServeJSON()
return
}
p := models.Project{
Id: pid,
ClosedAt: time.Now(),
Closed: true,
}
err = models.UpdateClosed(p)
if err != nil {
resp = Response{
Code: 400,
Msg: err.Error(),
}
} else {
resp = Response{
Code: 200,
Msg: "发布成功",
}
}
u.Data["json"] = resp
u.ServeJSON()
}
// DeleteProject
// @Title
// @Description
// @Param pid path int true ""
// @Success 200 {Response}
// @Failure 400
// @Failure 401
// @Failure 403
// @router /:id/delete [post]
func (u *ProjectController) DeleteProject() {
pid, err := u.GetInt64(":id")
var resp Response
user := u.GetSessionUser()
if user == nil {
resp = Response{
Code: 401,
Msg: "请先登录",
}
u.Data["json"] = resp
u.ServeJSON()
return
}
if user.Tag != "teacher" {
resp = Response{
Code: 403,
Msg: "非法的用户",
}
u.Data["json"] = resp
u.ServeJSON()
return
}
p := models.Project{
Id: pid,
}
err = p.Delete()
if err != nil {
resp = Response{
Code: 400,
Msg: err.Error(),
}
} else {
resp = Response{
Code: 200,
Msg: "删除成功",
}
}
u.Data["json"] = resp
u.ServeJSON()
}
// RemoveStudent
// @Title
// @Description
// @Param pid path string true ""
// @Success 200 {object} Response
// @Failure 401
// @router /:projectId/remove/:studentId [post]
func (u *ProjectController) RemoveStudent() {
pid, err := u.GetInt64(":projectId")
sid := u.GetString(":studentId")
var resp Response
user := u.GetSessionUser()
if user == nil {
resp = Response{
Code: 401,
Msg: "请先登录",
}
u.Data["json"] = resp
u.ServeJSON()
return
}
l := &models.LearnProject{
StudentId: sid,
ProjectId: pid,
}
err = l.Delete()
if err != nil {
resp = Response{
Code: 400,
Msg: err.Error(),
}
u.Data["json"] = resp
} else {
resp = Response{
Code: 200,
Msg: "移除成功",
}
}
u.Data["json"] = resp
u.ServeJSON()
}
func getProjectSubjectsAndSkills(pid int64, subjects string, skills string) (subjectList []*models.ProjectSubject, skillList []*models.ProjectSkill, err error) {
var (
subjectL []string
skillL []string
)
if subjects == "" {
subjectL = make([]string, 0)
} else {
subjectL = strings.Split(subjects, ",")
}
if skills == "" {
skillL = make([]string, 0)
} else {
skillL = strings.Split(skills, ",")
}
n1 := len(subjectL)
n2 := len(skillL)
subjectList = make([]*models.ProjectSubject, n1)
skillList = make([]*models.ProjectSkill, n2)
for i:=0; i<n1; i++ {
subjectList[i] = &models.ProjectSubject{
Subject: subjectL[i],
ProjectId: pid,
}
}
for i:=0; i<n2; i++ {
skillList[i] = &models.ProjectSkill{
Skill: skillL[i],
ProjectId: pid,
}
}
return
}
type StudentsResponse struct {
Code int `json:"code"`
Students []models.LearnProject `json:"students"`
Count int64 `json:"count"`
}
// GetProjectStudents
// @Title
// @Description
// @Param from query int true ""
// @Param size query int true ""
// @Success 200 {object} StudentsResponse
// @Failure 403
// @router /:projectId/students [get]
func (p *ProjectController) GetProjectStudents() {
pid := p.GetString(":projectId")
from, err := p.GetInt("from")
if err != nil {
from = 0
}
size, err := p.GetInt("size")
if err != nil {
size = 10
}
students, rows, err := models.GetProjectStudents(pid, from, size)
if err != nil {
p.Data["json"] = StudentsResponse{
Code: 400,
Students: nil,
}
} else {
p.Data["json"] = StudentsResponse{
Code: 200,
Students: students,
Count: rows,
}
}
p.ServeJSON()
}

117
controllers/projectList.go Normal file
View File

@ -0,0 +1,117 @@
package controllers
import (
"OpenPBL/models"
"OpenPBL/util"
"github.com/astaxie/beego"
"github.com/casdoor/casdoor-go-sdk/auth"
"strconv"
)
// ProjectListController
// Operations about Projects
type ProjectListController struct {
beego.Controller
}
type ProjectList struct {
Code int `json:"code"`
Msg string `json:"msg"`
Count int64 `json:"count"`
Projects []models.ProjectDetail `json:"projects"`
}
func (pl *ProjectListController) GetSessionUser() *auth.Claims {
s := pl.GetSession("user")
if s == nil {
return nil
}
claims := &auth.Claims{}
err := util.JsonToStruct(s.(string), claims)
if err != nil {
panic(err)
}
return claims
}
// GetUserProjectList
// @Title
// @Description
// @Param projectType path string true "editing published closed // learning finished"
// @Param from query string false "from"
// @Param size query string false "size"
// @Param orderBy query string false "orderBy"
// @Param orderType query string false "orderType"
// @Param subject query string false ""
// @Param skill query string false ""
// @Param text query string false ""
// @Success 200 {object} ProjectList
// @Failure 401
// @router /:projectType [get]
func (pl *ProjectListController) GetUserProjectList() {
orderBy := pl.GetString("orderBy")
if orderBy == "" {
orderBy = "create_at"
}
orderType := pl.GetString("orderType")
if orderType == "" {
orderType = "desc"
}
t := pl.GetString(":projectType")
from, err := strconv.Atoi(pl.GetString("from"))
if err != nil {
from = 0
}
size, err := strconv.Atoi(pl.GetString("size"))
if err != nil {
size = 10
}
skill := pl.GetString("skill")
subject := pl.GetString("subject")
text := pl.GetString("text")
user := pl.GetSessionUser()
var data ProjectList
if user == nil {
data = ProjectList{
Code: 401,
Msg: "请先登录",
}
pl.Data["json"] = data
pl.ServeJSON()
return
}
uid := user.Username
var projects []models.ProjectDetail
var count int64
if user.Tag == "student" {
if t == "learning" {
projects, count, err = models.GetMyProjectListBySid(uid, from, size, subject, skill, text, orderBy, orderType, true)
} else if t == "finished" {
projects, count, err = models.GetMyProjectListBySid(uid, from, size, subject, skill, text, orderBy, orderType, false)
} else if t == "public" {
projects, count, err = models.GetPublicProjectListForStudent(uid, from, size, subject, skill, text, orderBy, orderType)
}
} else if user.Tag == "teacher" {
if t == "editing" {
projects, count, err = models.GetMyProjectListByTid(uid, from, size, subject, skill, text, orderBy, orderType, false, false)
} else if t == "published" {
projects, count, err = models.GetMyProjectListByTid(uid, from, size, subject, skill, text, orderBy, orderType, true, false)
} else if t == "finished" {
projects, count, err = models.GetMyProjectListByTid(uid, from, size, subject, skill, text, orderBy, orderType, true, true)
} else if t == "public" {
projects, count, err = models.GetPublicProjectListForTeacher(uid, from, size, subject, skill, text, orderBy, orderType)
}
}
data = ProjectList{
Code: 200,
Count: count,
Projects: projects,
}
pl.Data["json"] = data
pl.ServeJSON()
}

124
controllers/resource.go Normal file
View File

@ -0,0 +1,124 @@
package controllers
import (
"OpenPBL/models"
"github.com/astaxie/beego"
)
// ResourceController
// Operations about Projects
type ResourceController struct {
beego.Controller
}
// GetResource
// @Title
// @Description
// @Param id path string true ""
// @Success 200 {object} models.Resource
// @router /:id [get]
func (p *ResourceController) GetResource() {
var err error
rid := p.GetString(":id")
resource, err := models.GetResourceById(rid)
if err != nil {
p.Data["json"] = map[string]models.Resource{"resource": {}}
} else {
p.Data["json"] = map[string]models.Resource{"resource": resource}
}
p.ServeJSON()
}
// CreateResource
// @Title
// @Description
// @Param body body models.Resource true ""
// @Success 200 {object} models.TeacherProject
// @Failure 400
// @router / [post]
func (p *ResourceController) CreateResource() {
s, err := p.GetInt64("SectionId")
resource := &models.Resource{
SectionId: s,
FileTitle: p.GetString("fileTitle"),
FileIntroduce: p.GetString("fileIntroduce"),
FilePath: p.GetString("FilePath"),
Content: p.GetString("Content"),
}
err = resource.Create()
if err != nil {
p.Data["json"] = Response{
Code: 400,
Msg: err.Error(),
}
} else {
p.Data["json"] = Response{
Code: 200,
Msg: "创建成功",
Data: resource.Id,
}
}
p.ServeJSON()
}
// UpdateResource
// @Title
// @Description
// @Param body body models.Resource true ""
// @Success 200 {object} models.TeacherProject
// @Failure 400
// @router /:id [post]
func (p *ResourceController) UpdateResource() {
s, err := p.GetInt64("sectionId")
id, err := p.GetInt64(":id")
resource := &models.Resource{
Id: id,
SectionId: s,
FileTitle: p.GetString("fileTitle"),
FileIntroduce: p.GetString("fileIntroduce"),
FilePath: p.GetString("filePath"),
Content: p.GetString("content"),
}
err = resource.Update()
if err != nil {
p.Data["json"] = Response{
Code: 400,
Msg: err.Error(),
}
} else {
p.Data["json"] = Response{
Code: 200,
Msg: "更新成功",
}
}
p.ServeJSON()
}
// UpdateResourceContent
// @Title
// @Description
// @Param id path string true ""
// @Param content body string true ""
// @Success 200 {object} models.TeacherProject
// @Failure 400
// @router /:id/content [post]
func (p *ResourceController) UpdateResourceContent() {
id, err := p.GetInt64(":id")
resource := &models.Resource{
Id: id,
Content: p.GetString("content"),
}
err = resource.Update()
if err != nil {
p.Data["json"] = Response{
Code: 400,
Msg: err.Error(),
}
} else {
p.Data["json"] = Response{
Code: 200,
Msg: "保存成功",
}
}
p.ServeJSON()
}

223
controllers/section.go Normal file
View File

@ -0,0 +1,223 @@
package controllers
import (
"OpenPBL/models"
"encoding/json"
"strconv"
)
type SectionResponse struct {
Response
Section models.SectionDetail `json:"section"`
}
// GetSectionDetail
// @Title
// @Description
// @Param sid path string true ""
// @Param pid path string true ""
// @Success 200 {object}
// @Failure 403 body is empty
// @router /:projectId/section/:sectionId [get]
func (p *ProjectController) GetSectionDetail() {
sid := p.GetString(":sectionId")
section, err := models.GetSectionDetailById(sid)
if err != nil {
p.Data["json"] = SectionResponse{
Section: models.SectionDetail{},
Response: Response{
Code: 400,
Msg: err.Error(),
},
}
} else {
p.Data["json"] = SectionResponse{
Section: section,
Response: Response{
Code: 200,
},
}
}
p.ServeJSON()
}
// GetChapterSections
// @Title
// @Description
// @Param cid path string true "chapter id"
// @Success 200 {object} []models.Section
// @Failure 403 body is empty
// @router /:projectId/chapter/:chapterId/sections [get]
func (p *ProjectController) GetChapterSections() {
cid := p.GetString(":chapterId")
if cid != "" {
sections, err := models.GetSectionsByCid(cid)
if err != nil {
p.Data["json"] = map[string][]models.Section{"sections": nil}
} else {
p.Data["json"] = map[string][]models.Section{"sections": sections}
}
}
p.ServeJSON()
}
// CreateChapterSection
// @Title
// @Description
// @Param body body models.Section true ""
// @Success 200 {object}
// @Failure 403 body is empty
// @router /:projectId/chapter/:chapterId/section [post]
func (p *ProjectController) CreateChapterSection() {
cid, err := p.GetInt64(":chapterId")
sectionNumber, err := p.GetInt("sectionNumber")
chapterNumber, err := p.GetInt("chapterNumber")
section := &models.Section{
ChapterId: cid,
SectionName: p.GetString("sectionName"),
SectionNumber: sectionNumber,
ChapterNumber: chapterNumber,
}
if err != nil {
p.Data["json"] = Response{
Code: 400,
Msg: err.Error(),
}
}
err = section.Create()
if err != nil {
p.Data["json"] = Response{
Code: 400,
Msg: err.Error(),
}
} else {
p.Data["json"] = Response{
Code: 200,
Msg: "创建成功",
Data: strconv.FormatInt(section.Id, 10),
}
}
p.ServeJSON()
}
// UpdateChapterSection
// @Title
// @Description
// @Param body body models.Section true ""
// @Success 200 {object}
// @Failure 401
// @router /:projectId/chapter/:chapterId/section/:sectionId [post]
func (p *ProjectController) UpdateChapterSection() {
sid, err := p.GetInt64(":sectionId")
cid, err := p.GetInt64(":chapterId")
sectionNumber, err := p.GetInt("sectionNumber")
chapterNumber, err := p.GetInt("chapterNumber")
section := &models.Section{
Id: sid,
ChapterId: cid,
SectionName: p.GetString("sectionName"),
SectionNumber: sectionNumber,
ChapterNumber: chapterNumber,
}
err = section.Update()
if err != nil {
p.Data["json"] = Response{
Code: 400,
Msg: "更新失败",
}
} else {
p.Data["json"] = Response{
Code: 200,
Msg: "更新成功",
Data: true,
}
}
p.ServeJSON()
}
// DeleteChapterSection
// @Title
// @Description
// @Param sid path string true ""
// @Success 200 {object}
// @Failure 401
// @router /:projectId/chapter/:chapterId/section/:sectionId/delete [post]
func (p *ProjectController) DeleteChapterSection() {
sid, err := p.GetInt64(":sectionId")
cid, err := p.GetInt64(":chapterId")
sectionNumber, err := p.GetInt("sectionNumber")
chapterNumber, err := p.GetInt("chapterNumber")
section := &models.Section{
Id: sid,
ChapterId: cid,
SectionName: p.GetString("sectionName"),
SectionNumber: sectionNumber,
ChapterNumber: chapterNumber,
}
err = section.Delete()
if err != nil {
p.Data["json"] = Response{
Code: 400,
Msg: "删除失败",
}
} else {
p.Data["json"] = Response{
Code: 200,
Msg: "删除成功",
Data: true,
}
}
p.ServeJSON()
}
// ExchangeChapterSection
// @Title
// @Description
// @Param sid path string true ""
// @Success 200 {object}
// @Failure 401
// @router /:projectId/chapter/:chapterId/sections/exchange [post]
func (p *ProjectController) ExchangeChapterSection() {
sid1 := p.GetString("sectionId1")
sid2 := p.GetString("sectionId2")
err := models.ExchangeSections(sid1, sid2)
if err != nil {
p.Data["json"] = Response{
Code: 400,
Msg: err.Error(),
}
} else {
p.Data["json"] = Response{
Code: 200,
Data: true,
}
}
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()
}

301
controllers/student.go Normal file
View File

@ -0,0 +1,301 @@
package controllers
import (
"OpenPBL/models"
"OpenPBL/util"
"encoding/json"
"github.com/astaxie/beego"
"github.com/casdoor/casdoor-go-sdk/auth"
)
// StudentController
// Operations about Student
type StudentController struct {
beego.Controller
}
func (u *StudentController) GetSessionUser() *auth.Claims {
s := u.GetSession("user")
if s == nil {
return nil
}
claims := &auth.Claims{}
err := util.JsonToStruct(s.(string), claims)
if err != nil {
panic(err)
}
return claims
}
// LearnProject
// @Title
// @Description
// @Param pid path string true ""
// @Success 200 {object} Response
// @Failure 401
// @Failure 403
// @router /learn/:projectId [post]
func (u *StudentController) LearnProject() {
pid, err := u.GetInt64(":projectId")
var resp Response
user := u.GetSessionUser()
if user == nil {
resp = Response{
Code: 401,
Msg: "请先登录",
}
u.Data["json"] = resp
u.ServeJSON()
return
}
if user.Tag != "student" {
resp = Response{
Code: 403,
Msg: "非法的用户",
}
u.Data["json"] = resp
u.ServeJSON()
return
}
uid := user.Username
l := &models.LearnProject{
Avatar: user.Avatar,
Name: user.Name,
StudentId: uid,
ProjectId: pid,
Learning: true,
}
err = l.Create()
if err != nil {
resp = Response{
Code: 400,
Msg: err.Error(),
}
u.Data["json"] = resp
}
resp = Response{
Code: 200,
Msg: "加入成功",
}
u.Data["json"] = resp
u.ServeJSON()
}
// ExitProject
// @Title
// @Description
// @Param pid path string true ""
// @Success 200 {object} Response
// @Failure 401
// @Failure 403
// @router /exit/:projectId [post]
func (u *StudentController) ExitProject() {
pid, err := u.GetInt64(":projectId")
var resp Response
user := u.GetSessionUser()
if user == nil {
resp = Response{
Code: 401,
Msg: "请先登录",
}
u.Data["json"] = resp
u.ServeJSON()
return
}
if user.Tag != "student" {
resp = Response{
Code: 403,
Msg: "非法的用户",
}
u.Data["json"] = resp
u.ServeJSON()
return
}
uid := user.Username
l := &models.LearnProject{
StudentId: uid,
ProjectId: pid,
}
err = l.Delete()
if err != nil {
resp = Response{
Code: 400,
Msg: err.Error(),
}
u.Data["json"] = resp
}
resp = Response{
Code: 200,
Msg: "退出成功",
}
u.Data["json"] = resp
u.ServeJSON()
}
// FinishedProject
// @Title
// @Description
// @Param body body models.LearnProject true ""
// @Success 200 {object} models.Project
// @Failure 403
// @router /finished [post]
func (u *StudentController) FinishedProject() {
var l models.LearnProject
err := json.Unmarshal(u.Ctx.Input.RequestBody, &l)
if err != nil {
u.Data["json"] = map[string]string{"error": err.Error()}
}
err = l.Update()
if err != nil {
u.Data["json"] = map[string]string{"error": err.Error()}
}
u.Data["json"] = map[string]bool{"result": true}
u.ServeJSON()
}
// GetLearnSection
// @Title
// @Description
// @Param body body models.LearnSection true ""
// @Success 200 {object} Response
// @Failure 400
// @router /project/:projectId/section/:sectionId [get]
func (u *StudentController) GetLearnSection() {
var resp Response
user := u.GetSessionUser()
if user == nil {
resp = Response{
Code: 401,
Msg: "请先登录",
}
u.Data["json"] = resp
u.ServeJSON()
return
}
if user.Tag != "student" {
resp = Response{
Code: 403,
Msg: "非法的用户",
}
u.Data["json"] = resp
u.ServeJSON()
return
}
uid := user.Username
sid, err := u.GetInt64(":sectionId")
projectId, err := u.GetInt64(":projectId")
l, err := models.GetLearnSection(sid, uid, projectId)
if err != nil {
u.Data["json"] = Response{
Code: 400,
Msg: err.Error(),
}
} else {
u.Data["json"] = Response{
Code: 200,
Data: l,
}
}
u.ServeJSON()
}
// UpdateLearnSection
// @Title
// @Description
// @Param body body models.LearnSection true ""
// @Success 200 {object} Response
// @Failure 400
// @router /project/:projectId/section/:sectionId [post]
func (u *StudentController) UpdateLearnSection() {
var resp Response
user := u.GetSessionUser()
if user == nil {
resp = Response{
Code: 401,
Msg: "请先登录",
}
u.Data["json"] = resp
u.ServeJSON()
return
}
if user.Tag != "student" {
resp = Response{
Code: 403,
Msg: "非法的用户",
}
u.Data["json"] = resp
u.ServeJSON()
return
}
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,
}
pid, err := u.GetInt64(":projectId")
err = l.Update(pid)
if err != nil {
u.Data["json"] = Response{
Code: 400,
Msg: err.Error(),
}
} else {
u.Data["json"] = Response{
Code: 200,
}
}
u.ServeJSON()
}
// GetLastLearnSection
// @Title
// @Description
// @Success 200 {object} Response
// @Failure 400
// @router /last-learn/project/:projectId [get]
func (u *StudentController) GetLastLearnSection() {
var resp Response
user := u.GetSessionUser()
if user == nil {
resp = Response{
Code: 401,
Msg: "请先登录",
}
u.Data["json"] = resp
u.ServeJSON()
return
}
if user.Tag != "student" {
resp = Response{
Code: 200,
Msg: "",
}
u.Data["json"] = resp
u.ServeJSON()
return
}
uid := user.Username
projectId := u.GetString(":projectId")
l, err := models.GetLastLearnSection(uid, projectId)
if err != nil {
u.Data["json"] = Response{
Code: 400,
Msg: err.Error(),
}
} else {
u.Data["json"] = Response{
Code: 200,
Data: l,
}
}
u.ServeJSON()
}

138
controllers/submit.go Normal file
View File

@ -0,0 +1,138 @@
package controllers
import (
"OpenPBL/models"
"encoding/json"
"strconv"
"time"
)
// CreateSubmit
// @Title
// @Description
// @Param body body models.Submit true ""
// @Success 200 {object}
// @Failure 403 body is empty
// @router /:projectId/task/:taskId/submit [post]
func (p *ProjectController) CreateSubmit() {
var resp Response
user := p.GetSessionUser()
if user == nil {
resp = Response{
Code: 401,
Msg: "请先登录",
}
p.Data["json"] = resp
p.ServeJSON()
return
}
if user.Tag != "student" {
resp = Response{
Code: 403,
Msg: "非法用户",
}
p.Data["json"] = resp
p.ServeJSON()
return
}
uid := user.Username
tid, err := p.GetInt64(":taskId")
pid, err := p.GetInt64(":projectId")
submit := &models.Submit{
ProjectId: pid,
StudentId: uid,
TaskId: tid,
SubmitType: p.GetString("submitType"),
SubmitTitle: p.GetString("submitTitle"),
SubmitIntroduce: p.GetString("submitIntroduce"),
SubmitContent: p.GetString("submitContent"),
FilePath: p.GetString("filePath"),
CreateAt: time.Now(),
Scored: true,
Score: 100,
}
if submit.SubmitType == "file" {
submit.Scored = false
submit.Score = 0
}
var c = make([]models.Choice, 0)
if submit.SubmitType == "survey" {
err = json.Unmarshal([]byte(p.GetString("choices")), &c)
}
err = submit.Create(c)
if err != nil {
p.Data["json"] = Response{
Code: 400,
Msg: err.Error(),
}
} else {
p.Data["json"] = Response{
Code: 200,
Msg: "提交成功",
Data: strconv.FormatInt(submit.Id, 10),
}
}
p.ServeJSON()
}
// UpdateSubmit
// @Title
// @Description
// @Param body body models.Submit true ""
// @Success 200 {object}
// @Failure 403 body is empty
// @router /:projectId/task/:taskId/submit/:submitId [post]
func (p *ProjectController) UpdateSubmit() {
var resp Response
user := p.GetSessionUser()
if user == nil {
resp = Response{
Code: 401,
Msg: "请先登录",
}
p.Data["json"] = resp
p.ServeJSON()
return
}
var uid string
if user.Tag == "student" {
uid = user.Username
}
tid, err := p.GetInt64(":taskId")
sid, err := p.GetInt64(":submitId")
pid, err := p.GetInt64(":projectId")
score, err := p.GetInt("score")
scored, err := p.GetBool("scored")
f := &models.Submit{
Id: sid,
ProjectId: pid,
StudentId: uid,
TaskId: tid,
SubmitType: p.GetString("submitType"),
SubmitTitle: p.GetString("submitTitle"),
SubmitIntroduce: p.GetString("submitIntroduce"),
SubmitContent: p.GetString("submitContent"),
FilePath: p.GetString("filePath"),
CreateAt: time.Now(),
Score: score,
Scored: scored,
}
var c = make([]models.Choice, 0)
if user.Tag == "student" && f.SubmitType == "survey" {
err = json.Unmarshal([]byte(p.GetString("choices")), &c)
}
err = f.Update(c)
if err != nil {
p.Data["json"] = Response{
Code: 400,
Msg: err.Error(),
}
} else {
p.Data["json"] = Response{
Code: 200,
Msg: "提交成功",
}
}
p.ServeJSON()
}

224
controllers/survey.go Normal file
View File

@ -0,0 +1,224 @@
package controllers
import (
"OpenPBL/models"
"strconv"
)
// GetSurveyDetailByTaskId
// @Title
// @Description
// @Param sid path string true ""
// @Success 200 {object}
// @Failure 400
// @router /:projectId/task/:taskId/survey [get]
func (p *ProjectController) GetSurveyDetailByTaskId() {
tid := p.GetString(":taskId")
survey, qns, err := models.GetSurveyByTaskId(tid)
if err != nil {
p.Data["json"] = map[string]interface{}{"survey": nil, "questions": qns}
} else {
p.Data["json"] = map[string]interface{}{"survey": survey, "questions": qns}
}
p.ServeJSON()
}
// CreateSurvey unused
// @Title
// @Description
// @Param body body models.Survey true ""
// @Success 200 {object} Response
// @Failure 400
// @router /:projectId/task/:taskId/survey [post]
func (p *ProjectController) CreateSurvey() {
tid, err := p.GetInt64(":taskId")
survey := &models.Survey{
TaskId: tid,
SurveyTitle: p.GetString("surveyTitle"),
SurveyIntroduce: p.GetString("surveyIntroduce"),
}
if err != nil {
p.Data["json"] = map[string]string{"error": err.Error()}
}
err = survey.Create()
if err != nil {
p.Data["json"] = Response{
Code: 400,
Msg: "创建失败",
}
} else {
p.Data["json"] = Response{
Code: 200,
Msg: "创建成功",
Data: strconv.FormatInt(survey.Id, 10),
}
}
p.ServeJSON()
}
// UpdateSurvey unused
// @Title
// @Description
// @Param body body models.Survey true ""
// @Success 200 {object} Response
// @Failure 400
// @router /:projectId/task/:taskId/survey/:sid [post]
func (p *ProjectController) UpdateSurvey() {
sid, err := p.GetInt64(":sid")
tid, err := p.GetInt64(":tid")
survey := &models.Survey{
Id: sid,
TaskId: tid,
SurveyTitle: p.GetString("surveyTitle"),
SurveyIntroduce: p.GetString("surveyIntroduce"),
}
if err != nil {
p.Data["json"] = map[string]string{"error": err.Error()}
}
err = survey.Create()
if err != nil {
p.Data["json"] = Response{
Code: 400,
Msg: "更新失败",
}
} else {
p.Data["json"] = Response{
Code: 200,
Msg: "更新成功",
}
}
p.ServeJSON()
}
// CreateQuestion
// @Title
// @Description
// @Param body body models.Question true ""
// @Success 200 {object} Response
// @Failure 400
// @router /:projectId/task/:taskId/survey/:surveyId/question [post]
func (p *ProjectController) CreateQuestion() {
sid, err := p.GetInt64(":surveyId")
o, err := p.GetInt("questionOrder")
question := &models.Question{
SurveyId: sid,
QuestionOrder: o,
QuestionType: p.GetString("questionType"),
QuestionTitle: p.GetString("questionTitle"),
QuestionOptions: p.GetString("questionOptions"),
}
if err != nil {
p.Data["json"] = map[string]string{"error": err.Error()}
}
err = question.Create()
if err != nil {
p.Data["json"] = Response{
Code: 400,
Msg: "创建失败",
}
} else {
p.Data["json"] = Response{
Code: 200,
Msg: "创建成功",
Data: strconv.FormatInt(question.Id, 10),
}
}
p.ServeJSON()
}
// UpdateQuestion
// @Title
// @Description
// @Param body body models.Survey true ""
// @Success 200 {object} Response
// @Failure 400
// @router /:projectId/task/:taskId/survey/:surveyId/question/:questionId [post]
func (p *ProjectController) UpdateQuestion() {
qid, err := p.GetInt64(":questionId")
sid, err := p.GetInt64(":surveyId")
o, err := p.GetInt("questionOrder")
question := &models.Question{
Id: qid,
SurveyId: sid,
QuestionOrder: o,
QuestionType: p.GetString("questionType"),
QuestionTitle: p.GetString("questionTitle"),
QuestionOptions: p.GetString("questionOptions"),
}
if err != nil {
p.Data["json"] = map[string]string{"error": err.Error()}
}
err = question.Update()
if err != nil {
p.Data["json"] = Response{
Code: 400,
Msg: "更新失败",
}
} else {
p.Data["json"] = Response{
Code: 200,
Msg: "更新成功",
}
}
p.ServeJSON()
}
// ExchangeQuestion
// @Title
// @Description
// @Param body body string true ""
// @Success 200 {object} Response
// @Failure 400
// @router /:projectId/task/:taskId/survey/:surveyId/questions/exchange [post]
func (p *ProjectController) ExchangeQuestion() {
id1 := p.GetString("questionId1")
id2 := p.GetString("questionId2")
err := models.ExchangeQuestion(id1, id2)
if err != nil {
p.Data["json"] = Response{
Code: 400,
}
} else {
p.Data["json"] = Response{
Code: 200,
Data: true,
}
}
p.ServeJSON()
}
// DeleteQuestion
// @Title
// @Description
// @Param qid path string true ""
// @Success 200 {object} Response
// @Failure 400
// @router /:projectId/task/:taskId/survey/:surveyId/question/:questionId/delete [post]
func (p *ProjectController) DeleteQuestion() {
qid, err := p.GetInt64(":questionId")
question := &models.Question{
Id: qid,
}
if err != nil {
p.Data["json"] = Response{
Code: 400,
Msg: "参数非法",
}
p.ServeJSON()
return
}
err = question.Delete()
if err != nil {
p.Data["json"] = Response{
Code: 400,
Msg: "删除失败",
}
} else {
p.Data["json"] = Response{
Code: 200,
Msg: "删除成功",
}
}
p.ServeJSON()
}

308
controllers/task.go Normal file
View File

@ -0,0 +1,308 @@
package controllers
import (
"OpenPBL/models"
"strconv"
)
type TaskResponse struct {
Response
Tasks []models.TaskDetail `json:"tasks"`
Learning bool `json:"learning"`
Editable bool `json:"editable"`
TeacherScore bool `json:"teacherScore"`
}
// GetSectionTasksDetail
// @Title
// @Description get section tasks with submit and survey
// @Param sid path string true ""
// @Success 200 {object} TaskResponse
// @Failure 400
// @router /:projectId/section/:sectionId/tasks [get]
func (p *ProjectController) GetSectionTasksDetail() {
var resp TaskResponse
sid := p.GetString(":sectionId")
var learning bool
user := p.GetSessionUser()
if user == nil {
resp = TaskResponse{
Response: Response{
Code: 401,
Msg: "请先登录",
},
}
p.Data["json"] = resp
p.ServeJSON()
return
}
if user.Tag != "student" {
learning = false
}
uid := user.Username
pid := p.GetString(":projectId")
learning = models.IsLearningProject(pid, uid)
tasks, err := models.GetSectionTasks(sid, uid, learning)
if err != nil {
p.Data["json"] = TaskResponse{
Response: Response{
Code: 400,
Msg: err.Error(),
},
Tasks: nil,
Learning: false,
}
} else {
p.Data["json"] = TaskResponse{
Response: Response{
Code: 200,
},
Tasks: tasks,
Learning: learning,
Editable: learning,
}
}
p.ServeJSON()
}
// GetProjectTasks
// @Title
// @Description get all the tasks of a section
// @Param sid path string true ""
// @Success 200 {object}
// @Failure 400
// @router /:projectId/tasks [get]
func (p *ProjectController) GetProjectTasks() {
user := p.GetSessionUser()
if user == nil {
p.Data["json"] = TaskResponse{
Response: Response{
Code: 401,
Msg: "请先登录",
},
}
p.ServeJSON()
return
}
pid := p.GetString(":projectId")
tasks, err := models.GetProjectTasks(pid)
if err != nil {
p.Data["json"] = Response{
Code: 400,
Msg: err.Error(),
}
} else {
p.Data["json"] = Response{
Code: 200,
Data: tasks,
}
}
p.ServeJSON()
}
// GetProjectTasksDetail
// @Title
// @Description get all the tasks of a section
// @Param sid path string true ""
// @Success 200 {object}
// @Failure 400
// @router /:projectId/tasks-detail [get]
func (p *ProjectController) GetProjectTasksDetail() {
var resp TaskResponse
var learning bool
user := p.GetSessionUser()
if user == nil {
resp = TaskResponse{
Response: Response{
Code: 401,
Msg: "请先登录",
},
}
p.Data["json"] = resp
p.ServeJSON()
return
}
showSubmit := false
teacherScore := false
uid := user.Username
editable := true
pid := p.GetString(":projectId")
if user.Tag == "teacher" {
uid = p.GetString("studentId")
showSubmit = true
editable = false
teacherScore = true
}
if user.Tag != "student" {
learning = false
} else {
learning = models.IsLearningProject(pid, uid)
}
if learning {
showSubmit = true
}
tasks, err := models.GetProjectTasksDetail(pid, uid, showSubmit)
if err != nil {
p.Data["json"] = TaskResponse{
Response: Response{
Code: 400,
Msg: err.Error(),
},
Tasks: nil,
Learning: false,
}
} else {
p.Data["json"] = TaskResponse{
Response: Response{
Code: 200,
},
Tasks: tasks,
Learning: learning,
Editable: editable,
TeacherScore: teacherScore,
}
}
p.ServeJSON()
}
// CreateTask
// @Title
// @Description
// @Param body body models.Task true ""
// @Success 200 {object} Response
// @Failure 400
// @router /:projectId/task [post]
func (p *ProjectController) CreateTask() {
sid, err := p.GetInt64("sectionId")
pid, err := p.GetInt64(":projectId")
o, err := p.GetInt("taskOrder")
sn, err := p.GetInt("sectionNumber")
cn, err := p.GetInt("chapterNumber")
task := &models.Task{
SectionId: sid,
ProjectId: pid,
SectionNumber: sn,
ChapterNumber: cn,
TaskOrder: o,
TaskTitle: p.GetString("taskTitle"),
TaskIntroduce: p.GetString("taskIntroduce"),
TaskType: p.GetString("taskType"),
}
if err != nil {
p.Data["json"] = map[string]string{"error": err.Error()}
}
err = task.Create()
if err != nil {
p.Data["json"] = Response{
Code: 400,
Msg: "创建失败",
}
} else {
p.Data["json"] = Response{
Code: 200,
Msg: "创建成功",
Data: strconv.FormatInt(task.Id, 10),
}
}
p.ServeJSON()
}
// UpdateTask
// @Title
// @Description
// @Param body body models.Task true ""
// @Success 200 {object} Response
// @Failure 401
// @router /:projectId/task/:taskId [post]
func (p *ProjectController) UpdateTask() {
tid, err := p.GetInt64(":taskId")
sid, err := p.GetInt64("sectionId")
pid, err := p.GetInt64(":projectId")
o, err := p.GetInt("taskOrder")
sn, err := p.GetInt("sectionNumber")
cn, err := p.GetInt("chapterNumber")
task := &models.Task{
Id: tid,
SectionId: sid,
ProjectId: pid,
SectionNumber: sn,
ChapterNumber: cn,
TaskOrder: o,
TaskTitle: p.GetString("taskTitle"),
TaskIntroduce: p.GetString("taskIntroduce"),
TaskType: p.GetString("taskType"),
}
err = task.Update()
if err != nil {
p.Data["json"] = Response{
Code: 400,
Msg: "更新失败",
}
} else {
p.Data["json"] = Response{
Code: 200,
Msg: "更新成功",
Data: true,
}
}
p.ServeJSON()
}
// DeleteTask
// @Title
// @Description
// @Param cid path string true ""
// @Success 200 {object} Response
// @Failure 401
// @router /:projectId/task/:taskId/delete [post]
func (p *ProjectController) DeleteTask() {
tid, err := p.GetInt64(":taskId")
task := &models.Task{
Id: tid,
}
err = task.Delete()
if err != nil {
p.Data["json"] = Response{
Code: 400,
Msg: "删除失败",
}
} else {
p.Data["json"] = Response{
Code: 200,
Msg: "删除成功",
Data: true,
}
}
p.ServeJSON()
}
// ExchangeTask
// @Title
// @Description
// @Param cid path string true ""
// @Success 200 {object} Response
// @Failure 401
// @router /:projectId/tasks/exchange [post]
func (p *ProjectController) ExchangeTask() {
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()
}

19
docker-compose.yml Normal file
View File

@ -0,0 +1,19 @@
version: '3.1'
services:
openpbl:
build:
context: ./
dockerfile: Dockerfile
ports:
- 5000:5000
depends_on:
- openpbl-db
openpbl-db:
restart: always
image: mysql:8.0.25
ports:
- 3308:3306
environment:
MYSQL_ROOT_PASSWORD: root
volumes:
- /usr/local/docker/openpbl-mysql:/var/lib/mysql

11
go.mod Normal file
View File

@ -0,0 +1,11 @@
module OpenPBL
go 1.16
require (
github.com/astaxie/beego v1.12.3
github.com/casdoor/casdoor-go-sdk v0.0.1
github.com/go-sql-driver/mysql v1.6.0
github.com/smartystreets/goconvey v1.6.4
xorm.io/xorm v1.1.2
)

584
go.sum Normal file
View File

@ -0,0 +1,584 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=
cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=
cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=
cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=
cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a h1:lSA0F4e9A2NcQSqGqTOXqu2aRi/XEQxDCBwM8yJtE6s=
gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a/go.mod h1:EXuID2Zs0pAQhH8yz+DNjUbjppKQzKFAn28TMYPB6IU=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/Knetic/govaluate v3.0.0+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alicebob/gopher-json v0.0.0-20180125190556-5a6b3ba71ee6/go.mod h1:SGnFV6hVsYE877CKEZ6tDNTjaSXYUk6QqoIK6PrAtcc=
github.com/alicebob/miniredis v2.5.0+incompatible/go.mod h1:8HZjEj4yU0dwhYHky+DxYx+6BMjkBbe5ONFIF1MXffk=
github.com/astaxie/beego v1.12.3 h1:SAQkdD2ePye+v8Gn1r4X6IKZM1wd28EyUOVQ3PDSOOQ=
github.com/astaxie/beego v1.12.3/go.mod h1:p3qIm0Ryx7zeBHLljmd7omloyca1s4yu1a8kM1FkpIA=
github.com/beego/goyaml2 v0.0.0-20130207012346-5545475820dd/go.mod h1:1b+Y/CofkYwXMUU0OhQqGvsY2Bvgr4j6jfT699wyZKQ=
github.com/beego/x2j v0.0.0-20131220205130-a0352aadc542/go.mod h1:kSeGC/p1AbBiEp5kat81+DSQrZenVBZXklMLaELspWU=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/bradfitz/gomemcache v0.0.0-20180710155616-bc664df96737/go.mod h1:PmM6Mmwb0LSuEubjR8N7PtNe1KxZLtOUHtbeikc5h60=
github.com/casbin/casbin v1.7.0/go.mod h1:c67qKN6Oum3UF5Q1+BByfFxkwKvhwW57ITjqwtzR1KE=
github.com/casdoor/casdoor-go-sdk v0.0.1 h1:zTmejxbl22rKFH4KOJv64oRjB6eE88Ae8vMiz7x3fGE=
github.com/casdoor/casdoor-go-sdk v0.0.1/go.mod h1:PlKduZO7RV1E/Gcpfk5Y1LuzROl5ZELn0PCYPGRF6o8=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY=
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cloudflare/golz4 v0.0.0-20150217214814-ef862a3cdc58/go.mod h1:EOBUe0h4xcZ5GoxqC5SDxFQ8gwyZPKQoEzownBlhI80=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/couchbase/go-couchbase v0.0.0-20200519150804-63f3cdb75e0d/go.mod h1:TWI8EKQMs5u5jLKW/tsb9VwauIrMIxQG1r5fMsswK5U=
github.com/couchbase/gomemcached v0.0.0-20200526233749-ec430f949808/go.mod h1:srVSlQLB8iXBVXHgnqemxUXqN6FCvClgCMPCsjBDR7c=
github.com/couchbase/goutils v0.0.0-20180530154633-e865a1461c8a/go.mod h1:BQwMFlJzDjFDG3DJUdU0KORxn88UlsOULuxLExMh3Hs=
github.com/cupcake/rdb v0.0.0-20161107195141-43ba34106c76/go.mod h1:vYwsqCOLxGiisLwp9rITslkFNpZD5rz43tf41QFkTWY=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/denisenkom/go-mssqldb v0.9.0/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU=
github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/edsrzf/mmap-go v0.0.0-20170320065105-0bce6a688712/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M=
github.com/elastic/go-elasticsearch/v6 v6.8.5/go.mod h1:UwaDJsD3rWLM5rKNFzv9hgox93HoX8utj1kxD9aFUcI=
github.com/elazarl/go-bindata-assetfs v1.0.0 h1:G/bYguwHIzWq9ZoyUQqrjTmJbbYn3j3CKKpKinvZLFk=
github.com/elazarl/go-bindata-assetfs v1.0.0/go.mod h1:v+YaWX3bdea5J/mo8dSETolEo7R71Vk1u8bnjau5yw4=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/glendc/gopher-json v0.0.0-20170414221815-dc4743023d0c/go.mod h1:Gja1A+xZ9BoviGJNA2E9vFkPjjsl+CoJxSXiQM1UXtw=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-redis/redis v6.14.2+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA=
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE=
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/snappy v0.0.0-20170215233205-553a64147049/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db h1:woRePGFeVFfLKN/pOkfl+p/TAqKOfFu+7KPlMVpok/w=
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/gomodule/redigo v2.0.0+incompatible/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.3 h1:x95R7cp+rSeeqAMI2knLtQ0DKlaBhv2NrtrOvafPHRo=
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc=
github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.11 h1:uVUAXhF2To8cbw/3xN3pxj6kk7TYKs98NIrTqPlMWAQ=
github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/ledisdb/ledisdb v0.0.0-20200510135210-d35789ec47e6/go.mod h1:n931TsDuKuq+uX4v1fulaMbA/7ZLLhjc85h7chZGBCQ=
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.7.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
github.com/mattn/go-sqlite3 v2.0.3+incompatible h1:gXHsfypPkaMZrKbD5209QV9jbUTJKjyR5WD3HYQSd+U=
github.com/mattn/go-sqlite3 v2.0.3+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.12.0 h1:Iw5WCbBcaAAd0fpRb1c9r5YCylv4XDoCSigm1zLevwU=
github.com/onsi/ginkgo v1.12.0/go.mod h1:oUhWkIvk5aDxtKvDDuw8gItl8pKl42LzjC9KZE0HfGg=
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/onsi/gomega v1.7.1 h1:K0jcRCwNQM3vFGh1ppMtDh/+7ApJrjldlX8fA0jDTLQ=
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
github.com/pelletier/go-toml v1.0.1/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/peterh/liner v1.0.1-0.20171122030339-3681c2a91233/go.mod h1:xIteQHvHuaLYG9IFj6mSxM0fCKrs34IrEQUhOYuGPHc=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
github.com/prometheus/client_golang v1.7.0 h1:wCi7urQOGBsYcQROHqpUUX4ct84xp40t9R9JX0FuA/U=
github.com/prometheus/client_golang v1.7.0/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M=
github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/common v0.10.0 h1:RyRA7RzGXQZiW+tGMr7sxa85G1z0yOpM1qq5c8lNawc=
github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/procfs v0.1.3 h1:F0+tqvhOksq22sc6iCHF5WGlWjdwj92p0udFh1VFBS8=
github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 h1:OdAsTTz6OkFY5QxjkYwrChwuRruF69c169dPK26NUlk=
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/shiena/ansicolor v0.0.0-20151119151921-a422bbe96644 h1:X+yvsM2yrEktyI+b2qND5gpH8YhURn0k8OCaeRnkINo=
github.com/shiena/ansicolor v0.0.0-20151119151921-a422bbe96644/go.mod h1:nkxAfR/5quYxwPZhyDxgasBMnRtBZd0FCEpawpjMUFg=
github.com/siddontang/go v0.0.0-20170517070808-cb568a3e5cc0/go.mod h1:3yhqj7WBBfRhbBlzyOC3gUxftwsU0u8gqevxwIHQpMw=
github.com/siddontang/goredis v0.0.0-20150324035039-760763f78400/go.mod h1:DDcKzU3qCuvj/tPnimWSsZZzvk9qvkvrIL5naVBPh5s=
github.com/siddontang/rdb v0.0.0-20150307021120-fc89ed2e418d/go.mod h1:AMEsy7v5z92TR1JKMkLLoaOQk++LVnOKL3ScbJ8GNGA=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/ssdb/gossdb v0.0.0-20180723034631-88f6b59b84ec/go.mod h1:QBvMkMya+gXctz3kmljlUCu/yB3GZ6oee+dUozsezQE=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/syndtr/goleveldb v0.0.0-20160425020131-cfa635847112/go.mod h1:Z4AUp2Km+PwemOoO/VB5AOx9XSsIItzFjoJlOSiYmn0=
github.com/syndtr/goleveldb v0.0.0-20181127023241-353a9fca669c/go.mod h1:Z4AUp2Km+PwemOoO/VB5AOx9XSsIItzFjoJlOSiYmn0=
github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE=
github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ=
github.com/ugorji/go v0.0.0-20171122102828-84cb69a8af83/go.mod h1:hnLbHMwcvSihnDhEfx2/BzKp2xb0Y+ErdfYcrs9tkJQ=
github.com/wendal/errors v0.0.0-20130201093226-f66c77a7882b/go.mod h1:Q12BUT7DqIlHRmgv3RskH+UCM/4eqVMgI0EMmlSpAXc=
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/gopher-lua v0.0.0-20171031051903-609c9cd26973/go.mod h1:aEV29XrmTYFr3CiRxZeGHpkvbwq+prZduBqMaascyCU=
github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20201021035429-f5854403a974 h1:IX6qOQeG5uLjB/hjjwjedwfjND0hgjPMMyO1RoIXQNI=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c h1:pkQiBZBvdos9qq4wBAHqlzuZHEXo07pqV06ef90u1WI=
golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201126233918-771906719818/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c h1:VwygUrnw9jn88c4u8GD3rZQbqrP/tgas88tPUbBxQrk=
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20201124115921-2c860bdd6e78 h1:M8tBwCtWD/cZV9DZpFYRUgaymAYAr+aIUTWzDaM3uPs=
golang.org/x/tools v0.0.0-20201124115921-2c860bdd6e78/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/appengine v1.6.6 h1:lMO5rYAqUxkmaj76jAkRUvt5JZgFymx/+Q5Mzfivuhc=
google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=
google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
modernc.org/cc/v3 v3.31.5-0.20210308123301-7a3e9dab9009 h1:u0oCo5b9wyLr++HF3AN9JicGhkUxJhMz51+8TIZH9N0=
modernc.org/cc/v3 v3.31.5-0.20210308123301-7a3e9dab9009/go.mod h1:0R6jl1aZlIl2avnYfbfHBS1QB6/f+16mihBObaBC878=
modernc.org/ccgo/v3 v3.9.0 h1:JbcEIqjw4Agf+0g3Tc85YvfYqkkFOv6xBwS4zkfqSoA=
modernc.org/ccgo/v3 v3.9.0/go.mod h1:nQbgkn8mwzPdp4mm6BT6+p85ugQ7FrGgIcYaE7nSrpY=
modernc.org/httpfs v1.0.6 h1:AAgIpFZRXuYnkjftxTAZwMIiwEqAfk8aVB2/oA6nAeM=
modernc.org/httpfs v1.0.6/go.mod h1:7dosgurJGp0sPaRanU53W4xZYKh14wfzX420oZADeHM=
modernc.org/libc v1.7.13-0.20210308123627-12f642a52bb8/go.mod h1:U1eq8YWr/Kc1RWCMFUWEdkTg8OTcfLw2kY8EDwl039w=
modernc.org/libc v1.8.0 h1:Pp4uv9g0csgBMpGPABKtkieF6O5MGhfGo6ZiOdlYfR8=
modernc.org/libc v1.8.0/go.mod h1:U1eq8YWr/Kc1RWCMFUWEdkTg8OTcfLw2kY8EDwl039w=
modernc.org/mathutil v1.1.1/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
modernc.org/mathutil v1.2.2 h1:+yFk8hBprV+4c0U9GjFtL+dV3N8hOJ8JCituQcMShFY=
modernc.org/mathutil v1.2.2/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
modernc.org/memory v1.0.4 h1:utMBrFcpnQDdNsmM6asmyH/FM9TqLPS7XF7otpJmrwM=
modernc.org/memory v1.0.4/go.mod h1:nV2OApxradM3/OVbs2/0OsP6nPfakXpi50C7dcoHXlc=
modernc.org/opt v0.1.1 h1:/0RX92k9vwVeDXj+Xn23DKp2VJubL7k8qNffND6qn3A=
modernc.org/opt v0.1.1/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=
modernc.org/sqlite v1.10.1-0.20210314190707-798bbeb9bb84 h1:rgEUzE849tFlHSoeCrKyS9cZAljC+DY7MdMHKq6R6sY=
modernc.org/sqlite v1.10.1-0.20210314190707-798bbeb9bb84/go.mod h1:PGzq6qlhyYjL6uVbSgS6WoF7ZopTW/sI7+7p+mb4ZVU=
modernc.org/strutil v1.1.0 h1:+1/yCzZxY2pZwwrsbH+4T7BQMoLQ9QiBshRC9eicYsc=
modernc.org/strutil v1.1.0/go.mod h1:lstksw84oURvj9y3tn8lGvRxyRC1S2+g5uuIzNfIOBs=
modernc.org/tcl v1.5.0 h1:euZSUNfE0Fd4W8VqXI1Ly1v7fqDJoBuAV88Ea+SnaSs=
modernc.org/tcl v1.5.0/go.mod h1:gb57hj4pO8fRrK54zveIfFXBaMHK3SKJNWcmRw1cRzc=
modernc.org/token v1.0.0 h1:a0jaWiNMDhDUtqOj09wvjWWAqd3q7WpBulmL9H2egsk=
modernc.org/token v1.0.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
modernc.org/z v1.0.1-0.20210308123920-1f282aa71362/go.mod h1:8/SRk5C/HgiQWCgXdfpb+1RvhORdkz5sw72d3jjtyqA=
modernc.org/z v1.0.1 h1:WyIDpEpAIx4Hel6q/Pcgj/VhaQV5XPJ2I6ryIYbjnpc=
modernc.org/z v1.0.1/go.mod h1:8/SRk5C/HgiQWCgXdfpb+1RvhORdkz5sw72d3jjtyqA=
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
xorm.io/builder v0.3.8 h1:P/wPgRqa9kX5uE0aA1/ukJ23u9KH0aSRpHLwDKXigSE=
xorm.io/builder v0.3.8/go.mod h1:aUW0S9eb9VCaPohFCH3j7czOx1PMW3i1HrSzbLYGBSE=
xorm.io/xorm v1.1.2 h1:bje+1KZvK3m5AHtZNfUDlKEEyuw/IRHT+an0CLIG5TU=
xorm.io/xorm v1.1.2/go.mod h1:Cb0DKYTHbyECMaSfgRnIZp5aiUgQozxcJJ0vzcLGJSg=

53
main.go Normal file
View File

@ -0,0 +1,53 @@
package main
import (
"OpenPBL/controllers"
"OpenPBL/models"
"OpenPBL/routers"
_ "OpenPBL/routers"
"github.com/astaxie/beego"
"github.com/astaxie/beego/plugins/cors"
"log"
"os"
)
func main() {
mode := os.Getenv("RUNMODE")
var err error
if mode == "prod" {
err = beego.LoadAppConfig("ini", "conf/app-prod.conf")
} else if mode == "dev" {
err = beego.LoadAppConfig("ini", "conf/app-dev.conf")
} else {
err = beego.LoadAppConfig("ini", "conf/app-dev.conf")
}
if err != nil {
panic(err)
}
log.Println("App start with runmode: " + mode)
models.InitAdapter()
controllers.InitCasdoor()
beego.InsertFilter("*", beego.BeforeRouter, cors.Allow(&cors.Options{
AllowOrigins: []string{"*"},
AllowMethods: []string{"*"},
AllowHeaders: []string{"Origin"},
ExposeHeaders: []string{"Content-Length"},
AllowCredentials: true,
}))
if beego.BConfig.RunMode == "dev" {
beego.BConfig.WebConfig.DirectoryIndex = true
beego.BConfig.WebConfig.StaticDir["/swagger"] = "swagger"
}
beego.SetStaticPath("/static", "openpbl-landing/build/static")
beego.BConfig.WebConfig.DirectoryIndex = true
beego.InsertFilter("/", beego.BeforeRouter, routers.TransparentStatic)
beego.InsertFilter("/*", beego.BeforeRouter, routers.TransparentStatic)
beego.BConfig.WebConfig.Session.SessionName = "openct_session_id"
beego.BConfig.WebConfig.Session.SessionProvider = "file"
beego.BConfig.WebConfig.Session.SessionProviderConfig = "./tmp"
beego.BConfig.WebConfig.Session.SessionGCMaxLifetime = 3600 * 24 * 365
beego.Run()
}

108
models/adapter.go Normal file
View File

@ -0,0 +1,108 @@
package models
import (
"fmt"
"runtime"
"github.com/astaxie/beego"
_ "github.com/go-sql-driver/mysql"
"xorm.io/xorm"
)
var adapter *Adapter
func InitAdapter() {
adapter = NewAdapter(beego.AppConfig.String("driverName"), beego.AppConfig.String("dataSourceName"), beego.AppConfig.String("dbName"))
adapter.createTable()
}
// Adapter represents the MySQL adapter for policy storage.
type Adapter struct {
driverName string
dataSourceName string
dbName string
Engine *xorm.Engine
}
// finalizer is the destructor for Adapter.
func finalizer(a *Adapter) {
err := a.Engine.Close()
if err != nil {
panic(err)
}
}
// NewAdapter is the constructor for Adapter.
func NewAdapter(driverName string, dataSourceName string, dbName string) *Adapter {
a := &Adapter{}
a.driverName = driverName
a.dataSourceName = dataSourceName
a.dbName = dbName
// Open the DB, create it if not existed.
a.open()
// Call the destructor when the object is released.
runtime.SetFinalizer(a, finalizer)
return a
}
func (a *Adapter) createDatabase() error {
engine, err := xorm.NewEngine(a.driverName, a.dataSourceName)
if err != nil {
return err
}
defer engine.Close()
_, err = engine.Exec(fmt.Sprintf("CREATE DATABASE IF NOT EXISTS %s default charset utf8 COLLATE utf8_general_ci", a.dbName))
return err
}
func (a *Adapter) open() {
if a.driverName != "postgres" {
if err := a.createDatabase(); err != nil {
panic(err)
}
}
engine, err := xorm.NewEngine(a.driverName, a.dataSourceName+a.dbName)
if err != nil {
panic(err)
}
a.Engine = engine
}
func (a *Adapter) close() {
a.Engine.Close()
a.Engine = nil
}
func (a *Adapter) createTable() {
err := a.Engine.Sync2(
new(Project),
new(LearnProject),
new(LearnSection),
new(LastLearn),
new(Chapter),
new(Section),
new(Resource),
new(Task),
new(Survey),
new(Question),
new(Submit),
new(Choice),
new(ProjectSkill),
new(ProjectSubject),
)
if err != nil {
fmt.Println(err)
panic(err)
}
}

78
models/chapter.go Normal file
View File

@ -0,0 +1,78 @@
package models
import (
"fmt"
"xorm.io/xorm"
)
type Chapter struct {
Id int64 `json:"id" xorm:"not null pk autoincr"`
ProjectId int64 `json:"projectId" xorm:"index"`
ChapterName string `json:"chapterName"`
ChapterNumber int `json:"chapterNumber" xorm:"index"`
}
type Outline struct {
Chapter `xorm:"extends"`
Sections []SectionMinute `json:"sections" xorm:"extends"`
}
func (p *Chapter) GetEngine() *xorm.Session {
return adapter.Engine.Table(p)
}
func (p *Chapter) Create() (err error) {
_, err = p.GetEngine().Insert(p)
return
}
func (p *Chapter) Update() (err error) {
_, err = p.GetEngine().ID(p.Id).Update(p)
return
}
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)
fmt.Println(err)
_, err = session.Table(&Section{}).Delete(Section{ChapterId: p.Id})
_, err = session.Table(&Chapter{}).ID(p.Id).Delete(p)
session.Commit()
return
}
func ExchangeChapters(cid1 string, cid2 string) (err error) {
_, err = adapter.Engine.
Exec("update chapter c1 join chapter c2 on (c1.id = ? and c2.id = ?) " +
"set c1.chapter_number = c2.chapter_number, c2.chapter_number = c1.chapter_number", cid1, cid2)
return
}
func GetChaptersByPid(pid string, uid string) (outline []Outline, err error) {
var c []Chapter
err = (&Chapter{}).GetEngine().
Where("project_id = ?", pid).
Asc("chapter_number").
Find(&c)
outline = make([]Outline, len(c))
for i:=0; i< len(c); i++ {
sections := make([]SectionMinute, 0)
outline[i].Chapter = c[i]
if uid == "" {
err = (&Section{}).GetEngine().
Where("chapter_id = ?", c[i].Id).
Asc("section_number").
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 = ? order by s.section_number", c[i].Id, uid).
Find(&sections)
}
outline[i].Sections = sections
}
return
}

29
models/comment.go Normal file
View File

@ -0,0 +1,29 @@
package models
import "time"
type Comment struct {
Id int64 `json:"id" xorm:"not null pk autoincr"`
ProjectId int64 `json:"projectId" xorm:"not null index"`
UserId int64 `json:"userId" xorm:"not null index"`
IsTeacher bool `json:"isTeacher" xorm:"not null index default false"`
Content string `json:"content" xorm:"text"`
CreateAt time.Time `json:"createAt" xorm:"created"`
}
type CommentReply struct {
Id int64 `json:"id" xorm:"not null pk autoincr"`
CommentId int64 `json:"commentId" xorm:"not null index"`
UserId int64 `json:"userId" xorm:"not null index"`
IsTeacher bool `json:"isTeacher" xorm:"not null index default false"`
Content string `json:"content" xorm:"text"`
CreateAt time.Time `json:"createAt" xorm:"created"`
}

25
models/evaluate.go Normal file
View File

@ -0,0 +1,25 @@
package models
type Evaluate struct {
Chapter `xorm:"extends"`
SectionOutline `xorm:"extends"`
}
func GetProjectEvaluate(pid string) (evaluate []Evaluate, err error) {
var c []Chapter
err = (&Chapter{}).GetEngine().
Where("project_id = ?", pid).
Asc("chapter_number").
Find(&c)
evaluate = make([]Evaluate, len(c))
var outline SectionOutline
for i:=0; i< len(c); i++ {
evaluate[i].Chapter = c[i]
err = (&Section{}).GetEngine().
Where("chapter_id = ?", c[i].Id).
Find(&outline)
evaluate[i].SectionOutline = outline
}
return
}

64
models/generateSql.go Normal file
View File

@ -0,0 +1,64 @@
package models
import (
"bytes"
"strings"
)
func getSubjectExistSql(subject string) (sql string) {
if subject == "" {
return ""
} else {
s := strings.Split(subject, ",")
sql := `
and exists (
select project_subject.project_id from project_subject where project_subject.project_id = project.id and (`
var buf bytes.Buffer
buf.WriteString(sql)
n := len(s)
for i:=0; i<n; i++ {
buf.WriteString(" project_subject.subject = '")
buf.WriteString(s[i])
buf.WriteString("' ")
if i < n-1 {
buf.WriteString(" or ")
}
}
buf.WriteString(" ) ) ")
return buf.String()
}
}
func getSkillExistSql(skill string) (sql string) {
if skill == "" {
return ""
} else {
s := strings.Split(skill, ",")
sql := `
and exists (
select project_skill.project_id from project_skill where project_skill.project_id = project.id and (`
var buf bytes.Buffer
buf.WriteString(sql)
n := len(s)
for i:=0; i<n; i++ {
buf.WriteString(" project_skill.skill = '")
buf.WriteString(s[i])
buf.WriteString("' ")
if i < n-1 {
buf.WriteString(" or ")
}
}
buf.WriteString(" ) ) ")
return buf.String()
}
}
func getTextSql(text string) (sql string) {
if text == "" {
return ""
} else {
var buf bytes.Buffer
buf.WriteString(" and project.project_title like '%")
buf.WriteString(text)
buf.WriteString("%'")
return buf.String()
}
}

158
models/project.go Normal file
View File

@ -0,0 +1,158 @@
package models
import (
"errors"
"time"
"xorm.io/xorm"
)
type Project struct {
Id int64 `json:"id" xorm:"not null pk autoincr"`
Image string `json:"image" xorm:"longtext"`
ProjectTitle string `json:"projectTitle"`
ProjectIntroduce string `json:"projectIntroduce"`
ProjectGoal string `json:"projectGoal"`
TeacherId string `json:"teacherId" xorm:"index"`
Subjects string `json:"subjects" xorm:"default ''"`
Skills string `json:"skills" xorm:"default ''"`
Closed bool `json:"closed" xorm:"default false index"`
CreateAt time.Time `json:"createAt" xorm:"created"`
PublishedAt time.Time `json:"publishedAt"`
ClosedAt time.Time `json:"closedAt"`
ReadNum int64 `json:"readNum" xorm:"default 0"`
JoinNum int64 `json:"joinNum" xorm:"default 0"`
Published bool `json:"published" xorm:"default false index"`
LearnMinuteWeight int `json:"learnMinuteWeight" xorm:"default 100"`
}
type ProjectDetail struct {
Project `xorm:"extends"`
Learning bool `json:"learning"`
}
type ProjectSkill struct {
Skill string `json:"skill" xorm:"not null pk"`
ProjectId int64 `json:"projectId" xorm:"not null pk"`
}
type ProjectSubject struct {
Subject string `json:"subject" xorm:"not null pk"`
ProjectId int64 `json:"projectId" xorm:"not null pk"`
}
func (p *Project) GetEngine() *xorm.Session {
return adapter.Engine.Table(p)
}
func (p *ProjectSkill) GetEngine() *xorm.Session {
return adapter.Engine.Table(p)
}
func (p *ProjectSubject) GetEngine() *xorm.Session {
return adapter.Engine.Table(p)
}
func GetProjectByPidForTeacher(pid string) (pd ProjectDetail, err error) {
var p Project
c, err := (&Project{}).GetEngine().
ID(pid).
Get(&p)
pd = ProjectDetail{
Project: p,
Learning: false,
}
if !c {
err = errors.New("404")
}
return
}
func GetProjectByPidForStudent(pid string, uid string) (pd ProjectDetail, err error) {
c, err := (&Project{}).GetEngine().
Where("project.id = ?", pid).
Join("LEFT OUTER", LearnProject{}, "project.id = learn_project.project_id and student_id = ?", uid).
Get(&pd)
if !c {
err = errors.New("404")
}
return
}
func (p *Project) Create() (err error) {
_, err = p.GetEngine().Insert(p)
return
}
func (p *ProjectSkill) Create() (err error) {
_, err = p.GetEngine().Insert(p)
return
}
func (p *ProjectSubject) Create() (err error) {
_, err = p.GetEngine().Insert(p)
return
}
func (p *Project) Update() (err error) {
_, err = p.GetEngine().ID(p.Id).Update(p)
return
}
func UpdateWeight(p Project, t[]Task) (err error) {
session := adapter.Engine.NewSession()
defer session.Close()
session.Begin()
_, err = session.Table(&Project{}).ID(p.Id).Update(&p)
for i:=0; i< len(t); i++ {
(&t[i]).Update()
}
session.Commit()
return
}
func (p *Project) UpdateInfo(subjects []*ProjectSubject, skills []*ProjectSkill) (err error) {
_, err = p.GetEngine().ID(p.Id).Update(p)
n1 := len(subjects)
for i:=0; i<n1; i++ {
_ = subjects[i].Create()
}
n2 := len(skills)
for i:=0; i<n2; i++ {
_ = skills[i].Create()
}
return
}
func (p *Project) Delete() (err error) {
_, 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)
return
}
func UpdatePublished(p Project) (err error) {
_, err = (&Project{}).GetEngine().
Where("id = ?", p.Id).
Cols("published", "publish_at").
Update(p)
return
}
func UpdateClosed(p Project) (err error) {
_, err = (&Project{}).GetEngine().
Where("id = ?", p.Id).
Cols("closed", "closed_at").
Update(p)
return
}

115
models/projectList.go Normal file
View File

@ -0,0 +1,115 @@
package models
import (
"fmt"
)
// ============= student ===================
func GetMyProjectListBySid(sid string, from int, size int,
subject string, skill string, text string, orderBy string, orderType string, learning bool) (p []ProjectDetail, rows int64, err error) {
const baseSql = `
select %s from (
select * from project
inner join learn_project on (
learn_project.student_id = '%s' and
learn_project.learning = %v and
project.id = learn_project.project_id
)
) as project where true
%s %s %s
`
const pageSql = " order by %s %s limit %d, %d "
e1 := getSubjectExistSql(subject)
e2 := getSkillExistSql(skill)
e3 := getTextSql(text)
sql1 := fmt.Sprintf(baseSql, "*", sid, learning, e1, e2, e3) +
fmt.Sprintf(pageSql, orderBy, orderType, from, size)
sql2 := fmt.Sprintf(baseSql, "count(*)", sid, learning, e1, e2, e3)
err = adapter.Engine.
SQL(sql1).
Find(&p)
_, err = adapter.Engine.
SQL(sql2).
Get(&rows)
return
}
func GetPublicProjectListForStudent(sid string, from int, size int,
subject string, skill string, text string, orderBy string, orderType string) (p []ProjectDetail, rows int64, err error) {
const baseSql = `
select %s from (
select * from project where project.published = true
%s %s %s
) as p1 left join learn_project on (
p1.id = learn_project.project_id and learn_project.student_id = '%s'
)
`
const pageSql = " order by p1.%s %s limit %d, %d "
e1 := getSubjectExistSql(subject)
e2 := getSkillExistSql(skill)
e3 := getTextSql(text)
sql1 := fmt.Sprintf(baseSql, "*", e1, e2, e3, sid) +
fmt.Sprintf(pageSql, orderBy, orderType, from, size)
sql2 := fmt.Sprintf(baseSql, "count(*)", e1, e2, e3, sid)
err = adapter.Engine.
SQL(sql1).
Find(&p)
_, err = adapter.Engine.
SQL(sql2).
Get(&rows)
return
}
// ============= teacher ===================
func GetMyProjectListByTid(tid string, from int, size int,
subject string, skill string, text string, orderBy string, orderType string, published bool, closed bool) (p []ProjectDetail, rows int64, err error) {
const baseSql = `
select %s from project where teacher_id = '%s'
and published = %v
and closed = %v
%s %s %s
`
const pageSql = " order by %s %s limit %d, %d "
e1 := getSubjectExistSql(subject)
e2 := getSkillExistSql(skill)
e3 := getTextSql(text)
sql1 := fmt.Sprintf(baseSql, "*", tid, published, closed, e1, e2, e3) +
fmt.Sprintf(pageSql, orderBy, orderType, from, size)
sql2 := fmt.Sprintf(baseSql, "count(*)", tid, published, closed, e1, e2, e3)
err = adapter.Engine.
SQL(sql1).
Find(&p)
_, err = adapter.Engine.
SQL(sql2).
Get(&rows)
return
}
func GetPublicProjectListForTeacher(sid string, from int, size int,
subject string, skill string, text string, orderBy string, orderType string) (p []ProjectDetail, rows int64, err error) {
baseSql := "select %s from project where published = true %s %s %s "
pageSql := " order by %s %s limit %d, %d "
e1 := getSubjectExistSql(subject)
e2 := getSkillExistSql(skill)
e3 := getTextSql(text)
sql1 := fmt.Sprintf(baseSql, "*", e1, e2, e3) +
fmt.Sprintf(pageSql, orderBy, orderType, from, size)
sql2 := fmt.Sprintf(baseSql, "count(*)", e1, e2, e3)
err = adapter.Engine.
SQL(sql1).
Find(&p)
_, err = adapter.Engine.
SQL(sql2).
Get(&rows)
return
}

36
models/resource.go Normal file
View File

@ -0,0 +1,36 @@
package models
import (
"xorm.io/xorm"
)
type Resource struct {
Id int64 `json:"id" xorm:"not null pk autoincr"`
SectionId int64 `json:"sectionId" xorm:"not null index unique"`
Content string `json:"content" xorm:"longtext"`
FileTitle string `json:"fileTitle"`
FileIntroduce string `json:"fileIntroduce"`
FilePath string `json:"filePath"`
}
func (p *Resource) GetEngine() *xorm.Session {
return adapter.Engine.Table(p)
}
func (p *Resource) Create() (err error) {
_, err = p.GetEngine().Insert(p)
return
}
func (p *Resource) Update() (err error) {
_, err = p.GetEngine().
ID(p.Id).
Update(p)
return
}
func GetResourceById(id string) (r Resource, err error) {
_, err = (&Resource{}).GetEngine().
ID(id).
Get(&r)
return
}

94
models/section.go Normal file
View File

@ -0,0 +1,94 @@
package models
import (
"fmt"
"xorm.io/xorm"
)
type Section struct {
Id int64 `json:"id" xorm:"not null pk autoincr"`
ChapterId int64 `json:"chapterId" xorm:"index"`
SectionName string `json:"sectionName"`
SectionNumber int `json:"sectionNumber" xorm:"index"`
ChapterNumber int `json:"chapterNumber" xorm:"index"`
SectionMinute int `json:"sectionMinute" xorm:"default 10"`
}
type SectionMinute struct {
Section `xorm:"extends"`
LearnSection `xorm:"extends"`
}
type SectionDetail struct {
Section `xorm:"extends"`
Resource `json:"resource" xorm:"extends"`
}
type SectionOutline struct {
Section `xorm:"extends"`
Tasks []Task `json:"tasks" xorm:"extends"`
}
func (p *Section) GetEngine() *xorm.Session {
return adapter.Engine.Table(p)
}
func (p *Section) Create() (err error) {
session := adapter.Engine.NewSession()
defer session.Close()
session.Begin()
_, err = session.Insert(p)
_, err = session.Insert(Resource{
SectionId: p.Id,
})
session.Commit()
return
}
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) {
session := adapter.Engine.NewSession()
defer session.Close()
session.Begin()
_, err = session.Engine().
Exec("update section set section_number = section_number - 1 " +
"where chapter_id = ? and section_number > ?", p.ChapterId, p.SectionNumber)
_, err = session.Table(&Section{}).ID(p.Id).Delete(p)
session.Commit()
return
}
func ExchangeSections(id1 string, id2 string) (err error) {
_, err = adapter.Engine.
Exec("update section c1 join section c2 on (c1.id = ? and c2.id = ?) " +
"set c1.section_number = c2.section_number, c2.section_number = c1.section_number", id1, id2)
return
}
func GetSectionsByCid(cid string) (s []Section, err error) {
err = (&Section{}).GetEngine().
Where("chapter_id = ?", cid).
Asc("section_number").
Find(&s)
return
}
func GetSectionDetailById(sid string) (s SectionDetail, err error) {
_, err = adapter.Engine.
Table("section").
Where("section.id = ?", sid).
Join("INNER", "resource", "resource.section_id = section.id").
Get(&s)
return
}

View File

@ -0,0 +1,23 @@
select * from (
select * from project
inner join learn_project on (
learn_project.student_id = 1 and
learn_project.learning = true and
project.id = learn_project.project_id
)
) as project where true
and exists (
select project_subject.project_id from project_subject
where project_subject.project_id = project.id and (
project_subject.subject = '英语' or project_subject.subject = '数学'
)
)
and exists (
select project_skill.project_id from project_skill
where project_skill.project_id = project.id and (
project_skill.skill = '生活与职业技能'
)
)
and project.project_title like '%神奇%'
order by create_at desc limit 0, 10

View File

@ -0,0 +1,18 @@
select * from project where teacher_id = 1
and published = true
and closed = false
and exists (
select project_subject.project_id from project_subject
where project_subject.project_id = project.id and (
project_subject.subject = '英语' or project_subject.subject = '数学'
)
)
and exists (
select project_skill.project_id from project_skill
where project_skill.project_id = project.id and (
project_skill.skill = '生活与职业技能'
)
)
and project.project_title like '%神奇%'
order by create_at desc limit 0, 10

View File

@ -0,0 +1,19 @@
select * from (
select * from project where project.published = true
and exists (
select project_subject.project_id from project_subject
where project_subject.project_id = project.id and (
project_subject.subject = '英语' or project_subject.subject = '数学'
)
)
and exists (
select project_skill.project_id from project_skill
where project_skill.project_id = project.id and (
project_skill.skill = '生活与职业技能'
)
)
and project.project_title like '%%'
) as p1 left join learn_project on (
p1.id = learn_project.project_id and learn_project.student_id = 1
)
order by p1.create_at desc limit 0, 10

View File

@ -0,0 +1,16 @@
select * from project where published = true
and exists (
select project_subject.project_id from project_subject
where project_subject.project_id = project.id and (
project_subject.subject = '英语' or project_subject.subject = '数学'
)
)
and exists (
select project_skill.project_id from project_skill
where project_skill.project_id = project.id and (
project_skill.skill = '生活与职业技能'
)
)
and project.project_title like '%神奇%'
order by create_at desc limit 0, 10

154
models/student.go Normal file
View File

@ -0,0 +1,154 @@
package models
import (
"strconv"
"time"
"xorm.io/xorm"
)
type LearnProject struct {
Avatar string `json:"avatar" xorm:"text"`
Name string `json:"name"`
StudentId string `json:"studentId" xorm:"not null index pk"`
ProjectId int64 `json:"projectId" xorm:"not null index pk"`
Learning bool `json:"learning" xorm:"index default 0"`
JoinTime time.Time `json:"joinTime" xorm:"created"`
}
type LearnSection struct {
StudentId string `json:"studentId" xorm:"not null pk"`
SectionId int64 `json:"sectionId" xorm:"not null pk"`
LearnMinute int `json:"learnMinute" xorm:"default 0"`
LearnSecond int `json:"learnSecond" xorm:"default 0"`
}
type LastLearn struct {
StudentId string `json:"studentId" xorm:"not null pk"`
ProjectId int64 `json:"projectId" xorm:"not null pk"`
SectionId int64 `json:"sectionId" xorm:"not null index"`
ExitAt time.Time `json:"exitAt" xorm:"updated"`
}
type LastLearnSection struct {
LastLearn `xorm:"extends"`
Id int64 `json:"id"`
SectionName string `json:"sectionName"`
ChapterNumber int `json:"chapterNumber"`
SectionNumber int `json:"sectionNumber"`
Last bool `json:"last"`
}
func (l *LearnProject) GetEngine() *xorm.Session {
return adapter.Engine.Table(l)
}
func (l *LearnSection) GetEngine() *xorm.Session {
return adapter.Engine.Table(l)
}
func (l *LastLearn) GetEngine() *xorm.Session {
return adapter.Engine.Table(l)
}
func (l *LearnProject) Create() (err error) {
_, err = (&LearnProject{}).GetEngine().Insert(l)
_, err = adapter.Engine.
Exec("update project set join_num = join_num + 1 where id = ?", l.ProjectId)
return
}
func (l *LearnProject) Update() (err error) {
_, err = (&LearnProject{}).GetEngine().
Where("student_id = ?", l.StudentId).
Where("project_id = ?", l.ProjectId).
MustCols("learning").
Update(l)
return
}
func (l *LearnProject) Delete() (err error) {
_, err = (&LearnProject{}).GetEngine().
Where("student_id = ?", l.StudentId).
Where("project_id = ?", l.ProjectId).
Delete(l)
_, err = adapter.Engine.
Exec("update project set join_num = join_num - 1 where id = ?", l.ProjectId)
return
}
func IsLearningProject(pid string, uid string) (e bool) {
var err error
id, err := strconv.ParseInt(pid, 10, 64)
e, err = (&LearnProject{}).GetEngine().Exist(&LearnProject{
StudentId: uid,
ProjectId: id,
Learning: true,
})
if err != nil {
e = false
}
return
}
func GetProjectStudents(pid string, from int, size int) (s []LearnProject, rows int64, err error) {
err = (&LearnProject{}).GetEngine().
Where("project_id = ?", pid).
Desc("join_time").
Limit(size, from).
Find(&s)
rows, err = (&LearnProject{}).GetEngine().
Where("project_id = ?", pid).
Count()
return
}
func GetLearnSection(sectionId int64, studentId string, projectId int64) (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
err = (&l).Create(projectId)
}
return
}
func GetLastLearnSection(studentId string, projectId string) (l LastLearnSection, err error) {
var b bool
b, err = (&LastLearn{}).GetEngine().
Where("student_id = ?", studentId).
Where("project_id = ?", projectId).
Join("LEFT OUTER", Section{}, "last_learn.section_id = section.id").
Get(&l)
l.Last = b
return
}
func (l *LearnSection) Create(projectId int64) (err error) {
_, err = (&LearnSection{}).GetEngine().Insert(l)
_, err = (&LastLearn{}).GetEngine().Insert(&LastLearn{
StudentId: l.StudentId,
ProjectId: projectId,
SectionId: l.SectionId,
ExitAt: time.Now(),
})
return
}
func (l *LearnSection) Update(projectId int64) (err error) {
_, err = (&LearnSection{}).GetEngine().
Where("student_id = ?", l.StudentId).
Where("section_id = ?", l.SectionId).
Update(l)
_, err = (&LastLearn{}).GetEngine().
Where("student_id = ?", l.StudentId).
Where("project_id = ?", projectId).
Update(&LastLearn{
StudentId: l.StudentId,
ProjectId: projectId,
SectionId: l.SectionId,
ExitAt: time.Now(),
})
return
}

89
models/submit.go Normal file
View File

@ -0,0 +1,89 @@
package models
import (
"time"
"xorm.io/xorm"
)
type Submit struct {
Id int64 `json:"id" xorm:"not null pk autoincr"`
ProjectId int64 `json:"projectId" xorm:"not null index"`
StudentId string `json:"studentId" xorm:"not null index"`
TaskId int64 `json:"taskId" xorm:"not null index"`
SubmitType string `json:"submitType" xorm:"index"`
SubmitTitle string `json:"submitTitle"`
SubmitIntroduce string `json:"submitIntroduce" xorm:"text"`
SubmitContent string `json:"submitContent" xorm:"text"`
FilePath string `json:"filePath"`
CreateAt time.Time `json:"createAt"`
Score int `json:"score" xorm:"default 0"`
Scored bool `json:"scored" xorm:"default false"`
}
type Choice struct {
Id int64 `json:"id" xorm:"not null pk autoincr"`
SubmitId int64 `json:"submitId" xorm:"not null index"`
ChoiceOrder int `json:"choiceOrder"`
ChoiceOptions string `json:"choiceOptions" xorm:"text"`
}
type SubmitDetail struct {
Submit `json:"submit" xorm:"extends"`
Choices []Choice `json:"choices" xorm:"extends"`
Submitted bool `json:"submitted"`
}
func (p *Submit) GetEngine() *xorm.Session {
return adapter.Engine.Table(p)
}
func (c *Choice) GetEngine() *xorm.Session {
return adapter.Engine.Table(c)
}
func (c *Choice) Create() (err error) {
_, err = c.GetEngine().Insert(c)
return
}
func (c *Choice) Update() (err error) {
_, err = c.GetEngine().ID(c.Id).Update(c)
return
}
func (p *Submit) Create(c []Choice) (err error) {
_, err = p.GetEngine().Insert(p)
if p.SubmitType == "survey" {
for i := 0; i < len(c); i ++ {
ci := &Choice{
SubmitId: p.Id,
ChoiceOrder: c[i].ChoiceOrder,
ChoiceOptions: c[i].ChoiceOptions,
}
err = ci.Create()
}
CountSubmit(c, nil, p.TaskId)
}
return
}
func (p *Submit) Update(c []Choice) (err error) {
_, err = p.GetEngine().ID(p.Id).Update(p)
if len(c) > 0 && p.SubmitType == "survey" {
var cs []Choice
err = (&Choice{}).GetEngine().
Where("submit_id = ?", p.Id).
Find(&cs)
for i:=0; i< len(c); i++ {
err = (&c[i]).Update()
}
CountSubmit(c, cs, p.TaskId)
}
return
}
func CountSubmit(c []Choice, cl []Choice, taskId int64) {
}

76
models/survey.go Normal file
View File

@ -0,0 +1,76 @@
package models
import (
"xorm.io/xorm"
)
type Survey struct {
Id int64 `json:"id" xorm:"not null pk autoincr"`
TaskId int64 `json:"taskId" xorm:"not null index"`
SurveyTitle string `json:"surveyTitle"`
SurveyIntroduce string `json:"surveyIntroduce"`
}
type Question struct {
Id int64 `json:"id" xorm:"not null pk autoincr"`
SurveyId int64 `json:"surveyId" xorm:"not null index"`
QuestionOrder int `json:"questionOrder" xorm:"not null index"`
QuestionTitle string `json:"questionTitle"`
QuestionType string `json:"questionType"`
QuestionOptions string `json:"questionOptions" xorm:"text"`
QuestionCount string `json:"question" xorm:"text"`
}
type SurveyDetail struct {
Survey `json:"survey" xorm:"extends"`
Questions []Question `json:"questions" xorm:"extends"`
}
func (s *Survey) GetEngine() *xorm.Session {
return adapter.Engine.Table(s)
}
func (s *Survey) Create() (err error) {
_, err = s.GetEngine().Insert(s)
return
}
func (s *Survey) Update() (err error) {
_, err = s.GetEngine().ID(s.Id).Update(s)
return
}
func (s *Survey) Delete() (err error) {
_, err = s.GetEngine().ID(s.Id).Delete(s)
return
}
func (q *Question) GetEngine() *xorm.Session {
return adapter.Engine.Table(q)
}
func (q *Question) Create() (err error) {
_, err = q.GetEngine().Insert(q)
return
}
func (q *Question) Update() (err error) {
_, err = q.GetEngine().ID(q.Id).Update(q)
return
}
func (q *Question) Delete() (err error) {
_, err = q.GetEngine().ID(q.Id).Delete(q)
return
}
func GetSurveyByTaskId(tid string) (s Survey, qs []Question, err error) {
_, err = (&Survey{}).GetEngine().
Where("task_id = ?", tid).
Get(&s)
err = (&Question{}).GetEngine().
Where("survey_id = ?", s.Id).
Asc("question_order").
Find(&qs)
return
}
func ExchangeQuestion(id1 string, id2 string) (err error) {
_, err = adapter.Engine.
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
}

193
models/task.go Normal file
View File

@ -0,0 +1,193 @@
package models
import (
"xorm.io/xorm"
)
type Task struct {
Id int64 `json:"id" xorm:"not null pk autoincr"`
SectionId int64 `json:"sectionId" xorm:"not null index"`
ProjectId int64 `json:"projectId" xorm:"not null index"`
SectionNumber int `json:"sectionNumber" xorm:"index"`
ChapterNumber int `json:"chapterNumber" xorm:"index"`
TaskOrder int `json:"taskOrder"`
TaskTitle string `json:"taskTitle"`
TaskIntroduce string `json:"taskIntroduce" xorm:"text"`
TaskType string `json:"taskType" xorm:"index"`
TaskWeight int `json:"taskWeight"`
}
type TaskEvaluate struct {
Task `xorm:"extends"`
Submitted bool `json:"submitted"`
Submit Submit `json:"submit"`
}
type TaskDetail struct {
Task `xorm:"extends"`
SurveyDetail `xorm:"extends"`
SubmitDetail `xorm:"extends"`
}
func (t *Task) GetEngine() *xorm.Session {
return adapter.Engine.Table(t)
}
func (t *Task) Create() (err error) {
session := adapter.Engine.NewSession()
defer session.Close()
session.Begin()
_, err = session.Insert(t)
if t.TaskType == "survey" {
_, err = session.Insert(Survey{
TaskId: t.Id,
})
}
session.Commit()
return
}
func (t *Task) Update() (err error) {
_, err = t.GetEngine().ID(t.Id).Update(t)
return
}
func (t *Task) Delete() (err error) {
_, err = t.GetEngine().ID(t.Id).Delete(t)
return
}
func GetSectionTasks(sid string, uid string, learning bool) (t []TaskDetail, err error) {
err = (&Task{}).GetEngine().
Where("section_id = ?", sid).
Asc("task_order").
Find(&t)
var b bool
for i := 0; i < len(t); i ++ {
var s Survey
var qs []Question
var c []Choice
if t[i].TaskType == "survey" {
var m Submit
_, err = (&Survey{}).GetEngine().
Where("task_id = ?", t[i].Id).
Get(&s)
err = (&Question{}).GetEngine().
Where("survey_id = ?", s.Id).
Asc("question_order").
Find(&qs)
t[i].Survey = s
t[i].Questions = qs
if learning {
b, err = (&Submit{}).GetEngine().
Where("task_id = ?", t[i].Id).
Where("student_id = ?", uid).
Get(&m)
if b {
t[i].Submitted = true
}
t[i].Submit = m
err = (&Choice{}).GetEngine().
Where("submit_id = ?", m.Id).
Asc("choice_order").
Find(&c)
t[i].Choices = c
}
} else {
var m Submit
if learning {
b, err = (&Submit{}).GetEngine().
Where("task_id = ?", t[i].Id).
Where("student_id = ?", uid).
Get(&m)
t[i].Submit = m
if b {
t[i].Submitted = true
}
}
}
}
return
}
func ExchangeTasks(cid1 string, cid2 string) (err error) {
_, err = adapter.Engine.
Exec("update task t1 join task t2 on (t1.id = ? and t2.id = ?) " +
"set t1.task_order = t2.task_order, t2.task_order = t1.task_order", cid1, cid2)
return
}
func GetProjectTasksDetail(sid string, uid string, showSubmit bool) (t []TaskDetail, err error) {
err = (&Task{}).GetEngine().
Where("project_id = ?", sid).
Asc("chapter_number").
Asc("section_number").
Asc("task_order").
Find(&t)
var b bool
for i := 0; i < len(t); i ++ {
var s Survey
var qs []Question
var c []Choice
if t[i].TaskType == "survey" {
var m Submit
b, err = (&Survey{}).GetEngine().
Where("task_id = ?", t[i].Id).
Get(&s)
if b {
err = (&Question{}).GetEngine().
Where("survey_id = ?", s.Id).
Asc("question_order").
Find(&qs)
t[i].Questions = qs
}
t[i].Survey = s
if showSubmit {
b, err = (&Submit{}).GetEngine().
Where("task_id = ?", t[i].Id).
Where("student_id = ?", uid).
Get(&m)
if b {
t[i].Submitted = true
err = (&Choice{}).GetEngine().
Where("submit_id = ?", m.Id).
Asc("choice_order").
Find(&c)
t[i].Choices = c
}
t[i].Submit = m
}
} else {
var m Submit
if showSubmit {
b, err = (&Submit{}).GetEngine().
Where("task_id = ?", t[i].Id).
Where("student_id = ?", uid).
Get(&m)
t[i].Submit = m
if b {
t[i].Submitted = true
}
}
}
}
return
}
func GetProjectTasks(pid string) (t []TaskEvaluate, err error) {
err = (&Task{}).GetEngine().
Where("project_id = ?", pid).
Asc("chapter_number").
Asc("section_number").
Asc("task_order").
Find(&t)
return
}

13
openpbl-landing/.env Normal file
View File

@ -0,0 +1,13 @@
REACT_APP_BASE_URL='http://localhost:5000/api'
REACT_APP_OSS_REGION='oss-cn-hangzhou'
REACT_APP_OSS_ACCESSKEYID=''
REACT_APP_OSS_ACCESSKEYSECRET=''
REACT_APP_OSS_BUCKET=''
REACT_APP_CASDOOR_ENDPOINT='http://localhost:8000'
REACT_APP_CLIENT_ID=''
REACT_APP_APP_NAME=''
REACT_APP_CASDOOR_ORGANIZATION=''
GENERATE_SOURCEMAP=false

31
openpbl-landing/.gitignore vendored Normal file
View File

@ -0,0 +1,31 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
# testing
/coverage
# production
/build
# misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.idea
.vscode
### Config file
.env.development
.env.production

70
openpbl-landing/README.md Normal file
View File

@ -0,0 +1,70 @@
# Getting Started with Create React App
This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
## Available Scripts
In the project directory, you can run:
### `yarn start`
Runs the app in the development mode.\
Open [http://localhost:3000](http://localhost:3000) to view it in the browser.
The page will reload if you make edits.\
You will also see any lint errors in the console.
### `yarn test`
Launches the test runner in the interactive watch mode.\
See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
### `yarn build`
Builds the app for production to the `build` folder.\
It correctly bundles React in production mode and optimizes the build for the best performance.
The build is minified and the filenames include the hashes.\
Your app is ready to be deployed!
See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
### `yarn eject`
**Note: this is a one-way operation. Once you `eject`, you cant go back!**
If you arent satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project.
Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point youre on your own.
You dont have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldnt feel obligated to use this feature. However we understand that this tool wouldnt be useful if you couldnt customize it when you are ready for it.
## Learn More
You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).
To learn React, check out the [React documentation](https://reactjs.org/).
### Code Splitting
This section has moved here: [https://facebook.github.io/create-react-app/docs/code-splitting](https://facebook.github.io/create-react-app/docs/code-splitting)
### Analyzing the Bundle Size
This section has moved here: [https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size](https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size)
### Making a Progressive Web App
This section has moved here: [https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app](https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app)
### Advanced Configuration
This section has moved here: [https://facebook.github.io/create-react-app/docs/advanced-configuration](https://facebook.github.io/create-react-app/docs/advanced-configuration)
### Deployment
This section has moved here: [https://facebook.github.io/create-react-app/docs/deployment](https://facebook.github.io/create-react-app/docs/deployment)
### `yarn build` fails to minify
This section has moved here: [https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify](https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify)

View File

@ -0,0 +1,29 @@
const CracoLessPlugin = require('craco-less');
const path = require("path");
const resolve = dir => path.resolve(__dirname, dir);
module.exports = {
plugins: [
{
plugin: CracoLessPlugin,
options: {
lessLoaderOptions: {
lessOptions: {
javascriptEnabled: true,
},
},
},
},
],
babel: {
plugins: [
['import', { libraryName: 'antd', libraryDirectory: 'es', style: 'css' }],
['@babel/plugin-proposal-decorators', { legacy: true }]
]
},
webpack: {
alias: {
'@': resolve("src"),
}
}
};

View File

@ -0,0 +1,68 @@
{
"name": "openpbl-landing",
"version": "0.1.0",
"private": true,
"dependencies": {
"@ant-design/icons": "^4.6.2",
"@babel/plugin-proposal-decorators": "^7.14.5",
"@craco/craco": "^6.2.0",
"@testing-library/jest-dom": "^5.11.4",
"@testing-library/react": "^11.1.0",
"@testing-library/user-event": "^12.1.10",
"ali-oss": "^6.16.0",
"antd": "^4.16.6",
"antd-img-crop": "^3.14.2",
"axios": "^0.21.1",
"babel-plugin-import": "^1.13.3",
"craco-less": "^1.18.0",
"echarts": "^5.1.2",
"echarts-for-react": "^3.0.1",
"enquire-js": "^0.2.1",
"localStorage": "^1.0.4",
"lodash": "^4.17.21",
"prop-types": "^15.7.2",
"qs": "^6.10.1",
"rc-banner-anim": "^2.4.5",
"rc-queue-anim": "^1.8.5",
"rc-scroll-anim": "^2.7.6",
"rc-tween-one": "^2.7.3",
"react": "^17.0.2",
"react-document-title": "^2.0.3",
"react-dom": "^17.0.2",
"react-github-button": "^0.1.11",
"react-lz-editor": "^0.12.1",
"react-pdf": "^5.3.2",
"react-redux": "^7.2.4",
"react-router-dom": "^5.2.0",
"react-scripts": "4.0.3",
"serve": "^12.0.0",
"web-vitals": "^1.0.1"
},
"scripts": {
"start": "craco start",
"build": "craco build",
"test": "craco test",
"eject": "react-scripts eject"
},
"eslintConfig": {
"extends": [
"react-app",
"react-app/jest"
]
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
},
"devDependencies": {
"rc-animate": "^3.1.1"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

View File

@ -0,0 +1,43 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta
name="description"
content="Web site created using create-react-app"
/>
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
<!--
manifest.json provides metadata used when your web app is installed on a
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
-->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>React App</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.
You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.
To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
</body>
</html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

View File

@ -0,0 +1,25 @@
{
"short_name": "React App",
"name": "Create React App Sample",
"icons": [
{
"src": "favicon.ico",
"sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon"
},
{
"src": "logo192.png",
"type": "image/png",
"sizes": "192x192"
},
{
"src": "logo512.png",
"type": "image/png",
"sizes": "512x512"
}
],
"start_url": ".",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#ffffff"
}

View File

@ -0,0 +1,3 @@
# https://www.robotstxt.org/robotstxt.html
User-agent: *
Disallow:

View File

@ -0,0 +1,57 @@
import { Route, BrowserRouter, Switch } from "react-router-dom";
import './App.less';
import Home from './pages/Home/index'
import Project from "./pages/Project";
import MyProject from "./pages/Project/MyProject";
import ProjectInfo from "./pages/Project/ProjectInfo/index";
import PublicProject from "./pages/Project/PublicProject";
import LearningProject from "./pages/Project/LearningProject";
import FinishedProject from "./pages/Project/FinishedProject";
import AuthCallback from "./pages/User/Auth/AuthCallback";
import Learning from "./pages/Project/LearningPage";
import EditInfo from "./pages/Project/CreateProject/Info";
import EditOutlined from "./pages/Project/CreateProject/Outline"
import SectionEditPage from "./pages/Project/CreateProject/Section/SectionEditPage";
import PreviewSection from "./pages/Project/PreviewProject/PreviewSection";
import SurveyEditPage from "./pages/Project/CreateProject/Survey/SurveyEditPage";
import Evidence from "./pages/Project/Evidence";
function App() {
return (
<div className="App">
<BrowserRouter>
<Switch>
<Route exact path="/" component={Home} />
<Route exact path="/callback" component={AuthCallback} />
<Route exact path="/landing" component={Home} />
<Route exact path="/project" component={Project} />
<Route exact path="/my-project" component={MyProject} />
<Route exact path="/project/public" component={PublicProject} />
<Route exact path="/project/learning" component={LearningProject} />
<Route exact path="/project/finished" component={FinishedProject} />
<Route exact path="/project/:id/info" component={ProjectInfo} />
<Route exact path="/project/:id/info/edit" component={EditInfo} />
<Route exact path="/project/:id/outline/edit" component={EditOutlined} />
<Route exact path="/project/:pid/student/:sid/evidence" component={Evidence} />
<Route exact path="/project/:pid/section/:sid/edit" component={SectionEditPage} />
<Route exact path="/project/:pid/section/:sid/task/:tid/survey/edit" component={SurveyEditPage} />
<Route exact path="/project/:pid/section/:sid/preview" component={PreviewSection} />
<Route exact path="/project/learning/:pid/:cid/:sid" component={Learning} />
</Switch>
</BrowserRouter>
</div>
);
}
export default App;

View File

@ -0,0 +1,40 @@
@import '~antd/dist/antd.less';
.App {
text-align: center;
}
.App-logo {
height: 40vmin;
pointer-events: none;
}
@media (prefers-reduced-motion: no-preference) {
.App-logo {
animation: App-logo-spin infinite 20s linear;
}
}
.App-header {
background-color: #282c34;
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
font-size: calc(10px + 2vmin);
color: white;
}
.App-link {
color: #61dafb;
}
@keyframes App-logo-spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}

View File

@ -0,0 +1,8 @@
import { render, screen } from '@testing-library/react';
import App from './App';
test('renders learn react link', () => {
render(<App />);
const linkElement = screen.getByText(/learn react/i);
expect(linkElement).toBeInTheDocument();
});

View File

@ -0,0 +1,26 @@
import request from './request'
import qs from 'qs';
const AuthApi = {
login(code, state) {
return request({
url: '/auth/login',
method: 'post',
data: qs.stringify({code: code, state: state}),
})
},
logout() {
return request({
url: '/auth/logout',
method: 'post'
})
},
getAccount() {
return request({
url: '/auth/account',
method: 'get',
})
}
}
export default AuthApi

View File

@ -0,0 +1,43 @@
import request from "./request";
import qs from "qs";
const ChapterApi = {
getProjectChapters(id, studentId) {
return request({
url: `/project/${id}/chapters`,
method: 'get',
params: {studentId: studentId}
})
},
createProjectChapter(chapter) {
return request({
url: `/project/${chapter.projectId}/chapter`,
method: 'post',
data: qs.stringify(chapter)
})
},
updateProjectChapter(chapter) {
return request({
url: `/project/${chapter.projectId}/chapter/${chapter.id}`,
method: 'post',
data: qs.stringify(chapter)
})
},
deleteProjectChapter(c) {
return request({
url: `/project/${c.projectId}/chapter/${c.id}/delete`,
method: 'post',
data: qs.stringify(c)
})
},
exchangeProjectChapter(pid, id1, id2) {
return request({
url: `/project/${pid}/chapters/exchange`,
method: 'post',
data: qs.stringify({chapterId1: id1, chapterId2: id2})
})
},
}
export default ChapterApi

View File

@ -0,0 +1,79 @@
import request from './request'
import qs from 'qs'
const ProjectApi = {
getProjectDetail(id) {
return request({
url:'/project/' + id,
method: 'get'
})
},
createProject(data) {
return request({
url:'/project',
method: 'post',
data: qs.stringify(data)
})
},
updateProject(data, id) {
return request({
url:'/project/' + id,
method: 'post',
data: qs.stringify(data)
})
},
publishProject(pid) {
return request({
url: `/project/${pid}/publish`,
method: 'post',
})
},
closeProject(pid) {
return request({
url: `/project/${pid}/close`,
method: 'post',
})
},
deleteProject(pid) {
return request({
url: `/project/${pid}/delete`,
method: 'post',
})
},
getSectionFiles(id) {
return request({
url:'/project/chapter/section/files/' + id,
method: 'get',
})
},
createSectionFile(f) {
return request({
url:'/project/chapter/section/file',
method: 'post',
data: qs.stringify(f)
})
},
getProjectStudents(pid) {
return request({
url: `/project/${pid}/students`,
method: 'get',
})
},
removeStudent(pid, sid) {
return request({
url: `/project/${pid}/remove/${sid}`,
method: 'post'
})
},
updateWeight(pid, data) {
return request({
url: `project/${pid}/weight`,
method: 'post',
data: qs.stringify(data)
})
}
}
export default ProjectApi

View File

@ -0,0 +1,13 @@
import request from './request'
const ProjectListApi = {
getUserProjectList(mode, q) {
return request({
url: '/project-list/' + mode,
params: q,
method: 'get'
})
},
}
export default ProjectListApi

View File

@ -0,0 +1,35 @@
import request from "./request";
import qs from 'qs'
const ResourceApi = {
getResource(id) {
return request({
url: `/resource/${id}`,
method: 'get',
})
},
createResource(q) {
return request({
url: '/resource',
method: 'post',
data: qs.stringify(q)
})
},
updateResource(q) {
return request({
url: `/${q.id}/resource`,
method: 'post',
data: qs.stringify(q)
})
},
updateResourceContent(id, content) {
return request({
url: `/resource/${id}`,
method: 'post',
data: qs.stringify({content: content})
})
},
}
export default ResourceApi

View File

@ -0,0 +1,58 @@
import request from "./request";
import qs from "qs";
const SectionApi = {
getChapterSections(item) {
return request({
url: `/project/${item.projectId}/chapter/${item.id}/sections`,
method: 'get',
})
},
createChapterSection(pid, section) {
return request({
url: `/project/${pid}/chapter/${section.chapterId}/section`,
method: 'post',
data: qs.stringify(section)
})
},
updateChapterSection(pid, section) {
return request({
url: `/project/${pid}/chapter/${section.chapterId}/section/${section.id}`,
method: 'post',
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`,
method: 'post',
data: qs.stringify(s)
})
},
exchangeChapterSection(chapter, id1, id2) {
return request({
url: `/project/${chapter.projectId}/chapter/${chapter.id}/sections/exchange`,
method: 'post',
data: qs.stringify({
sectionId1: id1,
sectionId2: id2
})
})
},
getSectionDetail(id, pid) {
return request({
url: `/project/${pid}/section/${id}`,
method: 'get',
})
},
}
export default SectionApi

View File

@ -0,0 +1,49 @@
import request from './request'
import qs from 'qs'
const StudentApi = {
learnProject(pid) {
return request({
url: '/student/learn/' + pid,
method: 'post',
})
},
exitProject(pid) {
return request({
url: '/student/exit/' + pid,
method: 'post',
})
},
FinishedProject(sid, pid) {
return request({
url: '/student/finished',
method: 'post',
data: qs.stringify({
studentId: sid,
projectId: pid,
learning: false
})
})
},
getLearnSection(pid, sid) {
return request({
url: `/student/project/${pid}/section/${sid}`,
method: 'get'
})
},
updateLearnSection(pid, sid, data) {
return request({
url: `/student/project/${pid}/section/${sid}`,
method: 'post',
data: qs.stringify(data)
})
},
getLastLearnSection(pid) {
return request({
url: `/student/last-learn/project/${pid}`,
method: 'get'
})
}
}
export default StudentApi

View File

@ -0,0 +1,21 @@
import request from "./request";
import qs from "qs";
const SubmitApi = {
createSubmit(pid, tid, data) {
return request({
url: `/project/${pid}/task/${tid}/submit`,
method: 'post',
data: qs.stringify(data)
})
},
updateSubmit(pid, tid, sid, data) {
return request({
url: `/project/${pid}/task/${tid}/submit/${sid}`,
method: 'post',
data: qs.stringify(data)
})
},
}
export default SubmitApi

View File

@ -0,0 +1,43 @@
import request from "./request";
import qs from 'qs'
const SurveyApi = {
getSurveyDetailByTaskId(pid, tid) {
return request({
url: `/project/${pid}/task/${tid}/survey`,
method: 'get'
})
},
createQuestion(pid, tid, q) {
return request({
url: `/project/${pid}/task/${tid}/survey/${q.surveyId}/question`,
method: 'post',
data: qs.stringify(q)
})
},
updateQuestion(pid, tid, q) {
return request({
url: `/project/${pid}/task/${tid}/survey/${q.surveyId}/question/${q.id}`,
method: 'post',
data: qs.stringify(q)
})
},
deleteQuestion(pid, tid, sid, qid) {
return request({
url: `/project/${pid}/task/${tid}/survey/${sid}/question/${qid}/delete`,
method: 'post',
})
},
exchangeQuestion(pid, tid, suid, id1, id2) {
return request({
url: `/project/${pid}/task/${tid}/survey/${suid}/questions/exchange`,
method: 'post',
data: qs.stringify({
questionId1: id1,
questionId2: id2
})
})
}
}
export default SurveyApi

View File

@ -0,0 +1,58 @@
import request from "./request";
import qs from 'qs'
const TaskApi = {
getSectionTasks(sid, pid) {
return request({
url: `/project/${pid}/section/${sid}/tasks`,
method: 'get',
})
},
getProjectTasks(pid) {
return request({
url: `/project/${pid}/tasks`,
method: 'get',
})
},
getProjectTasksDetail(pid, sid) {
return request({
url: `/project/${pid}/tasks-detail`,
method: 'get',
params: {
studentId: sid
}
})
},
createTask(pid, q) {
return request({
url: `/project/${pid}/task`,
method: 'post',
data: qs.stringify(q)
})
},
updateTask(pid, q) {
return request({
url: `/project/${pid}/task/${q.id}`,
method: 'post',
data: qs.stringify(q)
})
},
deleteTask(pid, id) {
return request({
url: `/project/${pid}/task/${id}/delete`,
method: 'post'
})
},
exchangeTask(pid, id1, id2) {
return request({
url: `/project/${pid}/tasks/exchange`,
method: 'post',
data: qs.stringify({
taskId1: id1,
taskId2: id2
})
})
}
}
export default TaskApi

View File

@ -0,0 +1,26 @@
import axios from 'axios'
import {message} from 'antd'
const request = axios.create({
baseURL: process.env.REACT_APP_BASE_URL,
timeout: 10000,
withCredentials: true
})
request.interceptors.response.use(res=>{
if (res.data.code === 401) {
message.error(res.data.msg)
setTimeout(()=>{
window.location.href = '/'
}, 1000)
} else if (res.data.code === 403) {
message.error(res.data.msg)
setTimeout(()=>{
window.location.href = '/'
}, 1000)
} else {
return res
}
})
export default request

View File

@ -0,0 +1,13 @@
body {
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
code {
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
monospace;
}

View File

@ -0,0 +1,17 @@
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById('root')
);
// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 841.9 595.3"><g fill="#61DAFB"><path d="M666.3 296.5c0-32.5-40.7-63.3-103.1-82.4 14.4-63.6 8-114.2-20.2-130.4-6.5-3.8-14.1-5.6-22.4-5.6v22.3c4.6 0 8.3.9 11.4 2.6 13.6 7.8 19.5 37.5 14.9 75.7-1.1 9.4-2.9 19.3-5.1 29.4-19.6-4.8-41-8.5-63.5-10.9-13.5-18.5-27.5-35.3-41.6-50 32.6-30.3 63.2-46.9 84-46.9V78c-27.5 0-63.5 19.6-99.9 53.6-36.4-33.8-72.4-53.2-99.9-53.2v22.3c20.7 0 51.4 16.5 84 46.6-14 14.7-28 31.4-41.3 49.9-22.6 2.4-44 6.1-63.6 11-2.3-10-4-19.7-5.2-29-4.7-38.2 1.1-67.9 14.6-75.8 3-1.8 6.9-2.6 11.5-2.6V78.5c-8.4 0-16 1.8-22.6 5.6-28.1 16.2-34.4 66.7-19.9 130.1-62.2 19.2-102.7 49.9-102.7 82.3 0 32.5 40.7 63.3 103.1 82.4-14.4 63.6-8 114.2 20.2 130.4 6.5 3.8 14.1 5.6 22.5 5.6 27.5 0 63.5-19.6 99.9-53.6 36.4 33.8 72.4 53.2 99.9 53.2 8.4 0 16-1.8 22.6-5.6 28.1-16.2 34.4-66.7 19.9-130.1 62-19.1 102.5-49.9 102.5-82.3zm-130.2-66.7c-3.7 12.9-8.3 26.2-13.5 39.5-4.1-8-8.4-16-13.1-24-4.6-8-9.5-15.8-14.4-23.4 14.2 2.1 27.9 4.7 41 7.9zm-45.8 106.5c-7.8 13.5-15.8 26.3-24.1 38.2-14.9 1.3-30 2-45.2 2-15.1 0-30.2-.7-45-1.9-8.3-11.9-16.4-24.6-24.2-38-7.6-13.1-14.5-26.4-20.8-39.8 6.2-13.4 13.2-26.8 20.7-39.9 7.8-13.5 15.8-26.3 24.1-38.2 14.9-1.3 30-2 45.2-2 15.1 0 30.2.7 45 1.9 8.3 11.9 16.4 24.6 24.2 38 7.6 13.1 14.5 26.4 20.8 39.8-6.3 13.4-13.2 26.8-20.7 39.9zm32.3-13c5.4 13.4 10 26.8 13.8 39.8-13.1 3.2-26.9 5.9-41.2 8 4.9-7.7 9.8-15.6 14.4-23.7 4.6-8 8.9-16.1 13-24.1zM421.2 430c-9.3-9.6-18.6-20.3-27.8-32 9 .4 18.2.7 27.5.7 9.4 0 18.7-.2 27.8-.7-9 11.7-18.3 22.4-27.5 32zm-74.4-58.9c-14.2-2.1-27.9-4.7-41-7.9 3.7-12.9 8.3-26.2 13.5-39.5 4.1 8 8.4 16 13.1 24 4.7 8 9.5 15.8 14.4 23.4zM420.7 163c9.3 9.6 18.6 20.3 27.8 32-9-.4-18.2-.7-27.5-.7-9.4 0-18.7.2-27.8.7 9-11.7 18.3-22.4 27.5-32zm-74 58.9c-4.9 7.7-9.8 15.6-14.4 23.7-4.6 8-8.9 16-13 24-5.4-13.4-10-26.8-13.8-39.8 13.1-3.1 26.9-5.8 41.2-7.9zm-90.5 125.2c-35.4-15.1-58.3-34.9-58.3-50.6 0-15.7 22.9-35.6 58.3-50.6 8.6-3.7 18-7 27.7-10.1 5.7 19.6 13.2 40 22.5 60.9-9.2 20.8-16.6 41.1-22.2 60.6-9.9-3.1-19.3-6.5-28-10.2zM310 490c-13.6-7.8-19.5-37.5-14.9-75.7 1.1-9.4 2.9-19.3 5.1-29.4 19.6 4.8 41 8.5 63.5 10.9 13.5 18.5 27.5 35.3 41.6 50-32.6 30.3-63.2 46.9-84 46.9-4.5-.1-8.3-1-11.3-2.7zm237.2-76.2c4.7 38.2-1.1 67.9-14.6 75.8-3 1.8-6.9 2.6-11.5 2.6-20.7 0-51.4-16.5-84-46.6 14-14.7 28-31.4 41.3-49.9 22.6-2.4 44-6.1 63.6-11 2.3 10.1 4.1 19.8 5.2 29.1zm38.5-66.7c-8.6 3.7-18 7-27.7 10.1-5.7-19.6-13.2-40-22.5-60.9 9.2-20.8 16.6-41.1 22.2-60.6 9.9 3.1 19.3 6.5 28.1 10.2 35.4 15.1 58.3 34.9 58.3 50.6-.1 15.7-23 35.6-58.4 50.6zM320.8 78.4z"/><circle cx="420.9" cy="296.5" r="45.7"/><path d="M520.5 78.1z"/></g></svg>

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

@ -0,0 +1,64 @@
import React from 'react';
import PropTypes from 'prop-types';
import QueueAnim from 'rc-queue-anim';
import TweenOne from 'rc-tween-one';
import {Button} from 'antd';
import BannerSVGAnim from './component/BannerSVGAnim';
function Banner(props) {
return (
<div className="banner-wrapper">
{props.isMobile && (
<TweenOne animation={{opacity: 1}} className="banner-image-wrapper">
<div className="home-banner-image">
<img
alt="banner"
src="https://gw.alipayobjects.com/zos/rmsportal/rqKQOpnMxeJKngVvulsF.svg"
width="100%"
/>
</div>
</TweenOne>
)}
<QueueAnim className="banner-title-wrapper" type={props.isMobile ? 'bottom' : 'right'}>
<div key="line" className="title-line-wrapper">
<div
className="title-line"
style={{transform: 'translateX(-64px)'}}
/>
</div>
<h1 key="h1">OpenCT</h1>
<p key="content">
指向5C核心素养的在线交互式高阶思维能力测评体系
</p>
<div key="button" className="button-wrapper">
<a href="/login">
<Button type="primary">
开始命题
</Button>
</a>
<a href="/ticket-login-card">
<Button style={{margin: '0 16px'}} type="primary" ghost>
学生测试
</Button>
</a>
<a href="/register">
<Button type="primary">
用户注册
</Button>
</a>
</div>
</QueueAnim>
{!props.isMobile && (
<TweenOne animation={{opacity: 1}} className="banner-image-wrapper">
<BannerSVGAnim/>
</TweenOne>
)}
</div>
);
}
Banner.propTypes = {
isMobile: PropTypes.bool.isRequired,
};
export default Banner;

View File

@ -0,0 +1,173 @@
import React from 'react';
import {Button, Col, Row} from 'antd';
function Footer() {
return (
<footer id="footer" className="dark">
<div className="footer-wrap">
<Row>
<Col lg={6} sm={24} xs={24}>
<div className="footer-center">
<h2>OpenCT</h2>
<div>
<a target="_blank " href="https://github.com/ant-design/ant-design-pro">
OpenCT GitHub
</a>
</div>
<div>
<a target="_blank " href="http://ant.design">
OpenCT
</a>
</div>
<div>
<a href="http://mobile.ant.design">OpenCT Mobile</a>
</div>
<div>
<a href="http://ng.ant.design">NG-ZORRO</a>
<span> - </span>
OpenCT of Angular
</div>
<div>
<a target="_blank " href="https://github.com/websemantics/awesome-ant-design">
Awesome OpenCT
</a>
</div>
</div>
</Col>
<Col lg={6} sm={24} xs={24}>
<div className="footer-center">
<h2>OpenCT</h2>
<div>
<a href="http://scaffold.ant.design">Scaffolds</a>
<span> - </span>
<span>脚手架市场</span>
</div>
<div>
<a target="_blank" rel="noopener" href="http://motion.ant.design">Ant Motion</a>
<span> - </span>
<span>设计动效</span>
</div>
<div>
<a target="_blank" rel="noopener" href="http://library.ant.design/">Axure Library</a>
<span> - </span>
<span>Axure 部件库</span>
</div>
<div>
<a target="_blank" rel="noopener" href="http://ux.ant.design">Ant UX</a>
<span> - </span>
<span>页面逻辑素材</span>
</div>
<div>
<a target="_blank" rel="noopener" href="https://github.com/dvajs/dva">dva </a>
<span> - </span>
<span> 应用框架</span>
</div>
<div>
<a target="_blank" rel="noopener" href="https://github.com/dvajs/dva-cli">dva-cli </a>
<span> - </span>
<span> 开发工具</span>
</div>
</div>
</Col>
<Col lg={6} sm={24} xs={24}>
<div className="footer-center">
<h2>社区</h2>
<div>
<a href="#">
更新记录
</a>
</div>
<div>
<a href="#">
常见问题
</a>
</div>
<div>
<a target="_blank" rel="noopener" href="https://gitter.im/ant-design/ant-design-pro">
在线讨论
</a>
</div>
<div>
<a target="_blank" rel="noopener" href="https://github.com/ant-design/ant-design-pro/issues">
讨论列表
</a>
</div>
<div>
<a
target="_blank"
rel="noopener"
href="http://ant.design/docs/resource/work-with-us"
>
加入我们
</a>
</div>
</div>
</Col>
<Col lg={6} sm={24} xs={24}>
<div className="footer-center">
<h2>
更多产品
</h2>
<div>
<a target="_blank" rel="noopener" href="http://ant.design/">OpenCT</a>
<span> - </span>
<span>OpenCT 设计体系</span>
</div>
<div>
<a target="_blank" rel="noopener" href="https://antv.alipay.com/">OpenCT</a>
<span> - </span>
<span>OpenCT数据可视化方案</span>
</div>
<div>
<a target="_blank" rel="noopener" href="https://eggjs.org/">Egg</a>
<span> - </span>
<span>企业级 Node Web 开发框架</span>
</div>
</div>
</Col>
</Row>
</div>
<Row className="bottom-bar">
<Col lg={6} sm={24}>
<div className="translate-button">
<Button ghost size="small">
English
</Button>
</div>
</Col>
<Col lg={18} sm={24}>
<span
style={{
lineHeight: '16px',
paddingRight: 12,
marginRight: 11,
borderRight: '1px solid rgba(255, 255, 255, 0.55)',
}}
>
<a
href="https://docs.alipay.com/policies/privacy/antfin"
rel="noopener noreferrer"
target="_blank"
>
隐私权政策 ICP
</a>
</span>
<span style={{marginRight: 24}}>
<a
href="https://render.alipay.com/p/f/fd-izto3cem/index.html"
rel="noopener noreferrer"
target="_blank"
>
权益保障承诺书
</a>
</span>
<span style={{marginRight: 12}}>ICP 证京 B2-2-100257</span>
<span style={{marginRight: 12}}>Copyright © OpenCT国际联盟</span>
</Col>
</Row>
</footer>
);
}
export default Footer;

View File

@ -0,0 +1,202 @@
import React from 'react';
import PropTypes from 'prop-types';
import Parallax from 'rc-scroll-anim/lib/ScrollParallax';
import TweenOne from 'rc-tween-one';
const {TweenOneGroup} = TweenOne;
const featuresCN = [
{
title: '聚焦高阶思维',
content: '基于 5C 核心素养设计',
src: 'https://gw.alipayobjects.com/zos/rmsportal/VriUmzNjDnjoFoFFZvuh.svg',
color: '#13C2C2',
shadowColor: 'rgba(19,194,194,.12)',
},
{
title: '交互式测试',
content: '聚焦学生的过程性思维',
src: 'https://gw.alipayobjects.com/zos/rmsportal/smwQOoxCjXVbNAKMqvWk.svg',
color: '#2F54EB',
shadowColor: 'rgba(47,84,235,.12)',
},
{
title: '情境化学习',
content: '在线的真实问题解决',
src: 'https://gw.alipayobjects.com/zos/rmsportal/hBbIHzUsSbSxrhoRFYzi.svg',
color: '#F5222D',
shadowColor: 'rgba(245,34,45,.12)',
},
{
title: '自助组题测试模式',
content: '一线教师自助测评的组卷模式',
src: 'https://gw.alipayobjects.com/zos/rmsportal/BISfzKcCNCYFmTYcUygW.svg',
color: '#1AC44D',
shadowColor: 'rgba(26,196,77,.12)',
},
{
title: '开放式命题系统',
content: '多元素集成的块状命题结构',
src: 'https://gw.alipayobjects.com/zos/rmsportal/XxqEexmShHOofjMYOCHi.svg',
color: '#FAAD14',
shadowColor: 'rgba(250,173,20,.12)',
},
{
title: '即时报告反馈体系',
content: '自动化即时报告生成',
src: 'https://gw.alipayobjects.com/zos/rmsportal/JsixxWSViARJnQbAAPkI.svg',
color: '#722ED1',
shadowColor: 'rgba(114,46,209,.12)',
},
{
title: '数据灵活管理',
content: '灵活的数据管理机制',
src: 'https://gw.alipayobjects.com/zos/rmsportal/pbmKMSFpLurLALLNliUQ.svg',
color: '#FA8C16',
shadowColor: 'rgba(250,140,22,.12)',
},
{
title: '结构化数据分析',
content: '基于内嵌框架的自动数据分析',
src: 'https://gw.alipayobjects.com/zos/rmsportal/aLQyKyUyssIUhHTZqCIb.svg',
color: '#EB2F96',
shadowColor: 'rgba(235,45,150,.12)',
},
];
const pointPos = [
{x: -30, y: -10},
{x: 20, y: -20},
{x: -65, y: 15},
{x: -45, y: 80},
{x: 35, y: 5},
{x: 50, y: 50, opacity: 0.2},
];
class Page1 extends React.PureComponent {
static propTypes = {
isMobile: PropTypes.bool.isRequired,
}
constructor(props) {
super(props);
this.state = {
hoverNum: null,
};
}
onMouseOver = (i) => {
this.setState({
hoverNum: i,
});
}
onMouseOut = () => {
this.setState({
hoverNum: null,
});
}
getEnter = (e) => {
const i = e.index;
const r = (Math.random() * 2) - 1;
const y = (Math.random() * 10) + 5;
const delay = Math.round(Math.random() * (i * 50));
return [
{
delay, opacity: 0.4, ...pointPos[e.index], ease: 'easeOutBack', duration: 300,
},
{
y: r > 0 ? `+=${y}` : `-=${y}`,
duration: (Math.random() * 1000) + 2000,
yoyo: true,
repeat: -1,
}];
}
render() {
const {hoverNum} = this.state;
let children = [[], [], []];
featuresCN.forEach((item, i) => {
const isHover = hoverNum === i;
const pointChild = [
'point-0 left', 'point-0 right',
'point-ring', 'point-1', 'point-2', 'point-3',
].map(className => (
<TweenOne
component="i"
className={className}
key={className}
style={{
background: item.color,
borderColor: item.color,
}}
/>
));
const child = (
<li key={i.toString()}>
<div
className="page1-box"
onMouseEnter={() => {
this.onMouseOver(i);
}}
onMouseLeave={this.onMouseOut}
>
<TweenOneGroup
className="page1-point-wrapper"
enter={this.getEnter}
leave={{
x: 0, y: 30, opacity: 0, duration: 300, ease: 'easeInBack',
}}
resetStyleBool={false}
>
{(this.props.isMobile || isHover) && pointChild}
</TweenOneGroup>
<div
className="page1-image"
style={{
boxShadow: `${isHover ? '0 12px 24px' :
'0 6px 12px'} ${item.shadowColor}`,
}}
>
<img src={item.src} alt="img" style={i === 4 ? {marginLeft: -15} : {}}/>
</div>
<h3>{item.title}</h3>
<p>{item.content}</p>
</div>
</li>
);
children[Math.floor(i / 4)].push(child);
});
children = children.map((item, i) => (
<div
className="page1-box-wrapper"
>
{item}
</div>
));
return (
<div className="home-page">
<div className="home-page-wrapper" id="page1-wrapper">
{!this.props.isMobile && (
<Parallax
className="page1-bg"
animation={{translateY: -600, ease: 'linear', playScale: [0, 1.65]}}
location="page1-wrapper"
>
Feature
</Parallax>
)}
<h2>指向5C核心素养的<span>结构</span>体系</h2>
<div className="title-line-wrapper page1-line">
<div className="title-line"/>
</div>
{/*<OverPack>*/}
{children}
{/*</OverPack>*/}
</div>
</div>
);
}
}
export default Page1;

View File

@ -0,0 +1,39 @@
import React from 'react';
import {OverPack} from 'rc-scroll-anim';
import QueueAnim from 'rc-queue-anim';
import {Button} from 'antd';
function Page2() {
return (
<div className="home-page page2">
<div className="home-page-wrapper">
<div className="title-line-wrapper page2-line">
<div className="title-line"/>
</div>
<h2><span>细节功能</span></h2>
<OverPack>
<QueueAnim key="queue" type="bottom" leaveReverse className="page2-content">
<p key="p" className="page-content">
集成选择填空拖拽可视化编程等多种题型
</p>
<div key="code1" className="home-code">
<div>
$ <span>可视化编程</span> 集成块状编程系统
</div>
<div>$ <span>嵌入拖拽组件</span> 多模式拖拽</div>
<div>$ <span>设置选择模块</span> 多种选择题型</div>
<div>$ <span>整合填空模块</span> 多类型填空题</div>
</div>
<div key="button" style={{marginTop: 88}}>
<a href="/ticket-login-card" target="_blank" rel="noopener noreferrer">
<Button type="primary">开始测试</Button>
</a>
</div>
</QueueAnim>
</OverPack>
</div>
</div>
);
}
export default Page2;

View File

@ -0,0 +1,59 @@
import React from 'react';
import QueueAnim from 'rc-queue-anim';
import {Col, Row} from 'antd';
import OverPack from 'rc-scroll-anim/lib/ScrollOverPack';
import {getChildrenToRender} from './utils';
class Teams2 extends React.PureComponent {
getBlockChildren = (data) =>
data.map((item, i) => {
const {titleWrapper, image, ...$item} = item;
return (
<Col key={i.toString()} {...$item}>
<Row>
<Col span={7}>
<div {...image}>
<img src={image.children} alt="img"/>
</div>
</Col>
<Col span={17}>
<QueueAnim {...titleWrapper} type="bottom">
{titleWrapper.children.map(getChildrenToRender)}
</QueueAnim>
</Col>
</Row>
</Col>
);
});
render() {
const {...props} = this.props;
const {dataSource} = props;
delete props.dataSource;
delete props.isMobile;
const listChildren = this.getBlockChildren(dataSource.block.children);
return (
<div {...props} {...dataSource.wrapper}>
<div {...dataSource.page}>
<div {...dataSource.titleWrapper}>
{dataSource.titleWrapper.children.map(getChildrenToRender)}
</div>
<OverPack {...dataSource.OverPack}>
<QueueAnim type="bottom" key="tween" leaveReverse>
<QueueAnim
type="bottom"
key="block"
{...dataSource.block}
component={Row}
>
{listChildren}
</QueueAnim>
</QueueAnim>
</OverPack>
</div>
</div>
);
}
}
export default Teams2;

View File

@ -0,0 +1,113 @@
import React from 'react';
import QueueAnim from 'rc-queue-anim';
import {TweenOneGroup} from 'rc-tween-one';
import OverPack from 'rc-scroll-anim/lib/ScrollOverPack';
import {Col, Row} from 'antd';
import {page1} from './data';
const pointPos = [
{x: -90, y: -20},
{x: 35, y: -25},
{x: -120, y: 125},
{x: -100, y: 165},
{x: 95, y: -5},
{x: 90, y: 160, opacity: 0.2},
{x: 110, y: 50},
];
export default class Design extends React.PureComponent {
state = {
hoverNum: null,
}
onMouseOver = (i) => {
this.setState({
hoverNum: i,
});
}
onMouseOut = () => {
this.setState({
hoverNum: null,
});
}
getEnter = (e) => {
const i = e.index;
const r = (Math.random() * 2) - 1;
const y = (Math.random() * 10) + 5;
const delay = Math.round(Math.random() * (i * 50));
return [
{
delay, opacity: 0.4, ...pointPos[e.index], ease: 'easeOutBack', duration: 300,
},
{
y: r > 0 ? `+=${y}` : `-=${y}`,
duration: (Math.random() * 1000) + 2000,
yoyo: true,
repeat: -1,
}];
}
render() {
const {hoverNum} = this.state;
const {isMobile} = this.props;
const children = page1.children.map((item, i) => {
const isHover = hoverNum === i;
const pointChild = [
'point-ring left', 'point-ring point-ring-0 right',
'point-0', 'point-2', 'point-1', 'point-3', 'point-2',
].map((className, ii) => (
<i
className={className}
key={ii.toString()}
style={{
background: item.color,
borderColor: item.color,
}}
/>
));
return (
<Col md={8} xs={24} key={i.toString()} className="page1-item">
<a
className="page1-item-link"
href={item.link}
target="_blank"
onMouseEnter={() => {
this.onMouseOver(i);
}}
onMouseLeave={this.onMouseOut}
>
<TweenOneGroup
enter={this.getEnter}
leave={{
x: 0, y: 30, opacity: 0, duration: 300, ease: 'easeInBack',
}}
resetStyleBool={false}
className="point-wrapper"
>
{(isHover || isMobile) && pointChild}
</TweenOneGroup>
<div className="page1-item-img" style={{boxShadow: `0 16px 32px ${item.shadowColor}`}}>
<img src={item.src} alt="img"/>
</div>
<div className="page1-item-title">{item.title}</div>
<p>{item.content}</p>
</a>
</Col>);
});
return (
<div className="page-wrapper page1">
<div className="page">
<h1>{page1.title}</h1>
{/*<i />*/}
<div className="title-line-wrapper page1-line" style={{marginBottom: "-30px"}}>
<div className="title-line"/>
</div>
<OverPack>
<QueueAnim key="queue" type="bottom" leaveReverse component={Row}>
{children}
</QueueAnim>
</OverPack>
</div>
</div>);
}
}

View File

@ -0,0 +1,727 @@
/* eslint-disable */
import React from 'react';
import TweenOne from 'rc-tween-one';
import SvgDrawPlugin from 'rc-tween-one/lib/plugin/SvgDrawPlugin';
TweenOne.plugins.push(SvgDrawPlugin);
const animate = {
scale: {
scale: 0,
opacity: 0,
type: 'from',
ease: 'easeOutQuad',
},
alpha: {
opacity: 0,
type: 'from',
ease: 'easeOutQuad',
},
y: {
y: 30,
opacity: 0,
type: 'from',
ease: 'easeOutQuad',
},
y2: {
y: -30,
opacity: 0,
type: 'from',
ease: 'easeOutQuad',
},
x: {
x: 30,
opacity: 0,
type: 'from',
ease: 'easeOutQuad',
},
x2: {
x: -30,
opacity: 0,
type: 'from',
ease: 'easeOutQuad',
},
draw: {
SVGDraw: 0,
type: 'from',
ease: 'easeOutQuad',
},
loop: {
yoyo: true,
repeat: -1,
duration: 2500,
},
};
export default function () {
// safari transform g
return (
<svg className="home-banner-anim" width="100%" height="100%" viewBox="0 0 598 342"
xmlns="http://www.w3.org/2000/svg" version="1.1">
<defs>
<filter x="-4.6%" y="-6.1%" width="109.1%" height="116.7%" filterUnits="objectBoundingBox" id="filter-1">
<feOffset dx="0" dy="7" in="SourceAlpha" result="shadowOffsetOuter1"/>
<feGaussianBlur stdDeviation="7.5" in="shadowOffsetOuter1" result="shadowBlurOuter1"/>
<feColorMatrix values="0 0 0 0 0.411764706 0 0 0 0 0.482352941 0 0 0 0 0.549019608 0 0 0 0.2 0"
type="matrix" in="shadowBlurOuter1" result="shadowMatrixOuter1"/>
<feMerge>
<feMergeNode in="shadowMatrixOuter1"/>
<feMergeNode in="SourceGraphic"/>
</feMerge>
</filter>
<rect id="path-2" x="0" y="0.432276657" width="10.7772021" height="10.7896254"/>
<linearGradient x1="24.3804028%" y1="50%" x2="107.642915%" y2="50%" id="linearGradient-3">
<stop stopColor="#65D0E6" offset="0%"/>
<stop stopColor="#D3F3BA" offset="100%"/>
</linearGradient>
<rect id="path-4" x="0.7582038" y="0.544668588" width="35.9240069" height="43.1585014"/>
<linearGradient x1="143.292387%" y1="50%" x2="0%" y2="50%" id="linearGradient-5">
<stop stopColor="#76AEFF" stopOpacity="0.5" offset="0%"/>
<stop stopColor="#1D22F2" stopOpacity="0.5" offset="100%"/>
</linearGradient>
<rect id="path-6" x="39.3765112" y="99.4495677" width="43.1088083" height="10.7896254"/>
<rect id="path-7" x="76.1986183" y="130.919308" width="10.7772021" height="7.19308357"/>
<rect id="path-8" x="0" y="0" width="101" height="79"/>
<rect id="path-10" x="0" y="0" width="337.685665" height="57.5446686"/>
<rect id="path-12" x="0" y="0" width="122.141623" height="86.3170029"/>
<ellipse id="path-14" cx="39.5164076" cy="78.2247839" rx="53.8860104" ry="53.9481268"/>
<linearGradient x1="100%" y1="50%" x2="0%" y2="50%" id="linearGradient-15">
<stop stopColor="#FFF53D" stopOpacity="0.5" offset="0%"/>
<stop stopColor="#1B5DF5" stopOpacity="0.616225091" offset="100%"/>
</linearGradient>
<rect id="path-16" x="89.7685665" y="26.3659942" width="26.044905" height="28.7723343"/>
<linearGradient x1="50%" y1="2.77822066%" x2="50%" y2="100%" id="linearGradient-17">
<stop stopColor="#5292FD" offset="0%"/>
<stop stopColor="#532AF8" offset="100%"/>
</linearGradient>
<linearGradient x1="416.261243%" y1="-19.5474797%" x2="416.261244%" y2="218.694286%" id="linearGradient-18">
<stop stopColor="#76AEFF" stopOpacity="0.5" offset="0%"/>
<stop stopColor="#1D22F2" stopOpacity="0.5" offset="100%"/>
</linearGradient>
<rect id="path-19" x="47.5993092" y="36.2247839" width="8.08290155" height="57.5446686"/>
<rect id="path-20" x="65.5613126" y="50.610951" width="10.7772021" height="43.1585014"/>
<rect id="path-21" x="101.48532" y="75.7867435" width="10.7772021" height="17.9827089"/>
<rect id="path-22" x="119.447323" y="68.5936599" width="10.7772021" height="25.1757925"/>
<rect id="path-23" x="83.5233161" y="21.8386167" width="10.7772021" height="71.9308357"/>
<linearGradient x1="24.3804028%" y1="50%" x2="107.642915%" y2="50%" id="linearGradient-24">
<stop stopColor="#4CCAE9" offset="0%"/>
<stop stopColor="#D3F3BA" offset="100%"/>
</linearGradient>
<linearGradient x1="196.877572%" y1="11.6518021%" x2="-49.8197415%" y2="11.6518021%" id="linearGradient-25">
<stop stopColor="#75D4E1" offset="0%"/>
<stop stopColor="#4E56FB" offset="100%"/>
</linearGradient>
<rect id="path-26" x="29.0846287" y="37.4639769" width="28.7392055" height="28.7723343"/>
<linearGradient x1="50%" y1="110.15691%" x2="50%" y2="-14.8990438%" id="linearGradient-27">
<stop stopColor="#4CCAE9" stopOpacity="0.551120924" offset="0%"/>
<stop stopColor="#C3FF94" stopOpacity="0.4356601" offset="100%"/>
</linearGradient>
</defs>
<g id="首页" stroke="none" strokeWidth="1" fill="none" fillRule="evenodd">
<g transform="translate(-800.000000, -162.000000)" id="首屏">
<g transform="translate(130.000000, 170.000000)">
<g id="插图-页面" filter="url(#filter-1)" transform="translate(685.000000, 0.000000)">
<g id="s0">
<g className="abc" id="Group-24" transform="translate(1.000000, 0.000000)">
<TweenOne component="g" animation={[{...animate.scale}, {y: 10, ...animate.loop}]}>
<rect id="Rectangle-17-Copy-16" fill="#85A5FF"
transform="translate(47.497409, 7.193084) scale(1, -1) translate(-47.497409, -7.193084) "
x="40.3126079" y="1.13686838e-13" width="14.3696028" height="14.3861671"/>
</TweenOne>
<TweenOne component="g" animation={[{...animate.scale, delay: 50}, {y: -10, ...animate.loop}]}>
<g id="Group-4" transform="translate(11.000000, 14.000000)">
<polygon id="Rectangle-17-Copy-15" fill="#4060FF" opacity="0.634736474"
transform="translate(14.943005, 14.772334) scale(1, -1) translate(-14.943005, -14.772334) "
points="0.573402418 0.386167147 29.3126079 0.386167147 0.573402418 29.1585014"/>
<rect id="Rectangle-17-Copy-15" fill="#4060FF" opacity="0.888701026"
transform="translate(14.943005, 14.772334) scale(1, -1) translate(-14.943005, -14.772334) "
x="0.573402418" y="0.386167147" width="28.7392055" height="28.7723343"/>
<polygon id="Rectangle-17-Copy-15" fill="#294CF7" opacity="0.888701026"
transform="translate(14.943005, 14.772334) scale(1, -1) translate(-14.943005, -14.772334) "
points="0.573402418 0.386167147 29.3126079 0.386167147 29.3126079 29.1585014"/>
</g>
</TweenOne>
<g id="Group-7" transform="translate(0.000000, 265.000000)">
<TweenOne component="g" animation={[{...animate.scale, delay: 1000}, {y: 5, ...animate.loop}]}>
<rect id="Rectangle-17-Copy-20" fill="#339AFF" opacity="0.625583022"
transform="translate(492.955095, 5.639769) scale(1, -1) translate(-492.955095, -5.639769) "
x="487.566494" y="0.244956772" width="10.7772021" height="10.7896254"/>
</TweenOne>
<TweenOne component="g" animation={[{...animate.scale, delay: 1150}, {y: -5, ...animate.loop}]}>
<rect id="Rectangle-17-Copy-43" fill="#1890FF" opacity="0.907940765"
transform="translate(20.554404, 41.605187) scale(1, -1) translate(-20.554404, -41.605187) "
x="15.1658031" y="36.2103746" width="10.7772021" height="10.7896254"/>
</TweenOne>
<TweenOne component="g" animation={[{...animate.scale, delay: 1200}, {y: 5, ...animate.loop}]}>
<rect id="Rectangle-17-Copy-44" fill="#91D5FF" opacity="0.907940765"
transform="translate(4.388601, 35.311239) scale(1, -1) translate(-4.388601, -35.311239) "
x="0.796200345" y="31.7146974" width="7.18480138" height="7.19308357"/>
</TweenOne>
<TweenOne component="g" animation={{...animate.y, delay: 1050}}>
<rect id="Rectangle-17-Copy-9" fill="#1890FF" x="40.3126079" y="27.2190202" width="452.642487"
height="7.19308357"/>
</TweenOne>
<TweenOne component="g" animation={{...animate.y, delay: 1000}}>
<rect id="Rectangle-17-Copy-10" fill="#69C0FF" x="4.38860104" y="20.0259366" width="493.955095"
height="7.19308357"/>
</TweenOne>
</g>
</g>
</g>
<g id="s1">
<g id="Group-21" transform="translate(0.000000, 174.000000)">
<TweenOne component="g" animation={{...animate.x, delay: 700}}>
<path d="M42.2107081,5.82708934 L6.28670121,5.82708934" id="Path-5" stroke="#2F54EB"
strokeWidth="0.89775" strokeLinecap="round"
transform="translate(24.248705, 5.827089) scale(-1, 1) translate(-24.248705, -5.827089) "/>
<g id="Rectangle-17-Copy-34">
<use fill="#FFFFFF" fillRule="evenodd" xlinkHref="#path-2"/>
<rect stroke="#2F54EB" strokeWidth="0.89775" x="0.448875" y="0.881151657" width="9.87945207"
height="9.89187536"/>
</g>
</TweenOne>
</g>
</g>
<g id="s2">
<g id="Group-18" transform="translate(19.000000, 57.000000)">
<g id="Group-17" transform="translate(0.758204, 0.544669)">
<TweenOne component="g" animation={{...animate.y, delay: 300}}>
<rect id="Rectangle-17-Copy-2" fill="#1890FF" x="0" y="0" width="100.587219" height="151.054755"/>
</TweenOne>
<TweenOne component="g" animation={{...animate.y, delay: 320}}>
<rect id="Rectangle-17-Copy-2" fill="#95DE64" opacity="0.819204757" x="0" y="43.1585014"
width="100.587219" height="107.896254"/>
<polygon id="Rectangle-17-Copy-2" fill="#95DE64"
points="100.587219 43.1585014 100.587219 151.054755 0 151.054755"/>
</TweenOne>
</g>
<TweenOne animation={{...animate.draw, delay: 500}} component="polyline" id="Path-9" stroke="#FFFFFF"
strokeWidth="0.89775"
points="63.4561007,20.9071232 83.2184056,20.9071232 83.2184056,43.4136061"/>
<TweenOne component="g" animation={{...animate.x2, x: -5, delay: 450}}>
<rect id="Rectangle-17-Copy-23" fill="#FADB14"
transform="translate(40.274611, 17.628242) scale(1, -1) rotate(90.000000) translate(-40.274611, -17.628242) "
x="33.08981" y="14.0317003" width="14.3696028" height="7.19308357"/>
</TweenOne>
<TweenOne component="g" animation={{...animate.x2, x: -5, delay: 470}}>
<rect id="Rectangle-17-Copy-28" fill="#FADB14"
transform="translate(54.644214, 17.628242) scale(1, -1) rotate(90.000000) translate(-54.644214, -17.628242) "
x="47.4594128" y="14.0317003" width="14.3696028" height="7.19308357"/>
</TweenOne>
<TweenOne component="g" animation={{...animate.x2, x: -5, delay: 460}}>
<rect id="Rectangle-17-Copy-27" fill="#06080A"
transform="translate(47.459413, 22.123919) scale(1, -1) rotate(90.000000) translate(-47.459413, -22.123919) "
x="40.2746114" y="18.5273775" width="14.3696028" height="7.19308357"/>
</TweenOne>
<TweenOne component="g" animation={{...animate.x2, x: -5, delay: 480}}>
<rect id="Rectangle-17-Copy-29" fill="#06080A"
transform="translate(61.829016, 19.426513) scale(1, -1) rotate(90.000000) translate(-61.829016, -19.426513) "
x="58.2366149" y="15.8299712" width="7.18480138" height="7.19308357"/>
</TweenOne>
<g id="Rectangle-17-Copy-25" opacity="0.892432369"
transform="translate(18.720207, 22.123919) scale(1, -1) translate(-18.720207, -22.123919) ">
<TweenOne component="g" animation={{...animate.y2, delay: 340}}>
<use fill="url(#linearGradient-3)" xlinkHref="#path-4"/>
<use fillOpacity="0.550000012" fill="#91D5FF" xlinkHref="#path-4"/>
</TweenOne>
</g>
<TweenOne component="g" animation={{...animate.y, delay: 360}}>
<rect id="Rectangle-17-Copy-25" fill="#06080A"
transform="translate(18.720207, 40.106628) scale(1, -1) translate(-18.720207, -40.106628) "
x="0.7582038" y="36.5100865" width="35.9240069" height="7.19308357"/>
</TweenOne>
<TweenOne component="g" animation={{...animate.alpha, y: 5, delay: 600}}>
<rect id="Rectangle-17-Copy-17" fill="#FA541C"
transform="translate(83.383420, 43.703170) scale(1, -1) translate(-83.383420, -43.703170) "
x="76.1986183" y="41.9048991" width="14.3696028" height="3.59654179"/>
</TweenOne>
<TweenOne component="g" animation={{...animate.y, delay: 470}}>
<rect id="Rectangle-17-Copy-37" fill="#1890FF"
transform="translate(22.312608, 68.878963) scale(1, -1) rotate(90.000000) translate(-22.312608, -68.878963) "
x="15.1278066" y="61.685879" width="14.3696028" height="14.3861671" rx="7.18480138"/>
<ellipse id="Oval-3" fill="#06080A" cx="22.3126079" cy="68.8789625" rx="2.69430052"
ry="2.69740634"/>
</TweenOne>
<TweenOne component="g" animation={{...animate.scale, delay: 450}}>
<ellipse id="Oval-3" fill="#06080A" cx="18.7202073" cy="19.426513" rx="2.69430052" ry="2.69740634"/>
</TweenOne>
<TweenOne component="g" animation={{...animate.y, delay: 480}}>
<rect id="Rectangle-17-Copy-37" fill="#1890FF"
transform="translate(22.312608, 104.844380) scale(1, -1) rotate(90.000000) translate(-22.312608, -104.844380) "
x="15.1278066" y="97.6512968" width="14.3696028" height="14.3861671" rx="7.18480138"/>
<ellipse id="Oval-3-Copy-7" fill="#06080A" cx="22.3126079" cy="104.84438" rx="2.69430052"
ry="2.69740634"/>
</TweenOne>
<TweenOne component="g" animation={{...animate.y, delay: 490}}>
<path d="M22.3126079,68.8789625 L22.3126079,104.84438" id="Path-18-Copy" stroke="#2F54EB"
strokeWidth="0.53865" strokeLinecap="round"/>
</TweenOne>
<TweenOne component="g" animation={{...animate.y, delay: 500}}>
<polyline id="Path-19" stroke="#2F54EB" strokeWidth="0.7182" opacity="0.573344216"
strokeLinecap="round" strokeLinejoin="round"
points="76.7111668 66.1815562 76.7111668 79.8272874 80.6891192 82.2584505 73.5225397 82.2584505 80.6891192 85.5560721 73.5043178 85.5560721 80.6891192 89.1260808 73.5043178 89.1260808 80.6891192 92.7076803 73.5225397 92.7076803 77.0967185 96.0586202 77.0967185 101.247839"/>
</TweenOne>
<TweenOne component="g" animation={{...animate.y, delay: 510}}>
<rect id="Rectangle-17-Copy-39" fill="#FFC53D" x="39.3765112" y="63.4841499" width="43.1088083"
height="10.7896254"/>
</TweenOne>
<TweenOne component="g" animation={{...animate.y, delay: 520}}>
<rect id="Rectangle-17-Copy-39" fill="#000000" x="71.7081174" y="63.4841499" width="10.7772021"
height="10.7896254"/>
</TweenOne>
<TweenOne component="g" animation={{...animate.y, delay: 530}}>
<g id="Rectangle-17-Copy-40">
<use fill="#2471F5" xlinkHref="#path-6"/>
<use fillOpacity="0.370000005" fill="url(#linearGradient-5)" xlinkHref="#path-6"/>
</g>
</TweenOne>
<TweenOne component="g" animation={{...animate.y, delay: 540}}>
<rect id="Rectangle-17-Copy-40" fill="#2459F5" x="71.7081174" y="99.4495677" width="10.7772021"
height="10.7896254"/>
</TweenOne>
<TweenOne component="g" animation={{...animate.y, delay: 400}}>
<use fill="#2471F5" xlinkHref="#path-7"/>
<use fillOpacity="0.370000005" fill="url(#linearGradient-5)" xlinkHref="#path-7"/>
</TweenOne>
<TweenOne animation={{...animate.draw, delay: 700}} component="polyline" id="Path-20" stroke="#2668F4"
strokeWidth="0.7182"
points="43.8670121 110.239193 43.8670121 125.632262 81.3129252 125.632262 81.3129252 131.566266"/>
</g>
</g>
<g id="s3">
<g id="Group-3" transform="translate(5.000000, 195.000000)">
<TweenOne component="g" animation={{...animate.y, delay: 520}}>
<mask id="mask-9" fill="white">
<use xlinkHref="#path-8"/>
</mask>
<use id="Rectangle-17-Copy-18" fill="#1778FF" xlinkHref="#path-8"/>
<ellipse id="Oval-5" fill="#1793FF" opacity="0.499125466" mask="url(#mask-9)" cx="12.1741071"
cy="-8.43258427" rx="88.8258929" ry="87.4325843"/>
</TweenOne>
<g id="Group-15"
transform="translate(54.500000, 21.500000) scale(-1, 1) translate(-54.500000, -21.500000) translate(15.000000, 7.000000)">
<TweenOne component="g" animation={{...animate.y, delay: 520}}>
<rect id="Rectangle-17-Copy-30" fill="#40A9FF"
transform="translate(21.545455, 10.875000) scale(1, -1) translate(-21.545455, -10.875000) "
x="0" y="0" width="43.0909091" height="21.75"/>
</TweenOne>
<TweenOne component="g" animation={{...animate.y, delay: 530}}>
<rect id="Rectangle-17-Copy-30" fill="#FFFFFF"
transform="translate(21.545455, 18.125000) scale(1, -1) translate(-21.545455, -18.125000) "
x="0" y="14.5" width="43.0909091" height="7.25"/>
</TweenOne>
<TweenOne component="g" animation={{...animate.y, delay: 540}}>
<rect id="Rectangle-17-Copy-30" fill="#2A6FF5"
transform="translate(5.386364, 10.875000) scale(1, -1) translate(-5.386364, -10.875000) "
x="0" y="0" width="10.7727273" height="21.75"/>
</TweenOne>
<TweenOne component="g" animation={{...animate.y, delay: 550}}>
<rect id="Rectangle-17-Copy-30" fill="#40A9FF"
transform="translate(5.386364, 18.125000) scale(1, -1) translate(-5.386364, -18.125000) "
x="0" y="14.5" width="10.7727273" height="7.25"/>
</TweenOne>
<TweenOne component="g" animation={{...animate.y, delay: 560}}>
<rect id="Rectangle-17-Copy-31" fill="#06080A"
transform="translate(61.045455, 21.750000) scale(-1, -1) translate(-61.045455, -21.750000) "
x="57.4545455" y="14.5" width="7.18181818" height="14.5"/>
</TweenOne>
<TweenOne component="g" animation={{...animate.y, delay: 570}}>
<rect id="Rectangle-17-Copy-31" fill="#FFFFFF"
transform="translate(61.045455, 25.375000) scale(-1, -1) translate(-61.045455, -25.375000) "
x="57.4545455" y="21.75" width="7.18181818" height="7.25"/>
</TweenOne>
<TweenOne component="g" animation={{...animate.y, delay: 580}}>
<rect id="Rectangle-17-Copy-31" fill="#FFEC3D"
transform="translate(75.409091, 25.375000) scale(-1, -1) translate(-75.409091, -25.375000) "
x="71.8181818" y="21.75" width="7.18181818" height="7.25"/>
</TweenOne>
<TweenOne component="g" animation={{...animate.y, delay: 590}}>
<ellipse id="Oval-3" fill="#06080A" cx="10.7727273" cy="14.5" rx="2.69318182" ry="2.71875"/>
</TweenOne>
</g>
<TweenOne component="g" animation={{...animate.y, delay: 600}}>
<circle id="Oval-3-Copy-5" fill="#06080A" cx="84" cy="44" r="2"/>
</TweenOne>
<TweenOne component="g" animation={{...animate.y, delay: 610}}>
<circle id="Oval-3-Copy-6" fill="#06080A" cx="84" cy="67" r="2"/>
</TweenOne>
<TweenOne component="g" animation={{...animate.y, delay: 605}}>
<path d="M84.5,45 L84.5,67" id="Path-18" stroke="#FFFFFF" strokeWidth="0.53865"
strokeLinecap="round"/>
</TweenOne>
<TweenOne component="g" animation={{...animate.y, delay: 610}}>
<rect id="Rectangle-17-Copy-48" fill="#95DE64" x="15" y="50" width="36" height="11"/>
</TweenOne>
</g>
</g>
<g id="s4">
<g id="Group-25" transform="translate(120.000000, 57.000000)">
<g id="Group-19" transform="translate(0.345423, 0.544669)">
<TweenOne component="g" animation={{...animate.x, delay: 300}}>
<mask id="mask-11" fill="white">
<use xlinkHref="#path-10"/>
</mask>
</TweenOne>
<TweenOne component="g" animation={{...animate.x, delay: 310}}>
<use id="Rectangle-17-Copy-4" fill="#D9F2FF" xlinkHref="#path-10"/>
</TweenOne>
<TweenOne component="g" animation={{...animate.x, delay: 320}}>
<ellipse id="Oval-2" fill="#597EF7" style={{mixBlendMode: 'multiply'}} mask="url(#mask-11)"
cx="267.633851" cy="49.4524496" rx="84.4214162" ry="84.518732"/>
</TweenOne>
<TweenOne component="g" animation={{...animate.x, delay: 330}}>
<polygon id="Rectangle-17-Copy-12" fillOpacity="0.09" fill="#000000" mask="url(#mask-11)"
points="231.709845 0 265.837651 0 265.837651 34.167147 231.709845 34.167147"/>
</TweenOne>
<TweenOne component="g" animation={{...animate.x, delay: 340}}>
<polygon id="Rectangle-17-Copy-14" fillOpacity="0.09" fill="#000000" mask="url(#mask-11)"
points="199.378238 34.167147 231.709845 34.167147 231.709845 57.5446686 199.378238 57.5446686"/>
</TweenOne>
<TweenOne component="g" animation={{...animate.x, delay: 350}}>
<ellipse id="Oval-9-Copy" fill="#06080A" mask="url(#mask-11)" cx="210.15544" cy="19.7809798"
rx="3.59240069" ry="3.59654179"/>
</TweenOne>
<TweenOne component="g" animation={{...animate.x, delay: 360}}>
<ellipse id="Oval-9-Copy" fill="#06080A" mask="url(#mask-11)" cx="283.799655" cy="18.8818444"
rx="3.59240069" ry="3.59654179"/>
</TweenOne>
<TweenOne component="g" animation={{...animate.x, delay: 370}}>
<ellipse id="Oval-9-Copy" fill="#06080A" mask="url(#mask-11)" cx="319.723661" cy="18.8818444"
rx="3.59240069" ry="3.59654179"/>
</TweenOne>
<TweenOne animation={{...animate.draw, delay: 700}} component="path"
d="M283.799655,18.8818444 L320.079974,18.8818444" id="Path-15" stroke="#FFFFFF"
strokeWidth="0.89775" strokeLinecap="round"/>
<TweenOne component="g" animation={{...animate.x, delay: 380}}>
<ellipse id="Oval-3" fill="#06080A" mask="url(#mask-11)" cx="14.3696028" cy="32.3688761"
rx="2.69430052" ry="2.69740634"/>
</TweenOne>
<TweenOne component="g" animation={{...animate.x, delay: 390}}>
<rect id="Rectangle-17-Copy-38" fill="#FFFFFF" mask="url(#mask-11)"
transform="translate(248.773748, 20.680115) scale(1, -1) translate(-248.773748, -20.680115) "
x="245.181347" y="17.0835735" width="7.18480138" height="7.19308357"/>
</TweenOne>
<TweenOne component="g" animation={{...animate.x, delay: 400}}>
<rect id="Rectangle-17-Copy-45" fill="#000000" mask="url(#mask-11)"
transform="translate(248.773748, 13.487032) scale(1, -1) translate(-248.773748, -13.487032) "
x="245.181347" y="9.89048991" width="7.18480138" height="7.19308357"/>
</TweenOne>
</g>
<TweenOne component="polyline" animation={{...animate.draw, delay: 800}} id="Path-8-Copy-2"
stroke="#020B3C" strokeWidth="0.89775" strokeLinecap="round" strokeLinejoin="round"
points="14.6158175 32.1958978 21.5551548 12.414918 27.9131733 32.1958978 34.2803138 12.414918 40.5554052 32.1958978 47.1995221 12.414918 53.2540275 32.1958978 60.1104377 12.414918 65.9999182 32.1958978 72.551986 12.414918 78.9017118 32.1958978 85.5275847 12.414918 91.7205784 32.1958978 98.0611822 12.414918 104.400957 32.1958978 109 20 208.00505 20"/>
<TweenOne component="polyline" animation={[{duration: 0, SVGDraw: '-5% 0'}, {
delay: 1200,
SVGDraw: '100% 105%',
duration: 1500,
repeat: -1,
repeatDelay: 2000
}]} id="Path-8-Copy-2" stroke="#ffffff" strokeWidth="1.3" strokeLinecap="round" strokeLinejoin="round"
points="14.6158175 32.1958978 21.5551548 12.414918 27.9131733 32.1958978 34.2803138 12.414918 40.5554052 32.1958978 47.1995221 12.414918 53.2540275 32.1958978 60.1104377 12.414918 65.9999182 32.1958978 72.551986 12.414918 78.9017118 32.1958978 85.5275847 12.414918 91.7205784 32.1958978 98.0611822 12.414918 104.400957 32.1958978 109 20 208.00505 20"/>
</g>
</g>
<g id="s5">
<g id="Group-9" transform="translate(443.000000, 7.000000)" fill="#1890FF">
<TweenOne component="g"
animation={[{...animate.scale, delay: 300, ease: 'easeOutBack'}, {...animate.loop, y: -5}]}>
<ellipse id="Oval-8-Copy-2" cx="22.2158895" cy="21.7723343" rx="21.5544041" ry="21.5792507"/>
<path
d="M0.66148532,21.7723343 C0.66148532,33.6902254 10.3117208,43.351585 22.2158895,43.351585 L22.2158895,43.351585 C34.1200582,43.351585 43.7702936,33.6902254 43.7702936,21.7723343"
id="Oval-8-Copy-2" style={{mixBlendMode: 'screen'}} opacity="0.963677705"/>
</TweenOne>
</g>
</g>
<g id="s6">
<g id="Group-23" transform="translate(55.000000, 28.000000)">
<TweenOne component="g" animation={{...animate.x}}>
<rect id="Rectangle-17" fill="#A3DCFF" opacity="0.543901586" x="0.682210708" y="0.772334294"
width="402.348877" height="28.7723343"/>
</TweenOne>
<TweenOne component="g" animation={{...animate.x, delay: 100}}>
<rect id="Rectangle-17" fill="url(#linearGradient-3)" x="0.682210708" y="0.772334294"
width="296.373057" height="28.7723343"/>
</TweenOne>
<TweenOne component="g" animation={{...animate.x, delay: 200}}>
<polygon id="Rectangle-17-Copy-13" fill="#40A9FF"
transform="translate(351.839378, 25.948127) scale(-1, -1) translate(-351.839378, -25.948127) "
points="331.183074 22.351585 372.495682 22.351585 372.495682 29.5446686 331.183074 29.5446686"/>
</TweenOne>
</g>
</g>
<g id="s7">
<g id="Group-12" transform="translate(256.000000, 113.000000)">
<g id="Group-14" transform="translate(0.856649, 0.291066)">
<TweenOne component="g" animation={{...animate.x2, delay: 500}}>
<mask id="mask-13" fill="white">
<use xlinkHref="#path-12"/>
</mask>
</TweenOne>
<TweenOne component="g" animation={{...animate.x2, delay: 550}}>
<use id="Rectangle-17-Copy-6" fill="#37ABFF" opacity="0.777693563" xlinkHref="#path-12"/>
</TweenOne>
<TweenOne component="g" animation={{...animate.x2, delay: 600}}>
<g id="Oval-4" mask="url(#mask-13)">
<use fill="#466CFF" fillRule="evenodd" xlinkHref="#path-14"/>
<ellipse strokeOpacity="0" stroke="#4B4EE6" strokeWidth="7.182" cx="39.5164076" cy="78.2247839"
rx="50.2950104" ry="50.3571268"/>
</g>
</TweenOne>
<path
d="M39.5164076,24.2766571 C9.75598585,24.2766571 -14.3696028,48.4300561 -14.3696028,78.2247839 C-14.3696028,108.019512 9.75598585,132.172911 39.5164076,132.172911 L39.5164076,132.172911"
id="Oval-4" fill="#40A9FF" opacity="0" mask="url(#mask-13)"/>
</g>
<TweenOne component="g" animation={{...animate.y2, delay: 700, ease: 'easeOutBack'}}>
<rect id="Rectangle-21-Copy" stroke="#FFFFFF" strokeWidth="0.7182" x="102.701069" y="75.2784084"
width="8.26280173" height="6.47488357"/>
<path d="M106.83247,54.2391931 L106.83247,74.9193084" id="Path-13" stroke="#FFFFFF"
strokeWidth="0.7182"/>
</TweenOne>
<TweenOne component="g" animation={{...animate.y, delay: 630}}>
<g id="Rectangle-21">
<use fill="#5FACFF" xlinkHref="#path-16"/>
<use fillOpacity="0.560000002" fill="url(#linearGradient-15)" xlinkHref="#path-16"/>
</g>
</TweenOne>
<TweenOne component="g" animation={{...animate.scale, delay: 650}}>
<ellipse id="Oval-3-Copy-4" fill="#06080A" cx="111.322971" cy="47.0461095" rx="2.69430052"
ry="2.69740634"/>
</TweenOne>
<TweenOne component="polyline" animation={{...animate.draw, delay: 900, duration: 2000}} id="line1"
stroke="#FFFFFF" strokeWidth="0.7182" strokeLinecap="round" strokeLinejoin="round"
points="111.21759 47.1541214 94.4492377 47.1541214 94.4492377 68.9613531 23.986392 68.9613531 67.6019908 60.459878 23.986392 58.0577373 67.6019908 49.9726914 23.986392 47.1541214 94.4492377 32.987828 94.4492377 16.9029855 12.2720998 16.9029855"/>
</g>
</g>
<g id="s8">
<g id="Group-2" transform="translate(131.122625, 99.544669)">
<TweenOne component="g" animation={{...animate.y, delay: 400}}>
<polygon id="Rectangle-17-Copy-5" fill="#1890FF"
points="125.734024 7.45244957 136.511226 0.259365994 136.511226 93.7694524 125.734024 100.063401"/>
</TweenOne>
<TweenOne component="g" animation={{...animate.y, delay: 430}}>
<polygon id="Rectangle-17-Copy-5" fill="#77C2FF"
points="0 0.259365994 136.511226 0.259365994 136.511226 93.7694524 0 93.7694524"/>
</TweenOne>
<TweenOne component="g" animation={{...animate.y, delay: 460}}>
<polygon id="Rectangle-17-Copy-5" fill="#FADB14"
points="0 0.259365994 35.9240069 0.259365994 35.9240069 47.0144092 0 47.0144092"/>
</TweenOne>
<TweenOne component="g" animation={{...animate.y, delay: 490}}>
<polygon id="Rectangle-17-Copy-5" fill="url(#linearGradient-17)" opacity="0.777693563"
points="0 47.0144092 43.1088083 47.0144092 43.1088083 93.7694524 0 93.7694524"/>
</TweenOne>
<TweenOne component="g" animation={{...animate.y, delay: 520}}>
<polygon id="Rectangle-17-Copy-5" fill="#1890FF"
points="0 0.259365994 39.5164076 0.259365994 39.5164076 25.4351585 0 25.4351585"/>
</TweenOne>
<TweenOne component="g" animation={{...animate.y, delay: 550}}>
<polygon id="Rectangle-17-Copy-5" fill="#06080A"
transform="translate(58.376511, 47.014409) scale(-1, 1) translate(-58.376511, -47.014409) "
points="55.6822107 0.259365994 61.0708117 0.259365994 61.0708117 93.7694524 55.6822107 93.7694524"/>
</TweenOne>
<TweenOne component="g" animation={{...animate.y, delay: 570}}>
<ellipse id="Oval-3" fill="#000000" cx="17.9620035" cy="35.3256484" rx="2.69430052"
ry="2.69740634"/>
</TweenOne>
<TweenOne component="g" animation={{...animate.y, delay: 580}}>
<ellipse id="Oval-3-Copy" fill="#000000" cx="119.447323" cy="16.443804" rx="3.59240069"
ry="3.59654179"/>
</TweenOne>
<TweenOne component="g" animation={{...animate.y, delay: 520}}>
<g id="Rectangle-20-Copy-7" opacity="0.419484608">
<use fill="#2471F5" xlinkHref="#path-19"/>
<use fillOpacity="0.370000005" fill="url(#linearGradient-18)" xlinkHref="#path-19"/>
</g>
<g id="Rectangle-20-Copy-8" opacity="0.419484608">
<use fill="#2471F5" xlinkHref="#path-20"/>
<use fillOpacity="0.370000005" fill="url(#linearGradient-18)" xlinkHref="#path-20"/>
</g>
<g id="Rectangle-20-Copy-9" opacity="0.419484608">
<use fill="#2471F5" xlinkHref="#path-21"/>
<use fillOpacity="0.370000005" fill="url(#linearGradient-18)" xlinkHref="#path-21"/>
</g>
<g id="Rectangle-20-Copy-10" opacity="0.419484608">
<use fill="#2471F5" xlinkHref="#path-22"/>
<use fillOpacity="0.370000005" fill="url(#linearGradient-18)" xlinkHref="#path-22"/>
</g>
<g id="Rectangle-20-Copy-11" opacity="0.419484608">
<use fill="#2471F5" xlinkHref="#path-23"/>
<use fillOpacity="0.370000005" fill="url(#linearGradient-18)" xlinkHref="#path-23"/>
</g>
</TweenOne>
<TweenOne component="g" animation={{...animate.scale, delay: 600, ease: 'easeOutBack'}}>
<ellipse id="Oval-8-Copy-5" fill="#FFFFFF" cx="82.6252159" cy="48.8126801" rx="21.5544041"
ry="21.5792507"/>
<path
d="M104.17962,48.8126801 C104.17962,36.894789 94.5293846,27.2334294 82.6252159,27.2334294 C70.7210472,27.2334294 61.0708117,36.894789 61.0708117,48.8126801"
id="Oval-8-Copy-5" fill="#06080A"/>
</TweenOne>
<TweenOne component="g" animation={{...animate.scale, delay: 700, ease: 'easeOutBack'}}>
<ellipse id="Oval-8-Copy-6" fill="#FFFFFF" cx="57.4784111" cy="74.8876081" rx="14.3696028"
ry="14.3861671"/>
<path
d="M57.4784111,89.2737752 L57.4784111,89.2737752 C65.4145235,89.2737752 71.8480138,82.8328688 71.8480138,74.8876081 C71.8480138,66.9423473 65.4145235,60.5014409 57.4784111,60.5014409"
id="Oval-8-Copy-6" fill="#FA541C"/>
</TweenOne>
<TweenOne component="polyline" animation={{...animate.draw, delay: 1000, duration: 1200}} id="Path-12"
stroke="#F5222D" strokeWidth="0.89775" strokeLinecap="round" strokeLinejoin="round"
points="17.9620035 36.0139709 17.9620035 74.8876081 57.8206649 74.8876081 83.039186 49.0506578 119.447323 49.0506578 119.447323 16.443804"/>
</g>
</g>
<g id="s9">
<g id="Group-22" transform="translate(399.000000, 91.000000)">
<TweenOne component="g" animation={{...animate.x2, delay: 650}}>
<polygon id="Rectangle-17-Copy-7" fill="#1890FF"
points="32.8134715 0.711815562 39.9982729 29.4841499 39.9982729 108.608069 32.8134715 79.8357349"/>
</TweenOne>
<TweenOne component="g" animation={{...animate.x2, delay: 680}}>
<rect id="Rectangle-17-Copy-7" fill="#40A9FF" x="32.8134715" y="0.711815562" width="107.772021"
height="79.1239193"/>
</TweenOne>
<TweenOne component="g" animation={{...animate.x2, delay: 700}}>
<rect id="Rectangle-17-Copy-7" fill="#2F54EB" x="39.9982729" y="0.711815562" width="100.587219"
height="50.351585"/>
</TweenOne>
<TweenOne component="g" animation={{...animate.x2, delay: 720}}>
<rect id="Rectangle-17-Copy-7" fill="#FADB14"
transform="translate(72.329879, 65.449568) scale(1, -1) translate(-72.329879, -65.449568) "
x="32.8134715" y="51.0634006" width="79.0328152" height="28.7723343"/>
</TweenOne>
<TweenOne component="g" animation={{...animate.alpha, delay: 1000}}>
<path
d="M169.324698,79.8357349 C169.324698,63.9452134 156.457717,51.0634006 140.585492,51.0634006 C124.713267,51.0634006 111.846287,63.9452134 111.846287,79.8357349"
id="Oval-3-Copy-2" fill="url(#linearGradient-24)" style={{mixBlendMode: 'multiply'}}
opacity="0.892432369"/>
</TweenOne>
<TweenOne component="g" animation={{...animate.scale, delay: 700, ease: 'easeOutBack'}}>
<ellipse id="Oval-8-Copy-7" fill="#06080A" cx="126.215889" cy="15.0979827" rx="3.59240069"
ry="3.59654179"/>
</TweenOne>
<TweenOne component="g" animation={{...animate.scale, delay: 730, ease: 'easeOutBack'}}>
<ellipse id="Oval-8-Copy-7" fill="#06080A" cx="66.2158895" cy="15.0979827" rx="3.59240069"
ry="3.59654179"/>
</TweenOne>
<TweenOne component="polyline" animation={{...animate.draw, delay: 1000, duration: 2000}} id="Path-14"
stroke="#40A9FF" strokeWidth="0.89775" strokeLinecap="round" strokeLinejoin="round"
points="65.3177893 15.0979827 0.654576857 15.0979827 33.9100503 28.0247718 1.27412828 28.0247718 33.9100503 40.6309287 1.27412828 40.6309287 33.9100503 54.2730979 0.654576857 54.2730979 0.654576857 97.8184438"/>
</g>
</g>
<g id="s10">
<g id="Group-5" transform="translate(120.000000, 208.000000)">
<TweenOne component="g" animation={{...animate.y, delay: 700}}>
<rect id="Rectangle-17-Copy-8" fill="#597AFF" x="0.345423143" y="0.599423631" width="359.240069"
height="98.0057637"/>
</TweenOne>
<TweenOne component="g" animation={{...animate.y, delay: 750}}>
<rect id="Rectangle-17-Copy-8" fill="#06080A" x="14.7150259" y="14.9855908" width="330.500864"
height="70.1325648"/>
</TweenOne>
<TweenOne component="g" animation={{...animate.y, delay: 800}}>
<ellipse id="Oval-8-Copy-4" fill="#4E56FB" cx="326.355786" cy="50.0518732" rx="8.08290155"
ry="8.09221902"/>
</TweenOne>
<TweenOne component="g" animation={{...animate.y, delay: 850}}>
<use fill="#4E56FB" xlinkHref="#path-26"/>
<use fillOpacity="0.659999967" fill="url(#linearGradient-25)" xlinkHref="#path-26"/>
</TweenOne>
<TweenOne component="path" animation={{...animate.draw, delay: 1000, duration: 1200}}
d="M39,62 L101,62 C106.620648,60.6554657 109.953981,56.3221324 111,49 C112.569028,38.0168014 108.5,27 106,27 C103.5,27 100.069028,42.4831986 101,49 C101.930972,55.5168014 110,62 111,62 C111.666667,62 113.333333,62 116,62 C121.620648,60.6554657 124.953981,56.3221324 126,49 C127.569028,38.0168014 123.5,27 121,27 C118.5,27 115.069028,42.4831986 116,49 C116.930972,55.5168014 125,62 126,62 C126.666667,62 128.333333,62 131,62 C136.620648,60.6554657 139.953981,56.3221324 141,49 C142.569028,38.0168014 138.5,27 136,27 C133.5,27 130.069028,42.4831986 131,49 C131.930972,55.5168014 140,62 141,62 C141.666667,62 143.333333,62 146,62 C151.620648,60.6554657 154.953981,56.3221324 156,49 C157.569028,38.0168014 153.5,27 151,27 C148.5,27 145.069028,42.4831986 146,49 C146.930972,55.5168014 156,62 156,62 C156,62 157.666667,62 161,62 C166.620648,60.6554657 169.953981,56.3221324 171,49 C172.569028,38.0168014 168.5,27 166,27 C163.5,27 160.069028,42.4831986 161,49 C161.930972,55.5168014 170,62 171,62 C171.666667,62 173.333333,62 176,62 C181.620648,60.6554657 184.953981,56.3221324 186,49 C187.569028,38.0168014 183.5,27 181,27 C178.5,27 175.069028,42.4831986 176,49 C176.930972,55.5168014 185,62 186,62 C186.666667,62 188.333333,62 191,62 C196.620648,60.6554657 199.953981,56.3221324 201,49 C202.569028,38.0168014 198.5,27 196,27 C193.5,27 190.069028,42.4831986 191,49 C191.620648,53.3445343 194.953981,57.6778676 201,62 C203.666667,62.6666667 205.666667,62.3333333 207,61 C209,59 211,50 217,50 C221,50 256,50 322,50"
id="Path" stroke="#6991FF" strokeWidth="1.4"/>
<TweenOne component="path" animation={[{duration: 0, SVGDraw: '-5% 0'}, {
delay: 2700,
SVGDraw: '100% 105%',
duration: 2000,
repeat: -1,
repeatDelay: 1500
}]}
d="M39,62 L101,62 C106.620648,60.6554657 109.953981,56.3221324 111,49 C112.569028,38.0168014 108.5,27 106,27 C103.5,27 100.069028,42.4831986 101,49 C101.930972,55.5168014 110,62 111,62 C111.666667,62 113.333333,62 116,62 C121.620648,60.6554657 124.953981,56.3221324 126,49 C127.569028,38.0168014 123.5,27 121,27 C118.5,27 115.069028,42.4831986 116,49 C116.930972,55.5168014 125,62 126,62 C126.666667,62 128.333333,62 131,62 C136.620648,60.6554657 139.953981,56.3221324 141,49 C142.569028,38.0168014 138.5,27 136,27 C133.5,27 130.069028,42.4831986 131,49 C131.930972,55.5168014 140,62 141,62 C141.666667,62 143.333333,62 146,62 C151.620648,60.6554657 154.953981,56.3221324 156,49 C157.569028,38.0168014 153.5,27 151,27 C148.5,27 145.069028,42.4831986 146,49 C146.930972,55.5168014 156,62 156,62 C156,62 157.666667,62 161,62 C166.620648,60.6554657 169.953981,56.3221324 171,49 C172.569028,38.0168014 168.5,27 166,27 C163.5,27 160.069028,42.4831986 161,49 C161.930972,55.5168014 170,62 171,62 C171.666667,62 173.333333,62 176,62 C181.620648,60.6554657 184.953981,56.3221324 186,49 C187.569028,38.0168014 183.5,27 181,27 C178.5,27 175.069028,42.4831986 176,49 C176.930972,55.5168014 185,62 186,62 C186.666667,62 188.333333,62 191,62 C196.620648,60.6554657 199.953981,56.3221324 201,49 C202.569028,38.0168014 198.5,27 196,27 C193.5,27 190.069028,42.4831986 191,49 C191.620648,53.3445343 194.953981,57.6778676 201,62 C203.666667,62.6666667 205.666667,62.3333333 207,61 C209,59 211,50 217,50 C221,50 256,50 322,50"
id="Path" stroke="rgba(255,255,255,.45)" strokeWidth="2"/>
</g>
</g>
<g id="s11">
<g id="Group-11" transform="translate(55.000000, 28.000000)">
<TweenOne component="g" animation={{...animate.x, delay: 50}}>
<rect id="Rectangle-17-Copy-2" fill="#06080A"
transform="translate(33.013817, 15.158501) scale(1, -1) translate(-33.013817, -15.158501) "
x="0.682210708" y="0.772334294" width="64.6632124" height="28.7723343"/>
</TweenOne>
<TweenOne component="g" animation={{...animate.scale, delay: 100}}>
<ellipse id="Oval-8-Copy-3" fill="#FA541C" cx="13.2556131" cy="15.1585014" rx="3.59240069"
ry="3.59654179"/>
</TweenOne>
<TweenOne component="g" animation={{...animate.x, delay: 150}}>
<rect id="Rectangle-17-Copy-36" fill="#FFFFFF"
transform="translate(39.300518, 15.158501) scale(1, -1) translate(-39.300518, -15.158501) "
x="24.9309154" y="10.6628242" width="28.7392055" height="8.99135447"/>
</TweenOne>
<TweenOne component="g" animation={{...animate.x, delay: 200}}>
<rect id="Rectangle-17-Copy-36" fill="#5471FE"
transform="translate(33.911917, 15.158501) scale(1, -1) translate(-33.911917, -15.158501) "
x="24.9309154" y="10.6628242" width="17.9620035" height="8.99135447"/>
</TweenOne>
</g>
</g>
<g id="s12">
<g id="Group-26" transform="translate(37.000000, 77.000000)" stroke="#FFFFFF" strokeWidth="0.89775"
strokeLinecap="round">
<TweenOne animation={{...animate.draw, delay: 750}} component="path"
d="M0.933191213,0.0542562538 L50.9075638,139.096936" id="Path-17"/>
</g>
</g>
<g id="s13">
<g id="Group-6" transform="translate(386.000000, 176.000000)">
<TweenOne component="g" animation={{...animate.alpha, y: 10, delay: 600}}>
<ellipse id="Oval-3-Copy-4" fill="#06080A" cx="13.6545769" cy="2.8184438" rx="2.69430052"
ry="2.69740634"/>
</TweenOne>
<TweenOne component="g" animation={{...animate.alpha, y: 10, delay: 600}}>
<rect id="Rectangle-17-Copy-11" fill="#E9ECF0" x="0.183074266" y="2.02881844" width="122.141623"
height="21.5792507"/>
</TweenOne>
<TweenOne component="g" animation={{...animate.alpha, y: 10, delay: 700}}>
<rect id="Rectangle-17-Copy-11" fill="url(#linearGradient-24)" opacity="0.892432369" x="0.183074266"
y="2.02881844" width="81.7271157" height="21.5792507"/>
</TweenOne>
<TweenOne component="g" animation={[{...animate.scale, delay: 1200}, {y: 10, ...animate.loop}]}>
<rect id="Rectangle-17-Copy-22" fill="#73D13D" opacity="0.471898321"
transform="translate(98.075993, 13.717579) scale(1, -1) translate(-98.075993, -13.717579) "
x="95.3816926" y="11.0201729" width="5.38860104" height="5.39481268"/>
</TweenOne>
<TweenOne component="g" animation={[{...animate.scale, delay: 1250}, {y: -10, ...animate.loop}]}>
<rect id="Rectangle-17-Copy-24" fill="#73D13D" opacity="0.471898321"
transform="translate(107.955095, 20.910663) scale(1, -1) translate(-107.955095, -20.910663) "
x="105.260794" y="18.2132565" width="5.38860104" height="5.39481268"/>
</TweenOne>
<TweenOne component="g" animation={[{...animate.scale, delay: 1300}, {y: 15, ...animate.loop}]}>
<rect id="Rectangle-17-Copy-26" fill="#73D13D" opacity="0.471898321"
transform="translate(131.305699, 6.524496) scale(1, -1) translate(-131.305699, -6.524496) "
x="128.611399" y="3.82708934" width="5.38860104" height="5.39481268"/>
</TweenOne>
</g>
</g>
<g id="s14">
<g transform="translate(386.000000, 199.000000)">
<TweenOne component="g"
animation={{...animate.alpha, repeat: -1, repeatDelay: 300, yoyo: true, delay: 1200}}>
<polygon id="Rectangle-17-Copy-11" fill="url(#linearGradient-27)" opacity="0.73046875"
points="52.2728843 58.8090207 68.4386874 58.8090207 122.324698 0.608069164 0.183074266 0.608069164"/>
</TweenOne>
<TweenOne component="g" animation={{...animate.alpha, delay: 1000}}>
<rect id="Rectangle-17-Copy-49" fill="#57DF83" opacity="0.907940765"
transform="translate(58.559585, 35.674352) scale(1, -1) translate(-58.559585, -35.674352) "
x="55.865285" y="32.9769452" width="5.38860104" height="5.39481268"/>
<rect id="Rectangle-17-Copy-49" fill="#FADB14" opacity="0.907940765"
transform="translate(67.540587, 35.674352) scale(1, -1) translate(-67.540587, -35.674352) "
x="64.8462867" y="32.9769452" width="5.38860104" height="5.39481268"/>
<rect id="Rectangle-17-Copy-49" fill="#FFFFFF" opacity="0.907940765"
transform="translate(49.578584, 35.674352) scale(1, -1) translate(-49.578584, -35.674352) "
x="46.8842832" y="32.9769452" width="5.38860104" height="5.39481268"/>
</TweenOne>
</g>
</g>
</g>
</g>
</g>
</g>
</svg>
);
}

View File

@ -0,0 +1,222 @@
export const header = [
{
title: '产品',
children: [
{
title: '云凤蝶', desc: '移动建站平台', img: 'https://gw.alipayobjects.com/zos/rmsportal/fLPzRmwAurHkPDVfHHiQ.svg', link: 'https://fengdie.alipay-eco.com/intro', top: '2px',
},
],
},
{
title: '设计体系',
children: [
{
title: '设计价值观', desc: 'Design Values', img: 'https://gw.alipayobjects.com/zos/rmsportal/zMeJnhxAtpXPZAUhUKJH.svg', link: 'https://ant.design/docs/spec/values-cn',
},
{
title: '视觉', desc: 'Visual', img: 'https://gw.alipayobjects.com/zos/rmsportal/qkNZxQRDqvFJscXVDmKp.svg', link: 'https://ant.design/docs/spec/colors-cn',
},
{
title: '可视化', desc: 'Visualisation', img: 'https://gw.alipayobjects.com/zos/rmsportal/MrUQjZNOJhYJCSZZuJDr.svg', link: 'https://antv.alipay.com/zh-cn/vis/index.html',
},
],
},
{
title: '技术方案',
children: [
{
title: 'Ant Design', desc: '蚂蚁 UI 体系', img: 'https://gw.alipayobjects.com/zos/rmsportal/ruHbkzzMKShUpDYMEmHM.svg', link: 'https://ant.design',
},
{
title: 'AntV', desc: '蚂蚁数据可视化解决方案', img: 'https://gw.alipayobjects.com/zos/rmsportal/crqUoMinEgjMeGGFAKzG.svg', link: 'https://antv.alipay.com',
},
{
title: 'Egg', desc: '企业级 Node 开发框架', img: 'https://gw.alipayobjects.com/zos/rmsportal/nEEwwpmNVihZimnBAtMf.svg', link: 'https://eggjs.org',
},
],
},
{
title: '关于',
children: [
{
title: '蚂蚁金服体验科技专栏', desc: '探索极致用户体验与最佳工程实践', img: 'https://gw.alipayobjects.com/zos/rmsportal/VsVqfjYxPTJaFbPcZqMb.svg', link: 'https://zhuanlan.zhihu.com/xtech',
},
],
},
];
export const banner = [
{
img: 'https://gw.alipayobjects.com/zos/rmsportal/cTyLQiaRrpzxFAuWwoDQ.svg',
imgMobile: 'https://gw.alipayobjects.com/zos/rmsportal/ksMYqrCyhwQNdBKReFIU.svg',
className: 'seeconf-wrap',
children: [
{ children: 'Seeking Experience & Engineering Conference', className: 'seeconf-en-name' },
{ children: '首届蚂蚁体验科技大会', className: 'seeconf-title', tag: 'h1' },
{ children: '探索极致用户体验与最佳工程实践', className: 'seeconf-cn-name' },
{
children: '了解详细',
className: 'banner-button',
tag: 'button',
link: 'https://seeconf.alipay.com/',
},
{ children: '2018.01.06 / 中国·杭州', className: 'seeconf-time' },
],
},
{
img: 'https://gw.alipayobjects.com/zos/rmsportal/cTyLQiaRrpzxFAuWwoDQ.svg',
imgMobile: 'https://gw.alipayobjects.com/zos/rmsportal/ksMYqrCyhwQNdBKReFIU.svg',
className: 'seeconf-wrap',
children: [
{ children: 'Seeking Experience & Engineering Conference', className: 'seeconf-en-name' },
{ children: '首届蚂蚁体验科技大会', className: 'seeconf-title', tag: 'h1' },
{ children: '探索极致用户体验与最佳工程实践', className: 'seeconf-cn-name' },
{
children: '了解详细',
className: 'banner-button',
tag: 'button',
link: 'https://seeconf.alipay.com/',
},
{ children: '2018.01.06 / 中国·杭州', className: 'seeconf-time' },
],
},
];
export const page1 = {
title: '高阶思维能力测试版块',
children: [
{
title: '数学建模',
content: 'Computational Thinking',
src: 'https://gw.alipayobjects.com/zos/rmsportal/vUxYuDdsbBBcMDxSGmwc.svg',
color: '#EB2F96',
shadowColor: 'rgba(166, 55, 112, 0.08)',
link: '/ticket-login-card',
},
{
title: '智能计算思维',
content: 'Culture Competency',
src: 'https://gw.alipayobjects.com/zos/rmsportal/qIcZMXoztWjrnxzCNTHv.svg',
color: '#1890FF',
shadowColor: 'rgba(15, 93, 166, 0.08)',
link: 'https://ant.design/docs/spec/colors-cn',
},
{
title: '文化理解与创新',
content: 'Critical Thinking',
src: 'https://gw.alipayobjects.com/zos/rmsportal/eLtHtrKjXfabZfRchvVT.svg',
color: '#AB33F7',
shadowColor: 'rgba(112, 73, 166, 0.08)',
link: 'https://antv.alipay.com/zh-cn/vis/index.html',
},
{
title: '跨学科问题解决',
content: 'Creativity',
src: 'https://gw.alipayobjects.com/zos/rmsportal/QCcDSfdbCIbVSsUZJaQK.svg',
color: '#EB2F96',
shadowColor: 'rgba(166, 55, 112, 0.08)',
link: 'https://ant.design/docs/spec/values-cn',
},
{
title: '审辩阅读',
content: 'Communication',
src: 'https://gw.alipayobjects.com/zos/rmsportal/hMSnSxMzmiGSSIXxFtNf.svg',
color: '#fadb14',
shadowColor: 'rgba(191, 188, 21, 0.08)',
link: 'https://ant.design/docs/spec/colors-cn',
},
{
title: '创造力倾向',
content: 'Collaboration',
src: 'https://gw.alipayobjects.com/zos/rmsportal/OMEOieDFPYDcWXMpqqzd.svg',
color: '#2f54eb',
shadowColor: 'rgba(73, 101, 166, 0.08)',
link: 'https://antv.alipay.com/zh-cn/vis/index.html',
},
],
};
export const page3 = {
title: '大家都喜爱的产品',
children: [
{
img: 'https://gw.alipayobjects.com/zos/rmsportal/iVOzVyhyQkQDhRsuyBXC.svg',
imgMobile: 'https://gw.alipayobjects.com/zos/rmsportal/HxEfljPlykWElfhidpxR.svg',
src: 'https://gw.alipayobjects.com/os/rmsportal/gCFHQneMNZMMYEdlHxqK.mp4',
},
{
img: 'https://gw.alipayobjects.com/zos/rmsportal/iVOzVyhyQkQDhRsuyBXC.svg',
imgMobile: 'https://gw.alipayobjects.com/zos/rmsportal/HxEfljPlykWElfhidpxR.svg',
src: 'https://gw.alipayobjects.com/os/rmsportal/gCFHQneMNZMMYEdlHxqK.mp4',
},
],
};
export const page4 = {
title: '众多企业正在使用',
children: [
'https://gw.alipayobjects.com/zos/rmsportal/qImQXNUdQgqAKpPgzxyK.svg', // 阿里巴巴
'https://gw.alipayobjects.com/zos/rmsportal/LqRoouplkwgeOVjFBIRp.svg', // 蚂蚁金服
'https://gw.alipayobjects.com/zos/rmsportal/TLCyoAagnCGXUlbsMTWq.svg', // 人民网
'https://gw.alipayobjects.com/zos/rmsportal/HmCGMKcJQMwfPLNCIhOH.svg', // cisco
'https://gw.alipayobjects.com/zos/rmsportal/aqldfFDDqRVFRxqLUZOk.svg', // GrowingIO
'https://gw.alipayobjects.com/zos/rmsportal/rqNeEFCGFuwiDKHaVaPp.svg', // 饿了么
'https://gw.alipayobjects.com/zos/rmsportal/FdborlfwBxkWIqKbgRtq.svg', // 滴滴出行
'https://gw.alipayobjects.com/zos/rmsportal/coPmiBkAGVTuTNFVRUcg.png', // 飞凡网
],
};
export const footer = [
{
title: '蚂蚁科技',
children: [
{ title: '蚂蚁金服开放平台', link: 'https://open.alipay.com' },
{ title: '蚂蚁体验云', link: 'https://xcloud.alipay.com' },
{ title: '蚂蚁金融云', link: 'https://www.cloud.alipay.com' },
],
},
{
title: '相关会议',
children: [
{ title: 'ATEC', link: 'https://atec.antfin.com' },
{ title: 'SEE Conf', link: 'https://seeconf.alipay.com' },
],
},
{
title: '联系我们',
children: [
{ title: '蚂蚁金服体验科技专栏', link: 'https://zhuanlan.zhihu.com/xtech' },
{ title: '蚂蚁金服体验科技官微', link: 'https://weibo.com/p/1005056420205486' },
{ title: 'AntV 官微', link: 'https://weibo.com/antv2017' },
{ title: 'Ant Design 专栏', link: 'https://zhuanlan.zhihu.com/antdesign' },
],
},
{
title: '蚂蚁体验云',
icon: 'https://gw.alipayobjects.com/zos/rmsportal/wdarlDDcdCaVoCprCRwB.svg',
children: [
{ title: 'Ant Design', desc: '蚂蚁 UI 体系', link: 'https://ant.design' },
{ title: 'AntV', desc: '蚂蚁数据可视化方案', link: 'https://antv.alipay.com' },
// { title: 'AntG', desc: '蚂蚁互动图形技术', link: 'http://antg.alipay.net' },
{ title: 'Egg', desc: '企业级 Node Web 开发框架', link: 'https://eggjs.org' },
{ title: '云凤蝶', desc: '移动建站平台', link: 'https://fengdie.alipay-eco.com/intro' },
],
},
];
// 图处预加载;
if (typeof document !== 'undefined') {
const div = document.createElement('div');
div.style.display = 'none';
document.body.appendChild(div);
[
'https://gw.alipayobjects.com/zos/rmsportal/KtRzkMmxBuWCVjPbBgRY.svg',
'https://gw.alipayobjects.com/zos/rmsportal/qIcZMXoztWjrnxzCNTHv.svg',
'https://gw.alipayobjects.com/zos/rmsportal/eLtHtrKjXfabZfRchvVT.svg',
'https://gw.alipayobjects.com/zos/rmsportal/iVOzVyhyQkQDhRsuyBXC.svg',
'https://gw.alipayobjects.com/zos/rmsportal/HxEfljPlykWElfhidpxR.svg',
'https://gw.alipayobjects.com/zos/rmsportal/wdarlDDcdCaVoCprCRwB.svg',
].concat(page4.children).forEach((src) => {
const img = new Image();
img.src = src;
div.appendChild(img);
});
}

View File

@ -0,0 +1,276 @@
export const Teams20DataSource = {
wrapper: { className: 'home-page-wrapper teams2-wrapper' },
page: { className: 'home-page teams2' },
OverPack: { playScale: 0.3, className: '' },
titleWrapper: {
className: 'title-wrapper',
children: [{ name: 'title', children: '团队成员' }],
},
block: {
className: 'block-wrapper',
gutter: 72,
children: [
{
name: 'block0',
className: 'block',
md: 8,
xs: 24,
image: {
name: 'image',
className: 'teams2-image',
children:
'https://gw.alipayobjects.com/mdn/rms_ae7ad9/afts/img/A*--rVR4hclJYAAAAAAAAAAABjARQnAQ',
},
titleWrapper: {
className: 'teams2-textWrapper',
children: [
{
name: 'title',
className: 'teams2-title',
children: '谢志勇',
},
{
name: 'content',
className: 'teams2-job',
children: '2018级博士生',
},
{
name: 'content1',
className: 'teams2-content',
children: '赣南师范大学研究生导师',
},
],
},
},
{
name: 'block1',
className: 'block',
md: 8,
xs: 24,
image: {
name: 'image',
className: 'teams2-image',
children:
'https://gw.alipayobjects.com/mdn/rms_ae7ad9/afts/img/A*njqxS5Ky7CQAAAAAAAAAAABjARQnAQ',
},
titleWrapper: {
className: 'teams2-textWrapper',
children: [
{
name: 'title',
className: 'teams2-title',
children: '罗海风',
},
{
name: 'content',
className: 'teams2-job',
children: '2017级博士生',
},
{
name: 'content1',
className: 'teams2-content',
children: '高阶思维能力测试组成员',
},
],
},
},
{
name: 'block2',
className: 'block',
md: 8,
xs: 24,
image: {
name: 'image',
className: 'teams2-image',
children:
'https://gw.alipayobjects.com/mdn/rms_ae7ad9/afts/img/A*--rVR4hclJYAAAAAAAAAAABjARQnAQ',
},
titleWrapper: {
className: 'teams2-textWrapper',
children: [
{ name: 'title', className: 'teams2-title', children: '刘启蒙' },
{
name: 'content',
className: 'teams2-job',
children: '中国教育创新研究院教师',
},
{
name: 'content1',
className: 'teams2-content',
children: '高阶思维能力测试组成员',
},
],
},
},
{
name: 'block3',
className: 'block',
md: 8,
xs: 24,
image: {
name: 'image',
className: 'teams2-image',
children:
'https://gw.alipayobjects.com/mdn/rms_ae7ad9/afts/img/A*--rVR4hclJYAAAAAAAAAAABjARQnAQ',
},
titleWrapper: {
className: 'teams2-textWrapper',
children: [
{ name: 'title', className: 'teams2-title', children: '王田' },
{
name: 'content',
className: 'teams2-job',
children: '2020级博士生',
},
{
name: 'content1',
className: 'teams2-content',
children: '高阶思维能力测试组成员',
},
],
},
},
{
name: 'block4',
className: 'block',
md: 8,
xs: 24,
image: {
name: 'image',
className: 'teams2-image',
children:
'https://gw.alipayobjects.com/mdn/rms_ae7ad9/afts/img/A*njqxS5Ky7CQAAAAAAAAAAABjARQnAQ',
},
titleWrapper: {
className: 'teams2-textWrapper',
children: [
{ name: 'title', className: 'teams2-title', children: '董晓舒' },
{
name: 'content',
className: 'teams2-job',
children: '2019级博士生',
},
{
name: 'content1',
className: 'teams2-content',
children: '高阶思维能力测试组成员',
},
],
},
},
{
name: 'block5',
className: 'block',
md: 8,
xs: 24,
image: {
name: 'image',
className: 'teams2-image',
children:
'https://gw.alipayobjects.com/mdn/rms_ae7ad9/afts/img/A*--rVR4hclJYAAAAAAAAAAABjARQnAQ',
},
titleWrapper: {
className: 'teams2-textWrapper',
children: [
{ name: 'title', className: 'teams2-title', children: '王佳敏' },
{
name: 'content',
className: 'teams2-job',
children: '区域教育质量健康体检项目组老师',
},
{
name: 'content1',
className: 'teams2-content',
children: '高阶思维能力测试组成员',
},
],
},
},
{
name: 'block6',
className: 'block',
md: 8,
xs: 24,
image: {
name: 'image',
className: 'teams2-image',
children:
'https://gw.alipayobjects.com/mdn/rms_ae7ad9/afts/img/A*--rVR4hclJYAAAAAAAAAAABjARQnAQ',
},
titleWrapper: {
className: 'teams2-textWrapper',
children: [
{ name: 'title', className: 'teams2-title', children: '赵萍萍' },
{
name: 'content',
className: 'teams2-job',
children: '北京师范大学博士后',
},
{
name: 'content1',
className: 'teams2-content',
children: '高阶思维能力测试组成员',
},
],
},
},
{
name: 'block7',
className: 'block',
md: 8,
xs: 24,
image: {
name: 'image',
className: 'teams2-image',
children:
'https://gw.alipayobjects.com/mdn/rms_ae7ad9/afts/img/A*njqxS5Ky7CQAAAAAAAAAAABjARQnAQ',
},
titleWrapper: {
className: 'teams2-textWrapper',
children: [
{ name: 'title', className: 'teams2-title', children: '胡梦玥' },
{
name: 'content',
className: 'teams2-job',
children: '区域教育质量健康体检项目组老师',
},
{
name: 'content1',
className: 'teams2-content',
children: '高阶思维能力测试组成员',
},
],
},
},
{
name: 'block8',
className: 'block',
md: 8,
xs: 24,
image: {
name: 'image',
className: 'teams2-image',
children:
'https://gw.alipayobjects.com/mdn/rms_ae7ad9/afts/img/A*--rVR4hclJYAAAAAAAAAAABjARQnAQ',
},
titleWrapper: {
className: 'teams2-textWrapper',
children: [
{ name: 'title', className: 'teams2-title', children: '董瑶瑶' },
{
name: 'content',
className: 'teams2-job',
children: '17级硕士生',
},
{
name: 'content1',
className: 'teams2-content',
children: '高阶思维能力测试组成员',
},
],
},
},
],
},
};

View File

@ -0,0 +1,54 @@
import React from 'react';
import DocumentTitle from 'react-document-title';
import {enquireScreen} from 'enquire-js';
import GlobalHeader from '../component/GlobalHeader/GlobalHeader';
import Banner from './Banner';
import Page1 from './Page1';
import Page2 from './Page2';
import Footer from './Footer';
import './static/style';
import TestPage from "./TestPage";
import Teams2 from "./Teams2";
import {Teams20DataSource} from './data.source';
let isMobile;
enquireScreen((b) => {
isMobile = b;
});
class Home extends React.PureComponent {
state = {
isMobile,
}
componentDidMount() {
enquireScreen((b) => {
this.setState({
isMobile: !!b,
});
});
}
render() {
return (
<DocumentTitle title="OpenCT">
<div style={{textAlign: 'left'}}>
<GlobalHeader current="landing"/>
<div className="home-wrapper">
<Banner isMobile={this.state.isMobile}/>
<TestPage isMobile={this.state.isMobile}/>
<Page1 isMobile={this.state.isMobile}/>
<Page2/>
<Teams2 dataSource={Teams20DataSource} isMobile={this.state.isMobile}/>
</div>
<Footer/>
</div>
</DocumentTitle>
);
}
}
export default Home;

View File

@ -0,0 +1,10 @@
body {
-webkit-font-smoothing: antialiased;
}
.text-center {
text-align: center!important;
}
a:focus {
text-decoration: none;
}

View File

@ -0,0 +1,6 @@
@import "~antd/lib/style/themes/default.less";
@site-heading-color: #0d1a26;
@site-text-color: #314659;
@site-text-color-secondary: #697b8c;
@site-border-color-split: #ebedf0;
@border-color: rgba(229, 231, 235, 100);

View File

@ -0,0 +1,86 @@
@import './default';
@padding-space: 114px;
footer.dark {
background-color: #000;
color: rgba(255, 255, 255, 0.65);
a {
color: #fff;
}
h2 {
color: rgba(255, 255, 255, 1);
& > span {
color: rgba(255, 255, 255, 1);
}
}
.bottom-bar {
border-top: 1px solid rgba(255, 255, 255, 0.25);
overflow: hidden;
}
}
footer {
border-top: 1px solid @border-color;
clear: both;
font-size: 12px;
background: #fff;
position: relative;
z-index: 100;
color: @site-text-color;
box-shadow: 0 1000px 0 1000px #fff;
.ant-row {
text-align: center;
.footer-center {
display: inline-block;
text-align: left;
> h2 {
font-size: 14px;
margin: 0 auto 24px;
font-weight: 500;
position: relative;
> .anticon {
font-size: 16px;
position: absolute;
left: -22px;
top: 3px;
color: #aaa;
}
}
> div {
margin: 12px 0;
}
}
}
.footer-wrap {
position: relative;
padding: 86px @padding-space 70px @padding-space;
}
.bottom-bar {
border-top: 1px solid @border-color;
text-align: right;
padding: 20px @padding-space;
margin: 0;
line-height: 24px;
a {
color: rgba(255, 255, 255, 0.65);
&:hover {
color: #fff;
}
}
.translate-button {
text-align: left;
width: 200px;
margin: 0 auto;
}
}
.footer-logo {
position: relative;
top: -2px;
}
.footer-flag {
position: relative;
top: -4px;
margin-right: 8px;
}
}

View File

@ -0,0 +1,78 @@
@import './default.less';
@import "~antd/lib/style/mixins/clearfix.less";
@header-height: 80px;
#header {
background-color: #fff;
position: relative;
z-index: 10;
height: 64px;
}
#logo {
overflow: hidden;
padding-left: 40px;
float: left;
line-height: 64px;
text-decoration: none;
height: 64px;
img {
display: inline;
vertical-align: middle;
margin-right: 16px;
height: 40px;
}
span {
color: @primary-color;
outline: none;
font-size: 14px;
line-height: 28px;
}
}
.header-meta {
.clearfix();
padding-right: 40px;
}
#menu {
float: right;
overflow: hidden;
height: 64px;
.ant-menu {
line-height: 60px;
}
.ant-menu-horizontal {
border-bottom: none;
& > .ant-menu-item {
border-top: 2px solid transparent;
&:hover {
border-top: 2px solid @primary-color;
border-bottom: 2px solid transparent;
}
}
& > .ant-menu-item-selected {
border-top: 2px solid @primary-color;
border-bottom: 2px solid transparent;
a {
color: @primary-color;
}
}
}
}
#preview {
padding-top: 17px;
float: right;
margin-left: 32px;
button {
border-radius: 32px;
}
}
#preview-button {
.ant-btn {
color: @site-text-color;
}
}

View File

@ -0,0 +1,508 @@
@import './default.less';
html,
body {
height: 100%;
}
body {
font-family: @font-family;
line-height: 1.5;
color: @site-text-color;
font-size: 14px;
background: #fff;
transition: background 1s cubic-bezier(0.075, 0.82, 0.165, 1);
overflow-x: hidden;
}
a {
transition: color .3s ease;
&:focus {
text-decoration: underline;
text-decoration-skip: ink;
}
}
.home-wrapper {
width: 100%;
color: #697b8c;
.ant-btn {
min-width: 110px;
height: 40px;
border-radius: 20px;
font-size: 16px;
&:hover {
transform: translateY(-4px);
box-shadow: 0 4px 12px rgba(24, 144, 255, .4);
}
}
}
svg g {
transform-origin: 50% 50%;
transform-box: fill-box;
}
.banner-wrapper {
height: 526px;
width: 100%;
max-width: 1500px;
margin: auto;
position: relative;
.banner-title-wrapper {
width: 40%;
max-width: 480px;
height: 245px;
position: absolute;
top: 0;
bottom: 0;
left: 8%;
margin: auto;
z-index: 1;
> * {
will-change: transform;
}
h1 {
font-family: "Futura", "Helvetica Neue For Number", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "Helvetica Neue", Helvetica, Arial, sans-serif;
font-size: 54px;
margin: 12px 0;
}
p {
font-size: 20px;
}
.button-wrapper {
margin-top: 64px;
line-height: 40px;
align-items: center;
display: flex;
.github-btn {
display: inline-block;
height: 28px;
.gh-btn {
height: 28px;
border-radius: 4px;
background: rgba(243, 243, 243, 1);
background: linear-gradient(to bottom, rgba(255, 255, 255, 1) 0%, rgba(243, 243, 243, 1) 100%);
border: 1px solid #ebedf0;
align-items: center;
display: flex;
padding: 0 12px;
font-size: 13px;
&:hover {
color: @primary-color;
}
.gh-ico {
margin-right: 8px;
}
}
.gh-count {
height: 28px;
line-height: 22px;
background: #fff;
border: 1px solid #ebedf0;
border-radius: 4px;
padding: 2px 8px;
font-size: 13px;
}
}
}
.title-line {
transform: translateX(-64px);
animation: bannerTitleLine 3s ease-in-out 0s infinite;
}
}
.banner-image-wrapper {
width: 45%;
max-width: 598px;
height: 324px;
position: absolute;
right: 8%;
margin: auto;
top: 0;
bottom: 0;
opacity: 0;
}
}
.home-banner-image {
display: none;
}
.title-line-wrapper {
height: 2px;
width: 100%;
overflow: hidden;
.title-line {
height: 100%;
width: 64px;
transform: translateX(-64px);
background: linear-gradient(to right, rgba(24, 144, 255, 0) 0%, rgba(24, 144, 255, 1) 100%);
}
}
.home-page {
margin: 50px auto;
h2 {
margin: 0 auto 32px;
font-size: 38px;
line-height: 46px;
color: #0d1a26;
font-weight: 400;
text-align: center;
}
}
.home-page-wrapper {
max-width: 1280px;
width: 100%;
margin: auto;
position: relative;
}
/** page1 **/
.page1 {
height: 1060px;
}
.page1-line.title-line-wrapper {
width: 312px;
margin: 24px auto 76px;
.title-line {
animation: page1TitleLine 3s ease-in-out 1.5s infinite;
}
}
.page1-bg {
font-size: 320px;
color: #ebedf0;
position: absolute;
width: 100%;
text-align: center;
opacity: .25;
top: 0;
transform: translateY(960px);
}
.page1-box-wrapper {
margin-bottom: 62px;
display: flex;
align-items: flex-start;
li {
width: 33.33%;
display: inline-block;
will-change: transform;
.page1-box {
width: 194px;
margin: auto;
text-align: center;
position: relative;
.page1-image {
width: 80px;
height: 80px;
border-radius: 40px;
margin: 20px auto 32px;
position: relative;
z-index: 1;
display: flex;
align-items: center;
justify-content: center;
background: #fff;
transition: box-shadow .3s ease-out, transform .3s ease-out;
}
&:hover .page1-image {
transform: translateY(-5px);
}
h3 {
color: #0d1a26;
font-size: 16px;
margin: 8px auto;
}
}
}
}
.page1-point-wrapper {
position: absolute;
width: 0;
left: 50%;
top: 0;
.point-0 {
width: 4px;
height: 4px;
}
.point-2, .point-ring {
width: 10px;
height: 10px;
}
.point-ring {
border-style: solid;
border-width: 1px;
background: transparent !important;
}
.point-1 {
width: 6px;
height: 6px;
}
.point-3 {
width: 15px;
height: 15px;
}
i {
display: inline-block;
border-radius: 100%;
position: absolute;
opacity: 0;
transform: translate(0, 30px);
}
}
/** page2 **/
.page2 {
text-align: center;
height: 588px;
.page2-content {
will-change: transform;
}
}
.page2-line {
margin: 148px auto 24px;
width: 114px;
.title-line {
animation: page2TitleLine 3s ease-in-out 0s infinite;
}
}
.page-content {
width: 760px;
margin: 24px auto 32px;
line-height: 28px;
}
.home-code {
width: 90%;
max-width: 840px;
border-radius: 4px;
background: #f2f4f5;
line-height: 28px;
margin: 16px auto;
color: #151e26;
font-size: 16px;
font-family: @code-family;
text-align: left;
padding: 20px 50px;
span {
color: #f5222d;
}
}
.home-code-comment {
color: @site-text-color-secondary !important;
}
@keyframes bannerTitleLine {
0%, 25% {
transform: translateX(-64px);
}
75%, 100% {
transform: translateX(544px);
}
}
@keyframes page1TitleLine {
0%, 25% {
transform: translateX(-64px);
}
75%, 100% {
transform: translateX(376px);
}
}
@keyframes page2TitleLine {
0%, 25% {
transform: translateX(-64px);
}
75%, 100% {
transform: translateX(178px);
}
}
@import "./default.less";
@import "./custom.less";
.page {
&-wrapper {
width: 100%;
will-change: transform;
}
width: 100%;
max-width: 1200px;
padding: 0 24px;
margin: auto;
overflow: hidden;
>h1 {
margin: 144px auto 32px;
font-size: 38px;
line-height: 46px;
color: #0d1a26;
font-weight: 400;
text-align: center;
}
>i {
width: 64px;
margin: auto;
height: 2px;
display: block;
background: rgb(22, 217, 227);
background: linear-gradient(to right, rgba(22, 217, 227, 1) 0%, rgba(22, 119, 227, 1) 100%);
}
}
.banner-anim {
&-arrow {
opacity: 0;
transition: opacity .3s @ease-out;
}
&:hover {
.banner-anim-arrow {
opacity: 1;
}
}
}
.banner {
background: url(https://gw.alipayobjects.com/zos/rmsportal/okhVRKJXxQpbpKGtKneS.svg) no-repeat center top;
background-size: contain;
overflow: hidden;
font-family: PingFang SC, Helvetica Neue For Number, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Hiragino Sans GB, Microsoft YaHei, Helvetica Neue, Helvetica, Arial, sans-serif;
.page {
max-width: 1248px;
}
.logo {
background: url(https://gw.alipayobjects.com/zos/rmsportal/khXpcyRYlACLokoNTzwc.svg) no-repeat;
width: 127px;
height: 110px;
margin: 86px auto 40px;
}
& &-anim {
margin: 0 auto 64px;
height: 600px;
box-shadow: 0 20px 32px rgba(1, 4, 8, .12);
background: #fff;
border-radius: 8px;
&-elem {
.banner-bg {
width: 100%;
height: 100%;
position: absolute;
top: 0;
left: 0;
overflow: hidden;
background-position: center;
background-repeat: no-repeat;
}
}
}
&-button {
.ant-btn {
color: #fff;
border: none;
}
}
}
.page1 {
min-height: 720px;
.page {
>h1 {
margin-top: 64px;
}
}
&-item {
text-align: center;
margin-top: 96px;
&-link {
display: block;
width: 180px;
margin: auto;
}
&-img {
width: 180px;
height: 180px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 100%;
background: #fff;
position: relative;
z-index: 1;
}
&-title {
margin-top: 56px;
font-size: 20px;
color: #0D1A26;
}
p {
color: #697B8C;
margin-top: 8px;
}
}
.point-wrapper {
position: absolute;
width: 0;
left: 50%;
top: 0;
z-index: 0;
.point-0 {
width: 4px;
height: 4px;
}
.point-2 {
width: 10px;
height: 10px;
}
.point-ring {
width: 12px;
height: 12px;
border-style: solid;
border-width: 1px;
background: transparent !important;
}
.point-ring-0 {
width: 4px;
height: 4px;
}
.point-1 {
width: 12px;
height: 12px;
}
.point-3 {
width: 21px;
height: 21px;
}
i {
display: inline-block;
border-radius: 100%;
position: absolute;
opacity: 0;
transform: translate(0, 30px);
}
}
}

View File

@ -0,0 +1,331 @@
@import './default.less';
.nav-phone-icon {
display: none;
position: absolute;
right: 30px;
top: 25px;
z-index: 1;
width: 16px;
height: 22px;
cursor: pointer;
}
@media only screen and (min-width: 1440px) and (max-width: 1599px) {
.main-wrapper > .ant-row > .ant-col-xl-5 {
width: 274px;
}
#header .ant-row .ant-col-xl-5 {
width: 274px;
}
}
@media only screen and (max-width: 1300px) {
#search-box {
display: none;
}
}
@media only screen and (max-width: @screen-xl) {
#logo {
padding: 0 40px;
}
.banner-wrapper .banner-title-wrapper {
h1 {
font-size: 36px;
}
p {
font-size: 16px;
}
}
}
@media only screen and (max-width: @screen-lg) {
.code-boxes-col-2-1, .code-boxes-col-1-1 {
float: none;
width: 100%;
}
.preview-image-boxes {
margin: 0 !important;
float: none;
width: 100%;
}
.preview-image-box {
padding-left: 0;
margin: 10px 0;
}
.banner-entry {
position: relative;
top: 30px;
left: 0;
text-align: center;
}
.image-wrapper {
display: none;
}
.banner-wrapper {
background-position: 40%;
}
.content-wrapper .text-wrapper {
float: none;
text-align: center;
left: 0;
width: 100%;
padding: 0;
> p {
max-width: 100% !important;
padding: 0 40px;
}
}
.content-wrapper.page {
min-height: 300px;
height: 300px;
}
.banner-text-wrapper {
left: 50%;
transform: translateX(-50%);
text-align: center;
.start-button {
text-align: center;
> a {
margin: 0 4px;
}
}
.github-btn {
text-align: center;
float: none;
display: inline-block;
}
.line {
display: none;
}
}
button.lang {
display: block;
margin: 29px auto 16px;
color: @site-text-color;
border-color: @site-text-color;
}
div.version {
display: block;
margin: 29px auto 16px;
& > .ant-select-selection {
color: @site-text-color;
&:not(:hover) {
border-color: @site-text-color;
}
}
}
.popover-menu {
width: 300px;
button.lang {
margin: 16px auto;
float: none;
}
div.version {
margin: 32px auto 16px;
float: none;
}
.ant-popover-inner {
overflow: hidden;
&-content {
padding: 0;
}
}
}
.toc {
display: none;
}
.nav-phone-icon {
display: block;
}
.nav-phone-icon:before {
content: "";
display: block;
border-radius: 2px;
width: 16px;
height: 2px;
background: #777;
box-shadow: 0 6px 0 0 #777, 0 12px 0 0 #777;
position: absolute;
}
.main {
height: calc(100% - 86px);
}
.aside-container {
float: none;
width: auto;
padding-bottom: 30px;
border-right: 0;
margin-bottom: 30px;
}
.main-container {
padding-left: 16px;
padding-right: 16px;
margin-right: 0;
> .markdown > * {
width: 100% !important;
}
}
.main-wrapper {
width: 100%;
border-radius: 0;
margin: 0;
}
#footer {
text-align: center;
.footer-wrap {
padding: 40px;
}
.footer-center {
text-align: center;
}
h2 {
margin-top: 16px;
}
.bottom-bar {
text-align: center;
.translate-button {
width: auto;
text-align: center;
margin-bottom: 16px;
}
}
}
.prev-next-nav {
margin-left: 16px;
width: ~"calc(100% - 32px)";
}
.drawer {
.ant-menu-inline .ant-menu-item:after,
.ant-menu-vertical .ant-menu-item:after {
left: 0;
right: auto;
}
}
}
@media only screen and (max-width: @screen-md) {
#logo {
padding: 0;
display: block;
margin-left: auto;
margin-right: auto;
float: none;
width: 200px;
}
.header-meta {
padding-right: 80px;
}
.home-banner-image {
display: block;
}
.home-banner-anim {
display: none;
}
.banner-wrapper {
width: 80%;
height: calc(~"100vh - 64px");
overflow: hidden;
.banner-title-wrapper, .banner-image-wrapper {
display: block;
position: initial;
width: 100%;
height: auto;
}
.banner-title-wrapper {
text-align: center;
max-width: 480px;
.button-wrapper {
text-align: center;
display: block;
margin-top: 5vh;
a {
display: inline-block;
}
.github-btn {
display: flex;
margin: 20px auto;
justify-content: center;
}
}
}
.banner-image-wrapper {
margin: 10vh auto 5vh;
}
}
.home-page {
width: 90%;
}
.home-code, .page-content {
width: 100%;
}
#footer {
.footer-wrap {
padding: 0;
}
}
}
@media only screen and (max-width: @screen-xs) {
.page1 {
height: 2400px;
}
.page2 {
height: 628px;
}
.page1-box-wrapper {
display: block;
li {
width: 80%;
display: block;
margin: 0 auto 100px;
.page1-box {
width: 100%;
}
}
}
.banner-wrapper .banner-title-wrapper {
h1 {
font-size: 28px;
}
p {
font-size: 16px;
}
}
}
@media only screen and (max-width: 320px) {
.home-page h2 {
font-size: 24px;
}
.page2 {
height: 648px;
}
}

View File

@ -0,0 +1,6 @@
import 'react-github-button/assets/style.css';
import './header.less';
import './home.less';
import './footer.less';
import './responsive.less';
import './teams2.less';

View File

@ -0,0 +1,72 @@
@teams2: teams2;
.@{teams2}-wrapper {
min-height: 446px;
overflow: hidden;
.@{teams2} {
overflow: hidden;
height: 100%;
padding: 0px 24px 64px 24px;
>.title-wrapper {
margin: 0 auto 48px;
text-align: center;
}
.block-wrapper {
position: relative;
height: 100%;
overflow: hidden;
padding: 20px 0;
min-height: 456px;
.block {
margin-bottom: 48px;
vertical-align: text-top;
&.queue-anim-leaving {
position: relative !important;
}
}
}
&-image {
color: #404040;
width: 100%;
img {
width: 100%;
}
}
&-textWrapper {
padding-left: 16px;
}
&-title {
font-size: 18px;
margin-bottom: 2px;
}
&-job {
margin-bottom: 4px;
}
&-job,
&-content {
font-size: 12px;
color: #919191;
}
}
}
@media screen and (max-width: 767px) {
.@{teams2}-wrapper {
min-height: 1440px;
.@{teams2}.home-page {
padding-bottom: 0;
}
}
}

View File

@ -0,0 +1,18 @@
import React from 'react';
import { Button } from 'antd';
export const isImg = /^http(s)?:\/\/([\w-]+\.)+[\w-]+(\/[\w-./?%&=]*)?/;
export const getChildrenToRender = (item, i) => {
let tag = item.name.indexOf('title') === 0 ? 'h1' : 'div';
tag = item.href ? 'a' : tag;
let children = typeof item.children === 'string' && item.children.match(isImg)
? React.createElement('img', { src: item.children, alt: 'img' })
: item.children;
if (item.name.indexOf('button') === 0 && typeof item.children === 'object') {
children = React.createElement(Button, {
...item.children
});
}
return React.createElement(tag, { key: i.toString(), ...item }, children);
};

View File

@ -0,0 +1,254 @@
import React, {useEffect, useState} from "react";
import {Button, Col, Input, message, Row, Select, Upload} from "antd";
import {Link} from 'react-router-dom'
import ImgCrop from 'antd-img-crop';
import '../../Outline/index.less'
import {LoadingOutlined, PlusOutlined} from "@ant-design/icons";
import ProjectApi from "../../../../../api/ProjectApi";
function InfoEditPage(obj) {
const pid = obj.pid
const subjects = ['语文', '数学', '英语', '科学']
const skills = ['学习与创新技能', '信息、媒体与技术技能', '生活与职业技能']
const [change, setChange] = useState(false)
const [selectedSubjects, setSelectedSubjects] = useState([])
const [selectedSkills, setSelectedSkills] = useState([])
const [loading, setLoading] = useState(false)
const [imageUrl, setImageUrl] = useState()
const [projectTitle, setProjectTitle] = useState('')
const [projectIntroduce, setProjectIntroduce] = useState('')
const [projectGoal, setProjectGoal] = useState('')
const stringToList = str => {
if (str === '') {
return []
}
return str.split(',')
}
useEffect(() => {
ProjectApi.getProjectDetail(pid)
.then((res) => {
if (res.data.code === 200) {
setImageUrl(res.data.project.image)
setProjectTitle(res.data.project.projectTitle)
setProjectIntroduce(res.data.project.projectIntroduce)
setProjectGoal(res.data.project.projectGoal)
setSelectedSkills(stringToList(res.data.project.skills))
setSelectedSubjects(stringToList(res.data.project.subjects))
}
})
.catch((e) => {
console.log(e)
})
}, [])
const changeTitle = value => {
setChange(true)
setProjectTitle(value.target.value)
}
const changeIntroduce = value => {
setChange(true)
setProjectIntroduce(value.target.value)
}
const changeGoal = value => {
setChange(true)
setProjectGoal(value.target.value)
}
const onUploadImage = (file) => {
setChange(true)
setLoading(true)
file = file.file;
const isJpgOrPng = file.type === 'image/jpeg' || file.type === 'image/png';
if (!isJpgOrPng) {
message.error('只能上传 JPG/PNG 格式文件');
}
const isLt2M = file.size / 1024 / 1024 < 2;
if (!isLt2M) {
message.error('图片应当小于 2MB');
}
const r = new FileReader();
r.addEventListener('load', () => upload(r.result));
r.readAsDataURL(file);
}
const upload = (file) => {
setImageUrl(file)
setLoading(true)
}
const onPreview = file => {
let src = file.url;
if (!src) {
src = new Promise(resolve => {
const reader = new FileReader();
reader.readAsDataURL(file.originFileObj);
reader.onload = () => resolve(reader.result);
});
}
const image = new Image();
image.src = src;
const imgWindow = window.open(src);
imgWindow.document.write(image.outerHTML);
}
const onFinish = () => {
let data = {
id: pid,
image: imageUrl,
projectTitle: projectTitle,
projectIntroduce: projectIntroduce,
projectGoal: projectGoal,
subjects: selectedSubjects.toString(),
skills: selectedSkills.toString(),
}
ProjectApi.updateProject(data, pid)
.then((res) => {
if (res.data.code === 200) {
message.success(res.data.msg)
setTimeout(()=>{
window.location.href = `/project/${pid}/outline/edit`
}, 200)
}
})
.catch((e) => {
console.log(e)
})
}
const nextPage = () => {
if (checkInput()) {
if (change) {
onFinish();
} else {
window.location.href = `/project/${pid}/outline/edit`
}
}
}
const checkInput = () => {
if (imageUrl === "") {
message.error("请上传图片")
return false
}
if (projectTitle === "") {
message.error("请输入标题")
return false
}
if (projectIntroduce === "") {
message.error("请输入简介")
return false
}
return true
}
const handleSubjectsChange = selected => {
setChange(true)
setSelectedSubjects(selected)
}
const handleSkillsChange = selected => {
setChange(true)
setSelectedSkills(selected)
}
return (
<div>
<Row>
<Col span={4} offset={2}>上传图片</Col>
<Col span={16}>
<ImgCrop rotate aspect="1.2">
<Upload
name="avatar"
listType="picture-card"
className="avatar-uploader"
showUploadList={false}
customRequest={onUploadImage}
onPreview={onPreview}
>
{
imageUrl ? <img src={imageUrl} alt="avatar" style={{width: '100%'}}/>
:
<div>
{loading ? <LoadingOutlined/> : <PlusOutlined/>}
<div style={{marginTop: 8}}>上传图片</div>
</div>
}
</Upload>
</ImgCrop>
</Col>
</Row>
<Row style={{marginTop: '20px'}}>
<Col span={4} offset={2}>输入标题</Col>
<Col span={16}><Input value={projectTitle} maxLength={40} onChange={changeTitle}/></Col>
</Row>
<Row style={{marginTop: '20px'}}>
<Col span={4} offset={2}>输入简介</Col>
<Col span={16}>
<Input.TextArea rows={5} value={projectIntroduce} maxLength={1000} onChange={changeIntroduce}/>
</Col>
</Row>
<Row style={{marginTop: '20px'}}>
<Col span={4} offset={2}>输入目标</Col>
<Col span={16}>
<Input.TextArea rows={3} value={projectGoal} maxLength={500} onChange={changeGoal}/>
</Col>
</Row>
<Row style={{marginTop: '20px'}}>
<Col span={4} offset={2}>选择学科</Col>
<Col span={16}>
<Select
showArrow
mode="multiple"
placeholder="选择学科"
value={selectedSubjects}
onChange={handleSubjectsChange}
style={{width: '100%'}}
>
{subjects.map(item => (
<Select.Option key={item} value={item}>
{item}
</Select.Option>
))}
</Select>
</Col>
</Row>
<Row style={{marginTop: '20px'}}>
<Col span={4} offset={2}>选择技能</Col>
<Col span={16}>
<Select
showArrow
mode="multiple"
placeholder="选择技能"
value={selectedSkills}
onChange={handleSkillsChange}
style={{width: '100%'}}
>
{skills.map(item => (
<Select.Option key={item} value={item}>
{item}
</Select.Option>
))}
</Select>
</Col>
</Row>
<Row style={{marginTop: '20px'}}>
<Col span={4} offset={20}>
<Button
size="middle"
style={{marginRight: '20px'}}
onClick={nextPage}
>下一页
</Button>
</Col>
</Row>
</div>
)
}
export default InfoEditPage

View File

@ -0,0 +1,58 @@
import React from "react";
import DocumentTitle from 'react-document-title';
import {Card, Divider, PageHeader, Steps} from "antd";
import '../Outline/index.less'
import InfoEditPage from "./component/InfoEditPage";
const {Step} = Steps;
class EditInfo extends React.PureComponent {
state = {
current: 0,
pid: this.props.match.params.id
}
componentDidMount() {
}
back = e => {
window.location.href = `/project/${this.state.pid}/info`
}
render() {
const {pid} = this.state
return (
<DocumentTitle title="Project">
<div style={{backgroundColor: '#f2f4f5', minHeight: '100vh',}}>
<PageHeader
className="site-page-header"
onBack={() => this.back()}
title="返回"
subTitle="我的项目"
/>
<div
style={{
paddingTop: '20px',
paddingLeft: '20px',
paddingRight: '20px',
paddingBottom: '20px'
}}
>
<Card className="edit-card">
<Steps current={0} style={{marginLeft: '20px'}}>
<Step title="填写信息"/>
<Step title="添加章节" style={{marginRight: '20px'}}/>
</Steps>
<Divider/>
<InfoEditPage pid={pid}/>
</Card>
</div>
</div>
</DocumentTitle>
)
}
}
export default EditInfo

View File

@ -0,0 +1,350 @@
import React, {useEffect, useState} from "react";
import {Button, Col, Input, Menu, message, Modal, Popconfirm, Row} from 'antd'
import {ArrowDownOutlined, ArrowUpOutlined, DeleteOutlined, EditOutlined} from '@ant-design/icons'
import {Link} from 'react-router-dom'
import ChapterApi from "../../../../../api/ChapterApi"
import SectionApi from "../../../../../api/SectionApi"
import util from "../../../component/Util"
const {SubMenu} = Menu;
function OutlineEditPage(obj) {
const pid = obj.pid
const [chapters, setChapters] = useState([])
const [chapterModalVisible, setChapterModalVisible] = useState(false)
const [sectionModalVisible, setSectionModalVisible] = useState(false)
const [opt, setOpt] = useState('add')
const [chapter, setChapter] = useState({})
const [section, setSection] = useState({})
const [index, setIndex] = useState('')
const [subIndex, setSubIndex] = useState('')
const [chapterName, setChapterName] = useState('')
const [sectionName, setSectionName] = useState('')
useEffect(() => {
getChapters()
}, [])
const getChapters = () => {
ChapterApi.getProjectChapters(pid)
.then((res) => {
if (res.data.chapters === null) {
setChapters([])
} else {
setChapters(res.data.chapters)
}
})
.catch((e) => {
console.log(e)
})
}
const addChapter = e => {
setOpt('add')
setChapterModalVisible(true)
}
const modifyChapter = (e, c, index) => {
e.stopPropagation()
setChapter(c)
setChapterName(c.chapterName)
setIndex(index)
setOpt('modify')
setChapterModalVisible(true)
}
const addSection = (c, index) => {
setOpt('add')
setChapter(c)
setIndex(index)
setSectionModalVisible(true)
}
const modifySection = (s, index, subIndex) => {
setSection(s)
setSectionName(s.sectionName)
setIndex(index)
setSubIndex(subIndex)
setOpt('modify')
setSectionModalVisible(true)
}
const del = e => {
e.stopPropagation()
}
const cancel = e => {
e.stopPropagation()
}
const deleteChapter = (e, c, index) => {
e.stopPropagation()
ChapterApi.deleteProjectChapter(c)
.then((res) => {
if (res.data.code === 200) {
message.success(res.data.msg)
getChapters()
}
})
.catch((e) => {
console.log(e)
})
}
const deleteSection = (s, index, subIndex) => {
SectionApi.deleteChapterSection(pid, s)
.then((res) => {
if (res.data.code === 200) {
message.success(res.data.msg)
getChapters()
}
})
}
const doChapter = e => {
if (chapterName === '') {
message.error('请输入章名')
return
}
if (opt === 'modify') {
let c = {
id: chapter.id,
projectId: chapter.projectId,
chapterName: chapterName,
chapterNumber: chapter.chapterNumber,
}
ChapterApi.updateProjectChapter(c)
.then((res) => {
if (res.data.code === 200) {
setChapterModalVisible(false)
message.success(res.data.msg)
chapters[index].chapterName = chapterName
setChapters([...chapters])
setChapterName('')
}
})
.catch((e) => {
console.log(e)
})
} else if (opt === 'add') {
let len = chapters.length
let l = 0
if (len > 0) {
l = chapters[len - 1].chapterNumber + 1
}
let cp = {chapterName: chapterName, chapterNumber: l, projectId: pid}
ChapterApi.createProjectChapter(cp)
.then((res) => {
setChapterModalVisible(false)
setChapterName('')
if (res.data.code === 200) {
cp.id = res.data.data
chapters.push(cp)
setChapters([...chapters])
}
})
.catch((e) => {
console.log(e)
})
}
}
const doSection = () => {
if (sectionName === '') {
message.error('请输入小节名')
return
}
if (opt === 'modify') {
let s = {
id: section.id,
chapterId: section.chapterId,
sectionName: sectionName,
sectionNumber: section.sectionNumber,
chapterNumber: section.chapterNumber
}
SectionApi.updateChapterSection(pid, s)
.then((res) => {
if (res.data.code === 200) {
setSectionModalVisible(false)
chapters[index].sections[subIndex].sectionName = sectionName
setChapters([...chapters])
setSectionName('')
}
})
.catch((e) => {
console.log(e)
})
} else if (opt === 'add') {
let l = 0
if (chapters[index].sections !== undefined && chapters[index].sections !== null) {
let len = chapters[index].sections.length
if (len > 0) {
l = chapters[index].sections[len - 1].sectionNumber + 1
}
}
let sec = {
sectionName: sectionName,
sectionNumber: l,
chapterId: chapters[index].id,
chapterNumber: chapters[index].chapterNumber
}
SectionApi.createChapterSection(pid, sec)
.then((res) => {
setSectionModalVisible(false)
setSectionName('')
if (res.data.code === 200) {
sec.id = res.data.data;
if (chapters[index].sections === undefined || chapters[index].sections === null) {
chapters[index].sections = [sec]
} else {
chapters[index].sections.push(sec)
}
setChapters([...chapters])
setChapterName('')
}
})
.catch((e) => {
console.log(e)
})
}
}
const cancelDoChapter = e => {
setChapterModalVisible(false)
setChapterName('')
}
const cancelDoSection = e => {
setSectionModalVisible(false)
setSectionName('')
}
const changeChapterName = value => {
setChapterName(value.target.value)
}
const changeSectionName = value => {
setSectionName(value.target.value)
}
const exchangeChapter = (e, index, index2) => {
e.stopPropagation()
if (index < 0 || index2 >= chapters.length) {
} else {
let id1 = chapters[index].id
let id2 = chapters[index2].id
ChapterApi.exchangeProjectChapter(pid, id1, id2)
.then((res) => {
if (res.data.code === 200) {
getChapters()
}
})
.catch(e => {
console.log(e)
})
}
}
const exchangeSection = (index, subIndex, subIndex2) => {
if (subIndex < 0 || subIndex2 >= chapters[index].sections.length) {
} else {
let id1 = chapters[index].sections[subIndex].id
let id2 = chapters[index].sections[subIndex2].id
SectionApi.exchangeChapterSection(chapters[index], id1, id2)
.then(res => {
if (res.data.code === 200) {
getChapters()
}
})
.catch(e => {
console.log(e)
})
}
}
return (
<div>
<Menu
style={{width: '100%'}}
defaultSelectedKeys={[]}
defaultOpenKeys={[]}
mode="inline"
>{chapters.map((item, index) => (
<SubMenu
style={{fontSize: '2.7vh'}}
key={index.toString()}
title={
<div>
{util.FormatChapterName(item.chapterName, item.chapterNumber)}
<span style={{float: 'right', marginRight: '20px'}}>
<Button shape="circle" type="text" onClick={e => modifyChapter(e, item, index)} icon={<EditOutlined/>}/>
<Button shape="circle" type="text" icon={<ArrowUpOutlined/>} onClick={e => exchangeChapter(e, index - 1, index)}/>
<Button shape="circle" type="text" icon={<ArrowDownOutlined/>} onClick={e => exchangeChapter(e, index, index + 1)}/>
<Popconfirm title="确定删除章节?" onConfirm={e => deleteChapter(e, item, index)} onCancel={cancel} placement="topLeft">
<Button onClick={del} shape="circle" type="text" icon={<DeleteOutlined/>} style={{color: 'red', marginLeft: '20px'}}/>
</Popconfirm>
</span>
</div>
}
>
{(item.sections === null || item.sections === undefined) ? null :
item.sections.map((subItem, subIndex) => (
<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`}>
<Button type="text">编辑资源</Button>
</Link>
<Button shape="circle" type="text" onClick={e => modifySection(subItem, index, subIndex)} icon={<EditOutlined/>}/>
<Button shape="circle" type="text" icon={<ArrowUpOutlined/>} onClick={e => exchangeSection(index, subIndex - 1, subIndex)}/>
<Button shape="circle" type="text" icon={<ArrowDownOutlined/>} onClick={e => exchangeSection(index, subIndex, subIndex + 1)}/>
<Popconfirm title="确定删除小节?" onConfirm={e => deleteSection(subItem, index, subIndex)} placement="topLeft">
<Button shape="circle" type="text" icon={<DeleteOutlined/>} style={{color: 'red', marginLeft: '20px'}}/>
</Popconfirm>
</span>
</Menu.Item>
))
}
<Button type="round" style={{float: 'right', margin: '3px'}}
onClick={e => addSection(item, index)}>添加小节</Button>
</SubMenu>
))}
</Menu>
<div style={{textAlign: 'right'}}>
<Button type="round" style={{margin: '5px'}} onClick={addChapter}>添加章节</Button>
</div>
<div style={{textAlign: 'right', marginTop: '20px', marginRight: '20px'}}>
<Link to={`/project/${pid}/info`}>
<Button type="primary" size="middle" style={{float: 'right'}}>完成</Button>
</Link>
<Link to={`/project/${pid}/info/edit`}>
<Button size="middle" style={{float: 'right', marginRight: '20px'}}>上一页</Button>
</Link>
</div>
<Modal title="" visible={chapterModalVisible} onOk={doChapter} onCancel={cancelDoChapter}>
<br/>
<Row>
<Col span={3}>
<p>章名</p>
</Col>
<Col span={20}>
<Input value={chapterName} onChange={changeChapterName} onPressEnter={doChapter}/>
</Col>
</Row>
</Modal>
<Modal visible={sectionModalVisible} onOk={doSection} onCancel={cancelDoSection}>
<br/>
<Row>
<Col span={3}>
<p>小节名</p>
</Col>
<Col span={20}>
<Input value={sectionName} onChange={changeSectionName} onPressEnter={doSection}/>
</Col>
</Row>
</Modal>
</div>
)
}
export default OutlineEditPage

View File

@ -0,0 +1,59 @@
import React from "react";
import DocumentTitle from 'react-document-title';
import {Card, Divider, PageHeader, Steps} from "antd";
import './index.less'
import OutlineEditPage from "./component/OutlineEditPage";
const {Step} = Steps;
class EditOutline extends React.PureComponent {
state = {
current: 0,
pid: this.props.match.params.id
}
componentDidMount() {
}
back = e => {
this.props.history.push(`/project/${this.state.pid}/info/`)
}
render() {
const {pid} = this.state
return (
<DocumentTitle title="Project">
<div style={{backgroundColor: '#f2f4f5', minHeight: '100vh',}}>
<PageHeader
className="site-page-header"
onBack={() => this.back()}
title="返回"
subTitle="我的项目"
/>
<div
style={{
paddingTop: '20px',
paddingLeft: '20px',
paddingRight: '20px',
paddingBottom: '20px'
}}
>
<Card className="edit-card">
<Steps current={1} style={{marginLeft: '20px'}}>
<Step title="填写信息"/>
<Step title="添加章节" style={{marginRight: '20px'}}/>
</Steps>
<Divider/>
<OutlineEditPage pid={pid}/>
</Card>
</div>
</div>
</DocumentTitle>
)
}
}
export default EditOutline

View File

@ -0,0 +1,5 @@
.edit-card {
max-width: 1200px;
margin: auto;
text-align: left;
}

View File

@ -0,0 +1,59 @@
import React, {useEffect, useState} from "react";
import {Button, Card, PageHeader} from "antd";
import DocumentTitle from 'react-document-title';
import {Link} from "react-router-dom";
import SectionApi from "../../../../api/SectionApi";
import RichWords from "./component/RichWords";
import FileResource from "../component/FileResource";
import StudentTask from "./component/StudentTask";
function SectionEditPage(obj) {
const pid = obj.match.params.pid
const sid = obj.match.params.sid
const [section, setSection] = useState({resource: {}})
useEffect(() => {
SectionApi.getSectionDetail(sid, pid)
.then(res => {
setSection(res.data.section)
})
.catch(e => {
console.log(e)
})
}, [])
const back = e => {
window.location.href = `/project/${pid}/outline/edit`
}
return (
<DocumentTitle title="Project">
<div style={{backgroundColor: '#f2f4f5', minHeight: '100vh'}}>
<PageHeader
className="site-page-header"
onBack={back}
title="返回"
subTitle="我的项目"
/>
<div style={{padding: '20px', margin: 'auto'}}>
<Card>
<h2 style={{fontWeight: 'bold'}}>{section.sectionName}</h2>
</Card>
<RichWords section={section}/>
<FileResource section={section}/>
<StudentTask section={section} pid={pid}/>
</div>
<Link to={`/project/${pid}/section/${sid}/preview?back=/project/${pid}/section/${sid}/edit`}>
<Button style={{marginBottom: '20px'}}>预览</Button>
</Link>
</div>
</DocumentTitle>
)
}
export default SectionEditPage

View File

@ -0,0 +1,104 @@
import React, {useEffect, useState} from "react";
import {Button, Card, Divider, message} from "antd";
import LzEditor from "react-lz-editor"
import uniqBy from 'lodash/uniqBy';
import findIndex from 'lodash/findIndex';
import OSSUploaderFile from "../../component/oss";
import ResourceApi from "../../../../../api/ResourceApi";
import "./section-edit.less"
function RichWords(obj) {
const sid = obj.section.id
const [fileList, setFileList] = useState([])
const [content, setContent] = useState(obj.content)
useEffect(() => {
}, [])
const receiveHtml = (content) => {
setContent(content)
}
const saveContent = e => {
ResourceApi.updateResourceContent(obj.section.resource.id, content)
.then(res => {
if (res.data.code === 200) {
obj.section.resource.content = content
message.success(res.data.msg)
} else {
message.error(res.data.msg)
}
})
.catch(e => {
console.log(e)
})
}
const handleChange = (changedValue) => {
let currFileList = changedValue.fileList;
console.error(JSON.stringify(changedValue));
currFileList = currFileList.filter(f => (!f.length));
currFileList = currFileList.map((file) => {
if (file.response) {
file.url = file.response.url;
}
if (!file.length) {
return file;
}
});
currFileList = currFileList.filter((file) => {
const hasNoExistCurrFileInUploadedList = !~findIndex(
fileList, item => item.name === file.name,
);
if (hasNoExistCurrFileInUploadedList) {
fileList.push(file);
}
return !!file.response || (!!file.url && file.status === 'done') || file.status === 'uploading';
});
currFileList = uniqBy(currFileList, 'name');
if (!!currFileList && currFileList.length !== 0) {
setFileList(currFileList);
}
};
const customRequest = ({
file,
onError,
onSuccess,
}) => {
OSSUploaderFile(file, '/openct/' + sid + '/content/', onSuccess, onError);
};
const uploadProps = {
onChange: handleChange,
listType: 'picture',
fileList: fileList,
customRequest: customRequest,
multiple: true,
showUploadList: true
}
return (
<Card className="resource-card">
<p className="card-title">文本内容</p>
<Divider/>
<div style={{textAlign: 'left'}}>
<LzEditor
readOnly={true}
active={true}
importContent={obj.section.resource.content}
cbReceiver={receiveHtml}
uploadProps={uploadProps}
/>
</div>
<div style={{marginTop: '10px'}}>
<span style={{float: 'right'}}>
<Button type="primary" onClick={saveContent}>保存</Button>
</span>
</div>
</Card>
)
}
export default RichWords

View File

@ -0,0 +1,161 @@
import React, {useEffect, useState} from "react";
import {Button, Card, Divider, Dropdown, Input, Menu, message, Popconfirm} from "antd";
import {ArrowDownOutlined, ArrowUpOutlined, DeleteOutlined, UpOutlined} from "@ant-design/icons";
import TaskApi from "../../../../../api/TaskApi";
import "./section-edit.less"
function StudentTask(obj) {
const pid = obj.pid
const sid = obj.section.id
const [tasks, setTasks] = useState([])
useEffect(() => {
if (sid !== undefined) {
getTasks()
}
}, [sid])
const getTasks = () => {
TaskApi.getSectionTasks(sid, pid)
.then(res => {
if (res.data.tasks === null) {
setTasks([])
} else {
setTasks(res.data.tasks)
}
})
}
const saveContent = (item, index) => {
TaskApi.updateTask(pid, item)
.then(res => {
if (res.data.code === 200) {
message.success(res.data.msg)
} else {
message.error(res.data.msg)
}
})
}
const addTask = (tp) => {
let len = tasks.length
let o = 0
if (len > 0) {
o = tasks[len - 1].taskOrder + 1
}
// taskType: survey file comment
let t = {
sectionId: sid,
taskOrder: o,
taskType: tp.key,
chapterNumber: obj.section.chapterNumber,
sectionNumber: obj.section.sectionNumber
}
TaskApi.createTask(pid, t)
.then(res => {
if (res.data.code === 200) {
t.id = res.data.data
tasks.push(t)
setTasks([...tasks])
}
})
}
const changeTitle = (value, index) => {
tasks[index].taskTitle = value.target.value
setTasks([...tasks])
}
const changeIntroduce = (value, index) => {
tasks[index].taskIntroduce = value.target.value
setTasks([...tasks])
}
const deleteTask = (item, index) => {
TaskApi.deleteTask(pid, item.id)
.then(res => {
if (res.data.code === 200) {
tasks.splice(index, 1)
setTasks([...tasks])
} else {
message.error(res.data.msg)
}
})
.catch(e => {
console.log(e)
})
}
const exchangeTask = (index1, index2) => {
if (index1 < 0 || index2 >= tasks.length) {
} else {
let id1 = tasks[index1].id
let id2 = tasks[index2].id
TaskApi.exchangeTask(id1, id2)
.then(res => {
if (res.data.code === 200) {
let t1 = tasks[index1]
tasks[index1] = tasks[index2]
tasks[index2] = t1
setTasks([...tasks])
} else {
message.error(res.data.msg)
}
})
}
}
const gotoSurvey = item => {
window.location.href = `/project/${pid}/section/${obj.section.id}/task/${item.id}/survey/edit`
}
return (
<Card className="resource-card">
<p className="card-title">学生任务</p>
{tasks.map((item, index) => (
<div key={index.toString()}>
<Divider/>
<p className="task-title">
{item.taskType === 'file' ? '学生上传文件' : null}
{item.taskType === 'comment' ? '学生评论' : null}
{item.taskType === 'survey' ? '学生填写问卷' : null}
<span style={{float: 'right'}}>
<Button shape="circle" type="text" icon={<ArrowUpOutlined/>}
onClick={e => exchangeTask(index - 1, index)}/>
<Button shape="circle" type="text" icon={<ArrowDownOutlined/>}
onClick={e => exchangeTask(index, index + 1)}/>
&nbsp;&nbsp;
<Popconfirm title="确定删除任务?" onConfirm={e => deleteTask(item, index)} placement="topRight">
<Button shape="circle" type="text" icon={<DeleteOutlined/>} style={{color: 'red'}}/>
</Popconfirm>
</span>
</p>
<Input placeholder="任务标题" value={item.taskTitle} onChange={e => changeTitle(e, index)}/>
<Input.TextArea placeholder="任务描述" value={item.taskIntroduce} onChange={e => changeIntroduce(e, index)}
style={{marginTop: '20px'}}/>
{item.taskType === 'survey' ?
<Button style={{marginTop: '10px'}} onClick={e => gotoSurvey(item)}>查看问卷</Button>
: null
}
<div style={{marginTop: '10px', textAlign: 'right'}}>
<span>
<Button type="primary" onClick={e => saveContent(item, index)}>保存</Button>
</span>
</div>
</div>
))}
<div style={{marginTop: '20px', textAlign: 'right'}}>
<Dropdown
trigger="click"
overlay={
<Menu onClick={addTask}>
<Menu.Item key="file">文件任务</Menu.Item>
<Menu.Item key="comment">评论任务</Menu.Item>
<Menu.Item key="survey">问卷任务</Menu.Item>
</Menu>
}
>
<Button>添加任务<UpOutlined/></Button>
</Dropdown>
</div>
</Card>
)
}
export default StudentTask

View File

@ -0,0 +1,21 @@
.resource-card {
margin-top: 10px;
text-align: left;
}
.card-title {
text-align: left;
font-weight: bold;
font-size: 20px;
}
.task-title {
text-align: left;
font-size: 16px;
}
.submit-status{
font-weight: normal;
font-size: 14px;
margin-left: 10px;
}

View File

@ -0,0 +1,268 @@
import React, {useEffect, useState} from "react";
import {Affix, Card, Divider, Layout, Menu, message, PageHeader, Radio} from "antd";
import DocumentTitle from 'react-document-title';
import SurveyApi from "../../../../api/SurveyApi";
import SingleChoice from "./component/SingleChoice";
import MultipleChoice from "./component/MultipleChoice";
import BlankFill from "./component/BlankFill";
import BriefAnswer from "./component/BriefAnswer";
import Question from "./component/Question";
import Scale from "./component/Scale";
const qType = {
'singleChoice': '单选',
'multipleChoice': '多选',
'blankFill': '填空',
'briefAnswer': '简答',
'scale5': '五点量表',
'scale7': '七点量表'
}
const blank = Question.blank
function SurveyEditPage(obj) {
const pid = obj.match.params.pid
const sid = obj.match.params.sid
const tid = obj.match.params.tid
const [survey, setSurvey] = useState({})
const [questions, setQuestions] = useState([])
const [editing, setEditing] = useState([])
useEffect(() => {
if (tid !== undefined) {
SurveyApi.getSurveyDetailByTaskId(pid, tid)
.then(res => {
if (res.data.questions === null) {
setQuestions([])
} else {
let qs = res.data.questions
for (let i = 0; i < qs.length; i++) {
qs[i].questionOptions = qs[i].questionOptions.split(",")
}
setQuestions(qs)
setEditing(new Array(qs.length).fill(false))
}
setSurvey(res.data.survey)
})
}
}, [tid])
const createQuestion = (e) => {
let len = questions.length
let l = 0
if (len > 0) {
l = questions[len - 1].questionOrder + 1
}
let opt = ""
if (e.key === 'singleChoice' || e.key === 'multipleChoice') {
opt = ['选项1', '选项2']
} else if (e.key === 'blankFill') {
opt = ['题目描述', blank]
} else if (e.key === 'briefAnswer') {
opt = ['题目描述']
} else if (e.key === 'scale5') {
opt = ['选项1', '选项2', '选项3', '选项4', '选项5']
} else {
opt = ['选项1', '选项2', '选项3', '选项4', '选项5', '选项6', '选项7']
}
let q = {
surveyId: survey.id,
questionType: e.key,
questionOrder: l,
questionTitle: '标题',
questionOptions: opt
}
q.questionOptions = q.questionOptions.toString()
SurveyApi.createQuestion(pid, tid, q)
.then(res => {
if (res.data.code === 200) {
q.id = res.data.data
q.questionOptions = q.questionOptions.split(",")
questions.push(q)
setQuestions([...questions])
} else {
message.error(res.data.msg)
}
})
}
const saveQuestion = (item, title, opt, index) => {
let q = Object.assign({}, item)
q.questionOptions = opt.toString()
q.questionTitle = title
SurveyApi.updateQuestion(pid, tid, q)
.then(res => {
editing[index] = false
setEditing([...editing])
if (res.data.code === 200) {
q.questionOptions = q.questionOptions.split(",")
questions[index] = q
setQuestions([...questions])
message.success(res.data.msg)
} else {
message.error(res.data.msg)
}
})
.catch(e => {
console.log(e)
})
}
const deleteQuestion = (item, index) => {
SurveyApi.deleteQuestion(pid, tid, item.surveyId, item.id)
.then(res => {
if (res.data.code === 200) {
questions.splice(index, 1)
setQuestions([...questions])
} else {
message.error(res.data.msg)
}
})
.catch(e => {
console.log(e)
})
}
const exchangeQuestion = (index1, index2) => {
if (index1 < 0 || index2 >= questions.length) {
} else {
SurveyApi.exchangeQuestion(pid, tid, questions[index1].surveyId, questions[index1].id, questions[index2].id)
.then(res => {
if (res.data.code === 200) {
let q1 = questions[index1]
questions[index1] = questions[index2]
questions[index2] = q1
setQuestions([...questions])
} else {
message.error(res.data.msg)
}
})
}
}
const editQuestion = (item, index) => {
editing[index] = true
setEditing([...editing])
}
const back = e => {
window.location.href = `/project/${pid}/section/${sid}/edit`
}
const getType = t => {
return qType[t]
}
return (
<DocumentTitle title="Project">
<div style={{backgroundColor: '#f2f4f5', minHeight: '100vh'}}>
<PageHeader
className="site-page-header"
onBack={back}
title="返回"
subTitle="编辑资源"
/>
<div style={{padding: '20px', margin: 'auto'}}>
<Card>
<Layout>
<Affix offsetTop={0}>
<Layout.Sider breakpoint="lg" collapsedWidth="0" style={{backgroundColor: '#f2f4f5'}}>
<Menu theme="light" mode="inline" onClick={createQuestion}>
<Menu.SubMenu title="选择" key="1">
<Menu.Item key="singleChoice">单选</Menu.Item>
<Menu.Item key="multipleChoice">多选</Menu.Item>
</Menu.SubMenu>
<Menu.SubMenu title="问答" key="2">
<Menu.Item key="blankFill">填空</Menu.Item>
<Menu.Item key="briefAnswer">简答</Menu.Item>
</Menu.SubMenu>
<Menu.SubMenu title="量表" key="3">
<Menu.Item key="scale5">五点量表</Menu.Item>
<Menu.Item key="scale7">七点量表</Menu.Item>
</Menu.SubMenu>
</Menu>
</Layout.Sider>
</Affix>
<Layout.Content style={{backgroundColor: 'white'}}>
<div style={{margin: '30px'}}>
<h2>{survey.surveyTitle}</h2>
{questions.map((item, index) => (
<div key={index.toString()}>
<Divider/>
{item.questionType === 'singleChoice' ?
<SingleChoice
item={item}
index={index}
editing={editing[index]}
getType={getType}
editQuestion={editQuestion}
saveQuestion={saveQuestion}
deleteQuestion={deleteQuestion}
exchangeQuestion={exchangeQuestion}
/>
: null}
{item.questionType === 'scale5' || item.questionType === 'scale7' ?
<Scale
item={item}
index={index}
editing={editing[index]}
getType={getType}
editQuestion={editQuestion}
saveQuestion={saveQuestion}
deleteQuestion={deleteQuestion}
exchangeQuestion={exchangeQuestion}
/>
: null}
{item.questionType === 'multipleChoice' ?
<MultipleChoice
item={item}
index={index}
editing={editing[index]}
getType={getType}
editQuestion={editQuestion}
saveQuestion={saveQuestion}
deleteQuestion={deleteQuestion}
exchangeQuestion={exchangeQuestion}
/>
: null}
{item.questionType === 'blankFill' ?
<BlankFill
item={item}
index={index}
editing={editing[index]}
getType={getType}
editQuestion={editQuestion}
saveQuestion={saveQuestion}
deleteQuestion={deleteQuestion}
exchangeQuestion={exchangeQuestion}
/>
: null}
{item.questionType === 'briefAnswer' ?
<BriefAnswer
item={item}
index={index}
editing={editing[index]}
getType={getType}
editQuestion={editQuestion}
saveQuestion={saveQuestion}
deleteQuestion={deleteQuestion}
exchangeQuestion={exchangeQuestion}
/>
: null}
</div>
))}
</div>
</Layout.Content>
</Layout>
</Card>
</div>
</div>
</DocumentTitle>
)
}
export default SurveyEditPage

View File

@ -0,0 +1,131 @@
import React, {useState} from "react";
import {Button, Dropdown, Input, Menu, message, Popconfirm} from "antd";
import {
ArrowDownOutlined,
ArrowUpOutlined,
DeleteOutlined,
EditOutlined,
MinusOutlined,
PlusOutlined,
SaveOutlined
} from "@ant-design/icons";
import Question from "./Question";
const blank = Question.blank
function BlankFill(obj) {
const [opt, setOpt] = useState(obj.item.questionOptions)
const [title, setTitle] = useState(obj.item.questionTitle)
const changeTitle = value => {
setTitle(value.target.value)
}
const changeOpt = (value, index) => {
opt[index] = value.target.value
setOpt([...opt])
}
const addOpt = (key, index) => {
let v = '描述'
if (key.key === '2') {
v = '&nbsp;'
}
opt.splice(index + 1, 0, v)
setOpt([...opt])
}
const delOpt = index => {
if (opt.length === 1) {
message.error("不能删除最后一个选项")
} else {
opt.splice(index, 1)
setOpt([...opt])
}
}
return (
<div>
<div>
{obj.editing ?
<p style={{float: 'left'}}>
<Input value={title} onChange={changeTitle}/>
</p>
:
<p style={{float: 'left'}}>{obj.item.questionTitle}
<span style={{color: 'gray'}}>&nbsp;
[{obj.getType(obj.item.questionType)}]
</span>
</p>
}
<p style={{float: 'right'}}>
{obj.editing ?
<Button shape="circle" type="text" onClick={e => obj.saveQuestion(obj.item, title, opt, obj.index)}
icon={<SaveOutlined/>}/>
:
<>
<Button shape="circle" type="text" onClick={e => obj.editQuestion(obj.item, obj.index)}
icon={<EditOutlined/>}/>
<Button shape="circle" type="text" icon={<ArrowUpOutlined/>}
onClick={e => obj.exchangeQuestion(obj.index - 1, obj.index)}/>
<Button shape="circle" type="text" icon={<ArrowDownOutlined/>}
onClick={e => obj.exchangeQuestion(obj.index, obj.index + 1)}/>
</>
}
<Popconfirm title="确定删除题目?"
onConfirm={e => obj.deleteQuestion(obj.item, obj.index)}
placement="topLeft">
&nbsp;&nbsp;<Button shape="circle" type="text"
style={{color: 'red'}}
icon={<DeleteOutlined/>}/>
</Popconfirm>
</p>
</div>
<br/><br/>
{obj.editing ?
<div style={{textAlign: "left", marginTop: '10px'}}>
{opt.map((item, index) => (
<div style={{marginTop: '10px'}} key={index.toString()}>
{item === blank ?
<span style={{float: 'left', margin: '5px'}}>
<Input style={{borderBottom: '2px solid black'}} bordered={false}/>
</span>
:
<span style={{float: 'left', margin: '5px'}}>
<Input value={item} onChange={value => changeOpt(value, index)}/>
</span>
}
<span style={{float: 'left', margin: '5px'}}>
<Button type="circle" size="small" icon={<MinusOutlined/>} onClick={e => delOpt(index)}/>
<Dropdown
overlay={
<Menu onClick={key => addOpt(key, index)}>
<Menu.Item key="1">文字</Menu.Item>
<Menu.Item key="2">填空</Menu.Item>
</Menu>
}
trigger={['click']}>
<Button type="circle" size="small" className="ant-dropdown-link" icon={<PlusOutlined/>}
onClick={e => e.preventDefault()} style={{marginLeft: '4px'}}/>
</Dropdown>
</span>
</div>
))}
</div>
:
<div style={{textAlign: "left", marginTop: '10px'}}>
{obj.item.questionOptions.map((subItem, subIndex) => (
<div style={{marginTop: '10px'}} key={subIndex.toString()}>
{subItem === blank ?
<span style={{float: 'left', margin: '5px'}}>
<Input style={{borderBottom: '2px solid black'}} bordered={false}/>
</span>
:
<span style={{float: 'left', margin: '5px'}}>{subItem}</span>}
</div>
))}
</div>
}
</div>
)
}
export default BlankFill

View File

@ -0,0 +1,72 @@
import React, {useState} from "react";
import {Button, Input, Popconfirm} from "antd";
import {ArrowDownOutlined, ArrowUpOutlined, DeleteOutlined, EditOutlined, SaveOutlined} from "@ant-design/icons";
function BriefAnswer(obj) {
const [opt, setOpt] = useState(obj.item.questionOptions)
const [title, setTitle] = useState(obj.item.questionTitle)
const changeTitle = value => {
setTitle(value.target.value)
}
const changeQ = v => {
opt[0] = v.target.value
setOpt([...opt])
}
return (
<div>
<div>
{obj.editing ?
<p style={{float: 'left'}}>
<Input value={title} onChange={changeTitle}/>
</p>
:
<p style={{float: 'left'}}>{obj.item.questionTitle}
<span style={{color: 'gray'}}>&nbsp;
[{obj.getType(obj.item.questionType)}]
</span>
</p>
}
<p style={{float: 'right'}}>
{obj.editing ?
<Button shape="circle" type="text" onClick={e => obj.saveQuestion(obj.item, title, opt, obj.index)}
icon={<SaveOutlined/>}/>
:
<>
<Button shape="circle" type="text" onClick={e => obj.editQuestion(obj.item, obj.index)}
icon={<EditOutlined/>}/>
<Button shape="circle" type="text" icon={<ArrowUpOutlined/>}
onClick={e => obj.exchangeQuestion(obj.index - 1, obj.index)}/>
<Button shape="circle" type="text" icon={<ArrowDownOutlined/>}
onClick={e => obj.exchangeQuestion(obj.index, obj.index + 1)}/>
</>
}
<Popconfirm title="确定删除题目?"
onConfirm={e => obj.deleteQuestion(obj.item, obj.index)}
placement="topLeft">
&nbsp;&nbsp;<Button shape="circle" type="text"
style={{color: 'red'}}
icon={<DeleteOutlined/>}/>
</Popconfirm>
</p>
</div>
<br/><br/>
{obj.editing ?
<div style={{textAlign: "left", marginTop: '10px'}}>
<Input value={opt[0]} onChange={changeQ}/>
<Input.TextArea style={{marginTop: '5px'}}/>
</div>
:
<div style={{textAlign: "left", marginTop: '10px'}}>
<p>{obj.item.questionOptions[0]}</p>
<Input.TextArea/>
</div>
}
</div>
)
}
export default BriefAnswer

View File

@ -0,0 +1,106 @@
import React, {useState} from "react";
import {Button, Checkbox, Input, message, Popconfirm, Radio} from "antd";
import {
ArrowDownOutlined,
ArrowUpOutlined,
DeleteOutlined,
EditOutlined,
MinusOutlined,
PlusOutlined,
SaveOutlined
} from "@ant-design/icons";
function MultipleChoice(obj) {
const [opt, setOpt] = useState(obj.item.questionOptions)
const [title, setTitle] = useState(obj.item.questionTitle)
const changeTitle = value => {
setTitle(value.target.value)
}
const changeOpt = (value, index) => {
opt[index] = value.target.value
setOpt([...opt])
}
const addOpt = index => {
opt.splice(index + 1, 0, '')
setOpt([...opt])
}
const delOpt = index => {
if (opt.length === 1) {
message.error("不能删除最后一个选项")
} else {
opt.splice(index, 1)
setOpt([...opt])
}
}
return (
<div>
<div>
{obj.editing ?
<p style={{float: 'left'}}>
<Input value={title} onChange={changeTitle}/>
</p>
:
<p style={{float: 'left'}}>{obj.item.questionTitle}
<span style={{color: 'gray'}}>&nbsp;
[{obj.getType(obj.item.questionType)}]
</span>
</p>
}
<p style={{float: 'right'}}>
{obj.editing ?
<Button shape="circle" type="text" onClick={e => obj.saveQuestion(obj.item, title, opt, obj.index)}
icon={<SaveOutlined/>}/>
:
<>
<Button shape="circle" type="text" onClick={e => obj.editQuestion(obj.item, obj.index)}
icon={<EditOutlined/>}/>
<Button shape="circle" type="text" icon={<ArrowUpOutlined/>}
onClick={e => obj.exchangeQuestion(obj.index - 1, obj.index)}/>
<Button shape="circle" type="text" icon={<ArrowDownOutlined/>}
onClick={e => obj.exchangeQuestion(obj.index, obj.index + 1)}/>
</>
}
<Popconfirm title="确定删除题目?"
onConfirm={e => obj.deleteQuestion(obj.item, obj.index)}
placement="topLeft">
&nbsp;&nbsp;<Button shape="circle" type="text"
style={{color: 'red'}}
icon={<DeleteOutlined/>}/>
</Popconfirm>
</p>
</div>
<br/><br/>
{obj.editing ?
<div style={{textAlign: "left", marginTop: '10px'}}>
{opt.map((item, index) => (
<div style={{marginTop: '10px'}} key={index.toString()}>
<Checkbox>
<Input value={item} onChange={value => changeOpt(value, index)}/>
</Checkbox>
<Button type="circle" size="small" icon={<MinusOutlined/>} onClick={e => delOpt(index)}/>
<Button type="circle" size="small" icon={<PlusOutlined/>} onClick={e => addOpt(index)}
style={{marginLeft: '10px'}}/>
</div>
))}
</div>
:
<div style={{textAlign: "left", marginTop: '10px'}}>
<Radio.Group>
{obj.item.questionOptions.map((subItem, subIndex) => (
<div style={{marginTop: '10px'}} key={subIndex.toString()}>
<Checkbox>
{subItem}
</Checkbox>
</div>
))}
</Radio.Group>
</div>
}
</div>
)
}
export default MultipleChoice

View File

@ -0,0 +1,4 @@
const blank = '&nbsp;'
export default {
blank,
}

View File

@ -0,0 +1,104 @@
import React, {useState} from "react";
import {Button, Input, message, Popconfirm, Radio} from "antd";
import {
ArrowDownOutlined,
ArrowUpOutlined,
DeleteOutlined,
EditOutlined,
MinusOutlined, PlusOutlined,
SaveOutlined
} from "@ant-design/icons";
function Scale(obj) {
const [opt, setOpt] = useState(obj.item.questionOptions)
const [title, setTitle] = useState(obj.item.questionTitle)
const changeTitle = value => {
setTitle(value.target.value)
}
const changeOpt = (value, index) => {
opt[index] = value.target.value
setOpt([...opt])
}
const addOpt = index => {
opt.splice(index + 1, 0, '')
setOpt([...opt])
}
const delOpt = index => {
if (opt.length === 1) {
message.error("不能删除最后一个选项")
} else {
opt.splice(index, 1)
setOpt([...opt])
}
}
return (
<div>
<div>
{obj.editing ?
<p style={{float: 'left'}}>
<Input value={title} onChange={changeTitle}/>
</p>
:
<p style={{float: 'left'}}>{obj.item.questionTitle}
<span style={{color: 'gray'}}>&nbsp;
[{obj.getType(obj.item.questionType)}]
</span>
</p>
}
<p style={{float: 'right'}}>
{obj.editing ?
<Button shape="circle" type="text" onClick={e => obj.saveQuestion(obj.item, title, opt, obj.index)}
icon={<SaveOutlined/>}/>
:
<>
<Button shape="circle" type="text" onClick={e => obj.editQuestion(obj.item, obj.index)}
icon={<EditOutlined/>}/>
<Button shape="circle" type="text" icon={<ArrowUpOutlined/>}
onClick={e => obj.exchangeQuestion(obj.index - 1, obj.index)}/>
<Button shape="circle" type="text" icon={<ArrowDownOutlined/>}
onClick={e => obj.exchangeQuestion(obj.index, obj.index + 1)}/>
</>
}
<Popconfirm title="确定删除题目?"
onConfirm={e => obj.deleteQuestion(obj.item, obj.index)}
placement="topLeft">
&nbsp;&nbsp;<Button shape="circle" type="text"
style={{color: 'red'}}
icon={<DeleteOutlined/>}/>
</Popconfirm>
</p>
</div>
<br/><br/>
{obj.editing ?
<div style={{textAlign: "left", marginTop: '10px'}}>
<Radio.Group>
{opt.map((item, index) => (
<div style={{marginTop: '10px'}} key={index.toString()}>
<Radio value={index}>
<Input value={item} onChange={value => changeOpt(value, index)}/>
</Radio>
</div>
))}
</Radio.Group>
</div>
:
<div style={{textAlign: "left", marginTop: '10px'}}>
<Radio.Group>
{obj.item.questionOptions.map((subItem, subIndex) => (
<div style={{marginTop: '10px'}} key={subIndex.toString()}>
<Radio value={subIndex}>
{subItem}
</Radio>
</div>
))}
</Radio.Group>
</div>
}
</div>
)
}
export default Scale

View File

@ -0,0 +1,108 @@
import React, {useState} from "react";
import {Button, Input, message, Popconfirm, Radio} from "antd";
import {
ArrowDownOutlined,
ArrowUpOutlined,
DeleteOutlined,
EditOutlined,
MinusOutlined,
PlusOutlined,
SaveOutlined
} from "@ant-design/icons";
function SingleChoice(obj) {
const [opt, setOpt] = useState(obj.item.questionOptions)
const [title, setTitle] = useState(obj.item.questionTitle)
const changeTitle = value => {
setTitle(value.target.value)
}
const changeOpt = (value, index) => {
opt[index] = value.target.value
setOpt([...opt])
}
const addOpt = index => {
opt.splice(index + 1, 0, '')
setOpt([...opt])
}
const delOpt = index => {
if (opt.length === 1) {
message.error("不能删除最后一个选项")
} else {
opt.splice(index, 1)
setOpt([...opt])
}
}
return (
<div>
<div>
{obj.editing ?
<p style={{float: 'left'}}>
<Input value={title} onChange={changeTitle}/>
</p>
:
<p style={{float: 'left'}}>{obj.item.questionTitle}
<span style={{color: 'gray'}}>&nbsp;
[{obj.getType(obj.item.questionType)}]
</span>
</p>
}
<p style={{float: 'right'}}>
{obj.editing ?
<Button shape="circle" type="text" onClick={e => obj.saveQuestion(obj.item, title, opt, obj.index)}
icon={<SaveOutlined/>}/>
:
<>
<Button shape="circle" type="text" onClick={e => obj.editQuestion(obj.item, obj.index)}
icon={<EditOutlined/>}/>
<Button shape="circle" type="text" icon={<ArrowUpOutlined/>}
onClick={e => obj.exchangeQuestion(obj.index - 1, obj.index)}/>
<Button shape="circle" type="text" icon={<ArrowDownOutlined/>}
onClick={e => obj.exchangeQuestion(obj.index, obj.index + 1)}/>
</>
}
<Popconfirm title="确定删除题目?"
onConfirm={e => obj.deleteQuestion(obj.item, obj.index)}
placement="topLeft">
&nbsp;&nbsp;<Button shape="circle" type="text"
style={{color: 'red'}}
icon={<DeleteOutlined/>}/>
</Popconfirm>
</p>
</div>
<br/><br/>
{obj.editing ?
<div style={{textAlign: "left", marginTop: '10px'}}>
<Radio.Group>
{opt.map((item, index) => (
<div style={{marginTop: '10px'}} key={index.toString()}>
<Radio value={index}>
<Input value={item} onChange={value => changeOpt(value, index)}/>
</Radio>
<Button type="circle" size="small" icon={<MinusOutlined/>} onClick={e => delOpt(index)}/>
<Button type="circle" size="small" icon={<PlusOutlined/>} onClick={e => addOpt(index)}
style={{marginLeft: '10px'}}/>
</div>
))}
</Radio.Group>
</div>
:
<div style={{textAlign: "left", marginTop: '10px'}}>
<Radio.Group>
{obj.item.questionOptions.map((subItem, subIndex) => (
<div style={{marginTop: '10px'}} key={subIndex.toString()}>
<Radio value={subIndex}>
{subItem}
</Radio>
</div>
))}
</Radio.Group>
</div>
}
</div>
)
}
export default SingleChoice

Some files were not shown because too many files have changed in this diff Show More