feat: student teacher switching in profile page

Add teacher context for students: show current teacher in profile,
allow switching via new teacher-select page. Filter daka queries
by selected teacher when available.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-04-30 23:50:39 +08:00
parent d117eb06f6
commit 8589a6fc02
6 changed files with 117 additions and 2 deletions
+18 -2
View File
@@ -20,6 +20,10 @@
<span>当前机构{{ currentOrgName || '未选择机构' }}</span>
<van-button size="mini" plain type="primary" @click="goOrgSelect">切换机构</van-button>
</div>
<div v-if="isStudent" class="current-teacher">
<span>当前老师{{ currentTeacherName || '未选择老师' }}</span>
<van-button size="mini" plain type="primary" @click="goTeacherSelect">切换老师</van-button>
</div>
<div class="roles">
<template v-for="role in roles">
<van-tag type="primary" v-if="currentRole === role.role_key">{{role.role_name}}</van-tag>
@@ -41,6 +45,7 @@ import { useRouter } from "vue-router";
import {UpyunAccess} from "~/utils/upyun";
import {CurrentUserRole, HtyAuthToken, HtySudoToken} from "~/utils";
import useOrg from "~/store/org";
import useTeacher from "~/store/teacher";
export default defineComponent({
name: 'user-profile',
@@ -56,6 +61,7 @@ export default defineComponent({
const router = useRouter();
const { store, chooseRole, update, logout } = useUser(router);
const { store: orgStore, loadMyOrgs } = useOrg();
const { store: teacherStore, switchTeacher } = useTeacher();
const user = computed(() => store.current)
const state = reactive({
editing: false,
@@ -147,13 +153,23 @@ export default defineComponent({
return orgStore.orgs.find(org => org.id === currentOrgId)?.org_name || currentOrgId;
});
const isStudent = computed(() => store.currentRole === 'STUDENT');
const currentTeacherName = computed(() => {
if (!teacherStore.currentTeacherId) return '';
const teacher = store.teachers.mine.find(t => t.hty_id === teacherStore.currentTeacherId);
return teacher?.real_name || '';
});
const goTeacherSelect = () => router.push("/student/teacher-select");
onMounted(async () => {
if (!orgStore.orgs.length) {
await loadMyOrgs();
}
});
return { switching, hasMultiRoles, roles, user, currentRole, switchRole, editAvatar, avatarUrl, editName, state, logoutUser, currentOrgName, goOrgSelect }
return { switching, hasMultiRoles, roles, user, currentRole, switchRole, editAvatar, avatarUrl, editName, state, logoutUser, currentOrgName, goOrgSelect, isStudent, currentTeacherName, goTeacherSelect }
}
})
@@ -220,7 +236,7 @@ export default defineComponent({
}
}
.current-org {
.current-org, .current-teacher {
display: flex;
justify-content: center;
align-items: center;
+62
View File
@@ -0,0 +1,62 @@
<template>
<div class="main">
<h4>请选择老师</h4>
<van-empty v-if="!teachers.length" description="暂无可用老师" />
<van-cell-group v-else>
<van-cell
v-for="teacher in teachers"
:key="teacher.hty_id"
:title="teacher.real_name + (teacher.meta?.nickName ? ' (' + teacher.meta.nickName + ')' : '')"
:label="teacher.hty_id"
is-link
@click="onSelect(teacher.hty_id)"
>
<template #icon>
<hty-image :type="'avatar'" :url="teacher.avatar_url || teacher.meta?.avatarUrl" />
</template>
</van-cell>
</van-cell-group>
</div>
</template>
<script lang="ts">
import { defineComponent, computed, onMounted } from "vue";
import { Cell, CellGroup, Empty } from "vant";
import { useRouter } from "vue-router";
import useUser from "~/store/user";
import useTeacher from "~/store/teacher";
import HtyImage from "~/components/hty-image.vue";
export default defineComponent({
name: "student-teacher-select",
components: {
[Cell.name]: Cell,
[CellGroup.name]: CellGroup,
[Empty.name]: Empty,
[HtyImage.name]: HtyImage,
},
setup() {
const router = useRouter();
const { store: userStore, loadMyTeachers } = useUser();
const { switchTeacher } = useTeacher();
const teachers = computed(() => userStore.teachers.mine);
onMounted(async () => {
await loadMyTeachers();
});
const onSelect = (teacherId: string) => {
switchTeacher(teacherId);
router.back();
};
return { teachers, onSelect };
},
});
</script>
<style scoped lang="less">
.main {
padding: 0.5rem;
}
</style>
+2
View File
@@ -12,6 +12,7 @@ import StudentHome from '~/pages/student/home.vue'
import StudentDetail from '~/pages/student/detail.vue'
import StudentProfile from '~/pages/student/profile.vue'
import StudentTeachers from '~/pages/student/teachers.vue'
import StudentTeacherSelect from '~/pages/student/teacher-select.vue'
import StudentLianxiAdd from '~/pages/student/lianxi/add.vue';
import Teacher from '~/pages/teacher/index.vue'
import TeacherHome from '~/pages/teacher/home.vue'
@@ -91,6 +92,7 @@ export default [
{path: '/student/unclaimed', component: StudentUnclaimed, meta: {title: "选择学生"}},
{path: '/student/profile', component: StudentProfile, meta: {title: "我的"}},
{path: '/student/teachers', component: StudentTeachers, meta: {title: "我的老师"}},
{path: '/student/teacher-select', component: StudentTeacherSelect, meta: {title: "选择老师"}},
{
path: '/student/lianxi/add', component: StudentLianxiAdd,
meta: {title: "添加练习"}, props: ({query}: RouteQueryAndHash) => ({params: query})
+5
View File
@@ -18,6 +18,7 @@ import {
CourseSection, TeacherSummary
} from '~/types'
import useUser from "~/store/user";
import useTeacher from "~/store/teacher";
import useRefResource from "~/store/ref-resource";
import {formatDate} from "~/utils";
import useLoading from "~/store/loading";
@@ -86,6 +87,10 @@ export default function useDaka() {
params.scope = DakaScope.ALL;
} else if (currentRole === HtyBaseRoles.STUDENT) {
params.student_id = hty_id;
const { store: teacherStore } = useTeacher();
if (teacherStore.currentTeacherId) {
params.teacher_id = teacherStore.currentTeacherId;
}
}
store.query_cache = params;
load_start()
+28
View File
@@ -0,0 +1,28 @@
import { reactive } from "vue";
import { CurrentTeacherId } from "~/utils";
interface TeacherState {
currentTeacherId?: string;
}
const store = reactive<TeacherState>({
currentTeacherId: window.localStorage.getItem(CurrentTeacherId) || undefined,
});
export default function useTeacher() {
const switchTeacher = (teacherId: string) => {
window.localStorage.setItem(CurrentTeacherId, teacherId);
store.currentTeacherId = teacherId;
};
const clearTeacher = () => {
window.localStorage.removeItem(CurrentTeacherId);
store.currentTeacherId = undefined;
};
return {
store,
switchTeacher,
clearTeacher,
};
}
+2
View File
@@ -56,6 +56,7 @@ const CurrentUserRole: string = 'CurrentUserRole';
const CurrentOrgId: string = 'CurrentOrgId';
const UserStatusKey: string = 'UserStatus';
const CurrentUserName: string = 'CurrentUserName';
const CurrentTeacherId: string = 'CurrentTeacherId';
const PiyueLockMinutes: number = 30
const list2indexed = (list: any[]) => {
@@ -610,6 +611,7 @@ export {
CurrentUserRole,
CurrentOrgId,
UserStatusKey,
CurrentTeacherId,
CurrentUserName,
PiyueLockMinutes,
list2indexed,