优化首屏

This commit is contained in:
2025-10-05 21:07:29 +08:00
parent 7e6d03ca1a
commit 1ba042fae9
14 changed files with 233 additions and 114 deletions

View File

@@ -9,11 +9,14 @@
<script> <script>
window.localStorage.setItem("maxkbMaskTip", true) window.localStorage.setItem("maxkbMaskTip", true)
</script> </script>
<% if (process.env.PUBLIC_MAXKB_URL) { %>
<script <script
async async
defer defer
src="<%= process.env.PUBLIC_MAXKB_URL %>" src="<%= process.env.PUBLIC_MAXKB_URL %>"
></script> ></script>
<% } %>
</head> </head>
<body> <body>
<div id="app"></div> <div id="app"></div>

View File

@@ -72,7 +72,44 @@ export default defineConfig(({ envMode }) => {
performance: { performance: {
chunkSplit: { chunkSplit: {
strategy: "split-by-module", strategy: "split-by-module",
override: {
cacheGroups: {
// 将大型编辑器库单独分包
editor: {
test: /[\\/]node_modules[\\/](codemirror|@codemirror|vue-codemirror|@wangeditor-next|md-editor-v3|y-codemirror\.next)[\\/]/,
name: "vendor-editor",
priority: 30,
}, },
// Chart.js 独立分包(包含 canvas-confetti
charts: {
test: /[\\/]node_modules[\\/](chart\.js|vue-chartjs|@kurkle|canvas-confetti)[\\/]/,
name: "vendor-charts",
priority: 25,
},
// UI 框架Naive UI 及其依赖)
ui: {
test: /[\\/]node_modules[\\/](naive-ui|@css-render|css-render|seemly|vooks|vueuc|treemate|vdirs|evtd)[\\/]/,
name: "vendor-ui",
priority: 20,
},
// Vue 生态
vue: {
test: /[\\/]node_modules[\\/](vue|vue-router|pinia|@vue|@vueuse)[\\/]/,
name: "vendor-vue",
priority: 15,
},
// 其他常用库
common: {
test: /[\\/]node_modules[\\/]/,
name: "vendor-common",
priority: 10,
minChunks: 2,
},
},
},
},
// 移除 console.log生产环境
removeConsole: ["log"],
}, },
resolve: { resolve: {
alias: { alias: {

View File

@@ -1,17 +1,41 @@
<script setup lang="ts"> <script setup lang="ts">
import hljs from "highlight.js/lib/core"
import c from "highlight.js/lib/languages/c"
import cpp from "highlight.js/lib/languages/cpp"
import python from "highlight.js/lib/languages/python"
import { darkTheme, dateZhCN, zhCN } from "naive-ui" import { darkTheme, dateZhCN, zhCN } from "naive-ui"
import "normalize.css" import "normalize.css"
import "./index.css" import "./index.css"
const isDark = useDark()
// 延迟加载 highlight.js避免阻塞首屏
let hljsInstance: any = null
const loadHighlightJS = async () => {
if (hljsInstance) return hljsInstance
const hljs = (await import("highlight.js/lib/core")).default
const c = (await import("highlight.js/lib/languages/c")).default
const cpp = (await import("highlight.js/lib/languages/cpp")).default
const python = (await import("highlight.js/lib/languages/python")).default
hljs.registerLanguage("c", c) hljs.registerLanguage("c", c)
hljs.registerLanguage("python", python) hljs.registerLanguage("python", python)
hljs.registerLanguage("cpp", cpp) hljs.registerLanguage("cpp", cpp)
const isDark = useDark() hljsInstance = hljs
return hljs
}
// 在空闲时预加载
onMounted(() => {
if ("requestIdleCallback" in window) {
requestIdleCallback(() => loadHighlightJS())
} else {
setTimeout(() => loadHighlightJS(), 1000)
}
})
provide(
"hljs",
computed(() => hljsInstance),
)
</script> </script>
<template> <template>
@@ -19,7 +43,7 @@ const isDark = useDark()
:theme="isDark ? darkTheme : null" :theme="isDark ? darkTheme : null"
:locale="zhCN" :locale="zhCN"
:date-locale="dateZhCN" :date-locale="dateZhCN"
:hljs="hljs" :hljs="hljsInstance"
> >
<n-message-provider> <n-message-provider>
<router-view></router-view> <router-view></router-view>

View File

@@ -5,6 +5,27 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { Bar } from "vue-chartjs" import { Bar } from "vue-chartjs"
import {
Chart as ChartJS,
CategoryScale,
LinearScale,
BarElement,
Title,
Tooltip,
Legend,
Colors,
} from "chart.js"
// 仅注册柱状图所需的 Chart.js 组件
ChartJS.register(
CategoryScale,
LinearScale,
BarElement,
Title,
Tooltip,
Legend,
Colors,
)
const props = defineProps<{ const props = defineProps<{
difficulty: { [key: string]: number } difficulty: { [key: string]: number }

View File

@@ -5,6 +5,18 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { Pie } from "vue-chartjs" import { Pie } from "vue-chartjs"
import {
Chart as ChartJS,
ArcElement,
Title,
Tooltip,
Legend,
Colors,
} from "chart.js"
// 仅注册饼图所需的 Chart.js 组件
ChartJS.register(ArcElement, Title, Tooltip, Legend, Colors)
const props = defineProps<{ const props = defineProps<{
tags: { [key: string]: number } tags: { [key: string]: number }
}>() }>()

View File

@@ -8,9 +8,34 @@
<script setup lang="ts"> <script setup lang="ts">
import type { ChartData, ChartOptions, TooltipItem } from "chart.js" import type { ChartData, ChartOptions, TooltipItem } from "chart.js"
import { Chart } from "vue-chartjs" import { Chart } from "vue-chartjs"
import {
Chart as ChartJS,
CategoryScale,
LinearScale,
BarElement,
LineElement,
PointElement,
Title,
Tooltip,
Legend,
Colors,
} from "chart.js"
import { useAIStore } from "oj/store/ai" import { useAIStore } from "oj/store/ai"
import { parseTime } from "utils/functions" import { parseTime } from "utils/functions"
// 注册混合图表Bar + Line所需的 Chart.js 组件
ChartJS.register(
CategoryScale,
LinearScale,
BarElement,
LineElement,
PointElement,
Title,
Tooltip,
Legend,
Colors,
)
const props = defineProps<{ const props = defineProps<{
end: string end: string
}>() }>()

View File

@@ -6,7 +6,7 @@ export interface SyncStatusState {
} }
// 提供/注入的 key // 提供/注入的 key
export const SYNC_STATUS_KEY = Symbol('syncStatus') export const SYNC_STATUS_KEY = Symbol("syncStatus")
// 创建同步状态 // 创建同步状态
export function createSyncStatus() { export function createSyncStatus() {
@@ -42,10 +42,10 @@ export function provideSyncStatus() {
// 注入同步状态 // 注入同步状态
export function injectSyncStatus() { export function injectSyncStatus() {
const syncStatus = inject<ReturnType<typeof createSyncStatus>>(SYNC_STATUS_KEY) const syncStatus =
inject<ReturnType<typeof createSyncStatus>>(SYNC_STATUS_KEY)
if (!syncStatus) { if (!syncStatus) {
throw new Error('syncStatus must be provided by a parent component') throw new Error("syncStatus must be provided by a parent component")
} }
return syncStatus return syncStatus
} }

View File

@@ -197,7 +197,11 @@ defineExpose({
{{ SYNC_MESSAGES.SYNCING_WITH(syncStatus.otherUser.value.name) }} {{ SYNC_MESSAGES.SYNCING_WITH(syncStatus.otherUser.value.name) }}
</n-tag> </n-tag>
<n-tag <n-tag
v-if="userStore.isSuperAdmin && !syncStatus.otherUser.value && syncStatus.hadConnection.value" v-if="
userStore.isSuperAdmin &&
!syncStatus.otherUser.value &&
syncStatus.hadConnection.value
"
type="warning" type="warning"
> >
{{ SYNC_MESSAGES.STUDENT_LEFT }} {{ SYNC_MESSAGES.STUDENT_LEFT }}

View File

@@ -4,9 +4,19 @@ import { problem } from "oj/composables/problem"
import { DIFFICULTY, JUDGE_STATUS } from "utils/constants" import { DIFFICULTY, JUDGE_STATUS } from "utils/constants"
import { getACRateNumber, getTagColor, parseTime } from "utils/functions" import { getACRateNumber, getTagColor, parseTime } from "utils/functions"
import { Pie } from "vue-chartjs" import { Pie } from "vue-chartjs"
import {
Chart as ChartJS,
ArcElement,
Title,
Tooltip,
Legend,
Colors,
} from "chart.js"
import { getProblemBeatRate } from "oj/api" import { getProblemBeatRate } from "oj/api"
import { isDesktop } from "shared/composables/breakpoints" import { isDesktop } from "shared/composables/breakpoints"
import { registerChart } from "utils/registerChart"
// 仅注册饼图所需的 Chart.js 组件
ChartJS.register(ArcElement, Title, Tooltip, Legend, Colors)
const beatRate = ref("0") const beatRate = ref("0")
@@ -74,7 +84,6 @@ async function getBeatRate() {
beatRate.value = res.data beatRate.value = res.data
} }
onBeforeMount(registerChart)
onMounted(getBeatRate) onMounted(getBeatRate)
</script> </script>

View File

@@ -1,8 +1,29 @@
<script setup lang="ts"> <script setup lang="ts">
import { Bar } from "vue-chartjs" import { Bar } from "vue-chartjs"
import {
Chart as ChartJS,
CategoryScale,
LinearScale,
BarElement,
Title,
Tooltip,
Legend,
Colors,
} from "chart.js"
import { ChartType } from "utils/constants" import { ChartType } from "utils/constants"
import { Rank } from "utils/types" import { Rank } from "utils/types"
// 仅注册柱状图所需的 Chart.js 组件
ChartJS.register(
CategoryScale,
LinearScale,
BarElement,
Title,
Tooltip,
Legend,
Colors,
)
const props = defineProps<{ rankData: Rank[]; type: ChartType }>() const props = defineProps<{ rankData: Rank[]; type: ChartType }>()
const data = computed(() => { const data = computed(() => {

View File

@@ -1,5 +1,4 @@
import { RouteRecordRaw } from "vue-router" import { RouteRecordRaw } from "vue-router"
import { registerChart } from "./utils/registerChart"
export const ojs: RouteRecordRaw = { export const ojs: RouteRecordRaw = {
path: "/", path: "/",
@@ -66,7 +65,6 @@ export const ojs: RouteRecordRaw = {
{ {
path: "rank", path: "rank",
component: () => import("oj/rank/list.vue"), component: () => import("oj/rank/list.vue"),
beforeEnter: registerChart,
}, },
{ {
path: "announcement", path: "announcement",
@@ -97,7 +95,6 @@ export const ojs: RouteRecordRaw = {
path: "ai-analysis", path: "ai-analysis",
component: () => import("oj/ai/analysis.vue"), component: () => import("oj/ai/analysis.vue"),
meta: { requiresAuth: true }, meta: { requiresAuth: true },
beforeEnter: registerChart,
}, },
], ],
} }

View File

@@ -89,8 +89,8 @@ const initSync = async () => {
// 处理需要断开同步的情况 // 处理需要断开同步的情况
if ( if (
(status.errorCode === SYNC_ERROR_CODES.SUPER_ADMIN_LEFT || (status.errorCode === SYNC_ERROR_CODES.SUPER_ADMIN_LEFT ||
status.errorCode === SYNC_ERROR_CODES.MISSING_SUPER_ADMIN) status.errorCode === SYNC_ERROR_CODES.MISSING_SUPER_ADMIN) &&
&& !status.connected !status.connected
) { ) {
emit("syncClosed") emit("syncClosed")
} }

View File

@@ -46,7 +46,7 @@ export const SYNC_MESSAGES = {
// 类型定义 // 类型定义
type SyncState = "waiting" | "active" | "error" type SyncState = "waiting" | "active" | "error"
type SyncErrorCode = typeof SYNC_ERROR_CODES[keyof typeof SYNC_ERROR_CODES] type SyncErrorCode = (typeof SYNC_ERROR_CODES)[keyof typeof SYNC_ERROR_CODES]
interface UserInfo { interface UserInfo {
name: string name: string
@@ -210,7 +210,9 @@ export function useCodeSync() {
roomUsers, roomUsers,
canSync: false, canSync: false,
message: message:
roomUsers === 1 ? SYNC_MESSAGES.WAITING_STUDENT : SYNC_MESSAGES.WAITING_ADMIN, roomUsers === 1
? SYNC_MESSAGES.WAITING_STUDENT
: SYNC_MESSAGES.WAITING_ADMIN,
otherUser, otherUser,
}, },
onStatusChange, onStatusChange,

View File

@@ -1,36 +0,0 @@
import {
ArcElement,
BarElement,
BarController,
CategoryScale,
Chart as ChartJS,
Colors,
Legend,
LinearScale,
LineController,
Title,
Tooltip,
LineElement,
PointElement,
} from "chart.js"
let registered = false
export function registerChart() {
if (registered) return
ChartJS.register(
CategoryScale,
LinearScale,
BarElement,
BarController,
ArcElement,
LineElement,
LineController,
PointElement,
Colors,
Title,
Tooltip,
Legend,
)
registered = true
}