@@ -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
113
src/utils/permissions.ts
Normal 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
|
||||
}
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user