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
@@ -1,33 +0,0 @@
|
||||
<template>
|
||||
<n-select
|
||||
style="width: 140px"
|
||||
v-model:value="author"
|
||||
remote
|
||||
@update:show="getAuthorOptions"
|
||||
:options="authorOptions"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { getAuthors } from "oj/api"
|
||||
|
||||
interface Props {
|
||||
all?: boolean
|
||||
}
|
||||
|
||||
const { all = false } = defineProps<Props>()
|
||||
|
||||
const author = defineModel<string>("value")
|
||||
|
||||
const authorOptions = ref([{ label: "全部", value: "" }])
|
||||
|
||||
async function getAuthorOptions() {
|
||||
authorOptions.value = [{ label: "全部", value: "" }]
|
||||
const res = await getAuthors(all)
|
||||
const remotes = res.map((item) => ({
|
||||
label: `${item.username} (${item.problemCount})`,
|
||||
value: item.username,
|
||||
}))
|
||||
authorOptions.value = [...authorOptions.value, ...remotes]
|
||||
}
|
||||
</script>
|
||||
@@ -1,6 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import { Icon } from "@iconify/vue"
|
||||
import { errorKind } from "oj/classroom/errorKind"
|
||||
import { errorKind } from "utils/errorKind"
|
||||
import { useBreakpoints } from "shared/composables/breakpoints"
|
||||
import { useTeacherHelp } from "shared/composables/teacherHelp"
|
||||
import { useCollabStore } from "shared/store/collab"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import { Icon } from "@iconify/vue"
|
||||
import { useThemeVars } from "naive-ui"
|
||||
import RankAvatar from "oj/rank/components/RankAvatar.vue"
|
||||
import RankAvatar from "shared/components/RankAvatar.vue"
|
||||
import { classLabel, groupClassOptions } from "utils/classes"
|
||||
import { errorCode } from "utils/api"
|
||||
import { STORAGE_KEY } from "utils/constants"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import { Icon } from "@iconify/vue"
|
||||
import { useThemeVars } from "naive-ui"
|
||||
import { useLessonStore } from "oj/store/lesson"
|
||||
import { useLessonStore } from "shared/store/lesson"
|
||||
import { useProblemJump } from "shared/composables/problemJump"
|
||||
import { useUserStore } from "shared/store/user"
|
||||
import type { ClassActivityProblem } from "utils/types"
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
<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>
|
||||
Reference in new issue
Block a user