fix contest conflict.
This commit is contained in:
@@ -1,22 +1,10 @@
|
||||
import http from "utils/http"
|
||||
import { Problem } from "~/utils/types"
|
||||
|
||||
export async function getProblemList(
|
||||
offset = 0,
|
||||
limit = 10,
|
||||
searchParams: any = {}
|
||||
) {
|
||||
let params: any = {
|
||||
paging: true,
|
||||
offset,
|
||||
limit,
|
||||
}
|
||||
Object.keys(searchParams).forEach((element) => {
|
||||
if (searchParams[element]) {
|
||||
params[element] = searchParams[element]
|
||||
}
|
||||
export async function getProblemList(offset = 0, limit = 10, keyword: string) {
|
||||
const res = await http.get("admin/problem", {
|
||||
params: { paging: true, offset, limit, keyword },
|
||||
})
|
||||
const res = await http.get("admin/problem", { params })
|
||||
return {
|
||||
results: res.data.results.map((result: Problem) => ({
|
||||
id: result.id,
|
||||
@@ -41,3 +29,9 @@ export function editProblem(problem: Problem) {
|
||||
export function getProblem(id: number) {
|
||||
return http.get("admin/problem", { params: { id } })
|
||||
}
|
||||
|
||||
export function getUserList(offset = 0, limit = 10, keyword: string) {
|
||||
return http.get("admin/user", {
|
||||
params: { paging: true, offset, limit, keyword },
|
||||
})
|
||||
}
|
||||
|
||||
48
src/admin/problem/components/Actions.vue
Normal file
48
src/admin/problem/components/Actions.vue
Normal file
@@ -0,0 +1,48 @@
|
||||
<script lang="ts" setup>
|
||||
import { deleteProblem } from "~/admin/api"
|
||||
|
||||
interface Props {
|
||||
problemID: number
|
||||
}
|
||||
const props = defineProps<Props>()
|
||||
const emit = defineEmits(["deleted"])
|
||||
|
||||
const router = useRouter()
|
||||
const message = useMessage()
|
||||
|
||||
async function handleDeleteProblem(problemID: number) {
|
||||
await deleteProblem(problemID)
|
||||
message.success("删除成功")
|
||||
emit("deleted")
|
||||
}
|
||||
|
||||
function download() {
|
||||
console.log(props.problemID)
|
||||
}
|
||||
|
||||
function goEdit() {
|
||||
router.push({
|
||||
name: "problem edit",
|
||||
params: { problemID: props.problemID },
|
||||
})
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
<n-space align="center">
|
||||
<n-button size="small" secondary type="primary" @click="goEdit">
|
||||
编辑
|
||||
</n-button>
|
||||
<n-popconfirm @positive-click="() => handleDeleteProblem(props.problemID)">
|
||||
<template #trigger>
|
||||
<n-button secondary size="small" type="error">删除</n-button>
|
||||
</template>
|
||||
确定删除这道题目吗?相关的提交也会被相应删除哦 😯
|
||||
</n-popconfirm>
|
||||
<n-tooltip>
|
||||
<template #trigger>
|
||||
<n-button size="small" secondary @click="download">下载</n-button>
|
||||
</template>
|
||||
下载测试用例
|
||||
</n-tooltip>
|
||||
</n-space>
|
||||
</template>
|
||||
@@ -1,24 +0,0 @@
|
||||
<script lang="ts" setup>
|
||||
import { deleteProblem } from "~/admin/api"
|
||||
|
||||
interface Props {
|
||||
problemID: number
|
||||
}
|
||||
const props = defineProps<Props>()
|
||||
const emit = defineEmits(["deleted"])
|
||||
const message = useMessage()
|
||||
|
||||
async function handleDeleteProblem(problemID: number) {
|
||||
await deleteProblem(problemID)
|
||||
message.success("删除成功")
|
||||
emit("deleted")
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
<n-popconfirm @positive-click="() => handleDeleteProblem(props.problemID)">
|
||||
<template #trigger>
|
||||
<n-button tertiary size="small" type="error">删除</n-button>
|
||||
</template>
|
||||
确定删除这道题目吗?相关的提交也会被相应删除哦 😯
|
||||
</n-popconfirm>
|
||||
</template>
|
||||
@@ -1,15 +0,0 @@
|
||||
<script lang="ts" setup>
|
||||
const props = defineProps<{ problemID: number }>()
|
||||
|
||||
function download() {
|
||||
console.log(props.problemID)
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
<n-tooltip>
|
||||
<template #trigger>
|
||||
<n-button size="small" tertiary @click="download">下载</n-button>
|
||||
</template>
|
||||
下载测试用例
|
||||
</n-tooltip>
|
||||
</template>
|
||||
@@ -1,13 +1,10 @@
|
||||
<script setup lang="ts">
|
||||
import { getProblemList, getProblem, editProblem } from "../api"
|
||||
import Pagination from "~/shared/Pagination.vue"
|
||||
import { DataTableColumn, NButton, NSwitch } from "naive-ui"
|
||||
import { DataTableColumn, NSwitch } from "naive-ui"
|
||||
import { AdminProblemFiltered } from "~/utils/types"
|
||||
import { parseTime } from "~/utils/functions"
|
||||
import DownloadTestcases from "./components/DownloadTestcases.vue"
|
||||
import DeleteProblem from "./components/DeleteProblem.vue"
|
||||
|
||||
const router = useRouter()
|
||||
import Actions from "./components/Actions.vue"
|
||||
|
||||
const total = ref(0)
|
||||
const problems = ref<AdminProblemFiltered[]>([])
|
||||
@@ -41,38 +38,14 @@ const columns: DataTableColumn<AdminProblemFiltered>[] = [
|
||||
},
|
||||
{
|
||||
key: "edit",
|
||||
render: (row) =>
|
||||
h(
|
||||
NButton,
|
||||
{
|
||||
type: "primary",
|
||||
size: "small",
|
||||
tertiary: true,
|
||||
onClick: () =>
|
||||
router.push({
|
||||
name: "problem edit",
|
||||
params: { problemID: row.id },
|
||||
}),
|
||||
},
|
||||
{ default: () => "编辑" }
|
||||
),
|
||||
},
|
||||
{
|
||||
key: "delete",
|
||||
render: (row) =>
|
||||
h(DeleteProblem, { problemID: row.id, onDeleted: listProblems }),
|
||||
},
|
||||
{
|
||||
key: "download",
|
||||
render: (row) => h(DownloadTestcases, { problemID: row.id }),
|
||||
width: 200,
|
||||
render: (row) => h(Actions, { problemID: row.id, onDeleted: listProblems }),
|
||||
},
|
||||
]
|
||||
|
||||
async function listProblems() {
|
||||
const offset = (query.page - 1) * query.limit
|
||||
const res = await getProblemList(offset, query.limit, {
|
||||
keyword: query.keyword,
|
||||
})
|
||||
const res = await getProblemList(offset, query.limit, query.keyword)
|
||||
total.value = res.total
|
||||
problems.value = res.results
|
||||
}
|
||||
|
||||
15
src/admin/user/components/Actions.vue
Normal file
15
src/admin/user/components/Actions.vue
Normal file
@@ -0,0 +1,15 @@
|
||||
<script lang="ts" setup>
|
||||
import { User } from "~/utils/types"
|
||||
|
||||
interface Props {
|
||||
user: User
|
||||
}
|
||||
const props = defineProps<Props>()
|
||||
</script>
|
||||
<template>
|
||||
<n-space align="center">
|
||||
<n-button size="small" secondary type="primary">编辑</n-button>
|
||||
<n-button size="small" secondary type="error">封号</n-button>
|
||||
<n-button size="small" secondary type="default">删除</n-button>
|
||||
</n-space>
|
||||
</template>
|
||||
25
src/admin/user/components/Name.vue
Normal file
25
src/admin/user/components/Name.vue
Normal file
@@ -0,0 +1,25 @@
|
||||
<script setup lang="ts">
|
||||
import { User } from "~/utils/types"
|
||||
import { getUserRole } from "~/utils/functions"
|
||||
|
||||
interface Props {
|
||||
user: User
|
||||
}
|
||||
const props = defineProps<Props>()
|
||||
const isAdmin = computed(() => props.user.admin_type !== "Regular User")
|
||||
</script>
|
||||
<template>
|
||||
<n-space align="center">
|
||||
<n-tag v-if="props.user.is_disabled" type="error" size="small">
|
||||
封号中
|
||||
</n-tag>
|
||||
<n-tag
|
||||
v-if="isAdmin"
|
||||
:type="getUserRole(props.user.admin_type).type"
|
||||
size="small"
|
||||
>
|
||||
{{ getUserRole(props.user.admin_type).tagString }}
|
||||
</n-tag>
|
||||
{{ props.user.username }}
|
||||
</n-space>
|
||||
</template>
|
||||
@@ -1,7 +1,75 @@
|
||||
<script setup lang="ts"></script>
|
||||
<script setup lang="ts">
|
||||
import { DataTableColumn } from "naive-ui"
|
||||
import Pagination from "~/shared/Pagination.vue"
|
||||
import { parseTime } from "~/utils/functions"
|
||||
import { User } from "~/utils/types"
|
||||
import { getUserList } from "../api"
|
||||
import Actions from "./components/Actions.vue"
|
||||
import Name from "./components/Name.vue"
|
||||
|
||||
const total = ref(0)
|
||||
const users = ref<User[]>([])
|
||||
const query = reactive({
|
||||
limit: 10,
|
||||
page: 1,
|
||||
keyword: "",
|
||||
})
|
||||
|
||||
const columns: DataTableColumn<User>[] = [
|
||||
{ title: "ID", key: "id", width: 60 },
|
||||
{
|
||||
title: "用户名",
|
||||
key: "username",
|
||||
width: 150,
|
||||
render: (row) => h(Name, { user: row }),
|
||||
},
|
||||
{
|
||||
title: "创建时间",
|
||||
key: "create_time",
|
||||
width: 200,
|
||||
render: (row) => parseTime(row.create_time, "YYYY-MM-DD hh:mm:ss"),
|
||||
},
|
||||
{
|
||||
title: "上次登录",
|
||||
key: "last_login",
|
||||
width: 200,
|
||||
render: (row) =>
|
||||
row.last_login
|
||||
? parseTime(row.last_login, "YYYY-MM-DD hh:mm:ss")
|
||||
: "从未登录",
|
||||
},
|
||||
{ title: "真名", key: "real_name", width: 100 },
|
||||
{ title: "邮箱", key: "email", width: 200 },
|
||||
{
|
||||
key: "edit",
|
||||
width: 200,
|
||||
render: (row) => h(Actions, { user: row }),
|
||||
},
|
||||
]
|
||||
|
||||
async function listUsers() {
|
||||
const offset = (query.page - 1) * query.limit
|
||||
const res = await getUserList(offset, query.limit, query.keyword)
|
||||
total.value = res.data.total
|
||||
users.value = res.data.results
|
||||
}
|
||||
|
||||
onMounted(listUsers)
|
||||
watch(query, listUsers, { deep: true })
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>user list</div>
|
||||
<n-form inline label-placement="left">
|
||||
<n-form-item>
|
||||
<n-input placeholder="请输入关键字搜索" v-model:value="query.keyword" />
|
||||
</n-form-item>
|
||||
</n-form>
|
||||
<n-data-table :data="users" :columns="columns" size="small" striped />
|
||||
<Pagination
|
||||
:total="total"
|
||||
v-model:limit="query.limit"
|
||||
v-model:page="query.page"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
|
||||
34
src/components.d.ts
vendored
34
src/components.d.ts
vendored
@@ -9,31 +9,31 @@ export {}
|
||||
|
||||
declare module '@vue/runtime-core' {
|
||||
export interface GlobalComponents {
|
||||
IEpBell: typeof import("~icons/ep/bell")["default"]
|
||||
IEpCaretRight: typeof import("~icons/ep/caret-right")["default"]
|
||||
IEpLoading: typeof import("~icons/ep/loading")["default"]
|
||||
IEpLock: typeof import("~icons/ep/lock")["default"]
|
||||
IEpBell: typeof import('~icons/ep/bell')['default']
|
||||
IEpCaretRight: typeof import('~icons/ep/caret-right')['default']
|
||||
IEpLoading: typeof import('~icons/ep/loading')['default']
|
||||
IEpLock: typeof import('~icons/ep/lock')['default']
|
||||
IEpMenu: typeof import('~icons/ep/menu')['default']
|
||||
IEpMoon: typeof import('~icons/ep/moon')['default']
|
||||
IEpMoreFilled: typeof import("~icons/ep/more-filled")["default"]
|
||||
IEpMoreFilled: typeof import('~icons/ep/more-filled')['default']
|
||||
IEpSunny: typeof import('~icons/ep/sunny')['default']
|
||||
NAlert: typeof import('naive-ui')['NAlert']
|
||||
NAvatar: typeof import("naive-ui")["NAvatar"]
|
||||
NBreadcrumb: typeof import('naive-ui')['NBreadcrumb']
|
||||
NBreadcrumbItem: typeof import('naive-ui')['NBreadcrumbItem']
|
||||
NButton: typeof import('naive-ui')['NButton']
|
||||
NCard: typeof import("naive-ui")["NCard"]
|
||||
NCard: typeof import('naive-ui')['NCard']
|
||||
NCode: typeof import("naive-ui")["NCode"]
|
||||
NConfigProvider: typeof import('naive-ui')['NConfigProvider']
|
||||
NDataTable: typeof import('naive-ui')['NDataTable']
|
||||
NDescriptions: typeof import("naive-ui")["NDescriptions"]
|
||||
NDescriptionsItem: typeof import("naive-ui")["NDescriptionsItem"]
|
||||
NDescriptions: typeof import('naive-ui')['NDescriptions']
|
||||
NDescriptionsItem: typeof import('naive-ui')['NDescriptionsItem']
|
||||
NDropdown: typeof import('naive-ui')['NDropdown']
|
||||
NEmpty: typeof import("naive-ui")["NEmpty"]
|
||||
NEmpty: typeof import('naive-ui')['NEmpty']
|
||||
NForm: typeof import('naive-ui')['NForm']
|
||||
NFormItem: typeof import('naive-ui')['NFormItem']
|
||||
NGi: typeof import("naive-ui")["NGi"]
|
||||
NGrid: typeof import("naive-ui")["NGrid"]
|
||||
NGi: typeof import('naive-ui')['NGi']
|
||||
NGrid: typeof import('naive-ui')['NGrid']
|
||||
NIcon: typeof import('naive-ui')['NIcon']
|
||||
NInput: typeof import('naive-ui')['NInput']
|
||||
NLayout: typeof import('naive-ui')['NLayout']
|
||||
@@ -45,14 +45,14 @@ declare module '@vue/runtime-core' {
|
||||
NModal: typeof import('naive-ui')['NModal']
|
||||
NPagination: typeof import('naive-ui')['NPagination']
|
||||
NPopconfirm: typeof import('naive-ui')['NPopconfirm']
|
||||
NPopover: typeof import("naive-ui")["NPopover"]
|
||||
NScrollbar: typeof import("naive-ui")["NScrollbar"]
|
||||
NPopover: typeof import('naive-ui')['NPopover']
|
||||
NScrollbar: typeof import('naive-ui')['NScrollbar']
|
||||
NSelect: typeof import('naive-ui')['NSelect']
|
||||
NSpace: typeof import('naive-ui')['NSpace']
|
||||
NSwitch: typeof import("naive-ui")["NSwitch"]
|
||||
NTabPane: typeof import("naive-ui")["NTabPane"]
|
||||
NTabs: typeof import("naive-ui")["NTabs"]
|
||||
NTag: typeof import("naive-ui")["NTag"]
|
||||
NSwitch: typeof import('naive-ui')['NSwitch']
|
||||
NTabPane: typeof import('naive-ui')['NTabPane']
|
||||
NTabs: typeof import('naive-ui')['NTabs']
|
||||
NTag: typeof import('naive-ui')['NTag']
|
||||
NTooltip: typeof import('naive-ui')['NTooltip']
|
||||
NUpload: typeof import("naive-ui")["NUpload"]
|
||||
RouterLink: typeof import('vue-router')['RouterLink']
|
||||
|
||||
@@ -94,7 +94,9 @@ function select(key: string) {
|
||||
<n-space>
|
||||
<n-button @click="reset">重置</n-button>
|
||||
<n-button @click="goSubmissions">提交信息</n-button>
|
||||
<n-button v-if="userStore.isSuperAdmin" @click="edit">编辑</n-button>
|
||||
<n-button type="warning" v-if="userStore.isSuperAdmin" @click="edit">
|
||||
编辑
|
||||
</n-button>
|
||||
</n-space>
|
||||
</n-form-item>
|
||||
</n-form>
|
||||
|
||||
@@ -1,14 +1,19 @@
|
||||
<script setup lang="ts">
|
||||
import { useUserStore } from "~/shared/store/user"
|
||||
import { filterEmptyValue, getTagColor, debounce } from "utils/functions"
|
||||
import { filterEmptyValue, getTagColor } from "utils/functions"
|
||||
import { ProblemFiltered } from "utils/types"
|
||||
import { isDesktop } from "~/shared/composables/breakpoints"
|
||||
import { getProblemList, getProblemTagList, getRandomProblemID } from "oj/api"
|
||||
|
||||
import Pagination from "~/shared/Pagination.vue"
|
||||
import { DataTableColumn, NSpace, NTag } from "naive-ui"
|
||||
import ProblemStatus from "./components/ProblemStatus.vue"
|
||||
|
||||
interface Tag {
|
||||
id: number
|
||||
name: string
|
||||
checked: boolean
|
||||
}
|
||||
|
||||
interface Query {
|
||||
keyword: string
|
||||
difficulty: string
|
||||
@@ -30,12 +35,7 @@ const route = useRoute()
|
||||
const userStore = useUserStore()
|
||||
const problems = ref<ProblemFiltered[]>([])
|
||||
const total = ref(0)
|
||||
const tags = ref<{ id: number; name: string }[]>([])
|
||||
|
||||
const tagOptions = computed(() => [
|
||||
{ label: "全部", value: "" },
|
||||
...(tags.value?.map((t) => ({ label: t.name, value: t.name })) || []),
|
||||
])
|
||||
const tags = ref<Tag[]>([])
|
||||
|
||||
const query = reactive<Query>({
|
||||
keyword: <string>route.query.keyword ?? "",
|
||||
@@ -65,7 +65,10 @@ async function listProblems() {
|
||||
|
||||
async function listTags() {
|
||||
const res = await getProblemTagList()
|
||||
tags.value = res.data
|
||||
tags.value = res.data.map((r: Omit<Tag, "checked">) => ({
|
||||
...r,
|
||||
checked: false,
|
||||
}))
|
||||
}
|
||||
|
||||
function routerPush() {
|
||||
@@ -79,6 +82,18 @@ function search(value: string) {
|
||||
query.keyword = value
|
||||
}
|
||||
|
||||
function chooseTag(tag: Tag) {
|
||||
query.tag = tag.checked ? "" : tag.name
|
||||
tags.value = tags.value.map((t) => {
|
||||
if (t.id === tag.id) {
|
||||
t.checked = !t.checked
|
||||
} else {
|
||||
t.checked = false
|
||||
}
|
||||
return t
|
||||
})
|
||||
}
|
||||
|
||||
function clear() {
|
||||
query.keyword = ""
|
||||
query.tag = ""
|
||||
@@ -161,14 +176,7 @@ function rowProps(row: ProblemFiltered) {
|
||||
:options="difficultyOptions"
|
||||
/>
|
||||
</n-form-item>
|
||||
<n-form-item label="标签">
|
||||
<n-select
|
||||
class="select"
|
||||
v-model:value="query.tag"
|
||||
:options="tagOptions"
|
||||
/>
|
||||
</n-form-item>
|
||||
<n-form-item label="搜索">
|
||||
<n-form-item>
|
||||
<n-input placeholder="输入编号或标题后回车" clearable @change="search" />
|
||||
</n-form-item>
|
||||
<n-form-item>
|
||||
@@ -179,7 +187,21 @@ function rowProps(row: ProblemFiltered) {
|
||||
</n-space>
|
||||
</n-form-item>
|
||||
</n-form>
|
||||
<n-space>
|
||||
<div class="tagTitle">标签</div>
|
||||
<n-button
|
||||
@click="chooseTag(tag)"
|
||||
v-for="tag in tags"
|
||||
:key="tag.id"
|
||||
size="small"
|
||||
secondary
|
||||
:type="tag.checked ? 'success' : 'default'"
|
||||
>
|
||||
{{ tag.name }}
|
||||
</n-button>
|
||||
</n-space>
|
||||
<n-data-table
|
||||
class="table"
|
||||
striped
|
||||
size="small"
|
||||
:data="problems"
|
||||
@@ -194,7 +216,15 @@ function rowProps(row: ProblemFiltered) {
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.tagTitle {
|
||||
line-height: 28px;
|
||||
}
|
||||
|
||||
.select {
|
||||
width: 120px;
|
||||
}
|
||||
|
||||
.table {
|
||||
margin-top: 24px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -106,76 +106,76 @@ export const routes: RouteRecordRaw[] = [
|
||||
children: [
|
||||
{
|
||||
path: "",
|
||||
name: "home",
|
||||
name: "admin home",
|
||||
component: () => import("~/admin/setting/home.vue"),
|
||||
},
|
||||
{
|
||||
path: "config",
|
||||
name: "config",
|
||||
name: "admin config",
|
||||
component: () => import("admin/setting/config.vue"),
|
||||
},
|
||||
{
|
||||
path: "announcement",
|
||||
name: "announcement",
|
||||
name: "admin announcement",
|
||||
component: () => import("admin/setting/announcement.vue"),
|
||||
},
|
||||
{
|
||||
path: "user/list",
|
||||
name: "user list",
|
||||
name: "admin user list",
|
||||
component: () => import("admin/user/list.vue"),
|
||||
},
|
||||
{
|
||||
path: "user/importing",
|
||||
name: "user importing",
|
||||
name: "admin user importing",
|
||||
component: () => import("admin/user/import.vue"),
|
||||
},
|
||||
{
|
||||
path: "problem/list",
|
||||
name: "problem list",
|
||||
name: "admin problem list",
|
||||
component: () => import("admin/problem/list.vue"),
|
||||
},
|
||||
{
|
||||
path: "problem/create",
|
||||
name: "problem create",
|
||||
name: "admin problem create",
|
||||
component: () => import("admin/problem/detail.vue"),
|
||||
},
|
||||
{
|
||||
path: "problem/edit/:problemID",
|
||||
name: "problem edit",
|
||||
name: "admin problem edit",
|
||||
component: () => import("admin/problem/detail.vue"),
|
||||
props: true,
|
||||
},
|
||||
{
|
||||
path: "contest/list",
|
||||
name: "contest list",
|
||||
name: "admin contest list",
|
||||
component: () => import("admin/contest/list.vue"),
|
||||
},
|
||||
{
|
||||
path: "contest/create",
|
||||
name: "contest create",
|
||||
name: "admin contest create",
|
||||
component: () => import("admin/contest/detail.vue"),
|
||||
},
|
||||
{
|
||||
path: "contest/edit/:contestID",
|
||||
name: "contest edit",
|
||||
name: "admin contest edit",
|
||||
component: () => import("admin/contest/detail.vue"),
|
||||
props: true,
|
||||
},
|
||||
{
|
||||
path: "contest/:contestID/problems",
|
||||
name: "contest problems",
|
||||
name: "admin contest problems",
|
||||
component: () => import("admin/contest/detail.vue"),
|
||||
props: true,
|
||||
},
|
||||
{
|
||||
path: "contest/:contestID/problem/create",
|
||||
name: "contest problem create",
|
||||
name: "admin contest problem create",
|
||||
component: () => import("admin/problem/detail.vue"),
|
||||
props: true,
|
||||
},
|
||||
{
|
||||
path: "contest/:contestID/problem/edit/:problemID",
|
||||
name: "contest problem edit",
|
||||
name: "admin contest problem edit",
|
||||
component: () => import("admin/problem/detail.vue"),
|
||||
props: true,
|
||||
},
|
||||
|
||||
@@ -10,7 +10,7 @@ const options: MenuOption[] = [
|
||||
},
|
||||
{
|
||||
label: () => h(RouterLink, { to: "/admin" }, { default: () => "首页" }),
|
||||
key: "home",
|
||||
key: "admin home",
|
||||
},
|
||||
{
|
||||
label: "题目",
|
||||
@@ -24,7 +24,7 @@ const options: MenuOption[] = [
|
||||
{ to: "/admin/problem/list" },
|
||||
{ default: () => "题目列表" }
|
||||
),
|
||||
key: "problem list",
|
||||
key: "admin problem list",
|
||||
},
|
||||
{
|
||||
label: () =>
|
||||
@@ -33,13 +33,13 @@ const options: MenuOption[] = [
|
||||
{ to: "/admin/problem/create" },
|
||||
{ default: () => "创建题目" }
|
||||
),
|
||||
key: "problem create",
|
||||
key: "admin problem create",
|
||||
},
|
||||
{ label: "用户", key: "user", disabled: true },
|
||||
{
|
||||
label: () =>
|
||||
h(RouterLink, { to: "/admin/user/list" }, { default: () => "用户列表" }),
|
||||
key: "user list",
|
||||
key: "admin user list",
|
||||
},
|
||||
{
|
||||
label: () =>
|
||||
@@ -48,7 +48,7 @@ const options: MenuOption[] = [
|
||||
{ to: "/admin/user/importing" },
|
||||
{ default: () => "导入用户" }
|
||||
),
|
||||
key: "user importing",
|
||||
key: "admin user importing",
|
||||
},
|
||||
{ label: "比赛", key: "contest", disabled: true },
|
||||
{
|
||||
@@ -58,7 +58,7 @@ const options: MenuOption[] = [
|
||||
{ to: "/admin/contest/list" },
|
||||
{ default: () => "比赛列表" }
|
||||
),
|
||||
key: "contest list",
|
||||
key: "admin contest list",
|
||||
},
|
||||
{
|
||||
label: () =>
|
||||
@@ -67,13 +67,13 @@ const options: MenuOption[] = [
|
||||
{ to: "/admin/contest/create" },
|
||||
{ default: () => "创建比赛" }
|
||||
),
|
||||
key: "contest create",
|
||||
key: "admin contest create",
|
||||
},
|
||||
{ label: "其他", key: "other", disabled: true },
|
||||
{
|
||||
label: () =>
|
||||
h(RouterLink, { to: "/admin/config" }, { default: () => "系统配置" }),
|
||||
key: "config",
|
||||
key: "admin config",
|
||||
},
|
||||
{
|
||||
label: () =>
|
||||
@@ -82,7 +82,7 @@ const options: MenuOption[] = [
|
||||
{ to: "/admin/announcement" },
|
||||
{ default: () => "公告配置" }
|
||||
),
|
||||
key: "announcement",
|
||||
key: "admin announcement",
|
||||
},
|
||||
]
|
||||
|
||||
@@ -91,7 +91,7 @@ const active = computed(() => (route.name as string) || "home")
|
||||
|
||||
<template>
|
||||
<n-layout has-sider position="absolute">
|
||||
<n-layout-sider bordered :native-scrollbar="false">
|
||||
<n-layout-sider width="160" bordered :native-scrollbar="false">
|
||||
<n-menu :options="options" :value="active" />
|
||||
</n-layout-sider>
|
||||
<n-layout-content content-style="padding: 16px">
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { getTime, intervalToDuration, parseISO } from "date-fns"
|
||||
import { STORAGE_KEY } from "./constants"
|
||||
import { User } from "./types"
|
||||
|
||||
export function getACRate(acCount: number, totalCount: number) {
|
||||
let rate = totalCount === 0 ? 0.0 : ((acCount / totalCount) * 100).toFixed(2)
|
||||
@@ -103,3 +104,28 @@ export function debounce(fn: Function, n = 100) {
|
||||
}, n)
|
||||
}
|
||||
}
|
||||
|
||||
export function getUserRole(role: User["admin_type"]): {
|
||||
type: "default" | "info" | "error"
|
||||
tagString: "普通" | "管理员" | "超管"
|
||||
} {
|
||||
const obj: {
|
||||
type: "default" | "info" | "error"
|
||||
tagString: "普通" | "管理员" | "超管"
|
||||
} = { type: "default", tagString: "普通" }
|
||||
switch (role) {
|
||||
case "Regular User":
|
||||
obj.type = "default"
|
||||
obj.tagString = "普通"
|
||||
break
|
||||
case "Admin":
|
||||
obj.type = "info"
|
||||
obj.tagString = "管理员"
|
||||
break
|
||||
case "Super Admin":
|
||||
obj.type = "error"
|
||||
obj.tagString = "超管"
|
||||
break
|
||||
}
|
||||
return obj
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@ export interface User {
|
||||
id: number
|
||||
username: string
|
||||
email: string
|
||||
admin_type: string
|
||||
admin_type: "Regular User" | "Super Admin" | "Admin"
|
||||
problem_permission: string
|
||||
create_time: Date
|
||||
last_login: Date
|
||||
|
||||
Reference in New Issue
Block a user