1
0
mirror of https://github.com/fumiama/paper-manager.git synced 2026-06-05 07:50:23 +08:00

finish 试卷查重

This commit is contained in:
源文雨
2023-04-17 22:35:54 +08:00
parent 67b6abf001
commit e2a8cdf5b3
13 changed files with 475 additions and 34 deletions

View File

@@ -497,7 +497,7 @@ func (f *FileDatabase) DelFile(lstid, uid int, istemp bool) error {
if err != nil {
return err
}
if !user.IsSuper() {
if !user.IsSuper() && !istemp {
return ErrInvalidRole
}
ftable := ""
@@ -512,6 +512,9 @@ func (f *FileDatabase) DelFile(lstid, uid int, istemp bool) error {
if err != nil {
return err
}
if istemp && lst.Uploader != uid {
return ErrInvalidRole
}
if lst.Path == "" || strings.Contains(lst.Path, "..") {
return os.ErrNotExist
}

View File

@@ -83,14 +83,20 @@ 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() (lst []*List, err error) {
func (f *FileDatabase) ListUploadedFile(istemp bool) (lst []*List, err error) {
q := ""
if istemp {
q = "WHERE IsTemp ORDER BY UpTime DESC"
} else {
q = "WHERE (HasntAnalyzed AND IsTemp) OR (NOT HasntAnalyzed AND NOT IsTemp) ORDER BY UpTime DESC"
}
f.mu.RLock()
lst, err = sql.FindAll[List](&f.db, FileTableList, "WHERE (HasntAnalyzed AND IsTemp) OR (NOT HasntAnalyzed AND NOT IsTemp) ORDER BY UpTime DESC")
lst, err = sql.FindAll[List](&f.db, FileTableList, q)
f.mu.RUnlock()
return
}
func (f *FileDatabase) GetFileInfo(id int) (lst List, err error) {
func (f *FileDatabase) ListFileByID(id int) (lst List, err error) {
f.mu.RLock()
lst, err = sql.Find[List](&f.db, FileTableList, "WHERE ID="+strconv.Itoa(id))
f.mu.RUnlock()

View File

@@ -97,7 +97,9 @@ type loginResult struct {
}
var (
usertokens = ttl.NewCache[string, *global.User](time.Hour)
usertokens = ttl.NewCacheOn(time.Hour, [4]func(string, *global.User){
nil, nil, func(t string, _ *global.User) { loginstatus.Delete(t) }, nil,
})
)
func login(username, challenge string) (*loginResult, error) {

View File

@@ -54,6 +54,7 @@ func init() {
writeresult(w, codeError, nil, errInvalidToken.Error(), typeError)
return
}
istemp := r.URL.Query().Get("permanent") != "true"
count := -1
var err error
countstr := r.URL.Query().Get("count")
@@ -64,7 +65,7 @@ func init() {
return
}
}
lst, err := global.FileDB.ListUploadedFile()
lst, err := global.FileDB.ListUploadedFile(istemp)
if err != nil && err != sql.ErrNullResult {
writeresult(w, codeError, nil, err.Error(), typeError)
return
@@ -112,7 +113,7 @@ func init() {
writeresult(w, codeError, nil, err.Error(), typeError)
return
}
lst, err := global.FileDB.GetFileInfo(id)
lst, err := global.FileDB.ListFileByID(id)
if err != nil && err != sql.ErrNullResult {
writeresult(w, codeError, nil, err.Error(), typeError)
return
@@ -217,7 +218,8 @@ func init() {
writeresult(w, codeError, nil, errInvalidToken.Error(), typeError)
return
}
if !user.IsSuper() {
istemp := r.URL.Query().Get("permanent") != "true"
if !user.IsSuper() && !istemp {
writeresult(w, codeError, nil, errNoDeletePermission.Error(), typeError)
return
}
@@ -231,7 +233,7 @@ func init() {
writeresult(w, codeError, nil, err.Error(), typeError)
return
}
err = global.FileDB.DelFile(id, *user.ID, false)
err = global.FileDB.DelFile(id, *user.ID, istemp)
if err != nil {
writeresult(w, codeError, nil, err.Error(), typeError)
return
@@ -245,10 +247,6 @@ func init() {
writeresult(w, codeError, nil, errInvalidToken.Error(), typeError)
return
}
if !user.IsSuper() {
writeresult(w, codeError, nil, errNoDeletePermission.Error(), typeError)
return
}
idstr := r.URL.Query().Get("id")
if idstr == "" {
writeresult(w, codeError, nil, "empty id", typeError)
@@ -259,7 +257,7 @@ func init() {
writeresult(w, codeError, nil, err.Error(), typeError)
return
}
lst, err := global.FileDB.GetFileInfo(id)
lst, err := global.FileDB.ListFileByID(id)
if err != nil {
writeresult(w, codeError, nil, err.Error(), typeError)
return

View File

@@ -15,8 +15,8 @@ enum Api {
/**
* @description: Get file list
*/
export const getFileList = (count?: number) => {
return defHttp.get<getFileListModel>({ url: Api.GetFileList, params: { count: count } })
export const getFileList = (permanent: boolean, count?: number) => {
return defHttp.get<getFileListModel>({ url: Api.GetFileList, params: { count, permanent } })
}
/**
@@ -36,8 +36,8 @@ export const getFilePercent = (id: number) => {
/**
* @description: Get file percent
*/
export const delFile = (id: number) => {
return defHttp.get<string>({ url: Api.DelFile, params: { id: id } })
export const delFile = (id: number, permanent: boolean) => {
return defHttp.get<string>({ url: Api.DelFile, params: { id, permanent } })
}
/**

View File

@@ -0,0 +1,4 @@
export default {
name: '试卷查重',
file: '查重报告',
}

View File

@@ -0,0 +1,17 @@
import type { MenuModule } from '/@/router/types'
import { t } from '/@/hooks/web/useI18n'
const menu: MenuModule = {
orderNo: 20,
menu: {
name: t('routes.templist.name'),
path: '/templist',
children: [
{
path: 'index',
name: t('routes.templist.name'),
},
],
},
}
export default menu

View File

@@ -1,10 +1,7 @@
import type { AppRouteModule } from '/@/router/types'
import { ExceptionEnum } from '/@/enums/exceptionEnum'
import { LAYOUT } from '/@/router/constant'
import { t } from '/@/hooks/web/useI18n'
const ExceptionPage = () => import('/@/views/sys/exception/Exception.vue')
const filelist: AppRouteModule = {
path: '/filelist',
name: 'FileList',
@@ -38,17 +35,6 @@ const filelist: AppRouteModule = {
hideMenu: true,
},
},
{
path: '404',
name: 'PageNotFound',
component: ExceptionPage,
props: {
status: ExceptionEnum.PAGE_NOT_FOUND,
},
meta: {
title: '404',
},
},
],
}

View File

@@ -0,0 +1,41 @@
import type { AppRouteModule } from '/@/router/types'
import { LAYOUT } from '/@/router/constant'
import { t } from '/@/hooks/web/useI18n'
const templist: AppRouteModule = {
path: '/templist',
name: 'TempList',
component: LAYOUT,
redirect: '/templist/index',
meta: {
hideChildrenInMenu: true,
icon: 'ion:ios-analytics',
title: t('routes.templist.name'),
orderNo: 20,
},
children: [
{
path: 'index',
name: 'TempListPage',
component: () => import('/@/views/page/templist/index.vue'),
meta: {
title: t('routes.templist.name'),
icon: 'ion:file-tray-full-outline',
hideMenu: true,
},
},
{
path: 'file/:id',
name: 'TempFilePage',
component: () => import('/@/views/page/file/index.vue'),
meta: {
title: t('routes.templist.file'),
carryParam: true,
icon: 'bi:filetype-docx',
hideMenu: true,
},
},
],
}
export default templist

View File

@@ -34,7 +34,7 @@ export function getListOfPage(pageSize: number, page: number): any[] {
async function refreshFileList() {
const __cardList: any[] = []
const lst = (await getFileList()) as getFileListModel
const lst = (await getFileList(true)) as getFileListModel
let __totalSize = 0
let __totalQuestions = 0
for (let i = 0; i < lst.length; i++) {

View File

@@ -128,7 +128,7 @@
async function deleteFile(item: any) {
try {
item.delloading = true
const msg = await delFile(item.id)
const msg = await delFile(item.id, true)
if (msg) {
createMessage.success(msg)
setTimeout(() => {

View File

@@ -0,0 +1,115 @@
import { reactive } from 'vue'
import { getFileList, getFilePercent, getFileInfo } from '/@/api/page'
import { getFileListModel } from '/@/api/page/model/fileListModel'
export const random = (min: number, max: number) =>
Math.floor(Math.random() * (max - min + 1) + min)
export function refreshFilePercent(item: any) {
return async () => {
const p = await getFilePercent(item.id)
if (p) {
item.percent = p
if (p < 100) {
setTimeout(refreshFilePercent(item), 1000)
}
} else item.hassettimeout = false
}
}
export function getListOfPage(pageSize: number, page: number): any[] {
const i = page - 1
let lst: any[] = []
if (i < cardList._cardList.length / pageSize)
lst = reactive(cardList._cardList.slice(i * pageSize, page * pageSize))
else lst = reactive(cardList._cardList.slice((cardList._cardList.length / pageSize) * pageSize))
for (let i = 0; i < lst.length; i++) {
if (!lst[i].hassettimeout && lst[i].percent > 0 && lst[i].percent < 100) {
setTimeout(refreshFilePercent(lst[i]), 1000 + random(0, 1000))
lst[i].hassettimeout = true
}
}
return lst
}
async function refreshFileList() {
const __cardList: any[] = []
const lst = (await getFileList(false)) as getFileListModel
let __totalSize = 0
let __totalQuestions = 0
for (let i = 0; i < lst.length; i++) {
__cardList.push({
id: lst[i].id,
title: lst[i].title,
description: lst[i].description,
size: lst[i].size,
questions: lst[i].questions,
datetime: lst[i].datetime,
icon: 'bi:filetype-docx',
color: '#1890ff',
author: lst[i].author,
percent: lst[i].percent,
hassettimeout: false,
delloading: false,
})
__totalSize += lst[i].size
__totalQuestions += lst[i].questions
}
return {
_cardList: __cardList,
_totalSize: __totalSize,
_totalQuestions: __totalQuestions,
}
}
export const cardList = reactive(await refreshFileList())
export const pagination = reactive({
current: 1,
total: cardList._cardList.length,
show: true,
pageSize: 10,
onChange: function (page: number, pageSize: number) {
this.current = page
this.pageSize = pageSize
},
})
export function refreshCardList() {
refreshFileList().then((value) => {
cardList._cardList = value._cardList
cardList._totalQuestions = value._totalQuestions
cardList._totalSize = value._totalSize
pagination.current = 1
pagination.total = cardList._cardList.length
})
}
export function deleteFileByID(id: number) {
cardList._cardList.map((value: any, index: number) => {
if (value.id == id) {
cardList._cardList.splice(index, 1)
cardList._totalSize -= value.size
cardList._totalQuestions -= value.questions
pagination.total = cardList._cardList.length
}
})
}
export function refreshFileByID(id: number) {
getFileInfo(id).then((info) => {
cardList._cardList.map((value: any) => {
if (value.id == id) {
cardList._totalSize = cardList._totalSize - value.size + info.size
cardList._totalQuestions = cardList._totalQuestions - value.questions + info.questions
value.title = info.title
value.description = info.description
value.size = info.size
value.questions = info.questions
value.datetime = info.datetime
value.author = info.author
value.percent = info.percent
}
})
})
}

View File

@@ -0,0 +1,269 @@
<template>
<PageWrapper :class="prefixCls" :title="t('routes.templist.name')">
<template #headerContent>
<BasicUpload
name="paper"
v-if="hasPermission([RoleEnum.SUPER, RoleEnum.FILE_MANAGER])"
:maxSize="64"
:maxNumber="16"
:api="uploadApi"
@change="onChange"
:accept="['application/vnd.openxmlformats-officedocument.wordprocessingml.document']"
/>
</template>
<div :class="`${prefixCls}__top`">
<a-row :gutter="12">
<a-col :span="8" :class="`${prefixCls}__top-col`">
<div>总文件数</div>
<p> {{ pagination.total }}</p>
</a-col>
<a-col :span="8" :class="`${prefixCls}__top-col`">
<div>占用空间</div>
<p> {{ cardList._totalSize.toFixed(2) }}MB </p>
</a-col>
<a-col :span="8" :class="`${prefixCls}__top-col`">
<div>总题目数</div>
<p> {{ cardList._totalQuestions }} </p>
</a-col>
</a-row>
</div>
<div :class="`${prefixCls}__content`">
<a-list :pagination="pagination">
<template
v-for="item in getListOfPage(pagination.pageSize, pagination.current)"
:key="item.id"
>
<a-list-item class="list">
<a-list-item-meta>
<template #avatar>
<Icon class="icon" v-if="item.icon" :icon="item.icon" :color="item.color" />
</template>
<template #title>
<span>{{ item.title }}</span>
<div class="extra">
<a-button
color="success"
:disabled="item.percent < 100"
@click="openFile(item.id)"
>
查阅
</a-button>
&nbsp;&nbsp;
<a-button
color="warning"
:disabled="item.percent != 0"
@click="analyzeFile(item)"
>
解析
</a-button>
&nbsp;&nbsp;
<a-button
color="error"
:disabled="item.percent > 0 && item.percent < 100"
:loading="item.delloading"
@click="deleteFile(item)"
>
删除
</a-button>
</div>
</template>
<template #description>
<div class="description">
{{ item.description }}
</div>
<div class="info">
<div><span>文件大小</span>{{ item.size.toFixed(2) }}MB</div>
<div><span>上传用户</span>{{ item.author }}</div>
<div><span>上传时间</span>{{ item.datetime }}</div>
</div>
<div class="progress">
<div><span>解析进度</span></div>
<Progress
:percent="item.percent"
:status='((): "normal" | "success" | "active" | "exception" | undefined => {
if (item.percent < 100) return "active"
return "success"
})()'
/>
</div>
</template>
</a-list-item-meta>
</a-list-item>
</template>
</a-list>
</div>
</PageWrapper>
</template>
<script lang="ts">
import { Progress, Row, Col } from 'ant-design-vue'
import { defineComponent } from 'vue'
import { Icon } from '/@/components/Icon'
import { BasicUpload } from '/@/components/Upload'
import {
cardList,
getListOfPage,
deleteFileByID,
pagination,
refreshFilePercent,
random,
refreshCardList,
refreshFileByID,
} from './data'
import { PageWrapper } from '/@/components/Page'
import { useMessage } from '/@/hooks/web/useMessage'
import { usePermission } from '/@/hooks/web/usePermission'
import { RoleEnum } from '/@/enums/roleEnum'
import { List } from 'ant-design-vue'
import { uploadApi } from '/@/api/sys/upload'
import { useI18n } from '/@/hooks/web/useI18n'
import { delFile, analyzeFile } from '/@/api/page'
import { useGo } from '/@/hooks/web/usePage'
const { t } = useI18n()
const { createMessage } = useMessage()
async function deleteFile(item: any) {
try {
item.delloading = true
const msg = await delFile(item.id, false)
if (msg) {
createMessage.success(msg)
setTimeout(() => {
deleteFileByID(item.id)
}, 1000)
}
} catch (error) {
createMessage.error((error as unknown as Error).message)
} finally {
setTimeout(() => {
item.delloading = false
}, 500)
}
}
async function analFile(item: any) {
try {
const msg = await analyzeFile(item.id, false)
if (msg) {
createMessage.success(msg.msg)
if (msg.code == 0) {
item.percent = 100
refreshFileByID(item.id)
return
}
if (!item.hassettimeout && item.percent == 0) {
setTimeout(refreshFilePercent(item), 1000 + random(0, 1000))
item.hassettimeout = true
}
}
} catch (error) {
createMessage.error((error as unknown as Error).message)
}
}
export default defineComponent({
components: {
BasicUpload,
Icon,
Progress,
PageWrapper,
[List.name]: List,
[List.Item.name]: List.Item,
AListItemMeta: List.Item.Meta,
[Row.name]: Row,
[Col.name]: Col,
},
setup() {
const { hasPermission } = usePermission()
const go = useGo()
function openFile(id: number) {
go({ name: 'TempFilePage', params: { id } })
}
async function onChange(_: number[]) {
refreshCardList()
}
return {
t,
RoleEnum,
uploadApi,
hasPermission,
prefixCls: 'list-basic',
getListOfPage,
openFile,
deleteFile,
analyzeFile: analFile,
cardList,
pagination,
onChange,
}
},
})
</script>
<style lang="less" scoped>
.list-basic {
&__top {
padding: 24px;
text-align: center;
background-color: @component-background;
&-col {
&:not(:last-child) {
border-right: 1px dashed @border-color-base;
}
div {
margin-bottom: 12px;
font-size: 14px;
line-height: 22px;
color: @text-color;
}
p {
margin: 0;
font-size: 24px;
line-height: 32px;
color: @text-color;
}
}
}
&__content {
padding: 24px;
margin-top: 12px;
background-color: @component-background;
.list {
position: relative;
}
.icon {
font-size: 40px !important;
}
.extra {
position: absolute;
top: 38px;
right: 8px;
}
.description {
display: inline-block;
width: 20%;
}
.info {
display: inline-block;
width: 40%;
text-align: center;
vertical-align: top;
div {
display: inline-block;
padding: 0 20px;
span {
display: block;
}
}
}
.progress {
display: inline-block;
width: 15%;
vertical-align: top;
}
}
}
</style>