refactor: 添加角色选择
continuous-integration/drone/push Build is passing Details

This commit is contained in:
张哲铜 2025-11-22 17:31:21 +08:00
parent 58df855801
commit 8e05abbd2c
3 changed files with 177 additions and 80 deletions

View File

@ -90,16 +90,17 @@ const teacherName = computed(() => {
return userInfo.nickname || '教师' return userInfo.nickname || '教师'
}) })
const teacherSubject = computed(() => { const currentRoleText = computed(() => {
const userInfo = userStore.info const role = userStore.selectedRole
// if (!role)
const roleName = userInfo.roles?.[0]?.name return '暂无角色'
if (roleName) { const parts = [
// """" role.grade,
const subjectMatch = roleName.match(/^(.+?)教师$/) role.class,
return subjectMatch ? subjectMatch[1] : '学科' role.subject,
} role.role,
return '学科' ].filter(Boolean)
return parts.join(' ') || '教师'
}) })
// //
@ -245,13 +246,6 @@ const showClassPicker = ref(false)
// //
function handleClassChange() { function handleClassChange() {
if (homeStore.classOptions.length === 0) {
uni.showToast({
title: '暂无班级数据',
icon: 'none',
})
return
}
showClassPicker.value = true showClassPicker.value = true
} }
@ -259,8 +253,6 @@ function handleClassChange() {
watch(() => homeStore.selectedClassKey, () => { watch(() => homeStore.selectedClassKey, () => {
// //
fetchExamStats() fetchExamStats()
//
showClassPicker.value = false
}) })
// //
@ -400,7 +392,7 @@ onMounted(async () => {
</view> </view>
<view class="flex flex-col"> <view class="flex flex-col">
<text class="text-base text-white font-bold">{{ teacherName }}</text> <text class="text-base text-white font-bold">{{ teacherName }}</text>
<text class="text-xs text-blue-100 opacity-80">{{ teacherSubject }}教师</text> <text class="text-xs text-blue-100 opacity-80">{{ currentRoleText }}</text>
</view> </view>
</view> </view>
@ -625,26 +617,54 @@ onMounted(async () => {
<wd-popup v-model="showClassPicker" position="bottom" custom-class="rounded-t-3xl" :z-index="100"> <wd-popup v-model="showClassPicker" position="bottom" custom-class="rounded-t-3xl" :z-index="100">
<view class="bg-white pb-safe"> <view class="bg-white pb-safe">
<view class="border-b border-slate-100 p-4 text-center"> <!-- 班级列表 -->
<text class="text-lg text-slate-800 font-bold">切换班级</text> <view class="border-b border-slate-100 p-4">
</view> <text class="mb-2 block text-sm text-slate-600 font-bold">切换班级</text>
<scroll-view scroll-y class="box-border max-h-80 p-4"> <scroll-view scroll-y class="box-border max-h-40">
<view class="space-y-2"> <view v-if="homeStore.classOptions.length > 0" class="space-y-1">
<view
v-for="classItem in homeStore.classOptions"
:key="classItem.value"
class="flex items-center justify-between rounded-xl p-4 transition-colors"
:class="homeStore.selectedClassId === classItem.value ? 'bg-blue-50 border border-blue-100' : 'bg-slate-50'"
@tap="homeStore.selectedClassId = classItem.value;showClassPicker = false"
>
<text class="text-base" :class="homeStore.selectedClassId === classItem.value ? 'text-blue-600 font-bold' : 'text-slate-700'">{{ classItem.label }}</text>
<view <view
v-if="homeStore.selectedClassId === classItem.value" v-for="classItem in homeStore.classOptions"
class="i-mingcute:check-circle-fill text-xl text-blue-500" :key="classItem.value"
/> class="flex items-center justify-between rounded-lg px-3 py-2 transition-colors"
:class="homeStore.selectedClassId === classItem.value ? 'bg-blue-50 text-blue-600' : 'hover:bg-slate-50 text-slate-600'"
@tap="homeStore.selectedClassId = classItem.value;showClassPicker = false"
>
<text class="text-sm font-medium">{{ classItem.label }}</text>
<view
v-if="homeStore.selectedClassId === classItem.value"
class="i-mingcute:check-line text-lg"
/>
</view>
</view> </view>
</view> <view v-else class="py-4 text-center text-sm text-slate-400">
</scroll-view> 当前角色无可选班级
</view>
</scroll-view>
</view>
<!-- 角色选择区域 -->
<view v-if="userStore.teacherInfo?.role_class_list && userStore.teacherInfo.role_class_list.length > 0" class="border-b border-slate-100 p-4">
<text class="mb-2 block text-sm text-slate-600 font-bold">切换角色</text>
<scroll-view scroll-y class="max-h-40">
<view class="space-y-1">
<view
v-for="(roleItem, index) in userStore.teacherInfo.role_class_list"
:key="index"
class="flex items-center justify-between rounded-lg px-3 py-2 transition-colors"
:class="userStore.selectedRole === roleItem ? 'bg-blue-50 text-blue-600' : 'hover:bg-slate-50 text-slate-600'"
@tap="userStore.setSelectedRole(roleItem)"
>
<view class="flex items-center gap-2 text-sm">
<text class="font-medium">{{ roleItem.grade }}{{ roleItem.class }}</text>
<text>{{ roleItem.subject }}</text>
<text>{{ roleItem.role }}</text>
</view>
<view v-if="userStore.selectedRole === roleItem" class="i-mingcute:check-line text-lg" />
</view>
</view>
</scroll-view>
</view>
<view class="p-4"> <view class="p-4">
<wd-button block type="default" size="large" custom-class="!rounded-xl !bg-slate-100 !text-slate-600 !border-none" @click="showClassPicker = false"> <wd-button block type="default" size="large" custom-class="!rounded-xl !bg-slate-100 !text-slate-600 !border-none" @click="showClassPicker = false">
关闭 关闭

View File

@ -1,9 +1,10 @@
import type { ModelTeacherAnalysisClassInfo, ModelTeacherAnalysisExamInfo } from '@/api/data-contracts' import type { ModelTeacherAnalysisExamInfo } from '@/api/data-contracts'
import { whenever } from '@vueuse/core' import { whenever } from '@vueuse/core'
import { defineStore } from 'pinia' import { defineStore } from 'pinia'
import { computed, ref } from 'vue' import { computed, onMounted, ref } from 'vue'
import { teacherScoreAnalysisApi } from '@/api' import { teacherScoreAnalysisApi } from '@/api'
import { useUserStorage } from '@/composables/useUserStorage' import { useUserStorage } from '@/composables/useUserStorage'
import { useUserStore } from '@/store/user'
export interface SelectOption { export interface SelectOption {
label: string label: string
@ -14,23 +15,18 @@ export interface SelectOption {
gradeKey?: number gradeKey?: number
} }
// 班级信息类型
export interface ClassItem {
label: string
value: number
gradeKey: number
}
/** /**
* *
*/ */
export const useHomeStore = defineStore( export const useHomeStore = defineStore(
'homeStore', 'homeStore',
() => { () => {
// 获取 userStore
const userStore = useUserStore()
// 数据存储 // 数据存储
const loading = ref(false) const loading = ref(false)
const examDataMap = ref<Map<number, ModelTeacherAnalysisExamInfo>>(new Map()) const examDataMap = ref<Map<number, ModelTeacherAnalysisExamInfo>>(new Map())
const classList = ref<ClassItem[]>([])
// 已选择的数据(使用 useUserStorage 实现基于用户的持久化) // 已选择的数据(使用 useUserStorage 实现基于用户的持久化)
const selectedClassId = useUserStorage<number>('home_selectedClassId', 0) const selectedClassId = useUserStorage<number>('home_selectedClassId', 0)
@ -46,14 +42,53 @@ export const useHomeStore = defineStore(
})) }))
}) })
// 计算属性:班级选项 // 计算属性:班级选项(带角色权限筛选)
const classOptions = computed((): SelectOption[] => { const classOptions = computed((): SelectOption[] => {
return classList.value.map((item, index) => ({ if (!selectedExamId.value) {
label: item.label, return []
}
const examData = examDataMap.value.get(selectedExamId.value)
if (!examData?.classes) {
return []
}
const allClasses = examData.classes.map((item, index) => ({
label: `${item.grade_name || ''} ${item.class_name || ''}`.trim(),
value: index, value: index,
classKey: item.value, classKey: item.class_key,
gradeKey: item.gradeKey, gradeKey: Number(item.grade_key),
})) })) || []
// 如果没有选择角色,返回所有班级
const currentRole = userStore.selectedRole
if (!currentRole) {
return allClasses
}
// 根据角色类型筛选班级
return allClasses.filter((classItem) => {
// 1. 任课老师 (role_key: 1): 只能访问特定科目的特定班级
if (currentRole.role_key === 1) {
return currentRole.grade_key === classItem.gradeKey && currentRole.class_key === classItem.classKey
}
// 2. 班主任 (role_key: 2): 可以访问管理的班级
if (currentRole.role_key === 2) {
return currentRole.grade_key === classItem.gradeKey && currentRole.class_key === classItem.classKey
}
// 3. 年级主任 (role_key: 3): 可以访问对应年级的所有班级
if (currentRole.role_key === 3) {
return currentRole.grade_key === classItem.gradeKey
}
// 4. 学科组长 (role_key: 4): 可以访问对应科目的所有班级(在当前角色的年级内)
if (currentRole.role_key === 4) {
// 学科组长可以看当前角色年级的所有班级
return currentRole.grade_key === classItem.gradeKey
}
return false
})
}) })
const selectedClassKey = computed(() => { const selectedClassKey = computed(() => {
@ -64,7 +99,7 @@ export const useHomeStore = defineStore(
return classOptions.value.find(item => item.value === selectedClassId.value)?.gradeKey || 0 return classOptions.value.find(item => item.value === selectedClassId.value)?.gradeKey || 0
}) })
// 计算属性:根据选中考试获取科目选项 // 计算属性:根据选中考试获取科目选项(带角色权限筛选)
const subjectOptions = computed((): SelectOption[] => { const subjectOptions = computed((): SelectOption[] => {
if (!selectedExamId.value) { if (!selectedExamId.value) {
return [] return []
@ -73,12 +108,45 @@ export const useHomeStore = defineStore(
if (!examData?.subjects) { if (!examData?.subjects) {
return [] return []
} }
return examData.subjects.map(subject => ({
// 先生成所有科目选项
const allSubjects = examData.subjects.map(subject => ({
label: subject.subject_name || '', label: subject.subject_name || '',
value: subject.exam_subject_id || 0, value: subject.exam_subject_id || 0,
examSubjectId: subject.exam_subject_id || 0, examSubjectId: subject.exam_subject_id || 0,
subjectId: subject.subject_id || 0, subjectId: subject.subject_id || 0,
})) }))
// 如果没有选择角色,返回所有科目
const currentRole = userStore.selectedRole
if (!currentRole) {
return allSubjects
}
// 根据角色类型筛选科目
return allSubjects.filter((subjectItem) => {
// 1. 任课老师 (role_key: 1): 只能访问特定科目
if (currentRole.role_key === 1) {
return currentRole.subject_id === subjectItem.subjectId
}
// 2. 班主任 (role_key: 2): 可以访问所有科目
if (currentRole.role_key === 2) {
return true
}
// 3. 年级主任 (role_key: 3): 可以访问所有科目
if (currentRole.role_key === 3) {
return true
}
// 4. 学科组长 (role_key: 4): 只能访问负责的科目
if (currentRole.role_key === 4) {
return currentRole.subject_id === subjectItem.subjectId
}
return false
})
}) })
/** /**
@ -90,31 +158,19 @@ export const useHomeStore = defineStore(
const response = await teacherScoreAnalysisApi.dataList() const response = await teacherScoreAnalysisApi.dataList()
if (response) { if (response) {
// 处理班级数据并建立班级到年级的映射
classList.value = (response.class_list || []).map((item: ModelTeacherAnalysisClassInfo) => {
if (!item.class_key || !item.grade_key) {
return null
}
const classKey = item.class_key
const gradeKey = item.grade_key
return {
label: `${item.grade || ''} ${item.class || ''}`.trim(),
value: classKey,
gradeKey,
}
}).filter(item => item !== null) as ClassItem[]
// 处理考试数据,存储到 map 中 // 处理考试数据,存储到 map 中
examDataMap.value.clear() examDataMap.value.clear();
;(response.exam_list || []).forEach((exam: ModelTeacherAnalysisExamInfo) => { (response.exam_list || []).forEach((exam: ModelTeacherAnalysisExamInfo) => {
if (exam.exam_id) { if (exam.exam_id) {
examDataMap.value.set(exam.exam_id, exam) examDataMap.value.set(exam.exam_id, exam)
} }
}) })
if (!selectedExamId.value) {
selectedExamId.value = examOptions.value[0]?.value as number
}
console.log('数据获取完成:') console.log('数据获取完成:')
console.log('classList数量:', classList.value.length)
console.log('examDataMap数量:', examDataMap.value.size) console.log('examDataMap数量:', examDataMap.value.size)
} }
} }
@ -132,8 +188,10 @@ export const useHomeStore = defineStore(
*/ */
const onExamChange = (examId: number | null) => { const onExamChange = (examId: number | null) => {
selectedExamId.value = examId selectedExamId.value = examId
selectedSubjectId.value = subjectOptions.value[0].subjectId || null if (subjectOptions.value.length > 0) {
selectedExamSubjectId.value = subjectOptions.value[0].examSubjectId || null selectedSubjectId.value = subjectOptions.value[0].subjectId || null
selectedExamSubjectId.value = subjectOptions.value[0].examSubjectId || null
}
} }
/** /**
@ -158,7 +216,6 @@ export const useHomeStore = defineStore(
* store状态 * store状态
*/ */
const reset = () => { const reset = () => {
classList.value = []
examDataMap.value.clear() examDataMap.value.clear()
loading.value = false loading.value = false
clearSelections() clearSelections()
@ -166,8 +223,10 @@ export const useHomeStore = defineStore(
// 监听考试ID变化自动选择第一个科目 // 监听考试ID变化自动选择第一个科目
whenever(selectedExamId, (examId) => { whenever(selectedExamId, (examId) => {
selectedSubjectId.value = subjectOptions.value[0].subjectId || null if (subjectOptions.value.length > 0) {
selectedExamSubjectId.value = subjectOptions.value[0].examSubjectId || null selectedSubjectId.value = subjectOptions.value[0].subjectId || null
selectedExamSubjectId.value = subjectOptions.value[0].examSubjectId || null
}
}) })
onMounted(async () => { onMounted(async () => {

View File

@ -1,8 +1,9 @@
import type { ModelSysUser, ModelTeacherListResponse, ModelUserProfileResponse } from '@/api' import type { ModelSysUser, ModelTeacherListResponse, ModelTeacherRoleClassInfo, ModelUserProfileResponse } from '@/api'
import type { LoginRequest, SysUser } from '@/service/types' import type { LoginRequest, SysUser } from '@/service/types'
import { defineStore } from 'pinia' import { defineStore } from 'pinia'
import { computed, ref } from 'vue' import { computed, ref } from 'vue'
import { authApi } from '@/api' import { authApi } from '@/api'
import { useUserStorage } from '@/composables/useUserStorage'
import { import {
authCodeLoginUsingPost, authCodeLoginUsingPost,
authLoginUsingPost, authLoginUsingPost,
@ -24,6 +25,8 @@ export const useUserStore = defineStore(
const info = ref<Partial<ModelSysUser>>({}) const info = ref<Partial<ModelSysUser>>({})
// 教师信息 // 教师信息
const teacherInfo = ref<Partial<ModelTeacherListResponse>>({}) const teacherInfo = ref<Partial<ModelTeacherListResponse>>({})
// 当前选择的角色
const selectedRole = useUserStorage<ModelTeacherRoleClassInfo>('selectedRole', null)
// 访问令牌 // 访问令牌
const accessToken = ref('') const accessToken = ref('')
// 刷新令牌 // 刷新令牌
@ -46,6 +49,18 @@ export const useUserStore = defineStore(
const setUserInfo = (newInfo: ModelUserProfileResponse) => { const setUserInfo = (newInfo: ModelUserProfileResponse) => {
info.value = newInfo.sys_user info.value = newInfo.sys_user
teacherInfo.value = newInfo.teacher teacherInfo.value = newInfo.teacher
// 自动选择第一个角色作为默认值
if (newInfo.teacher?.role_class_list && newInfo.teacher.role_class_list.length > 0) {
selectedRole.value = newInfo.teacher.role_class_list[0]
}
}
/**
*
* @param role
*/
const setSelectedRole = (role: ModelTeacherRoleClassInfo) => {
selectedRole.value = role
} }
/** /**
@ -242,11 +257,14 @@ export const useUserStore = defineStore(
isLogin, isLogin,
isTeacher, isTeacher,
info, info,
teacherInfo,
selectedRole,
accessToken, accessToken,
refreshToken, refreshToken,
tokenExpireTime, tokenExpireTime,
getUserInfo, getUserInfo,
setUserInfo, setUserInfo,
setSelectedRole,
setLoginStatus, setLoginStatus,
setToken, setToken,
isTokenNearExpiry, isTokenNearExpiry,