mirror of
https://github.com/fumiama/paper-manager.git
synced 2026-06-05 07:50:23 +08:00
finish 试卷查重
This commit is contained in:
@@ -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
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 } })
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
4
frontend/vben/src/locales/lang/zh-CN/routes/templist.ts
Normal file
4
frontend/vben/src/locales/lang/zh-CN/routes/templist.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export default {
|
||||
name: '试卷查重',
|
||||
file: '查重报告',
|
||||
}
|
||||
17
frontend/vben/src/router/menus/modules/templist.ts
Normal file
17
frontend/vben/src/router/menus/modules/templist.ts
Normal 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
|
||||
@@ -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',
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
41
frontend/vben/src/router/routes/modules/templist.ts
Normal file
41
frontend/vben/src/router/routes/modules/templist.ts
Normal 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
|
||||
@@ -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++) {
|
||||
|
||||
@@ -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(() => {
|
||||
|
||||
115
frontend/vben/src/views/page/templist/data.tsx
Normal file
115
frontend/vben/src/views/page/templist/data.tsx
Normal 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
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
269
frontend/vben/src/views/page/templist/index.vue
Normal file
269
frontend/vben/src/views/page/templist/index.vue
Normal 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>
|
||||
|
||||
<a-button
|
||||
color="warning"
|
||||
:disabled="item.percent != 0"
|
||||
@click="analyzeFile(item)"
|
||||
>
|
||||
解析
|
||||
</a-button>
|
||||
|
||||
<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>
|
||||
Reference in New Issue
Block a user