chore(前端): 删掉三个从 ojnext 搬来就没人用的文件
阶段 1 是「原样搬入、不改业务代码」,所以 ojnext 里已经死掉的东西也一起 搬了过来。这三个文件全仓零引用: - utils/permissions.ts(usePermissions / checkRoutePermission) - oj/ai/components/StreakStats.vue - shared/components/IconButton.vue 确认方式:在 apps/web 全部 .ts / .vue / .json / .html / .js 里搜这五个名字 (排除文件自身)零命中;两个组件也不在自动生成的 components.d.ts 里, permissions.ts 的两个导出同样不在 auto-imports.d.ts 里 —— 也就是说 它们连自动导入的注册都没拿到,不存在「模板里隐式用到」的可能。 permissions.ts 在 ojnext 里也是死的,不是搬运过程产生的。 vue-tsc 0 error,vite build 通过。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,166 +0,0 @@
|
||||
<template>
|
||||
<n-card title="连续做题统计" size="small">
|
||||
<template #header-extra>
|
||||
<n-text depth="3" style="font-size: 12px">激励持续学习</n-text>
|
||||
</template>
|
||||
<n-spin :show="aiStore.loading.heatmap" :delay="50">
|
||||
<n-grid :cols="2" :x-gap="12" :y-gap="12">
|
||||
<n-gi>
|
||||
<n-statistic label="当前连续" :value="currentStreak">
|
||||
<template #suffix>
|
||||
<span style="font-size: 14px">天</span>
|
||||
<span
|
||||
v-if="currentStreak > 0"
|
||||
style="font-size: 20px; margin-left: 4px"
|
||||
>
|
||||
🔥
|
||||
</span>
|
||||
</template>
|
||||
</n-statistic>
|
||||
</n-gi>
|
||||
<n-gi>
|
||||
<n-statistic label="最长连续" :value="maxStreak">
|
||||
<template #suffix>
|
||||
<span style="font-size: 14px">天</span>
|
||||
<span
|
||||
v-if="maxStreak >= 7"
|
||||
style="font-size: 20px; margin-left: 4px"
|
||||
>
|
||||
⭐
|
||||
</span>
|
||||
</template>
|
||||
</n-statistic>
|
||||
</n-gi>
|
||||
<n-gi>
|
||||
<n-statistic label="本周做题" :value="weekCount">
|
||||
<template #suffix>
|
||||
<span style="font-size: 14px">天</span>
|
||||
</template>
|
||||
</n-statistic>
|
||||
</n-gi>
|
||||
<n-gi>
|
||||
<n-statistic label="本月做题" :value="monthCount">
|
||||
<template #suffix>
|
||||
<span style="font-size: 14px">天</span>
|
||||
</template>
|
||||
</n-statistic>
|
||||
</n-gi>
|
||||
</n-grid>
|
||||
<n-divider style="margin: 12px 0" />
|
||||
<n-flex vertical size="small">
|
||||
<n-text depth="2" style="font-size: 12px">
|
||||
<span v-if="currentStreak === 0"> 开始做题,建立学习连续记录! </span>
|
||||
<span v-else-if="currentStreak < 3"> 继续保持,争取连续3天! </span>
|
||||
<span v-else-if="currentStreak < 7">
|
||||
很棒!继续保持一周连续记录!
|
||||
</span>
|
||||
<span v-else-if="currentStreak < 30">
|
||||
太棒了!坚持满30天将获得「持之以恒」成就!
|
||||
</span>
|
||||
<span v-else>
|
||||
🎉 恭喜你!你已经连续学习 {{ currentStreak }} 天,真的非常厉害!
|
||||
</span>
|
||||
</n-text>
|
||||
</n-flex>
|
||||
</n-spin>
|
||||
</n-card>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useAIStore } from "oj/store/ai"
|
||||
|
||||
const aiStore = useAIStore()
|
||||
|
||||
// 计算连续天数
|
||||
const streakData = computed(() => {
|
||||
const heatmap = aiStore.heatmapData
|
||||
if (!heatmap || heatmap.length === 0) {
|
||||
return {
|
||||
currentStreak: 0,
|
||||
maxStreak: 0,
|
||||
weekCount: 0,
|
||||
monthCount: 0,
|
||||
}
|
||||
}
|
||||
|
||||
// 按时间戳排序
|
||||
const sortedData = [...heatmap].sort((a, b) => a.timestamp - b.timestamp)
|
||||
|
||||
let currentStreak = 0
|
||||
let maxStreak = 0
|
||||
let tempStreak = 0
|
||||
let lastDate: Date | null = null
|
||||
|
||||
const now = new Date()
|
||||
const today = new Date(now.getFullYear(), now.getMonth(), now.getDate())
|
||||
const weekAgo = new Date(today.getTime() - 7 * 24 * 60 * 60 * 1000)
|
||||
const monthAgo = new Date(today.getTime() - 30 * 24 * 60 * 60 * 1000)
|
||||
|
||||
let weekCount = 0
|
||||
let monthCount = 0
|
||||
|
||||
// 遍历数据计算连续天数
|
||||
for (const item of sortedData) {
|
||||
if (item.value > 0) {
|
||||
const currentDate = new Date(item.timestamp)
|
||||
|
||||
// 统计本周和本月
|
||||
if (currentDate >= weekAgo) {
|
||||
weekCount++
|
||||
}
|
||||
if (currentDate >= monthAgo) {
|
||||
monthCount++
|
||||
}
|
||||
|
||||
if (lastDate === null) {
|
||||
tempStreak = 1
|
||||
} else {
|
||||
const dayDiff = Math.floor(
|
||||
(currentDate.getTime() - lastDate.getTime()) / (24 * 60 * 60 * 1000),
|
||||
)
|
||||
if (dayDiff === 1) {
|
||||
tempStreak++
|
||||
} else {
|
||||
maxStreak = Math.max(maxStreak, tempStreak)
|
||||
tempStreak = 1
|
||||
}
|
||||
}
|
||||
|
||||
lastDate = currentDate
|
||||
}
|
||||
}
|
||||
|
||||
maxStreak = Math.max(maxStreak, tempStreak)
|
||||
|
||||
// 计算当前连续天数(必须包含今天或昨天)
|
||||
if (lastDate) {
|
||||
const yesterday = new Date(today.getTime() - 24 * 60 * 60 * 1000)
|
||||
const lastDateOnly = new Date(
|
||||
lastDate.getFullYear(),
|
||||
lastDate.getMonth(),
|
||||
lastDate.getDate(),
|
||||
)
|
||||
|
||||
if (
|
||||
lastDateOnly.getTime() === today.getTime() ||
|
||||
lastDateOnly.getTime() === yesterday.getTime()
|
||||
) {
|
||||
currentStreak = tempStreak
|
||||
} else {
|
||||
currentStreak = 0
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
currentStreak,
|
||||
maxStreak,
|
||||
weekCount,
|
||||
monthCount,
|
||||
}
|
||||
})
|
||||
|
||||
const currentStreak = computed(() => streakData.value.currentStreak)
|
||||
const maxStreak = computed(() => streakData.value.maxStreak)
|
||||
const weekCount = computed(() => streakData.value.weekCount)
|
||||
const monthCount = computed(() => streakData.value.monthCount)
|
||||
</script>
|
||||
@@ -1,29 +0,0 @@
|
||||
<template>
|
||||
<n-tooltip>
|
||||
<template #trigger>
|
||||
<n-button round :type="type ?? 'default'" @click="$emit('click')">
|
||||
<template #icon>
|
||||
<Icon :icon="icon" />
|
||||
</template>
|
||||
</n-button>
|
||||
</template>
|
||||
{{ tip }}
|
||||
</n-tooltip>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { Icon } from "@iconify/vue"
|
||||
|
||||
defineProps<{
|
||||
tip: string
|
||||
icon: string
|
||||
type?:
|
||||
| "default"
|
||||
| "tertiary"
|
||||
| "primary"
|
||||
| "info"
|
||||
| "success"
|
||||
| "warning"
|
||||
| "error"
|
||||
}>()
|
||||
defineEmits(["click"])
|
||||
</script>
|
||||
@@ -1,108 +0,0 @@
|
||||
import { useUserStore } from "shared/store/user"
|
||||
|
||||
export function usePermissions() {
|
||||
const userStore = useUserStore()
|
||||
|
||||
return {
|
||||
isAuthenticated: computed(() => userStore.isAuthed),
|
||||
isAdminRole: computed(() => userStore.isAdminRole),
|
||||
isTeacherOrAbove: computed(() => userStore.isTeacherOrAbove),
|
||||
isSuperAdmin: computed(() => userStore.isSuperAdmin),
|
||||
hasProblemPermission: computed(() => userStore.hasProblemPermission),
|
||||
|
||||
canManageUsers: computed(() => userStore.isSuperAdmin),
|
||||
canManageAnnouncements: computed(() => userStore.isSuperAdmin),
|
||||
canManageTutorials: computed(() => userStore.isSuperAdmin),
|
||||
canManageSystemConfig: computed(() => userStore.isSuperAdmin),
|
||||
canSendMessages: computed(() => userStore.isSuperAdmin),
|
||||
|
||||
canManageProblems: computed(() => userStore.hasProblemPermission),
|
||||
canManageContests: computed(() => userStore.isTeacherOrAbove),
|
||||
canManageProblemsets: computed(() => userStore.isTeacherOrAbove),
|
||||
canViewClassroomData: computed(() => userStore.isTeacherOrAbove),
|
||||
|
||||
canManageAllProblems: computed(
|
||||
() =>
|
||||
userStore.user?.problemPermission === "All" || userStore.isSuperAdmin,
|
||||
),
|
||||
canManageOwnProblems: computed(
|
||||
() =>
|
||||
userStore.user?.problemPermission === "Own" && !userStore.isSuperAdmin,
|
||||
),
|
||||
|
||||
getUserPermissionLevel: computed(() => {
|
||||
if (userStore.isSuperAdmin) return "超级管理员"
|
||||
if (userStore.isTeacherAdmin) return "教师管理员"
|
||||
if (userStore.isStudentAdmin) return "学生管理员"
|
||||
return "普通用户"
|
||||
}),
|
||||
|
||||
getProblemPermissionLevel: computed(() => {
|
||||
if (!userStore.user) return "无权限"
|
||||
switch (userStore.user.problemPermission) {
|
||||
case "All":
|
||||
return "管理所有题目"
|
||||
case "Own":
|
||||
return "管理自己的题目"
|
||||
case "None":
|
||||
return "无题目权限"
|
||||
default:
|
||||
return "无权限"
|
||||
}
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
export function checkRoutePermission(routeName: string): boolean {
|
||||
const userStore = useUserStore()
|
||||
|
||||
const superAdminRoutes = [
|
||||
"admin home",
|
||||
"admin config",
|
||||
"admin user list",
|
||||
"admin user generate",
|
||||
"admin announcement list",
|
||||
"admin announcement create",
|
||||
"admin announcement edit",
|
||||
"admin message list",
|
||||
"admin tutorial list",
|
||||
"admin tutorial create",
|
||||
"admin tutorial edit",
|
||||
]
|
||||
|
||||
const teacherAdminRoutes = [
|
||||
"admin contest list",
|
||||
"admin contest create",
|
||||
"admin contest edit",
|
||||
"admin contest problem list",
|
||||
"admin contest problem create",
|
||||
"admin contest problem edit",
|
||||
"admin contest helper",
|
||||
"admin problemset list",
|
||||
"admin problemset create",
|
||||
"admin problemset edit",
|
||||
"admin problemset detail",
|
||||
"admin stuck problems",
|
||||
"admin top ac trend",
|
||||
]
|
||||
|
||||
const problemPermissionRoutes = [
|
||||
"admin problem list",
|
||||
"admin problem create",
|
||||
"admin problem edit",
|
||||
]
|
||||
|
||||
if (superAdminRoutes.includes(routeName)) {
|
||||
return userStore.isSuperAdmin
|
||||
}
|
||||
|
||||
if (teacherAdminRoutes.includes(routeName)) {
|
||||
return userStore.isTeacherOrAbove
|
||||
}
|
||||
|
||||
if (problemPermissionRoutes.includes(routeName)) {
|
||||
return userStore.hasProblemPermission
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
Reference in New Issue
Block a user