mirror of
https://github.com/fumiama/paper-manager.git
synced 2026-06-08 17:40:23 +08:00
finish 试卷库
This commit is contained in:
@@ -371,8 +371,10 @@ func (f *FileDatabase) AddFile(lstid int, reg *Regex, istemp bool, progress func
|
||||
}
|
||||
v := make(map[string]uint8, len(words)*2)
|
||||
for _, word := range words {
|
||||
if word != "" && word != "\n" && word != " " {
|
||||
v[word]++
|
||||
if word != "" && !strings.Contains("\n 。,、的是使,.()()1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ", word) {
|
||||
if v[word] == 0 { // 二值化
|
||||
v[word] = 1
|
||||
}
|
||||
}
|
||||
}
|
||||
data, err := json.Marshal(v)
|
||||
@@ -448,9 +450,7 @@ func (f *FileDatabase) AddFile(lstid int, reg *Regex, istemp bool, progress func
|
||||
}
|
||||
}
|
||||
majorq.Sub = append(majorq.Sub, QuestionJSON{
|
||||
Name: queidstr,
|
||||
Points: 0, //TODO: fill sub points
|
||||
Rate: r,
|
||||
Name: queidstr, //TODO: fill sub points
|
||||
})
|
||||
}
|
||||
filequestions = append(filequestions, majorq)
|
||||
@@ -552,18 +552,20 @@ func (f *FileDatabase) DelFile(lstid, uid int, istemp bool) error {
|
||||
}
|
||||
|
||||
// GetFile get analyzed file's structure from List(ID)
|
||||
func (f *FileDatabase) GetFile(lstid, uid int) (*File, int64, error) {
|
||||
func (f *FileDatabase) GetFile(lstid, uid int) (file *File, sz int64, istemp bool, err error) {
|
||||
f.mu.RLock()
|
||||
defer f.mu.RUnlock()
|
||||
lst, err := sql.Find[List](&f.db, FileTableList, "WHERE ID="+strconv.Itoa(lstid))
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
return
|
||||
}
|
||||
if lst.HasntAnalyzed {
|
||||
return nil, 0, ErrHasntAnalyzed
|
||||
err = ErrHasntAnalyzed
|
||||
return
|
||||
}
|
||||
if lst.IsTemp && lst.Uploader != uid {
|
||||
return nil, 0, ErrNoGetFileStatusPermission
|
||||
err = ErrNoGetFileStatusPermission
|
||||
return
|
||||
}
|
||||
ftable := ""
|
||||
if lst.IsTemp {
|
||||
@@ -571,9 +573,9 @@ func (f *FileDatabase) GetFile(lstid, uid int) (*File, int64, error) {
|
||||
} else {
|
||||
ftable = FileTableFile
|
||||
}
|
||||
file, err := sql.Find[File](&f.db, ftable, "WHERE ListID="+strconv.Itoa(lstid))
|
||||
filestruct, err := sql.Find[File](&f.db, ftable, "WHERE ListID="+strconv.Itoa(lstid))
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
return
|
||||
}
|
||||
return &file, lst.Size, nil
|
||||
return &filestruct, lst.Size, lst.IsTemp, nil
|
||||
}
|
||||
|
||||
@@ -17,7 +17,6 @@ import (
|
||||
type QuestionJSON struct {
|
||||
Name string `json:"name"` // Name is name or Question ID
|
||||
Points int `json:"points,omitempty"` // Points is sum of subs' points or self
|
||||
Rate float64 `json:"rate,omitempty"` // Rate is the avg(non-leaf) or max(leaf) similarity
|
||||
Sub []QuestionJSON `json:"sub,omitempty"`
|
||||
}
|
||||
|
||||
@@ -88,6 +87,43 @@ type Question struct {
|
||||
Dup []byte // Dup is json of {queid: rate, ...}
|
||||
}
|
||||
|
||||
// GetQuestion by id
|
||||
func (f *FileDatabase) GetQuestion(id int64, istemp bool) (Question, error) {
|
||||
f.mu.RLock()
|
||||
defer f.mu.RUnlock()
|
||||
qtable := ""
|
||||
if istemp {
|
||||
qtable = FileTableTempQuestion
|
||||
} else {
|
||||
qtable = FileTableQuestion
|
||||
}
|
||||
return sql.Find[Question](&f.db, qtable, "WHERE ID="+strconv.FormatInt(id, 10))
|
||||
}
|
||||
|
||||
// GetQuestionHex by hexid
|
||||
func (f *FileDatabase) GetQuestionHex(hexid string, istemp bool) (q Question, err error) {
|
||||
idb, err := hex.DecodeString(hexid)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return f.GetQuestion(int64(binary.LittleEndian.Uint64(idb)), istemp)
|
||||
}
|
||||
|
||||
// MaxDuplicateRate parse q.Dup and get the max rate
|
||||
func (q *Question) MaxDuplicateRate() float64 {
|
||||
dupmap := make(map[string]float64, 64)
|
||||
if err := json.Unmarshal(q.Dup, &dupmap); err != nil {
|
||||
return 0
|
||||
}
|
||||
r := 0.0
|
||||
for _, v := range dupmap {
|
||||
if v > r {
|
||||
r = v
|
||||
}
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
// GetDuplicateRate calc q & que's dup rate
|
||||
func (q *Question) GetDuplicateRate(que *Question) (float64, error) {
|
||||
v1, v2 := make(map[string]uint8, 64), make(map[string]uint8, 64)
|
||||
|
||||
@@ -41,6 +41,7 @@ type filelist struct {
|
||||
type filestatus struct {
|
||||
Name string `json:"name"`
|
||||
Size float64 `json:"size"`
|
||||
Rate float64 `json:"rate"`
|
||||
Questions []question `json:"questions"`
|
||||
Duplications []duplication `json:"duplications"` // Duplications length == 10
|
||||
}
|
||||
@@ -308,12 +309,12 @@ func init() {
|
||||
writeresult(w, codeError, nil, err.Error(), typeError)
|
||||
return
|
||||
}
|
||||
file, sz, err := global.FileDB.GetFile(id, *user.ID)
|
||||
file, sz, istemp, err := global.FileDB.GetFile(id, *user.ID)
|
||||
if err != nil {
|
||||
writeresult(w, codeError, nil, err.Error(), typeError)
|
||||
return
|
||||
}
|
||||
qs, ds, err := parseFileQuestions(file.Questions)
|
||||
qs, ds, filerate, err := parseFileQuestions(file.Questions, istemp)
|
||||
if err != nil {
|
||||
writeresult(w, codeError, nil, err.Error(), typeError)
|
||||
return
|
||||
@@ -321,6 +322,7 @@ func init() {
|
||||
writeresult(w, codeSuccess, &filestatus{
|
||||
Name: file.Class + ".docx",
|
||||
Size: float64(sz) / 1024 / 1024, // MB
|
||||
Rate: filerate * 100,
|
||||
Questions: qs,
|
||||
Duplications: ds,
|
||||
}, messageOk, typeSuccess)
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
package backend
|
||||
|
||||
import (
|
||||
"container/heap"
|
||||
"encoding/json"
|
||||
"math"
|
||||
"strconv"
|
||||
|
||||
"github.com/fumiama/paper-manager/backend/global"
|
||||
)
|
||||
@@ -17,21 +20,78 @@ type duplication struct {
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
func parseFileQuestions(qb []byte) ([]question, []duplication, error) {
|
||||
type duplications []duplication
|
||||
|
||||
func (d *duplications) Len() int {
|
||||
return len(*d)
|
||||
}
|
||||
|
||||
// Less is actually more for a big-top heap
|
||||
func (d *duplications) Less(i, j int) bool {
|
||||
return (*d)[i].Percent > (*d)[j].Percent
|
||||
}
|
||||
|
||||
func (d *duplications) Swap(i, j int) {
|
||||
(*d)[i], (*d)[j] = (*d)[j], (*d)[i]
|
||||
}
|
||||
|
||||
func (d *duplications) Push(x any) {
|
||||
*d = append(*d, x.(duplication))
|
||||
}
|
||||
|
||||
func (d *duplications) Pop() any {
|
||||
if d.Len() == 0 {
|
||||
return nil
|
||||
}
|
||||
i := d.Len() - 1
|
||||
x := (*d)[i]
|
||||
*d = (*d)[:i]
|
||||
return x
|
||||
}
|
||||
|
||||
func parseFileQuestions(qb []byte, istemp bool) ([]question, []duplication, float64, error) {
|
||||
ques := make([]global.QuestionJSON, 0, 16)
|
||||
qs := make([]question, 0, 16)
|
||||
ds := make([]duplication, 0, 16)
|
||||
err := json.Unmarshal(qb, &qs)
|
||||
err := json.Unmarshal(qb, &ques)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
return nil, nil, 0, err
|
||||
}
|
||||
dh := make(duplications, 0, 16)
|
||||
heap.Init(&dh)
|
||||
sum := 0.0
|
||||
cnt := 0
|
||||
for _, q := range ques {
|
||||
qs = append(qs, question{
|
||||
Count: len(q.Sub),
|
||||
Point: q.Points,
|
||||
Name: q.Name,
|
||||
})
|
||||
// TODO: use heap to get top 10 ds
|
||||
for i, subq := range q.Sub {
|
||||
qstruct, err := global.FileDB.GetQuestionHex(subq.Name, istemp)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
p := qstruct.MaxDuplicateRate()
|
||||
heap.Push(&dh, duplication{
|
||||
Percent: int(math.Round(p * 100)),
|
||||
Name: q.Name + "." + strconv.Itoa(i+1),
|
||||
})
|
||||
sum += p
|
||||
cnt++
|
||||
}
|
||||
}
|
||||
return nil, nil, nil
|
||||
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 qs, ds, sum / float64(cnt), nil
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { MockMethod } from 'vite-plugin-mock'
|
||||
import { resultError, resultSuccess, getRequestToken, requestParams } from '../_util'
|
||||
// import { resultError, resultSuccess, getRequestToken, requestParams } from '../_util'
|
||||
|
||||
export default [
|
||||
/*{
|
||||
@@ -16,7 +16,7 @@ export default [
|
||||
})
|
||||
},
|
||||
},*/
|
||||
{
|
||||
/*{
|
||||
url: '/api/getFileStatus',
|
||||
timeout: 500,
|
||||
method: 'get',
|
||||
@@ -49,5 +49,5 @@ export default [
|
||||
],
|
||||
})
|
||||
},
|
||||
},
|
||||
},*/
|
||||
] as MockMethod[]
|
||||
|
||||
@@ -16,6 +16,7 @@ export interface Duplication {
|
||||
export interface FileStatus {
|
||||
name: string
|
||||
size: number
|
||||
rate: number
|
||||
questions: Question[]
|
||||
duplications: Duplication[]
|
||||
}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
<template>
|
||||
<PageWrapper :title="t('routes.filelist.file') + ': ' + docxNameRef">
|
||||
<template #headerContent>
|
||||
<a-button type="primary" @click="downloadDocx"> 下载试卷 ({{ docxSizeRef }}MB) </a-button>
|
||||
<a-button type="primary" @click="downloadDocx">
|
||||
下载试卷 ({{ docxSizeRef.toFixed(2) }}MB)
|
||||
</a-button>
|
||||
</template>
|
||||
<div ref="chartRef" :style="{ height, width }"></div>
|
||||
<div class="docxWrap" :style="{ width }">
|
||||
@@ -131,7 +133,7 @@
|
||||
},
|
||||
},
|
||||
{
|
||||
text: '重复率前十',
|
||||
text: '重复率: ' + ret.rate.toFixed(2) + '%, 前十如下',
|
||||
left: '40%',
|
||||
top: '1%',
|
||||
textStyle: {
|
||||
|
||||
Reference in New Issue
Block a user