style: synchronize sidebar toggle behavior and improve menu visibility

- Updated sidebar styles to ensure the collapse toggle remains visible across all student and teacher roles, removing conditional rendering.
- Enhanced specificity of styles to synchronize the toggle arrow and menu visibility with the sidebar's expanded/collapsed state.
- Added logic to force the menu to display correctly when the sidebar is expanded, addressing previous inconsistencies.
dev-QQq
qinzhenpen 10 hours ago
parent 5bf2ba612d
commit 71ac33be70

@ -1006,6 +1006,47 @@
} }
} }
/*
* 折叠把手的箭头再挂一条 .hideSidebar 链
*
* 侧栏宽度由 .app-wrapper 上的 .hideSidebar 控制,而箭头此前只由
* .sidebar-container 上的 .is-collapse 控制 —— 两者本该同源,却走了
* 两条独立的 class 链。一旦失步就会出现「宽度已切、箭头不动」的现象。
* 这里把箭头也绑到控制宽度的同一个 class 上,二者再也无法不同步。
* 特异性 (1,4,0) 与基础隐藏规则相同,靠书写顺序在后面取胜。
*/
#app .hideSidebar .sidebar-container .course-sidebar-toggle {
.toggle-arrow--left {
display: none;
}
.toggle-arrow--right {
display: inline-flex;
}
}
/*
* 兜底:展开态下不允许 el-menu 停留在折叠外观
*
* 菜单文字的显隐由 Element Plus 内部的 .el-menu--collapse 决定,它只跟随
* <el-menu :collapse> 这个 prop。若该 prop 与外层状态不同步(失步的根因),
* 就会出现「侧栏已展开,菜单却仍是 64px 图标轨道、文字不显示」。
* 这里以控制宽度的 .hideSidebar 为准做强制纠正:只要侧栏处于展开态,
* 即便 el-menu 仍带着 .el-menu--collapse,也要撑满并恢复文字。
* 特异性 (1,5,0)+ ,足以压过 Element Plus 自身的隐藏规则。
*/
#app .app-wrapper:not(.hideSidebar) .sidebar-container .student-course-menu.el-menu--collapse {
width: 100% !important;
> .el-menu-item > span,
> .el-sub-menu > .el-sub-menu__title > span {
height: auto;
width: auto;
overflow: visible;
visibility: visible;
display: inline-block;
}
}
/* /*
* 教师端 sidebar 宽度单独缩减 50%(304 → 152px) * 教师端 sidebar 宽度单独缩减 50%(304 → 152px)
* 教师端使用 el-menu 直显式布局,无需学生端的内嵌卡片空间,原 304px 造成大量空白 * 教师端使用 el-menu 直显式布局,无需学生端的内嵌卡片空间,原 304px 造成大量空白

@ -10,14 +10,16 @@
<div :class="[{ 'has-logo': showLogo }, { 'student-sidebar-shell': isCourseStyleSidebar }, { 'is-student-nav': isStudentRole }, { 'is-collapse': isCollapse }]" :style="sidebarStyle"> <div :class="[{ 'has-logo': showLogo }, { 'student-sidebar-shell': isCourseStyleSidebar }, { 'is-student-nav': isStudentRole }, { 'is-collapse': isCollapse }]" :style="sidebarStyle">
<!-- <logo v-if="showLogo" :collapse="isCollapse" /> --> <!-- <logo v-if="showLogo" :collapse="isCollapse" /> -->
<template v-if="isCourseStyleSidebar"> <template v-if="isCourseStyleSidebar">
<!-- 右缘折叠把手:贴侧边栏右缘垂直居中,点击收起/展开 --> <!-- 右缘折叠把手:贴侧边栏右缘垂直居中,点击收起/展开
常驻显示 —— 此前按「教师角色 + 学生实训页路由」做条件渲染,
学生端切到非实训页(首页 / AI 智能助手 / 学情分析 / 考试 / 实验报告 / BI 等)时
按钮会凭空消失,同一角色在不同页面表现不一致,这里去掉条件固定常显 -->
<button <button
v-if="isTeacherRole || isStudentTrainingWorkspace"
class="course-sidebar-toggle" class="course-sidebar-toggle"
type="button" type="button"
:aria-label="isCollapse ? '展开导航' : '收起导航'" :aria-label="isCollapse ? '展开导航' : '收起导航'"
:title="isCollapse ? '展开导航' : '收起导航'" :title="isCollapse ? '展开导航' : '收起导航'"
@click="toggleCourseSidebar" @click.stop="toggleCourseSidebar"
> >
<el-icon class="toggle-arrow toggle-arrow--left"><ArrowLeftBold /></el-icon> <el-icon class="toggle-arrow toggle-arrow--left"><ArrowLeftBold /></el-icon>
<el-icon class="toggle-arrow toggle-arrow--right"><ArrowRightBold /></el-icon> <el-icon class="toggle-arrow toggle-arrow--right"><ArrowRightBold /></el-icon>
@ -26,7 +28,7 @@
<!-- 学生端:el-menu / el-sub-menu,分组默认展开、可折叠 --> <!-- 学生端:el-menu / el-sub-menu,分组默认展开、可折叠 -->
<el-scrollbar v-if="isStudentRole" class="student-sidebar-scroll" wrap-class="scrollbar-wrapper"> <el-scrollbar v-if="isStudentRole" class="student-sidebar-scroll" wrap-class="scrollbar-wrapper">
<el-menu <el-menu
:key="studentMenuKey" :key="`${studentMenuKey}-${isCollapse ? 'collapsed' : 'expanded'}`"
class="student-course-menu" class="student-course-menu"
mode="vertical" mode="vertical"
:default-active="activeMenu" :default-active="activeMenu"
@ -339,9 +341,6 @@ const isCollapse = computed(() => !appStore.sidebar.opened);
const isTeacherRole = computed(() => Number(userStore.userInfo.roleId) === 3); const isTeacherRole = computed(() => Number(userStore.userInfo.roleId) === 3);
const isStudentRole = computed(() => ![1, 2, 3].includes(Number(userStore.userInfo.roleId))); const isStudentRole = computed(() => ![1, 2, 3].includes(Number(userStore.userInfo.roleId)));
const isCourseStyleSidebar = computed(() => isStudentRole.value || isTeacherRole.value); const isCourseStyleSidebar = computed(() => isStudentRole.value || isTeacherRole.value);
const isStudentTrainingWorkspace = computed(() => isStudentRole.value && [
"/foundation/", "/demand/", "/product/", "/channel/", "/assessment/", "/test/", "/comprehensive/",
].some((prefix) => route.path.startsWith(prefix)));
function toggleCourseSidebar() { function toggleCourseSidebar() {
appStore.toggleSideBar(false); appStore.toggleSideBar(false);
} }

@ -263,6 +263,13 @@ onMounted(() => {
loadDashboard(); loadDashboard();
}); });
// 学生首页被 AppMain.vue 的 <keep-alive> 缓存,再次进入不会触发 onMounted,
// 这里手动调用以保证每次进入都重新请求数据并重渲图表,
// 避免上次图表渲染未完成导致再次进入时因接口未重调而图表不显示。
onActivated(() => {
loadDashboard();
});
onBeforeUnmount(() => { onBeforeUnmount(() => {
window.removeEventListener("resize", resizeCharts); window.removeEventListener("resize", resizeCharts);
scoreChart?.dispose(); scoreChart?.dispose();

@ -6,10 +6,40 @@ const root = resolve(import.meta.dirname, "..");
const sidebar = readFileSync(resolve(root, "src/layout/components/Sidebar/index.vue"), "utf8"); const sidebar = readFileSync(resolve(root, "src/layout/components/Sidebar/index.vue"), "utf8");
const styles = readFileSync(resolve(root, "src/assets/styles/sidebar.scss"), "utf8"); const styles = readFileSync(resolve(root, "src/assets/styles/sidebar.scss"), "utf8");
assert.match(sidebar, /teacher-sidebar-toggle/, "教师侧栏应提供收起与展开按钮"); assert.match(sidebar, /course-sidebar-toggle/, "侧栏应提供收起与展开把手(教师端 / 学生端共用)");
assert.match(sidebar, /toggleSideBar\(/, "收起按钮应复用全局侧栏状态"); assert.match(sidebar, /toggleSideBar\(/, "收起按钮应复用全局侧栏状态");
assert.match(sidebar, /isCollapse/, "收起状态应同步到教师侧栏样式"); assert.match(sidebar, /isCollapse/, "收起状态应同步到侧栏样式");
assert.match(styles, /&\.hideSidebar/, "教师侧栏收起时应缩小侧栏并回收内容区空间"); assert.match(styles, /&\.hideSidebar/, "侧栏收起时应缩小侧栏并回收内容区空间");
assert.match(styles, /\.teacher-sidebar-toggle/, "收起按钮应有教师端专属样式"); assert.match(styles, /\.course-sidebar-toggle/, "收起把手应贴侧栏右缘展示");
console.log("teacher sidebar can collapse to an icon rail"); // 折叠把手必须常驻:不得再按路由条件渲染。
// 旧实现为 v-if="isTeacherRole || isStudentTrainingWorkspace",
// 导致学生端切到非实训页(首页 / AI 智能助手 / 学情分析 / 考试 / 实验报告 / BI 等)时
// 按钮凭空消失,同一角色在不同页面表现不一致。
const toggleIndex = sidebar.indexOf("course-sidebar-toggle");
assert.ok(toggleIndex > -1, "应存在折叠把手");
const buttonStart = sidebar.lastIndexOf("<button", toggleIndex);
const buttonEnd = sidebar.indexOf(">", toggleIndex);
const toggleTag = sidebar.slice(buttonStart, buttonEnd + 1);
assert.doesNotMatch(toggleTag, /v-if/, "折叠把手不得带 v-if 条件渲染,必须常驻显示");
assert.doesNotMatch(sidebar, /isStudentTrainingWorkspace/, "不应残留按路由判断按钮显隐的旧逻辑");
// 收起状态的三条 class 链必须收敛到同一来源,否则会出现
// 「宽度已切、箭头不动 / 菜单文字不显示」的失步现象。
assert.match(
sidebar,
/:key="`\$\{studentMenuKey\}-\$\{isCollapse \? 'collapsed' : 'expanded'\}`"/,
"折叠切换必须改变 el-menu 的 key,强制 Element Plus 重建菜单",
);
assert.match(
styles,
/#app \.hideSidebar \.sidebar-container \.course-sidebar-toggle/,
"箭头切换应同时挂在控制宽度的 .hideSidebar 链上",
);
assert.match(
styles,
/#app \.app-wrapper:not\(\.hideSidebar\)[\s\S]{0,200}el-menu--collapse/,
"展开态必须能强制恢复被 el-menu 折叠外观隐藏的菜单文字",
);
console.log("sidebar collapse toggle stays visible and keeps all states in sync");

Loading…
Cancel
Save