fix(老师权限): 老师在自己比赛里点题目「可见」没反应、看不到学生的智能分析入口等一批权限错位

- 比赛题的可见开关按题目作者判归属,从题库加进比赛的题作者是别人,老师点了 404 且无提示;
  改成和编辑、删除一样按比赛创建者判(canEdit)
- 学生主页「智能分析」和「查看指定用户」只给超管,后端本来就允许老师;改成老师可见
- 排名页班级详情的「AI 分析」学生也看得到,点了权限不足;只给老师显示
- 班级 PK 页解构 store 丢了响应性,资料晚到时老师看不到「AI 分析」
- 学生管理员能给任何人的流程图重新评分且不限流;别人的提交只有老师能重评,免限流也只给老师
- 超管没退登就过期的机房电脑上,下一个登录的老师继承演示模式、看不到后台和看板且关不掉;
  演示模式只对超管生效
- 老师的出题权限能设成「无权限」,设了比赛题目页全是权限不足;后端兜成「仅自己」,
  编辑框里老师不再给这个选项

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
xuyueandClaude Opus 5.5 committed 2026-10-08 14:00:04 +08:00
1 parent bde387e8bc
commit 30f34f0e36
10 files changed
+45 -15

No files matched your search

+7 -1
View File
@@ -130,6 +130,8 @@ const problemPermissionOptions: SelectOption[] = [
{ label: "仅管理自己创建", value: PROBLEM_PERMISSION.OWN },
{ label: "管理全部题目", value: PROBLEM_PERMISSION.ALL },
]
// 老师不给「无权限」:比赛题的接口要出题权限,设了就是比赛题目页全报权限不足,后端也会兜成「仅自己」
const teacherPermissionOptions = problemPermissionOptions.slice(1)
async function listUsers() {
if (query.page < 1) query.page = 1
@@ -336,7 +338,11 @@ watch(() => [query.page, query.limit, query.type, query.orderBy], listUsers)
>
<n-select
v-model:value="userEditing.problemPermission"
:options="problemPermissionOptions"
:options="
userEditing.adminType === USER_TYPE.TEACHER_ADMIN
? teacherPermissionOptions
: problemPermissionOptions
"
/>
</n-form-item-gi>
+1 -1
View File
@@ -5,7 +5,7 @@
<n-h3 style="margin: 0">请选择时间范围,智能分析学习情况</n-h3>
<n-flex align="center">
<n-input
v-if="userStore.isSuperAdmin"
v-if="userStore.isTeacherOrAbove"
v-model:value="urlUsername"
placeholder="查看指定用户"
clearable
+1 -1
View File
@@ -45,7 +45,7 @@ ChartJS.register(
)
const configStore = useConfigStore()
const { isTeacherOrAbove } = useUserStore()
const { isTeacherOrAbove } = storeToRefs(useUserStore())
const message = useMessage()
const { isDesktop } = useBreakpoints()
+6 -1
View File
@@ -843,7 +843,12 @@ watch(
<n-tag type="success" size="large">
综合分: {{ classDetailData.compositeScore.toFixed(1) }}
</n-tag>
<n-button type="info" :loading="classDetailAiLoading" @click="analyzeSingleClassWithAI">
<n-button
v-if="userStore.isTeacherOrAbove"
type="info"
:loading="classDetailAiLoading"
@click="analyzeSingleClassWithAI"
>
<template #icon>
<Icon icon="ph:sparkle" />
</template>
+1 -1
View File
@@ -165,7 +165,7 @@ onMounted(() => {
<h2>{{ profile.user.username }}</h2>
<p class="desc">{{ profile.mood }}</p>
<n-button
v-if="userStore.isSuperAdmin"
v-if="userStore.isTeacherOrAbove"
type="info"
secondary
@click="
+10 -6
View File
@@ -12,13 +12,17 @@ export const useUserStore = defineStore("user", () => {
const user = computed<SessionUser | null>(() => profile.value?.user ?? null)
const isAuthed = computed(() => !!user.value?.email)
// 演示模式:超管临时把界面伪装成普通学生,方便上课投屏
const demoMode = ref<boolean>(storage.get(STORAGE_KEY.DEMO_MODE) ?? false)
// 不受伪装影响的真实身份,只用于判断能否切换演示模式。
// 若这里用被伪装后的 isSuperAdmin,一进入演示模式入口就消失了,退不出来。
const realIsSuperAdmin = computed(() => user.value?.adminType === USER_TYPE.SUPER_ADMIN)
// 演示模式:超管临时把界面伪装成普通学生,方便上课投屏。
// 开关存在 storage 里、只有主动退登才清,会话过期后换人登录它还在 —— 所以只对超管
// 生效。不然老师在超管用过的机房电脑上一登录,后台、看板、求助全没了,
// 而「退出演示」只给超管,他自己关不掉
const demoSwitch = ref<boolean>(storage.get(STORAGE_KEY.DEMO_MODE) ?? false)
const demoMode = computed(() => demoSwitch.value && realIsSuperAdmin.value)
const isAdminRole = computed(
() =>
!demoMode.value &&
@@ -46,8 +50,8 @@ export const useUserStore = defineStore("user", () => {
const canToggleDemoMode = computed(() => realIsSuperAdmin.value)
function toggleDemoMode() {
demoMode.value = !demoMode.value
storage.set(STORAGE_KEY.DEMO_MODE, demoMode.value)
demoSwitch.value = !demoSwitch.value
storage.set(STORAGE_KEY.DEMO_MODE, demoSwitch.value)
}
const showSubmissions = computed(() => {
@@ -90,7 +94,7 @@ export const useUserStore = defineStore("user", () => {
function clearProfile() {
profile.value = null
demoMode.value = false
demoSwitch.value = false
// 登录框记的班级要跨退登活下来:机房一台机器对一个班,下课登出、下节课再来
// 还是同一个班,清掉的话每个人都得重新选一遍。它是机器的属性,不是谁的隐私。
const loginClass = storage.get(STORAGE_KEY.LOGIN_CLASS)