mirror of
https://github.com/fumiama/paper-manager.git
synced 2026-07-01 00:00:28 +08:00
finish chkdup
This commit is contained in:
164
backend/file.go
164
backend/file.go
@@ -1,7 +1,10 @@
|
|||||||
package backend
|
package backend
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"container/heap"
|
||||||
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
|
"math"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -31,8 +34,8 @@ type filelist struct {
|
|||||||
Per uint `json:"percent"`
|
Per uint `json:"percent"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func getFileList(count int, istemp *bool) ([]filelist, error) {
|
func getFileList(count, uid int, istemp *bool) ([]filelist, error) {
|
||||||
lst, err := global.FileDB.ListUploadedFile(istemp)
|
lst, err := global.FileDB.ListUploadedFile(istemp, uid)
|
||||||
if err != nil && err != sql.ErrNullResult {
|
if err != nil && err != sql.ErrNullResult {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -124,23 +127,127 @@ type filestatus struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func getFileStatus(lstid int, user *global.User) (*filestatus, error) {
|
func getFileStatus(lstid int, user *global.User) (*filestatus, error) {
|
||||||
file, sz, istemp, err := global.FileDB.GetFile(lstid, *user.ID)
|
file, lst, err := global.FileDB.GetFile(lstid, *user.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
qs, ds, filerate, err := parseFileQuestions(file.Questions, istemp)
|
qs, ds, filerate, err := parseFileQuestions(file.Questions, lst.IsTemp, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &filestatus{
|
return &filestatus{
|
||||||
Name: file.Class + ".docx",
|
Name: file.Class + ".docx",
|
||||||
Size: float64(sz) / 1024 / 1024, // MB
|
Size: float64(lst.Size) / 1024 / 1024, // MB
|
||||||
Rate: filerate * 100,
|
Rate: filerate * 100,
|
||||||
Questions: qs,
|
Questions: qs,
|
||||||
Duplications: ds,
|
Duplications: ds,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type filestatusdup struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Size float64 `json:"size"`
|
||||||
|
Rate int `json:"rate"`
|
||||||
|
Questions []question `json:"questions"`
|
||||||
|
Duplications []duplication `json:"duplications"` // Duplications length == 10
|
||||||
|
Files []duplication `json:"files"` // Files is {dup: desc}[] in yearstart..yearend
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkFileDup(lstid int, user *global.User, yearstart, yearend global.StudyYear) (*filestatusdup, error) {
|
||||||
|
file, lst, err := global.FileDB.GetFile(lstid, *user.ID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
files, err := global.FileDB.GetFilesByYearRange(yearstart, yearend)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
qs := make([]question, 0, 16)
|
||||||
|
filesdups := make([]duplication, 0, 16)
|
||||||
|
ques := make([]global.QuestionJSON, 0, 64)
|
||||||
|
myques := make([]global.QuestionJSON, 0, 64)
|
||||||
|
err = json.Unmarshal(file.Questions, &myques)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
dh := make(duplications, 0, 16)
|
||||||
|
heap.Init(&dh)
|
||||||
|
filerate := 0
|
||||||
|
isfirstmy := true
|
||||||
|
totl := 0
|
||||||
|
for _, f := range files {
|
||||||
|
if f.ListID == lstid {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
ques = ques[:0]
|
||||||
|
err := json.Unmarshal(f.Questions, &ques)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
sum := 0.0
|
||||||
|
cnt := 0
|
||||||
|
for _, q := range myques {
|
||||||
|
if isfirstmy {
|
||||||
|
qs = append(qs, question{
|
||||||
|
Count: len(q.Sub),
|
||||||
|
Point: q.Points,
|
||||||
|
Name: q.Name,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
for i, subq := range q.Sub {
|
||||||
|
p, err := getQuestionDupFromPaper(subq, ques, lst.IsTemp)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
heap.Push(&dh, duplication{
|
||||||
|
Percent: int(math.Round(p * 100)),
|
||||||
|
Name: q.Name + "." + strconv.Itoa(i+1),
|
||||||
|
})
|
||||||
|
sum += p
|
||||||
|
cnt++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
isfirstmy = false
|
||||||
|
pc := int(math.Round(sum * 100 / float64(cnt)))
|
||||||
|
flst, err := f.GetList(&global.FileDB)
|
||||||
|
name := ""
|
||||||
|
if err != nil {
|
||||||
|
name = err.Error()
|
||||||
|
} else {
|
||||||
|
name = flst.Desc
|
||||||
|
}
|
||||||
|
filesdups = append(filesdups, duplication{
|
||||||
|
Percent: pc,
|
||||||
|
Name: name,
|
||||||
|
})
|
||||||
|
filerate += pc
|
||||||
|
totl++
|
||||||
|
}
|
||||||
|
if totl == 0 {
|
||||||
|
return nil, sql.ErrNullResult
|
||||||
|
}
|
||||||
|
i := dh.Len()
|
||||||
|
ds := make([]duplication, 10)
|
||||||
|
if i > 10 {
|
||||||
|
i = 10
|
||||||
|
} else {
|
||||||
|
for j := i; j < 10; j++ {
|
||||||
|
ds[j] = duplication{Name: "N/A"}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for i--; i >= 0; i-- {
|
||||||
|
ds[i] = heap.Pop(&dh).(duplication)
|
||||||
|
}
|
||||||
|
return &filestatusdup{
|
||||||
|
Name: file.Class + ".docx",
|
||||||
|
Size: float64(lst.Size) / 1024 / 1024, // MB
|
||||||
|
Rate: filerate / totl,
|
||||||
|
Questions: qs,
|
||||||
|
Duplications: ds,
|
||||||
|
Files: filesdups,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
apimap["/api/getFileList"] = &apihandler{"GET", func(w http.ResponseWriter, r *http.Request) {
|
apimap["/api/getFileList"] = &apihandler{"GET", func(w http.ResponseWriter, r *http.Request) {
|
||||||
token := r.Header.Get("Authorization")
|
token := r.Header.Get("Authorization")
|
||||||
@@ -165,7 +272,7 @@ func init() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
lst, err := getFileList(count, istemp)
|
lst, err := getFileList(count, *user.ID, istemp)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
writeresult(w, codeError, nil, err.Error(), typeError)
|
writeresult(w, codeError, nil, err.Error(), typeError)
|
||||||
return
|
return
|
||||||
@@ -281,6 +388,51 @@ func init() {
|
|||||||
}
|
}
|
||||||
writeresult(w, codeSuccess, fstat, messageOk, typeSuccess)
|
writeresult(w, codeSuccess, fstat, messageOk, typeSuccess)
|
||||||
}}
|
}}
|
||||||
|
|
||||||
|
apimap["/api/checkFileDup"] = &apihandler{"GET", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
token := r.Header.Get("Authorization")
|
||||||
|
user := usertokens.Get(token)
|
||||||
|
if user == nil {
|
||||||
|
writeresult(w, codeError, nil, errInvalidToken.Error(), typeError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
idstr := r.URL.Query().Get("id")
|
||||||
|
if idstr == "" {
|
||||||
|
writeresult(w, codeError, nil, "empty id", typeError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
id, err := strconv.Atoi(idstr)
|
||||||
|
if err != nil {
|
||||||
|
writeresult(w, codeError, nil, err.Error(), typeError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ysstr := r.URL.Query().Get("ys")
|
||||||
|
if ysstr == "" {
|
||||||
|
writeresult(w, codeError, nil, "empty ys", typeError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ys, err := strconv.Atoi(ysstr)
|
||||||
|
if err != nil {
|
||||||
|
writeresult(w, codeError, nil, err.Error(), typeError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
yestr := r.URL.Query().Get("ye")
|
||||||
|
if yestr == "" {
|
||||||
|
writeresult(w, codeError, nil, "empty ye", typeError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ye, err := strconv.Atoi(yestr)
|
||||||
|
if err != nil {
|
||||||
|
writeresult(w, codeError, nil, err.Error(), typeError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fstat, err := checkFileDup(id, user, global.StudyYear(ys), global.StudyYear(ye))
|
||||||
|
if err != nil {
|
||||||
|
writeresult(w, codeError, nil, err.Error(), typeError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
writeresult(w, codeSuccess, fstat, messageOk, typeSuccess)
|
||||||
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
// FileHandler serves contents in global.FileFolder
|
// FileHandler serves contents in global.FileFolder
|
||||||
|
|||||||
@@ -98,6 +98,12 @@ func (sy StudyYear) String() string {
|
|||||||
return strconv.Itoa(int(sy)) + "-" + strconv.Itoa(int(next)) + "学年"
|
return strconv.Itoa(int(sy)) + "-" + strconv.Itoa(int(next)) + "学年"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (file *File) GetList(f *FileDatabase) (List, error) {
|
||||||
|
f.mu.RLock()
|
||||||
|
defer f.mu.RUnlock()
|
||||||
|
return sql.Find[List](&f.db, FileTableList, "WHERE ID="+strconv.Itoa(file.ListID))
|
||||||
|
}
|
||||||
|
|
||||||
// DelFile by listid
|
// DelFile by listid
|
||||||
func (f *FileDatabase) DelFile(lstid, uid int, istemp bool) error {
|
func (f *FileDatabase) DelFile(lstid, uid int, istemp bool) error {
|
||||||
user, err := UserDB.GetUserByID(uid)
|
user, err := UserDB.GetUserByID(uid)
|
||||||
@@ -162,10 +168,10 @@ func (f *FileDatabase) DelFile(lstid, uid int, istemp bool) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetFile get analyzed file's structure from List(ID)
|
// GetFile get analyzed file's structure from List(ID)
|
||||||
func (f *FileDatabase) GetFile(lstid, uid int) (file *File, sz int64, istemp bool, err error) {
|
func (f *FileDatabase) GetFile(lstid, uid int) (file *File, lst List, err error) {
|
||||||
f.mu.RLock()
|
f.mu.RLock()
|
||||||
defer f.mu.RUnlock()
|
defer f.mu.RUnlock()
|
||||||
lst, err := sql.Find[List](&f.db, FileTableList, "WHERE ID="+strconv.Itoa(lstid))
|
lst, err = sql.Find[List](&f.db, FileTableList, "WHERE ID="+strconv.Itoa(lstid))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -187,5 +193,12 @@ func (f *FileDatabase) GetFile(lstid, uid int) (file *File, sz int64, istemp boo
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
return &filestruct, lst.Size, lst.IsTemp, nil
|
return &filestruct, lst, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetFilesByYearRange ...
|
||||||
|
func (f *FileDatabase) GetFilesByYearRange(yearstart, yearend StudyYear) ([]*File, error) {
|
||||||
|
f.mu.RLock()
|
||||||
|
defer f.mu.RUnlock()
|
||||||
|
return sql.FindAll[File](&f.db, FileTableFile, "WHERE Year>="+strconv.Itoa(int(yearstart))+" AND Year<="+strconv.Itoa(int(yearend)))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -83,13 +83,13 @@ func (f *FileDatabase) SaveFileToTemp(uploader int, file io.Reader, name string)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ListUploadedFile will select all file that HasntAnalyzed && IsTemp or !HasntAnalyzed && !IsTemp
|
// ListUploadedFile will select all file that HasntAnalyzed && IsTemp or !HasntAnalyzed && !IsTemp
|
||||||
func (f *FileDatabase) ListUploadedFile(istemp *bool) (lst []*List, err error) {
|
func (f *FileDatabase) ListUploadedFile(istemp *bool, uid int) (lst []*List, err error) {
|
||||||
q := ""
|
q := ""
|
||||||
switch {
|
switch {
|
||||||
case istemp == nil:
|
case istemp == nil:
|
||||||
q = "ORDER BY UpTime DESC"
|
q = "WHERE (NOT IsTemp) OR (Uploader=" + strconv.Itoa(uid) + ") ORDER BY UpTime DESC"
|
||||||
case *istemp:
|
case *istemp:
|
||||||
q = "WHERE IsTemp ORDER BY UpTime DESC"
|
q = "WHERE IsTemp AND Uploader=" + strconv.Itoa(uid) + " ORDER BY UpTime DESC"
|
||||||
default:
|
default:
|
||||||
q = "WHERE (HasntAnalyzed AND IsTemp) OR (NOT HasntAnalyzed AND NOT IsTemp) ORDER BY UpTime DESC"
|
q = "WHERE (HasntAnalyzed AND IsTemp) OR (NOT HasntAnalyzed AND NOT IsTemp) ORDER BY UpTime DESC"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -103,7 +103,7 @@ func (f *FileDatabase) GetQuestion(id int64, istemp bool) (Question, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetQuestionHex by hexid
|
// GetQuestionHex by hexid
|
||||||
func (f *FileDatabase) GetQuestionHex(hexid string, istemp bool) (q Question, err error) {
|
func (f *FileDatabase) GetQuestionByHex(hexid string, istemp bool) (q Question, err error) {
|
||||||
idb, err := hex.DecodeString(hexid)
|
idb, err := hex.DecodeString(hexid)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -50,15 +50,19 @@ func (d *duplications) Pop() any {
|
|||||||
return x
|
return x
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseFileQuestions(qb []byte, istemp bool) ([]question, []duplication, float64, error) {
|
func parseFileQuestions(qb []byte, istemp, getdup bool) ([]question, []duplication, float64, error) {
|
||||||
ques := make([]global.QuestionJSON, 0, 16)
|
ques := make([]global.QuestionJSON, 0, 16)
|
||||||
qs := make([]question, 0, 16)
|
qs := make([]question, 0, 16)
|
||||||
err := json.Unmarshal(qb, &ques)
|
err := json.Unmarshal(qb, &ques)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, 0, err
|
return nil, nil, 0, err
|
||||||
}
|
}
|
||||||
dh := make(duplications, 0, 16)
|
dhp := (*duplications)(nil)
|
||||||
heap.Init(&dh)
|
if getdup {
|
||||||
|
dh := make(duplications, 0, 16)
|
||||||
|
heap.Init(&dh)
|
||||||
|
dhp = &dh
|
||||||
|
}
|
||||||
sum := 0.0
|
sum := 0.0
|
||||||
cnt := 0
|
cnt := 0
|
||||||
for _, q := range ques {
|
for _, q := range ques {
|
||||||
@@ -68,33 +72,63 @@ func parseFileQuestions(qb []byte, istemp bool) ([]question, []duplication, floa
|
|||||||
Name: q.Name,
|
Name: q.Name,
|
||||||
})
|
})
|
||||||
for i, subq := range q.Sub {
|
for i, subq := range q.Sub {
|
||||||
qstruct, err := global.FileDB.GetQuestionHex(subq.Name, istemp)
|
qstruct, err := global.FileDB.GetQuestionByHex(subq.Name, istemp)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
p := qstruct.MaxDuplicateRate()
|
p := qstruct.MaxDuplicateRate()
|
||||||
heap.Push(&dh, duplication{
|
if getdup {
|
||||||
Percent: int(math.Round(p * 100)),
|
heap.Push(dhp, duplication{
|
||||||
Name: q.Name + "." + strconv.Itoa(i+1),
|
Percent: int(math.Round(p * 100)),
|
||||||
})
|
Name: q.Name + "." + strconv.Itoa(i+1),
|
||||||
|
})
|
||||||
|
}
|
||||||
sum += p
|
sum += p
|
||||||
cnt++
|
cnt++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
i := dh.Len()
|
if getdup {
|
||||||
ds := make([]duplication, 10)
|
i := dhp.Len()
|
||||||
if i > 10 {
|
ds := make([]duplication, 10)
|
||||||
i = 10
|
if i > 10 {
|
||||||
} else {
|
i = 10
|
||||||
for j := i; j < 10; j++ {
|
} else {
|
||||||
ds[j] = duplication{Name: "N/A"}
|
for j := i; j < 10; j++ {
|
||||||
|
ds[j] = duplication{Name: "N/A"}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
for i--; i >= 0; i-- {
|
||||||
for i--; i >= 0; i-- {
|
ds[i] = heap.Pop(dhp).(duplication)
|
||||||
ds[i] = heap.Pop(&dh).(duplication)
|
}
|
||||||
|
return qs, ds, sum / float64(cnt), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return qs, ds, sum / float64(cnt), nil
|
return qs, nil, sum / float64(cnt), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// getQuestionDupFromPaper returns rate, error
|
||||||
|
func getQuestionDupFromPaper(que global.QuestionJSON, ques []global.QuestionJSON, istemp bool) (float64, error) {
|
||||||
|
myqstruct, err := global.FileDB.GetQuestionByHex(que.Name, istemp)
|
||||||
|
if err != nil {
|
||||||
|
return -1, err
|
||||||
|
}
|
||||||
|
maxp := 0.0
|
||||||
|
for _, q := range ques {
|
||||||
|
for _, subq := range q.Sub {
|
||||||
|
qstruct, err := global.FileDB.GetQuestionByHex(subq.Name, istemp)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
p, err := myqstruct.GetDuplicateRate(&qstruct)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if maxp < p {
|
||||||
|
maxp = p
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return maxp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import {
|
|||||||
FileListGroupItem,
|
FileListGroupItem,
|
||||||
GenerateConfig,
|
GenerateConfig,
|
||||||
} from './model/fileListModel'
|
} from './model/fileListModel'
|
||||||
import { DownloadFile, FileStatus } from './model/fileModel'
|
import { DownloadFile, FileDupStatus, FileStatus } from './model/fileModel'
|
||||||
|
|
||||||
enum Api {
|
enum Api {
|
||||||
GetFileList = '/getFileList',
|
GetFileList = '/getFileList',
|
||||||
@@ -15,6 +15,7 @@ enum Api {
|
|||||||
AnalyzeFile = '/analyzeFile',
|
AnalyzeFile = '/analyzeFile',
|
||||||
DlFile = '/dlFile',
|
DlFile = '/dlFile',
|
||||||
GetFileStatus = '/getFileStatus',
|
GetFileStatus = '/getFileStatus',
|
||||||
|
CheckFileDup = '/checkFileDup',
|
||||||
GetMajors = '/getMajors',
|
GetMajors = '/getMajors',
|
||||||
GenFile = '/genFile',
|
GenFile = '/genFile',
|
||||||
DlGen = '/dlGen',
|
DlGen = '/dlGen',
|
||||||
@@ -95,6 +96,16 @@ export const getFileStatus = (id: number) => {
|
|||||||
return defHttp.get<FileStatus>({ url: Api.GetFileStatus, params: { id: id } })
|
return defHttp.get<FileStatus>({ url: Api.GetFileStatus, params: { id: id } })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description: Check file duplication
|
||||||
|
*/
|
||||||
|
export const checkFileDup = (id: number, ys: number, ye: number) => {
|
||||||
|
return defHttp.get<FileDupStatus>(
|
||||||
|
{ url: Api.CheckFileDup, params: { id, ys, ye } },
|
||||||
|
{ errorMessageMode: 'none' },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @description: Get majors
|
* @description: Get majors
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -20,3 +20,12 @@ export interface FileStatus {
|
|||||||
questions: Question[]
|
questions: Question[]
|
||||||
duplications: Duplication[]
|
duplications: Duplication[]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface FileDupStatus {
|
||||||
|
name: string
|
||||||
|
size: number
|
||||||
|
rate: number
|
||||||
|
questions: Question[]
|
||||||
|
duplications: Duplication[]
|
||||||
|
files: Duplication[]
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { FormSchema } from '/@/components/Form'
|
import { FormSchema } from '/@/components/Form'
|
||||||
|
import { BasicColumn } from '/@/components/Table/src/types/table'
|
||||||
|
|
||||||
export const schemas: FormSchema[] = [
|
export const schemas: FormSchema[] = [
|
||||||
{
|
{
|
||||||
@@ -22,3 +23,16 @@ export const taskSchemas: FormSchema[] = [
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
export const columns: BasicColumn[] = [
|
||||||
|
{
|
||||||
|
title: '试卷',
|
||||||
|
dataIndex: 'name',
|
||||||
|
fixed: 'left',
|
||||||
|
width: 200,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '重复率(%)',
|
||||||
|
dataIndex: 'percent',
|
||||||
|
width: 150,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|||||||
@@ -24,8 +24,31 @@
|
|||||||
<a-card title="查重限定条件" :bordered="false" class="!mt-5">
|
<a-card title="查重限定条件" :bordered="false" class="!mt-5">
|
||||||
<BasicForm @register="registerTask" />
|
<BasicForm @register="registerTask" />
|
||||||
</a-card>
|
</a-card>
|
||||||
<a-card title="查重报告" :bordered="false" class="!mt-5">
|
<a-card title="查重报告" :bordered="false" class="!mt-5" v-if="tableRef && tableRef.length > 0">
|
||||||
<p> aaaaa </p>
|
<div ref="chartRef" :style="{ height, width }"></div>
|
||||||
|
<BasicTable
|
||||||
|
title="详细信息"
|
||||||
|
:columns="columns"
|
||||||
|
:dataSource="tableRef"
|
||||||
|
:canResize="canResize"
|
||||||
|
:loading="loading"
|
||||||
|
:striped="striped"
|
||||||
|
:bordered="border"
|
||||||
|
showTableSetting
|
||||||
|
:pagination="pagination"
|
||||||
|
>
|
||||||
|
<template #toolbar>
|
||||||
|
<a-button type="primary" @click="toggleCanResize">
|
||||||
|
{{ !canResize ? '自适应高度' : '取消自适应' }}
|
||||||
|
</a-button>
|
||||||
|
<a-button type="primary" @click="toggleBorder">
|
||||||
|
{{ !border ? '显示边框' : '隐藏边框' }}
|
||||||
|
</a-button>
|
||||||
|
<a-button type="primary" @click="toggleStriped">
|
||||||
|
{{ !striped ? '显示斑马纹' : '隐藏斑马纹' }}
|
||||||
|
</a-button>
|
||||||
|
</template>
|
||||||
|
</BasicTable>
|
||||||
</a-card>
|
</a-card>
|
||||||
|
|
||||||
<template #rightFooter>
|
<template #rightFooter>
|
||||||
@@ -35,23 +58,55 @@
|
|||||||
</template>
|
</template>
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { BasicForm, useForm, ApiSelect } from '/@/components/Form'
|
import { BasicForm, useForm, ApiSelect } from '/@/components/Form'
|
||||||
import { defineComponent, ref, unref, computed } from 'vue'
|
import { BasicTable } from '/@/components/Table'
|
||||||
|
import { defineComponent, ref, unref, computed, Ref } from 'vue'
|
||||||
import { useDebounceFn } from '@vueuse/core'
|
import { useDebounceFn } from '@vueuse/core'
|
||||||
|
import { useECharts } from '/@/hooks/web/useECharts'
|
||||||
import { PageWrapper } from '/@/components/Page'
|
import { PageWrapper } from '/@/components/Page'
|
||||||
import { schemas, taskSchemas } from './data'
|
import { useMessage } from '/@/hooks/web/useMessage'
|
||||||
|
import { schemas, taskSchemas, columns } from './data'
|
||||||
import { Card } from 'ant-design-vue'
|
import { Card } from 'ant-design-vue'
|
||||||
import { useI18n } from '/@/hooks/web/useI18n'
|
import { useI18n } from '/@/hooks/web/useI18n'
|
||||||
import { getFileOptions } from '/@/api/page'
|
import { getFileOptions, checkFileDup } from '/@/api/page'
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'FormHightPage',
|
name: 'FormHightPage',
|
||||||
components: { ApiSelect, BasicForm, PageWrapper, [Card.name]: Card },
|
components: { ApiSelect, BasicForm, BasicTable, PageWrapper, [Card.name]: Card },
|
||||||
|
props: {
|
||||||
|
width: {
|
||||||
|
type: String as PropType<string>,
|
||||||
|
default: '100%',
|
||||||
|
},
|
||||||
|
height: {
|
||||||
|
type: String as PropType<string>,
|
||||||
|
default: 'calc(100vh - 78px)',
|
||||||
|
},
|
||||||
|
},
|
||||||
setup() {
|
setup() {
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
const keyword = ref<string>('')
|
const keyword = ref<string>('')
|
||||||
const searchParams = computed<Recordable>(() => {
|
const searchParams = computed<Recordable>(() => {
|
||||||
return { keyword: unref(keyword) }
|
return { keyword: unref(keyword) }
|
||||||
})
|
})
|
||||||
|
const chartRef = ref<HTMLDivElement | null>(null)
|
||||||
|
const tableRef = ref<any>(null)
|
||||||
|
const { setOptions } = useECharts(chartRef as Ref<HTMLDivElement>)
|
||||||
|
const { createMessage } = useMessage()
|
||||||
|
|
||||||
|
const canResize = ref(false)
|
||||||
|
const loading = ref(false)
|
||||||
|
const striped = ref(true)
|
||||||
|
const border = ref(true)
|
||||||
|
const pagination = ref<any>(false)
|
||||||
|
function toggleCanResize() {
|
||||||
|
canResize.value = !canResize.value
|
||||||
|
}
|
||||||
|
function toggleStriped() {
|
||||||
|
striped.value = !striped.value
|
||||||
|
}
|
||||||
|
function toggleBorder() {
|
||||||
|
border.value = !border.value
|
||||||
|
}
|
||||||
|
|
||||||
const [register, { validate }] = useForm({
|
const [register, { validate }] = useForm({
|
||||||
layout: 'vertical',
|
layout: 'vertical',
|
||||||
@@ -74,8 +129,134 @@
|
|||||||
async function submitAll() {
|
async function submitAll() {
|
||||||
try {
|
try {
|
||||||
const [values, taskValues] = await Promise.all([validate(), validateTaskForm()])
|
const [values, taskValues] = await Promise.all([validate(), validateTaskForm()])
|
||||||
console.log('form data:', values, taskValues)
|
const ret = await checkFileDup(
|
||||||
} catch (error) {}
|
Number(values.f1),
|
||||||
|
taskValues.t1[0].$y,
|
||||||
|
taskValues.t1[1].$y,
|
||||||
|
)
|
||||||
|
if (
|
||||||
|
!(
|
||||||
|
ret &&
|
||||||
|
ret.duplications.length > 0 &&
|
||||||
|
ret.questions.length > 0 &&
|
||||||
|
ret.files.length > 0
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
createMessage.warn('请先分析该试卷!')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const barNames = ret.duplications.map((value) => {
|
||||||
|
return value.name
|
||||||
|
})
|
||||||
|
const barData = ret.duplications.map((value) => {
|
||||||
|
return value.percent
|
||||||
|
})
|
||||||
|
const queData = ret.questions.map((value) => {
|
||||||
|
return { value: value.count, name: value.name }
|
||||||
|
})
|
||||||
|
const ptData = ret.questions.map((value) => {
|
||||||
|
return { value: value.point, name: value.name }
|
||||||
|
})
|
||||||
|
tableRef.value = ret.files
|
||||||
|
setOptions({
|
||||||
|
title: [
|
||||||
|
{
|
||||||
|
text: '题量占比',
|
||||||
|
left: '2%',
|
||||||
|
top: '1%',
|
||||||
|
textStyle: {
|
||||||
|
fontSize: 20,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: '平均重复率: ' + ret.rate.toFixed(2) + '%, 前十如下',
|
||||||
|
left: '40%',
|
||||||
|
top: '1%',
|
||||||
|
textStyle: {
|
||||||
|
fontSize: 20,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: '分数占比',
|
||||||
|
left: '2%',
|
||||||
|
top: '50%',
|
||||||
|
textStyle: {
|
||||||
|
fontSize: 20,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
grid: [{ left: '50%', top: '7%', width: '45%', height: '90%' }],
|
||||||
|
tooltip: {
|
||||||
|
formatter: '{b} ({c})',
|
||||||
|
},
|
||||||
|
xAxis: [
|
||||||
|
{
|
||||||
|
gridIndex: 0,
|
||||||
|
axisTick: { show: false },
|
||||||
|
axisLabel: { show: false },
|
||||||
|
splitLine: { show: false },
|
||||||
|
axisLine: { show: false },
|
||||||
|
},
|
||||||
|
],
|
||||||
|
yAxis: [
|
||||||
|
{
|
||||||
|
gridIndex: 0,
|
||||||
|
interval: 0,
|
||||||
|
data: barNames,
|
||||||
|
axisTick: { show: false },
|
||||||
|
axisLabel: { show: true },
|
||||||
|
splitLine: { show: false },
|
||||||
|
axisLine: { show: true },
|
||||||
|
},
|
||||||
|
],
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
name: '题量占比',
|
||||||
|
type: 'pie',
|
||||||
|
radius: '30%',
|
||||||
|
center: ['22%', '25%'],
|
||||||
|
data: queData,
|
||||||
|
labelLine: { show: false },
|
||||||
|
label: {
|
||||||
|
show: true,
|
||||||
|
formatter: function (d) {
|
||||||
|
return d.name + '(' + d.value + ')'
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '分数占比',
|
||||||
|
type: 'pie',
|
||||||
|
radius: '30%',
|
||||||
|
center: ['22%', '75%'],
|
||||||
|
labelLine: { show: false },
|
||||||
|
data: ptData,
|
||||||
|
label: {
|
||||||
|
show: true,
|
||||||
|
formatter: '{b}\n ({d}%) ',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '重复率前十',
|
||||||
|
type: 'bar',
|
||||||
|
xAxisIndex: 0,
|
||||||
|
yAxisIndex: 0,
|
||||||
|
barWidth: '45%',
|
||||||
|
itemStyle: { color: '#86c9f4' },
|
||||||
|
label: {
|
||||||
|
show: true,
|
||||||
|
position: 'right',
|
||||||
|
formatter: function (d) {
|
||||||
|
return d.data + '%'
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data: barData,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
} catch (error) {
|
||||||
|
createMessage.error('加载分析数据错误: ' + (error as unknown as Error).message)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function onSearch(value: string) {
|
function onSearch(value: string) {
|
||||||
@@ -84,12 +265,23 @@
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
t,
|
t,
|
||||||
|
columns,
|
||||||
|
chartRef,
|
||||||
|
tableRef,
|
||||||
register,
|
register,
|
||||||
registerTask,
|
registerTask,
|
||||||
submitAll,
|
submitAll,
|
||||||
getFileOptions,
|
getFileOptions,
|
||||||
searchParams,
|
searchParams,
|
||||||
onSearch: useDebounceFn(onSearch, 300),
|
onSearch: useDebounceFn(onSearch, 300),
|
||||||
|
canResize,
|
||||||
|
loading,
|
||||||
|
striped,
|
||||||
|
border,
|
||||||
|
toggleStriped,
|
||||||
|
toggleCanResize,
|
||||||
|
toggleBorder,
|
||||||
|
pagination,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user