mirror of
https://github.com/fumiama/paper-manager.git
synced 2026-06-04 23:40:24 +08:00
finish chkdup
This commit is contained in:
164
backend/file.go
164
backend/file.go
@@ -1,7 +1,10 @@
|
||||
package backend
|
||||
|
||||
import (
|
||||
"container/heap"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"math"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -31,8 +34,8 @@ type filelist struct {
|
||||
Per uint `json:"percent"`
|
||||
}
|
||||
|
||||
func getFileList(count int, istemp *bool) ([]filelist, error) {
|
||||
lst, err := global.FileDB.ListUploadedFile(istemp)
|
||||
func getFileList(count, uid int, istemp *bool) ([]filelist, error) {
|
||||
lst, err := global.FileDB.ListUploadedFile(istemp, uid)
|
||||
if err != nil && err != sql.ErrNullResult {
|
||||
return nil, err
|
||||
}
|
||||
@@ -124,23 +127,127 @@ type filestatus struct {
|
||||
}
|
||||
|
||||
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 {
|
||||
return nil, err
|
||||
}
|
||||
qs, ds, filerate, err := parseFileQuestions(file.Questions, istemp)
|
||||
qs, ds, filerate, err := parseFileQuestions(file.Questions, lst.IsTemp, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &filestatus{
|
||||
Name: file.Class + ".docx",
|
||||
Size: float64(sz) / 1024 / 1024, // MB
|
||||
Size: float64(lst.Size) / 1024 / 1024, // MB
|
||||
Rate: filerate * 100,
|
||||
Questions: qs,
|
||||
Duplications: ds,
|
||||
}, 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() {
|
||||
apimap["/api/getFileList"] = &apihandler{"GET", func(w http.ResponseWriter, r *http.Request) {
|
||||
token := r.Header.Get("Authorization")
|
||||
@@ -165,7 +272,7 @@ func init() {
|
||||
return
|
||||
}
|
||||
}
|
||||
lst, err := getFileList(count, istemp)
|
||||
lst, err := getFileList(count, *user.ID, istemp)
|
||||
if err != nil {
|
||||
writeresult(w, codeError, nil, err.Error(), typeError)
|
||||
return
|
||||
@@ -281,6 +388,51 @@ func init() {
|
||||
}
|
||||
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
|
||||
|
||||
@@ -98,6 +98,12 @@ func (sy StudyYear) String() string {
|
||||
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
|
||||
func (f *FileDatabase) DelFile(lstid, uid int, istemp bool) error {
|
||||
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)
|
||||
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()
|
||||
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 {
|
||||
return
|
||||
}
|
||||
@@ -187,5 +193,12 @@ func (f *FileDatabase) GetFile(lstid, uid int) (file *File, sz int64, istemp boo
|
||||
if err != nil {
|
||||
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
|
||||
func (f *FileDatabase) ListUploadedFile(istemp *bool) (lst []*List, err error) {
|
||||
func (f *FileDatabase) ListUploadedFile(istemp *bool, uid int) (lst []*List, err error) {
|
||||
q := ""
|
||||
switch {
|
||||
case istemp == nil:
|
||||
q = "ORDER BY UpTime DESC"
|
||||
q = "WHERE (NOT IsTemp) OR (Uploader=" + strconv.Itoa(uid) + ") ORDER BY UpTime DESC"
|
||||
case *istemp:
|
||||
q = "WHERE IsTemp ORDER BY UpTime DESC"
|
||||
q = "WHERE IsTemp AND Uploader=" + strconv.Itoa(uid) + " ORDER BY UpTime DESC"
|
||||
default:
|
||||
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
|
||||
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)
|
||||
if err != nil {
|
||||
return
|
||||
|
||||
@@ -50,15 +50,19 @@ func (d *duplications) Pop() any {
|
||||
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)
|
||||
qs := make([]question, 0, 16)
|
||||
err := json.Unmarshal(qb, &ques)
|
||||
if err != nil {
|
||||
return nil, nil, 0, err
|
||||
}
|
||||
dh := make(duplications, 0, 16)
|
||||
heap.Init(&dh)
|
||||
dhp := (*duplications)(nil)
|
||||
if getdup {
|
||||
dh := make(duplications, 0, 16)
|
||||
heap.Init(&dh)
|
||||
dhp = &dh
|
||||
}
|
||||
sum := 0.0
|
||||
cnt := 0
|
||||
for _, q := range ques {
|
||||
@@ -68,33 +72,63 @@ func parseFileQuestions(qb []byte, istemp bool) ([]question, []duplication, floa
|
||||
Name: q.Name,
|
||||
})
|
||||
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 {
|
||||
continue
|
||||
}
|
||||
p := qstruct.MaxDuplicateRate()
|
||||
heap.Push(&dh, duplication{
|
||||
Percent: int(math.Round(p * 100)),
|
||||
Name: q.Name + "." + strconv.Itoa(i+1),
|
||||
})
|
||||
if getdup {
|
||||
heap.Push(dhp, duplication{
|
||||
Percent: int(math.Round(p * 100)),
|
||||
Name: q.Name + "." + strconv.Itoa(i+1),
|
||||
})
|
||||
}
|
||||
sum += p
|
||||
cnt++
|
||||
}
|
||||
}
|
||||
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"}
|
||||
if getdup {
|
||||
i := dhp.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)
|
||||
for i--; i >= 0; i-- {
|
||||
ds[i] = heap.Pop(dhp).(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() {
|
||||
|
||||
@@ -5,7 +5,7 @@ import {
|
||||
FileListGroupItem,
|
||||
GenerateConfig,
|
||||
} from './model/fileListModel'
|
||||
import { DownloadFile, FileStatus } from './model/fileModel'
|
||||
import { DownloadFile, FileDupStatus, FileStatus } from './model/fileModel'
|
||||
|
||||
enum Api {
|
||||
GetFileList = '/getFileList',
|
||||
@@ -15,6 +15,7 @@ enum Api {
|
||||
AnalyzeFile = '/analyzeFile',
|
||||
DlFile = '/dlFile',
|
||||
GetFileStatus = '/getFileStatus',
|
||||
CheckFileDup = '/checkFileDup',
|
||||
GetMajors = '/getMajors',
|
||||
GenFile = '/genFile',
|
||||
DlGen = '/dlGen',
|
||||
@@ -95,6 +96,16 @@ export const getFileStatus = (id: number) => {
|
||||
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
|
||||
*/
|
||||
|
||||
@@ -20,3 +20,12 @@ export interface FileStatus {
|
||||
questions: Question[]
|
||||
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 { BasicColumn } from '/@/components/Table/src/types/table'
|
||||
|
||||
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">
|
||||
<BasicForm @register="registerTask" />
|
||||
</a-card>
|
||||
<a-card title="查重报告" :bordered="false" class="!mt-5">
|
||||
<p> aaaaa </p>
|
||||
<a-card title="查重报告" :bordered="false" class="!mt-5" v-if="tableRef && tableRef.length > 0">
|
||||
<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>
|
||||
|
||||
<template #rightFooter>
|
||||
@@ -35,23 +58,55 @@
|
||||
</template>
|
||||
<script lang="ts">
|
||||
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 { useECharts } from '/@/hooks/web/useECharts'
|
||||
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 { useI18n } from '/@/hooks/web/useI18n'
|
||||
import { getFileOptions } from '/@/api/page'
|
||||
import { getFileOptions, checkFileDup } from '/@/api/page'
|
||||
|
||||
export default defineComponent({
|
||||
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() {
|
||||
const { t } = useI18n()
|
||||
const keyword = ref<string>('')
|
||||
const searchParams = computed<Recordable>(() => {
|
||||
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({
|
||||
layout: 'vertical',
|
||||
@@ -74,8 +129,134 @@
|
||||
async function submitAll() {
|
||||
try {
|
||||
const [values, taskValues] = await Promise.all([validate(), validateTaskForm()])
|
||||
console.log('form data:', values, taskValues)
|
||||
} catch (error) {}
|
||||
const ret = await checkFileDup(
|
||||
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) {
|
||||
@@ -84,12 +265,23 @@
|
||||
|
||||
return {
|
||||
t,
|
||||
columns,
|
||||
chartRef,
|
||||
tableRef,
|
||||
register,
|
||||
registerTask,
|
||||
submitAll,
|
||||
getFileOptions,
|
||||
searchParams,
|
||||
onSearch: useDebounceFn(onSearch, 300),
|
||||
canResize,
|
||||
loading,
|
||||
striped,
|
||||
border,
|
||||
toggleStriped,
|
||||
toggleCanResize,
|
||||
toggleBorder,
|
||||
pagination,
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user