重构用户权限
Some checks failed
Deploy / deploy (push) Has been cancelled

This commit is contained in:
2025-09-25 18:41:32 +08:00
parent 4429e2f018
commit efbca0e802
15 changed files with 619 additions and 187 deletions

View File

@@ -84,11 +84,11 @@ export function getContestProblem(id: number) {
export function getUserList(
offset = 0,
limit = 10,
admin = "0",
type = "",
keyword: string,
) {
return http.get("admin/user", {
params: { paging: true, offset, limit, keyword, admin },
params: { paging: true, offset, limit, keyword, type },
})
}

View File

@@ -2,6 +2,7 @@
import { NButton, NTag } from "naive-ui"
import { parseTime } from "~/utils/functions"
import { Server } from "~/utils/types"
import { usePermissions } from "~/utils/permissions"
import {
deleteJudgeServer,
editWebsite,
@@ -17,6 +18,14 @@ interface Testcase {
}
const message = useMessage()
const router = useRouter()
const { canManageSystemConfig } = usePermissions()
// 权限检查只有super_admin可以管理系统配置
if (!canManageSystemConfig.value) {
message.error("您没有权限访问此页面")
router.push("/admin")
}
const testcaseColumns: DataTableColumn<Testcase>[] = [
{ title: "测试用例 ID", key: "id" },

View File

@@ -3,6 +3,7 @@ import { NButton } from "naive-ui"
import { getRank } from "oj/api"
import Pagination from "~/shared/components/Pagination.vue"
import { useUserStore } from "~/shared/store/user"
import { usePermissions } from "~/utils/permissions"
import { getACRate } from "~/utils/functions"
import { Rank } from "~/utils/types"
import { getBaseInfo, randomUser10 } from "../api"
@@ -13,6 +14,13 @@ const contestCount = ref(0)
const userStore = useUserStore()
const router = useRouter()
const message = useMessage()
const { isSuperAdmin } = usePermissions()
// 权限检查只有super_admin可以访问管理员首页
if (!isSuperAdmin.value) {
message.error("您没有权限访问此页面")
router.push("/admin/problem/list")
}
const showModal = ref(false)
const luckyGuy = ref("")

View File

@@ -1,4 +1,5 @@
<script setup lang="ts">
import { USER_TYPE } from "~/utils/constants"
import { getUserRole } from "~/utils/functions"
import { User } from "~/utils/types"
@@ -6,7 +7,9 @@ interface Props {
user: User
}
const props = defineProps<Props>()
const isAdmin = computed(() => props.user.admin_type !== "Regular User")
const isNotRegularUser = computed(
() => props.user.admin_type !== USER_TYPE.REGULAR_USER,
)
</script>
<template>
<n-flex align="center">
@@ -14,7 +17,7 @@ const isAdmin = computed(() => props.user.admin_type !== "Regular User")
封号中
</n-tag>
<n-tag
v-if="isAdmin"
v-if="isNotRegularUser"
:type="getUserRole(props.user.admin_type).type"
size="small"
>

View File

@@ -1,8 +1,9 @@
<script setup lang="ts">
import { DataTableRowKey, SelectOption } from "naive-ui"
import Pagination from "~/shared/components/Pagination.vue"
import { parseTime } from "~/utils/functions"
import { parseTime, filterEmptyValue } from "~/utils/functions"
import { User } from "~/utils/types"
import { usePermissions } from "~/utils/permissions"
import {
deleteUsers,
editUser,
@@ -15,6 +16,15 @@ import Name from "./components/Name.vue"
import { USER_TYPE } from "~/utils/constants"
const message = useMessage()
const router = useRouter()
const route = useRoute()
const { canManageUsers } = usePermissions()
// 权限检查只有super_admin可以管理用户
if (!canManageUsers.value) {
message.error("您没有权限访问此页面")
router.push("/admin")
}
const total = ref(0)
const users = ref<User[]>([])
@@ -23,8 +33,14 @@ const query = reactive({
limit: 10,
page: 1,
keyword: "",
admin: false,
type: "",
})
const adminOptions = [
{ label: "全部用户", value: "" },
{ label: "管理员", value: USER_TYPE.ADMIN },
{ label: "超级管理员", value: USER_TYPE.SUPER_ADMIN },
]
const [create, toggleCreate] = useToggle(false)
const password = ref("")
const userIDs = ref<DataTableRowKey[]>([])
@@ -78,15 +94,27 @@ const columns: DataTableColumn<User>[] = [
]
const options: SelectOption[] = [
{ label: "普通", value: "Regular User" },
{ label: "管理员", value: "Admin" },
{ label: "超级管理员", value: "Super Admin" },
{ label: "普通", value: USER_TYPE.REGULAR_USER },
{ label: "管理员", value: USER_TYPE.ADMIN },
{ label: "超级管理员", value: USER_TYPE.SUPER_ADMIN },
]
function routerPush() {
router.push({
path: route.path,
query: filterEmptyValue(query),
})
}
async function listUsers() {
query.keyword = <string>route.query.keyword ?? ""
query.page = parseInt(<string>route.query.page) || 1
query.limit = parseInt(<string>route.query.limit) || 10
query.type = <string>route.query.type ?? ""
if (query.page < 1) query.page = 1
const offset = (query.page - 1) * query.limit
const isAdmin = query.admin ? "1" : "0"
const res = await getUserList(offset, query.limit, isAdmin, query.keyword)
const res = await getUserList(offset, query.limit, query.type, query.keyword)
total.value = res.data.total
users.value = res.data.results
}
@@ -127,7 +155,7 @@ function createNewUser() {
username: "",
real_name: "",
email: "",
admin_type: "Regular User",
admin_type: "Super Admin",
problem_permission: "",
create_time: new Date(),
last_login: new Date(),
@@ -177,7 +205,28 @@ async function handleEditUser() {
}
onMounted(listUsers)
watch(query, listUsers, { deep: true })
watch(() => query.page, routerPush)
watch(
() => [query.limit, query.keyword, query.type],
() => {
query.page = 1
routerPush()
},
)
watchDebounced(
() => query.keyword,
() => {
query.page = 1
routerPush()
},
{ debounce: 500, maxWait: 1000 },
)
watch(
() => route.name === "admin user list" && route.query,
(newVal) => {
if (newVal) listUsers()
},
)
</script>
<template>
@@ -200,8 +249,12 @@ watch(query, listUsers, { deep: true })
确定删除选中的用户吗删除后无法恢复
</n-popconfirm>
<n-flex align="center">
<span>超管出列</span>
<n-switch v-model:value="query.admin" />
<n-select
v-model:value="query.type"
:options="adminOptions"
placeholder="选择用户类型"
style="width: 120px"
/>
<div>
<n-input style="width: 200px" v-model:value="query.keyword" />
</div>

View File

@@ -31,17 +31,68 @@ const router = createRouter({
routes: [ojs, admins],
})
router.beforeEach((to, from, next) => {
router.beforeEach(async (to, from, next) => {
// 检查是否需要认证
if (to.matched.some((record) => record.meta.requiresAuth)) {
if (!storage.get(STORAGE_KEY.AUTHED)) {
toggleLogin(true)
next("/")
} else {
next()
return
}
} else {
next()
}
// 检查管理员权限
if (to.matched.some((record) =>
record.meta.requiresAdmin ||
record.meta.requiresSuperAdmin ||
record.meta.requiresProblemPermission
)) {
if (!storage.get(STORAGE_KEY.AUTHED)) {
toggleLogin(true)
next("/")
return
}
// 动态导入用户store来检查权限
const { useUserStore } = await import("./shared/store/user")
const userStore = useUserStore()
// 确保用户信息已加载
if (!userStore.user) {
try {
await userStore.getMyProfile()
} catch (error) {
next("/")
return
}
}
// 检查super admin权限
if (to.matched.some((record) => record.meta.requiresSuperAdmin)) {
if (!userStore.isSuperAdmin) {
next("/admin")
return
}
}
// 检查题目权限
else if (to.matched.some((record) => record.meta.requiresProblemPermission)) {
if (!userStore.hasProblemPermission) {
next("/admin")
return
}
}
// 检查基本admin权限
else if (to.matched.some((record) => record.meta.requiresAdmin)) {
if (!userStore.isAdminRole) {
next("/")
return
}
}
}
next()
})
ChartJS.register(

View File

@@ -70,16 +70,22 @@ async function listSubmissions() {
if (query.page < 1) query.page = 1
const offset = query.limit * (query.page - 1)
const res = await getSubmissions({
...query,
myself: query.myself ? "1" : "0",
offset,
problem_id: <string>route.query.problem ?? "",
contest_id: <string>route.params.contestID ?? "",
language: query.language,
})
submissions.value = res.data.results
total.value = res.data.total
try {
const res = await getSubmissions({
...query,
myself: query.myself ? "1" : "0",
offset,
problem_id: <string>route.query.problem ?? "",
contest_id: <string>route.params.contestID ?? "",
language: query.language,
})
submissions.value = res.data.results
total.value = res.data.total
} catch (error: any) {
if (error.data === "Problem doesn't exist") {
message.error("题目不存在")
}
}
}
async function getTodayCount() {

View File

@@ -107,113 +107,138 @@ export const admins: RouteRecordRaw = {
path: "",
name: "admin home",
component: () => import("~/admin/setting/home.vue"),
meta: { requiresAdmin: true },
},
// 只有super_admin可以访问的路由
{
path: "config",
name: "admin config",
component: () => import("admin/setting/config.vue"),
meta: { requiresSuperAdmin: true },
},
{
path: "user/list",
name: "admin user list",
component: () => import("admin/user/list.vue"),
meta: { requiresSuperAdmin: true },
},
{
path: "user/generate",
name: "admin user generate",
component: () => import("~/admin/user/generate.vue"),
meta: { requiresSuperAdmin: true },
},
// admin和super_admin都可以访问的路由 (需要题目权限)
{
path: "problem/list",
name: "admin problem list",
component: () => import("admin/problem/list.vue"),
meta: { requiresProblemPermission: true },
},
{
path: "problem/create",
name: "admin problem create",
component: () => import("admin/problem/detail.vue"),
meta: { requiresProblemPermission: true },
},
{
path: "problem/edit/:problemID",
name: "admin problem edit",
component: () => import("admin/problem/detail.vue"),
props: true,
meta: { requiresProblemPermission: true },
},
// admin和super_admin都可以访问的路由
{
path: "contest/list",
name: "admin contest list",
component: () => import("admin/contest/list.vue"),
meta: { requiresAdmin: true },
},
{
path: "contest/create",
name: "admin contest create",
component: () => import("admin/contest/detail.vue"),
meta: { requiresAdmin: true },
},
{
path: "contest/edit/:contestID",
name: "admin contest edit",
component: () => import("admin/contest/detail.vue"),
props: true,
meta: { requiresAdmin: true },
},
{
path: "contest/:contestID/problem/list",
name: "admin contest problem list",
component: () => import("admin/problem/list.vue"),
props: true,
meta: { requiresAdmin: true },
},
{
path: "contest/:contestID/problem/create",
name: "admin contest problem create",
component: () => import("admin/problem/detail.vue"),
props: true,
meta: { requiresAdmin: true },
},
{
path: "contest/:contestID/problem/edit/:problemID",
name: "admin contest problem edit",
component: () => import("admin/problem/detail.vue"),
props: true,
meta: { requiresAdmin: true },
},
// 只有super_admin可以访问的路由
{
path: "announcement/list",
name: "admin announcement list",
component: () => import("admin/announcement/list.vue"),
meta: { requiresSuperAdmin: true },
},
{
path: "announcement/create",
name: "admin announcement create",
component: () => import("admin/announcement/detail.vue"),
meta: { requiresSuperAdmin: true },
},
{
path: "announcement/edit/:announcementID",
name: "admin announcement edit",
component: () => import("admin/announcement/detail.vue"),
props: true,
meta: { requiresSuperAdmin: true },
},
{
path: "comment/list",
name: "admin comment list",
component: () => import("admin/communication/comments.vue"),
meta: { requiresSuperAdmin: true },
},
{
path: "message/list",
name: "admin message list",
component: () => import("admin/communication/messages.vue"),
meta: { requiresSuperAdmin: true },
},
{
path: "tutorial/list",
name: "admin tutorial list",
component: () => import("admin/tutorial/list.vue"),
meta: { requiresSuperAdmin: true },
},
{
path: "tutorial/create",
name: "admin tutorial create",
component: () => import("admin/tutorial/detail.vue"),
meta: { requiresSuperAdmin: true },
},
{
path: "tutorial/edit/:tutorialID",
name: "admin tutorial edit",
component: () => import("admin/tutorial/detail.vue"),
props: true,
meta: { requiresSuperAdmin: true },
},
],
}

View File

@@ -87,7 +87,12 @@ const menus = computed<MenuOption[]>(() => [
icon: renderIcon("streamline-emojis:palm-tree"),
},
{
label: () => h(RouterLink, { to: "/admin" }, { default: () => "后台" }),
label: () =>
h(
RouterLink,
{ to: userStore.isTheAdmin ? "/admin/problem/list" : "/admin" },
{ default: () => (userStore.isTheAdmin ? "我出的题" : "后台") },
),
show: userStore.isAdminRole,
key: "admin",
icon: renderIcon("streamline-emojis:ghost"),

View File

@@ -7,55 +7,92 @@ import { useUserStore } from "../store/user"
const route = useRoute()
const router = useRouter()
const userStore = useUserStore()
const options: MenuOption[] = [
{
label: () => h(RouterLink, { to: "/" }, { default: () => "前台" }),
key: "return to OJ",
},
{
label: () => h(RouterLink, { to: "/admin" }, { default: () => "管理" }),
key: "admin home",
},
{
label: () =>
h(RouterLink, { to: "/admin/config" }, { default: () => "设置" }),
key: "admin config",
},
{
label: () =>
h(RouterLink, { to: "/admin/problem/list" }, { default: () => "题目" }),
key: "admin problem list",
},
{
label: () =>
h(RouterLink, { to: "/admin/comment/list" }, { default: () => "评论" }),
key: "admin comment list",
},
{
label: () =>
h(RouterLink, { to: "/admin/user/list" }, { default: () => "用户" }),
key: "admin user list",
},
{
label: () =>
h(RouterLink, { to: "/admin/contest/list" }, { default: () => "比赛" }),
key: "admin contest list",
},
{
label: () =>
h(
RouterLink,
{ to: "/admin/announcement/list" },
{ default: () => "公告" },
),
key: "admin announcement list",
},
{
label: () =>
h(RouterLink, { to: "/admin/tutorial/list" }, { default: () => "教程" }),
key: "admin tutorial list",
},
]
// 根据用户权限动态生成菜单选项
const options = computed<MenuOption[]>(() => {
const baseOptions: MenuOption[] = [
{
label: () => h(RouterLink, { to: "/" }, { default: () => "前台" }),
key: "return to OJ",
},
]
// admin 可以访问的功能
if (userStore.isTheAdmin) {
baseOptions.push({
label: () =>
h(RouterLink, { to: "/admin/problem/list" }, { default: () => "题目" }),
key: "admin problem list",
})
}
// super_admin 可以访问的功能
if (userStore.isSuperAdmin) {
baseOptions.push(
{
label: () => h(RouterLink, { to: "/admin" }, { default: () => "管理" }),
key: "admin home",
},
{
label: () =>
h(
RouterLink,
{ to: "/admin/problem/list" },
{ default: () => "题目" },
),
key: "admin problem list",
},
{
label: () =>
h(
RouterLink,
{ to: "/admin/contest/list" },
{ default: () => "比赛" },
),
key: "admin contest list",
},
{
label: () =>
h(RouterLink, { to: "/admin/config" }, { default: () => "设置" }),
key: "admin config",
},
{
label: () =>
h(RouterLink, { to: "/admin/user/list" }, { default: () => "用户" }),
key: "admin user list",
},
{
label: () =>
h(
RouterLink,
{ to: "/admin/comment/list" },
{ default: () => "评论" },
),
key: "admin comment list",
},
{
label: () =>
h(
RouterLink,
{ to: "/admin/announcement/list" },
{ default: () => "公告" },
),
key: "admin announcement list",
},
{
label: () =>
h(
RouterLink,
{ to: "/admin/tutorial/list" },
{ default: () => "教程" },
),
key: "admin tutorial list",
},
)
}
return baseOptions
})
const active = computed(() => (route.name as string) || "home")

View File

@@ -13,6 +13,9 @@ export const useUserStore = defineStore("user", () => {
user.value?.admin_type === USER_TYPE.ADMIN ||
user.value?.admin_type === USER_TYPE.SUPER_ADMIN,
)
const isTheAdmin = computed(
() => user.value?.admin_type === USER_TYPE.ADMIN,
)
const isSuperAdmin = computed(
() => user.value?.admin_type === USER_TYPE.SUPER_ADMIN,
)
@@ -37,6 +40,7 @@ export const useUserStore = defineStore("user", () => {
isFinished,
user,
isAdminRole,
isTheAdmin,
isSuperAdmin,
hasProblemPermission,
isAuthed,

View File

@@ -1,34 +1,30 @@
import { getTime, intervalToDuration, parseISO } from "date-fns"
import { getTime, intervalToDuration, parseISO, type Duration } from "date-fns"
import { User } from "./types"
import { USER_TYPE } from "./constants"
export function getACRate(acCount: number, totalCount: number) {
let rate = ""
if (totalCount === 0) rate = "0.00"
else {
if (acCount >= totalCount) rate = "100.00"
else rate = ((acCount / totalCount) * 100).toFixed(2)
}
return `${rate}%`
function calculateACRate(acCount: number, totalCount: number): string {
if (totalCount === 0) return "0.00"
if (acCount >= totalCount) return "100.00"
return ((acCount / totalCount) * 100).toFixed(2)
}
export function getACRateNumber(acCount: number, totalCount: number) {
let rate = ""
if (totalCount === 0) rate = "0.00"
else {
if (acCount >= totalCount) rate = "100.00"
else rate = ((acCount / totalCount) * 100).toFixed(2)
}
return parseFloat(rate)
export function getACRate(acCount: number, totalCount: number): string {
return `${calculateACRate(acCount, totalCount)}%`
}
export function filterEmptyValue(object: any) {
let query: any = {}
Object.keys(object).forEach((key) => {
if (object[key] || object[key] === 0 || object[key] === false) {
query[key] = object[key]
export function getACRateNumber(acCount: number, totalCount: number): number {
return parseFloat(calculateACRate(acCount, totalCount))
}
export function filterEmptyValue<T extends Record<string, any>>(
object: T,
): Partial<T> {
return Object.entries(object).reduce((query, [key, value]) => {
if (value != null && value !== "" && value !== undefined) {
query[key as keyof T] = value
}
})
return query
return query
}, {} as Partial<T>)
}
export function getTagColor(
@@ -50,56 +46,52 @@ export function parseTime(utc: Date | string, format = "YYYY年M月D日") {
return time.value
}
function getDurationObject(start: Date | string, end: Date | string) {
return intervalToDuration({
start: getTime(parseISO(start.toString())),
end: getTime(parseISO(end.toString())),
})
}
function formatDurationUnits(
duration: Duration,
units: Array<{ key: keyof Duration; suffix: string }>,
): string {
return units
.filter(({ key }) => duration[key])
.map(({ key, suffix }) => duration[key] + suffix)
.join("")
}
export function duration(
start: Date | string,
end: Date | string,
showSeconds = false,
): string {
const duration = intervalToDuration({
start: getTime(parseISO(start.toString())),
end: getTime(parseISO(end.toString())),
})
let result = ""
if (duration.years) {
result += duration.years + "年"
}
if (duration.months) {
result += duration.months + "月"
}
if (duration.days) {
result += duration.days + "天"
}
if (duration.hours) {
result += duration.hours + "小时"
}
if (duration.minutes) {
result += duration.minutes + "分钟"
}
if (showSeconds && duration.seconds) {
result += duration.seconds + "秒"
}
return result
const durationObj = getDurationObject(start, end)
const units = [
{ key: "years" as const, suffix: "年" },
{ key: "months" as const, suffix: "月" },
{ key: "days" as const, suffix: "" },
{ key: "hours" as const, suffix: "小时" },
{ key: "minutes" as const, suffix: "分钟" },
...(showSeconds ? [{ key: "seconds" as const, suffix: "秒" }] : []),
]
return formatDurationUnits(durationObj, units)
}
export function durationToDays(
start: Date | string,
end: Date | string,
): string {
const duration = intervalToDuration({
start: getTime(parseISO(start.toString())),
end: getTime(parseISO(end.toString())),
})
let result = ""
if (duration.years) {
result += duration.years + "年"
}
if (duration.months) {
result += duration.months + "月"
}
if (duration.days) {
result += duration.days + "天"
}
return !!result ? result : "一天以内"
const durationObj = getDurationObject(start, end)
const units = [
{ key: "years" as const, suffix: "年" },
{ key: "months" as const, suffix: "月" },
{ key: "days" as const, suffix: "" },
]
const result = formatDurationUnits(durationObj, units)
return result || "一天以内"
}
export function secondsToDuration(seconds: number): string {
@@ -126,13 +118,14 @@ export function submissionTimeFormat(time: number | string | undefined) {
return time + "ms"
}
export function debounce(fn: Function, n = 100) {
let handle: any
return (...args: any[]) => {
if (handle) clearTimeout(handle)
handle = setTimeout(() => {
fn(...args)
}, n)
export function debounce<T extends (...args: any[]) => any>(
fn: T,
delay = 100,
): (...args: Parameters<T>) => void {
let timeoutId: ReturnType<typeof setTimeout>
return (...args: Parameters<T>) => {
clearTimeout(timeoutId)
timeoutId = setTimeout(() => fn(...args), delay)
}
}
@@ -140,50 +133,50 @@ 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
const roleMap = {
[USER_TYPE.REGULAR_USER]: {
type: "default" as const,
tagString: "普通" as const,
},
[USER_TYPE.ADMIN]: { type: "info" as const, tagString: "管理员" as const },
[USER_TYPE.SUPER_ADMIN]: {
type: "error" as const,
tagString: "超管" as const,
},
}
return obj
return roleMap[role] || roleMap[USER_TYPE.REGULAR_USER]
}
export function unique<T>(arr: T[]) {
return arr.reduce((prev: T[], curr: T) => {
if (!prev.includes(curr)) {
prev.push(curr)
}
return prev
}, [])
export function unique<T>(arr: T[]): T[] {
return [...new Set(arr)]
}
export function encode(string?: string) {
return btoa(String.fromCharCode(...new TextEncoder().encode(string ?? "")))
export function encode(string?: string): string {
try {
return btoa(String.fromCharCode(...new TextEncoder().encode(string ?? "")))
} catch (error) {
console.error("编码失败:", error)
return ""
}
}
export function decode(bytes?: string) {
const latin = atob(bytes ?? "")
return new TextDecoder("utf-8").decode(
Uint8Array.from({ length: latin.length }, (_, index) =>
latin.charCodeAt(index),
),
)
export function decode(bytes?: string): string {
try {
if (!bytes) return ""
const latin = atob(bytes)
return new TextDecoder("utf-8").decode(
Uint8Array.from({ length: latin.length }, (_, index) =>
latin.charCodeAt(index),
),
)
} catch (error) {
console.error("解码失败:", error)
return ""
}
}
export function getCSRFToken() {
export function getCSRFToken(): string {
if (typeof document === "undefined") {
return ""
}

113
src/utils/permissions.ts Normal file
View File

@@ -0,0 +1,113 @@
import { useUserStore } from "~/shared/store/user"
/**
* 权限检查工具函数
*/
export function usePermissions() {
const userStore = useUserStore()
return {
// 基本权限检查
isAuthenticated: computed(() => userStore.isAuthed),
isAdminRole: computed(() => userStore.isAdminRole),
isSuperAdmin: computed(() => userStore.isSuperAdmin),
hasProblemPermission: computed(() => userStore.hasProblemPermission),
// 功能权限检查
canManageUsers: computed(() => userStore.isSuperAdmin),
canManageAnnouncements: computed(() => userStore.isSuperAdmin),
canManageComments: computed(() => userStore.isSuperAdmin),
canManageTutorials: computed(() => userStore.isSuperAdmin),
canManageSystemConfig: computed(() => userStore.isSuperAdmin),
canSendMessages: computed(() => userStore.isSuperAdmin),
canManageProblems: computed(() => userStore.hasProblemPermission),
canManageContests: computed(() => userStore.isSuperAdmin),
// 题目权限细分检查
canManageAllProblems: computed(
() =>
userStore.user?.problem_permission === "All" || userStore.isSuperAdmin,
),
canManageOwnProblems: computed(
() =>
userStore.user?.problem_permission === "Own" && !userStore.isSuperAdmin,
),
// 获取用户权限级别描述
getUserPermissionLevel: computed(() => {
if (userStore.isSuperAdmin) return "超级管理员"
if (userStore.isAdminRole) return "管理员"
return "普通用户"
}),
// 获取题目权限描述
getProblemPermissionLevel: computed(() => {
if (!userStore.user) return "无权限"
switch (userStore.user.problem_permission) {
case "All":
return "管理所有题目"
case "Own":
return "管理自己的题目"
case "None":
return "无题目权限"
default:
return "无权限"
}
}),
}
}
/**
* 路由权限检查
*/
export function checkRoutePermission(routeName: string): boolean {
const userStore = useUserStore()
// 需要super admin权限的路由
const superAdminRoutes = [
"admin home",
"admin config",
"admin user list",
"admin user generate",
"admin announcement list",
"admin announcement create",
"admin announcement edit",
"admin comment list",
"admin message list",
"admin tutorial list",
"admin tutorial create",
"admin tutorial edit",
"admin contest list",
"admin contest create",
"admin contest edit",
"admin contest problem list",
"admin contest problem create",
"admin contest problem edit",
]
// 需要题目权限的路由
const problemPermissionRoutes = [
"admin problem list",
"admin problem create",
"admin problem edit",
]
// 需要基本admin权限的路由
const adminRoutes: string[] = ["admin problem list"]
if (superAdminRoutes.includes(routeName)) {
return userStore.isSuperAdmin
}
if (problemPermissionRoutes.includes(routeName)) {
return userStore.hasProblemPermission
}
if (adminRoutes.includes(routeName)) {
return userStore.isAdminRole
}
return true
}

View File

@@ -1,4 +1,4 @@
import { ContestStatus, ContestType } from "./constants"
import { ContestStatus, ContestType, USER_TYPE } from "./constants"
export interface Profile {
id: number
@@ -33,12 +33,14 @@ export interface Profile {
submission_number: number
}
export type UserAdminType = "Regular User" | "Admin" | "Super Admin"
export interface User {
id: number
username: string
real_name: string
email: string
admin_type: "Regular User" | "Super Admin" | "Admin"
admin_type: UserAdminType
problem_permission: string
create_time: Date
last_login: Date