优化首屏
This commit is contained in:
@@ -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>
|
||||||
|
|||||||
@@ -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: {
|
||||||
|
|||||||
42
src/App.vue
42
src/App.vue
@@ -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"
|
||||||
|
|
||||||
hljs.registerLanguage("c", c)
|
|
||||||
hljs.registerLanguage("python", python)
|
|
||||||
hljs.registerLanguage("cpp", cpp)
|
|
||||||
|
|
||||||
const isDark = useDark()
|
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("python", python)
|
||||||
|
hljs.registerLanguage("cpp", cpp)
|
||||||
|
|
||||||
|
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>
|
||||||
|
|||||||
@@ -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 }
|
||||||
|
|||||||
@@ -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 }
|
||||||
}>()
|
}>()
|
||||||
|
|||||||
@@ -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
|
||||||
}>()
|
}>()
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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 }}
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|
||||||
|
|||||||
@@ -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(() => {
|
||||||
|
|||||||
@@ -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,
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user