refactor(前端): shared / utils 不再 import oj 和 admin 下的任何东西
依赖方向原来是反的:顶栏、登录框、举手列表这些 shared 组件从 oj/ 里拿东西。 - 跨端的接口挪进 shared/api.ts(按 apps/web/CLAUDE.md 的分法):学生端题目列表、 出题人列表、这节课的题、待弹的成就 - 这节课的 store → shared/store/lesson.ts;头像 RankAvatar → shared/components - 纯函数挪进 utils/:举手列表的错法归类 errorKind、它依赖的答案错误分类 wrongAnswer 和 Python 报错翻译 pythonError、题目列表行的 toProblemRow - 只有后台在用的出题人下拉 AuthorSelect 挪回 admin/components 纯搬家。首页、题目列表、题目页、排名、课堂看板、后台题目列表打开无报错, 顶栏题号框敲 1001 回车直达题目页。 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
d4aadc7722
commit
e8a20d12f9
38 files changed
+78
-78
No files matched your search
@@ -3,7 +3,7 @@ import type { RankRow, WeeklyChampion } from "@oj2/contract"
|
||||
import { useThemeVars } from "naive-ui"
|
||||
import UserName from "shared/components/UserName.vue"
|
||||
import { parseTime } from "utils/functions"
|
||||
import RankAvatar from "./RankAvatar.vue"
|
||||
import RankAvatar from "shared/components/RankAvatar.vue"
|
||||
|
||||
/** 本班最近几周的每周冠军,底下再挂这张榜上「进步最大」的那个人 */
|
||||
defineProps<{
|
||||
|
||||
@@ -9,7 +9,7 @@ import { useBackClose } from "shared/composables/backClose"
|
||||
import { useBreakpoints } from "shared/composables/breakpoints"
|
||||
import { parseTime } from "utils/functions"
|
||||
import { useRankPalette } from "../palette"
|
||||
import RankAvatar from "./RankAvatar.vue"
|
||||
import RankAvatar from "shared/components/RankAvatar.vue"
|
||||
|
||||
// Markdown 连着整套代码高亮(两百多 KB),这里要等点开 / AI 写完才用得上,别让它拖慢页面
|
||||
const Markdown = defineAsyncComponent(() => import("shared/components/Markdown.vue"))
|
||||
|
||||
@@ -1,79 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import { useDark } from "@vueuse/core"
|
||||
import { rgba } from "shared/composables/tone"
|
||||
import { USERNAME_CLASS_RE } from "utils/constants"
|
||||
|
||||
/**
|
||||
* 排名页的头像。九成以上的学生没换过头像(1797 个号是 default.png),一排一模一样的
|
||||
* 默认图分不出人,所以默认头像画成「名字最后一个字 + 彩色圆」,颜色按用户名固定 ——
|
||||
* 同一个人在领奖台、赛道、「你」那张卡里是同一个颜色。自己是实心绿。
|
||||
*/
|
||||
const props = defineProps<{
|
||||
username: string
|
||||
avatar: string | null
|
||||
size: number
|
||||
me?: boolean
|
||||
}>()
|
||||
|
||||
const isDark = useDark()
|
||||
/** 头像文件丢了(备份恢复后没拷上传目录、被删)就退回名字那个字,别露出破图 */
|
||||
const broken = ref(false)
|
||||
watch(
|
||||
() => props.avatar,
|
||||
() => (broken.value = false),
|
||||
)
|
||||
|
||||
const PALETTE = ["#2f6fd0", "#c76a12", "#6a4bd8", "#0f7a6c", "#b4315f", "#4d5967", "#8a6a00"]
|
||||
|
||||
const letter = computed(() => {
|
||||
const name = props.username.replace(USERNAME_CLASS_RE, "") || props.username
|
||||
return Array.from(name).at(-1) ?? "?"
|
||||
})
|
||||
|
||||
const colors = computed(() => {
|
||||
if (props.me) return { background: "#18a058", color: "#ffffff" }
|
||||
let hash = 0
|
||||
for (const char of props.username) hash = (hash * 31 + char.codePointAt(0)!) >>> 0
|
||||
const base = PALETTE[hash % PALETTE.length]!
|
||||
return { background: rgba(base, isDark.value ? 0.28 : 0.13), color: base }
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<img
|
||||
v-if="avatar && !broken"
|
||||
class="avatar"
|
||||
:src="avatar"
|
||||
alt=""
|
||||
@error="broken = true"
|
||||
:style="{ width: `${size}px`, height: `${size}px` }"
|
||||
/>
|
||||
<span
|
||||
v-else
|
||||
class="avatar letter"
|
||||
aria-hidden="true"
|
||||
:style="{
|
||||
width: `${size}px`,
|
||||
height: `${size}px`,
|
||||
fontSize: `${Math.round(size * 0.5)}px`,
|
||||
...colors,
|
||||
}"
|
||||
>{{ letter }}</span
|
||||
>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.avatar {
|
||||
border-radius: 50%;
|
||||
flex-shrink: 0;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.letter {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-weight: 700;
|
||||
line-height: 1;
|
||||
}
|
||||
</style>
|
||||
@@ -4,7 +4,7 @@ import { useThemeVars } from "naive-ui"
|
||||
import UserName from "shared/components/UserName.vue"
|
||||
import { gapText, toPass } from "../utils"
|
||||
import MoodPopover from "./MoodPopover.vue"
|
||||
import RankAvatar from "./RankAvatar.vue"
|
||||
import RankAvatar from "shared/components/RankAvatar.vue"
|
||||
|
||||
export interface MiniRank {
|
||||
label: string
|
||||
|
||||
@@ -4,7 +4,7 @@ import { useThemeVars } from "naive-ui"
|
||||
import { useBreakpoints } from "shared/composables/breakpoints"
|
||||
import UserName from "shared/components/UserName.vue"
|
||||
import { useRankPalette } from "../palette"
|
||||
import RankAvatar from "./RankAvatar.vue"
|
||||
import RankAvatar from "shared/components/RankAvatar.vue"
|
||||
|
||||
/**
|
||||
* 领奖台:前三名站在台阶上,第一名戴皇冠。赛道从第 4 名接着排,前三不再出现第二遍。
|
||||
|
||||
@@ -5,7 +5,7 @@ import UserName from "shared/components/UserName.vue"
|
||||
import { useBreakpoints } from "shared/composables/breakpoints"
|
||||
import { useRankPalette } from "../palette"
|
||||
import MoodPopover from "./MoodPopover.vue"
|
||||
import RankAvatar from "./RankAvatar.vue"
|
||||
import RankAvatar from "shared/components/RankAvatar.vue"
|
||||
|
||||
/**
|
||||
* 赛道:每人一根条,条越长做对越多,一样长的先做到的排前面(设计稿 G1 / G2)。
|
||||
|
||||
@@ -3,7 +3,7 @@ import type { RankRow } from "@oj2/contract"
|
||||
import { useThemeVars } from "naive-ui"
|
||||
import UserName from "shared/components/UserName.vue"
|
||||
import { useRankPalette } from "../palette"
|
||||
import RankAvatar from "./RankAvatar.vue"
|
||||
import RankAvatar from "shared/components/RankAvatar.vue"
|
||||
|
||||
/** 这周本班前 5 名(做对过新题的),每周一清零 */
|
||||
const props = defineProps<{ rows: RankRow[]; meId?: number; loaded: boolean }>()
|
||||
|
||||
Reference in new issue
Block a user