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:
@@ -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;
|
||||
|
||||
@@ -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>
|
||||
@@ -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})
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user