feat(frontend): 响应式布局与移动端适配优化
1) 新增 useBreakpoints 统一断点管理;2) 管理/教师/园校/家长端布局支持移动端抽屉菜单与顶部导航;3) 全局 html/body/#app overflow 与 safe-area 处理,避免横向滚动和刘海遮挡;4) 各端内容区仅内部滚动,提升大屏与小屏的浏览体验 Made-with: Cursor
This commit is contained in:
parent
1b566be4dc
commit
31d4ed76f0
@ -3,7 +3,7 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover" />
|
||||||
<title>幼儿阅读教学服务平台</title>
|
<title>幼儿阅读教学服务平台</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|||||||
@ -12,16 +12,26 @@ const AConfigProvider = ConfigProvider;
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
html {
|
||||||
|
overflow-x: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
overflow-x: hidden;
|
||||||
|
/* 安全区域:刘海屏/横屏时留出边距 */
|
||||||
|
padding-left: env(safe-area-inset-left);
|
||||||
|
padding-right: env(safe-area-inset-right);
|
||||||
|
}
|
||||||
|
|
||||||
#app {
|
#app {
|
||||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue',
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue',
|
||||||
Arial, 'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol',
|
Arial, 'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol',
|
||||||
'Noto Color Emoji', sans-serif;
|
'Noto Color Emoji', sans-serif;
|
||||||
-webkit-font-smoothing: antialiased;
|
-webkit-font-smoothing: antialiased;
|
||||||
-moz-osx-font-smoothing: grayscale;
|
-moz-osx-font-smoothing: grayscale;
|
||||||
}
|
min-height: 100vh;
|
||||||
|
overflow-x: hidden;
|
||||||
body {
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
44
reading-platform-frontend/src/components.d.ts
vendored
44
reading-platform-frontend/src/components.d.ts
vendored
@ -7,75 +7,31 @@ export {}
|
|||||||
|
|
||||||
declare module 'vue' {
|
declare module 'vue' {
|
||||||
export interface GlobalComponents {
|
export interface GlobalComponents {
|
||||||
AAlert: typeof import('ant-design-vue/es')['Alert']
|
|
||||||
AAvatar: typeof import('ant-design-vue/es')['Avatar']
|
AAvatar: typeof import('ant-design-vue/es')['Avatar']
|
||||||
ABadge: typeof import('ant-design-vue/es')['Badge']
|
ABadge: typeof import('ant-design-vue/es')['Badge']
|
||||||
AButton: typeof import('ant-design-vue/es')['Button']
|
AButton: typeof import('ant-design-vue/es')['Button']
|
||||||
AButtonGroup: typeof import('ant-design-vue/es')['ButtonGroup']
|
|
||||||
ACard: typeof import('ant-design-vue/es')['Card']
|
ACard: typeof import('ant-design-vue/es')['Card']
|
||||||
ACheckbox: typeof import('ant-design-vue/es')['Checkbox']
|
|
||||||
ACheckboxGroup: typeof import('ant-design-vue/es')['CheckboxGroup']
|
|
||||||
ACol: typeof import('ant-design-vue/es')['Col']
|
ACol: typeof import('ant-design-vue/es')['Col']
|
||||||
ACollapse: typeof import('ant-design-vue/es')['Collapse']
|
|
||||||
ACollapsePanel: typeof import('ant-design-vue/es')['CollapsePanel']
|
|
||||||
ADatePicker: typeof import('ant-design-vue/es')['DatePicker']
|
|
||||||
ADescriptions: typeof import('ant-design-vue/es')['Descriptions']
|
|
||||||
ADescriptionsItem: typeof import('ant-design-vue/es')['DescriptionsItem']
|
|
||||||
ADivider: typeof import('ant-design-vue/es')['Divider']
|
|
||||||
ADrawer: typeof import('ant-design-vue/es')['Drawer']
|
ADrawer: typeof import('ant-design-vue/es')['Drawer']
|
||||||
ADropdown: typeof import('ant-design-vue/es')['Dropdown']
|
ADropdown: typeof import('ant-design-vue/es')['Dropdown']
|
||||||
AEmpty: typeof import('ant-design-vue/es')['Empty']
|
AEmpty: typeof import('ant-design-vue/es')['Empty']
|
||||||
AForm: typeof import('ant-design-vue/es')['Form']
|
AForm: typeof import('ant-design-vue/es')['Form']
|
||||||
AFormItem: typeof import('ant-design-vue/es')['FormItem']
|
AFormItem: typeof import('ant-design-vue/es')['FormItem']
|
||||||
AImage: typeof import('ant-design-vue/es')['Image']
|
|
||||||
AImagePreviewGroup: typeof import('ant-design-vue/es')['ImagePreviewGroup']
|
|
||||||
AInput: typeof import('ant-design-vue/es')['Input']
|
AInput: typeof import('ant-design-vue/es')['Input']
|
||||||
AInputNumber: typeof import('ant-design-vue/es')['InputNumber']
|
|
||||||
AInputPassword: typeof import('ant-design-vue/es')['InputPassword']
|
AInputPassword: typeof import('ant-design-vue/es')['InputPassword']
|
||||||
AInputSearch: typeof import('ant-design-vue/es')['InputSearch']
|
|
||||||
ALayout: typeof import('ant-design-vue/es')['Layout']
|
ALayout: typeof import('ant-design-vue/es')['Layout']
|
||||||
ALayoutContent: typeof import('ant-design-vue/es')['LayoutContent']
|
ALayoutContent: typeof import('ant-design-vue/es')['LayoutContent']
|
||||||
ALayoutHeader: typeof import('ant-design-vue/es')['LayoutHeader']
|
ALayoutHeader: typeof import('ant-design-vue/es')['LayoutHeader']
|
||||||
ALayoutSider: typeof import('ant-design-vue/es')['LayoutSider']
|
ALayoutSider: typeof import('ant-design-vue/es')['LayoutSider']
|
||||||
AList: typeof import('ant-design-vue/es')['List']
|
|
||||||
AListItem: typeof import('ant-design-vue/es')['ListItem']
|
|
||||||
AListItemMeta: typeof import('ant-design-vue/es')['ListItemMeta']
|
|
||||||
AMenu: typeof import('ant-design-vue/es')['Menu']
|
AMenu: typeof import('ant-design-vue/es')['Menu']
|
||||||
AMenuDivider: typeof import('ant-design-vue/es')['MenuDivider']
|
AMenuDivider: typeof import('ant-design-vue/es')['MenuDivider']
|
||||||
AMenuItem: typeof import('ant-design-vue/es')['MenuItem']
|
AMenuItem: typeof import('ant-design-vue/es')['MenuItem']
|
||||||
AModal: typeof import('ant-design-vue/es')['Modal']
|
|
||||||
APageHeader: typeof import('ant-design-vue/es')['PageHeader']
|
|
||||||
APagination: typeof import('ant-design-vue/es')['Pagination']
|
|
||||||
APopconfirm: typeof import('ant-design-vue/es')['Popconfirm']
|
|
||||||
AProgress: typeof import('ant-design-vue/es')['Progress']
|
|
||||||
ARadio: typeof import('ant-design-vue/es')['Radio']
|
|
||||||
ARadioButton: typeof import('ant-design-vue/es')['RadioButton']
|
|
||||||
ARadioGroup: typeof import('ant-design-vue/es')['RadioGroup']
|
|
||||||
ARangePicker: typeof import('ant-design-vue/es')['RangePicker']
|
ARangePicker: typeof import('ant-design-vue/es')['RangePicker']
|
||||||
ARate: typeof import('ant-design-vue/es')['Rate']
|
|
||||||
AResult: typeof import('ant-design-vue/es')['Result']
|
|
||||||
ARow: typeof import('ant-design-vue/es')['Row']
|
ARow: typeof import('ant-design-vue/es')['Row']
|
||||||
ASelect: typeof import('ant-design-vue/es')['Select']
|
|
||||||
ASelectOptGroup: typeof import('ant-design-vue/es')['SelectOptGroup']
|
|
||||||
ASelectOption: typeof import('ant-design-vue/es')['SelectOption']
|
|
||||||
ASkeleton: typeof import('ant-design-vue/es')['Skeleton']
|
|
||||||
ASpace: typeof import('ant-design-vue/es')['Space']
|
ASpace: typeof import('ant-design-vue/es')['Space']
|
||||||
ASpin: typeof import('ant-design-vue/es')['Spin']
|
ASpin: typeof import('ant-design-vue/es')['Spin']
|
||||||
AStatistic: typeof import('ant-design-vue/es')['Statistic']
|
|
||||||
AStep: typeof import('ant-design-vue/es')['Step']
|
|
||||||
ASteps: typeof import('ant-design-vue/es')['Steps']
|
|
||||||
ASubMenu: typeof import('ant-design-vue/es')['SubMenu']
|
ASubMenu: typeof import('ant-design-vue/es')['SubMenu']
|
||||||
ASwitch: typeof import('ant-design-vue/es')['Switch']
|
|
||||||
ATable: typeof import('ant-design-vue/es')['Table']
|
|
||||||
ATabPane: typeof import('ant-design-vue/es')['TabPane']
|
|
||||||
ATabs: typeof import('ant-design-vue/es')['Tabs']
|
|
||||||
ATag: typeof import('ant-design-vue/es')['Tag']
|
ATag: typeof import('ant-design-vue/es')['Tag']
|
||||||
ATextarea: typeof import('ant-design-vue/es')['Textarea']
|
|
||||||
ATimeRangePicker: typeof import('ant-design-vue/es')['TimeRangePicker']
|
|
||||||
ATooltip: typeof import('ant-design-vue/es')['Tooltip']
|
|
||||||
ATypographyText: typeof import('ant-design-vue/es')['TypographyText']
|
|
||||||
AUpload: typeof import('ant-design-vue/es')['Upload']
|
|
||||||
AUploadDragger: typeof import('ant-design-vue/es')['UploadDragger']
|
|
||||||
FilePreviewModal: typeof import('./components/FilePreviewModal.vue')['default']
|
FilePreviewModal: typeof import('./components/FilePreviewModal.vue')['default']
|
||||||
FileUploader: typeof import('./components/course/FileUploader.vue')['default']
|
FileUploader: typeof import('./components/course/FileUploader.vue')['default']
|
||||||
LessonConfigPanel: typeof import('./components/course/LessonConfigPanel.vue')['default']
|
LessonConfigPanel: typeof import('./components/course/LessonConfigPanel.vue')['default']
|
||||||
|
|||||||
47
reading-platform-frontend/src/composables/useBreakpoints.ts
Normal file
47
reading-platform-frontend/src/composables/useBreakpoints.ts
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
import { ref, onMounted, onUnmounted } from 'vue';
|
||||||
|
|
||||||
|
/** 统一断点(与 CSS 媒体查询一致) */
|
||||||
|
export const BREAKPOINTS = {
|
||||||
|
/** 手机竖屏 */
|
||||||
|
MOBILE: 768,
|
||||||
|
/** 平板 / 小屏 */
|
||||||
|
TABLET: 1024,
|
||||||
|
/** 桌面 */
|
||||||
|
DESKTOP: 1200,
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export type BreakpointKey = keyof typeof BREAKPOINTS;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 响应式断点:根据窗口宽度判断 isMobile / isTablet / isDesktop,
|
||||||
|
* 并随 resize 更新。全项目统一使用 768 为移动端分界。
|
||||||
|
*/
|
||||||
|
export function useBreakpoints() {
|
||||||
|
const width = ref(typeof window !== 'undefined' ? window.innerWidth : 1024);
|
||||||
|
|
||||||
|
const isMobile = ref(width.value < BREAKPOINTS.MOBILE);
|
||||||
|
const isTablet = ref(
|
||||||
|
width.value >= BREAKPOINTS.MOBILE && width.value < BREAKPOINTS.TABLET
|
||||||
|
);
|
||||||
|
const isDesktop = ref(width.value >= BREAKPOINTS.TABLET);
|
||||||
|
|
||||||
|
const update = () => {
|
||||||
|
if (typeof window === 'undefined') return;
|
||||||
|
const w = window.innerWidth;
|
||||||
|
width.value = w;
|
||||||
|
isMobile.value = w < BREAKPOINTS.MOBILE;
|
||||||
|
isTablet.value = w >= BREAKPOINTS.MOBILE && w < BREAKPOINTS.TABLET;
|
||||||
|
isDesktop.value = w >= BREAKPOINTS.TABLET;
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
update();
|
||||||
|
window.addEventListener('resize', update);
|
||||||
|
});
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
window.removeEventListener('resize', update);
|
||||||
|
});
|
||||||
|
|
||||||
|
return { width, isMobile, isTablet, isDesktop, BREAKPOINTS };
|
||||||
|
}
|
||||||
@ -1,6 +1,8 @@
|
|||||||
<template>
|
<template>
|
||||||
<a-layout class="admin-layout">
|
<a-layout class="admin-layout">
|
||||||
|
<!-- 桌面端侧边栏 -->
|
||||||
<a-layout-sider
|
<a-layout-sider
|
||||||
|
v-if="!isMobile"
|
||||||
v-model:collapsed="collapsed"
|
v-model:collapsed="collapsed"
|
||||||
:trigger="null"
|
:trigger="null"
|
||||||
collapsible
|
collapsible
|
||||||
@ -75,8 +77,63 @@
|
|||||||
</div>
|
</div>
|
||||||
</a-layout-sider>
|
</a-layout-sider>
|
||||||
|
|
||||||
|
<!-- 移动端抽屉菜单 -->
|
||||||
|
<a-drawer
|
||||||
|
v-if="isMobile"
|
||||||
|
v-model:open="drawerVisible"
|
||||||
|
placement="left"
|
||||||
|
:closable="false"
|
||||||
|
:width="280"
|
||||||
|
class="admin-drawer"
|
||||||
|
:body-style="{ padding: 0 }"
|
||||||
|
>
|
||||||
|
<template #title>
|
||||||
|
<div class="drawer-header">
|
||||||
|
<img src="/logo.png" alt="Logo" class="drawer-logo" />
|
||||||
|
<span class="drawer-title">服务管理后台</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<a-menu
|
||||||
|
v-model:selectedKeys="selectedKeys"
|
||||||
|
mode="inline"
|
||||||
|
theme="light"
|
||||||
|
@select="handleDrawerMenuSelect"
|
||||||
|
class="drawer-menu"
|
||||||
|
>
|
||||||
|
<a-menu-item key="dashboard">
|
||||||
|
<template #icon><LayoutDashboard :size="18" :stroke-width="1.5" /></template>
|
||||||
|
<span>数据看板</span>
|
||||||
|
</a-menu-item>
|
||||||
|
<a-menu-item key="courses">
|
||||||
|
<template #icon><BookOpen :size="18" :stroke-width="1.5" /></template>
|
||||||
|
<span>课程包管理</span>
|
||||||
|
</a-menu-item>
|
||||||
|
<a-menu-item key="packages">
|
||||||
|
<template #icon><DatabaseOutlined /></template>
|
||||||
|
<span>套餐管理</span>
|
||||||
|
</a-menu-item>
|
||||||
|
<a-menu-item key="themes">
|
||||||
|
<template #icon><FormatPainterOutlined /></template>
|
||||||
|
<span>主题字典</span>
|
||||||
|
</a-menu-item>
|
||||||
|
<a-menu-item key="tenants">
|
||||||
|
<template #icon><Building2 :size="18" :stroke-width="1.5" /></template>
|
||||||
|
<span>租户管理</span>
|
||||||
|
</a-menu-item>
|
||||||
|
<a-menu-item key="resources">
|
||||||
|
<template #icon><FolderOpen :size="18" :stroke-width="1.5" /></template>
|
||||||
|
<span>资源库</span>
|
||||||
|
</a-menu-item>
|
||||||
|
<a-menu-item key="settings">
|
||||||
|
<template #icon><Settings :size="18" :stroke-width="1.5" /></template>
|
||||||
|
<span>系统设置</span>
|
||||||
|
</a-menu-item>
|
||||||
|
</a-menu>
|
||||||
|
</a-drawer>
|
||||||
|
|
||||||
<a-layout class="admin-layout-right">
|
<a-layout class="admin-layout-right">
|
||||||
<a-layout-header class="admin-header">
|
<!-- 桌面端顶部 -->
|
||||||
|
<a-layout-header v-if="!isMobile" class="admin-header">
|
||||||
<div class="header-left">
|
<div class="header-left">
|
||||||
<MenuUnfoldOutlined
|
<MenuUnfoldOutlined
|
||||||
v-if="collapsed"
|
v-if="collapsed"
|
||||||
@ -122,7 +179,27 @@
|
|||||||
</div>
|
</div>
|
||||||
</a-layout-header>
|
</a-layout-header>
|
||||||
|
|
||||||
<a-layout-content class="admin-content">
|
<!-- 移动端顶部 -->
|
||||||
|
<a-layout-header v-if="isMobile" class="admin-mobile-header">
|
||||||
|
<MenuOutlined class="menu-trigger" @click="drawerVisible = true" />
|
||||||
|
<span class="mobile-title">少儿智慧阅读</span>
|
||||||
|
<a-dropdown>
|
||||||
|
<a-space class="user-info" style="cursor: pointer;">
|
||||||
|
<a-avatar :size="32" class="user-avatar">
|
||||||
|
<template #icon><UserOutlined /></template>
|
||||||
|
</a-avatar>
|
||||||
|
</a-space>
|
||||||
|
<template #overlay>
|
||||||
|
<a-menu @click="handleUserMenuClick">
|
||||||
|
<a-menu-item key="profile"><UserOutlined /> 个人信息</a-menu-item>
|
||||||
|
<a-menu-divider />
|
||||||
|
<a-menu-item key="logout"><LogoutOutlined /> 退出登录</a-menu-item>
|
||||||
|
</a-menu>
|
||||||
|
</template>
|
||||||
|
</a-dropdown>
|
||||||
|
</a-layout-header>
|
||||||
|
|
||||||
|
<a-layout-content :class="['admin-content', { 'admin-content-mobile': isMobile }]">
|
||||||
<router-view />
|
<router-view />
|
||||||
</a-layout-content>
|
</a-layout-content>
|
||||||
</a-layout>
|
</a-layout>
|
||||||
@ -135,6 +212,7 @@ import { useRouter, useRoute } from 'vue-router';
|
|||||||
import {
|
import {
|
||||||
MenuUnfoldOutlined,
|
MenuUnfoldOutlined,
|
||||||
MenuFoldOutlined,
|
MenuFoldOutlined,
|
||||||
|
MenuOutlined,
|
||||||
BellOutlined,
|
BellOutlined,
|
||||||
UserOutlined,
|
UserOutlined,
|
||||||
DownOutlined,
|
DownOutlined,
|
||||||
@ -150,13 +228,20 @@ import {
|
|||||||
} from 'lucide-vue-next';
|
} from 'lucide-vue-next';
|
||||||
import { DatabaseOutlined, FormatPainterOutlined } from '@ant-design/icons-vue';
|
import { DatabaseOutlined, FormatPainterOutlined } from '@ant-design/icons-vue';
|
||||||
import { useUserStore } from '@/stores/user';
|
import { useUserStore } from '@/stores/user';
|
||||||
|
import { useBreakpoints } from '@/composables/useBreakpoints';
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const userStore = useUserStore();
|
const userStore = useUserStore();
|
||||||
|
const { isMobile } = useBreakpoints();
|
||||||
|
|
||||||
const collapsed = ref(false);
|
const collapsed = ref(false);
|
||||||
const selectedKeys = ref<string[]>(['dashboard']);
|
const selectedKeys = ref<string[]>(['dashboard']);
|
||||||
|
const drawerVisible = ref(false);
|
||||||
|
|
||||||
|
watch(isMobile, (mobile) => {
|
||||||
|
if (!mobile) drawerVisible.value = false;
|
||||||
|
});
|
||||||
|
|
||||||
// 根据路由设置选中的菜单
|
// 根据路由设置选中的菜单
|
||||||
watch(
|
watch(
|
||||||
@ -181,8 +266,6 @@ watch(
|
|||||||
{ immediate: true }
|
{ immediate: true }
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleMenuSelect = ({ key }: { key: string | number }) => {
|
|
||||||
const keyStr = String(key);
|
|
||||||
const routeMap: Record<string, string> = {
|
const routeMap: Record<string, string> = {
|
||||||
dashboard: '/admin/dashboard',
|
dashboard: '/admin/dashboard',
|
||||||
courses: '/admin/courses',
|
courses: '/admin/courses',
|
||||||
@ -193,8 +276,15 @@ const handleMenuSelect = ({ key }: { key: string | number }) => {
|
|||||||
settings: '/admin/settings',
|
settings: '/admin/settings',
|
||||||
};
|
};
|
||||||
|
|
||||||
if (routeMap[keyStr]) {
|
const handleMenuSelect = ({ key }: { key: string | number }) => {
|
||||||
router.push(routeMap[keyStr]);
|
const keyStr = String(key);
|
||||||
|
if (routeMap[keyStr]) router.push(routeMap[keyStr]);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDrawerMenuSelect = ({ key }: { key: string | number }) => {
|
||||||
|
if (routeMap[String(key)]) {
|
||||||
|
router.push(routeMap[String(key)]);
|
||||||
|
drawerVisible.value = false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -395,5 +485,76 @@ $bg-dark: #111827;
|
|||||||
border-radius: 16px;
|
border-radius: 16px;
|
||||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.04);
|
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.04);
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
|
overflow-x: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.admin-content-mobile {
|
||||||
|
margin: 12px;
|
||||||
|
padding: 16px;
|
||||||
|
border-radius: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 移动端抽屉与顶部
|
||||||
|
.admin-drawer {
|
||||||
|
.drawer-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
.drawer-logo {
|
||||||
|
width: 36px;
|
||||||
|
height: 36px;
|
||||||
|
object-fit: contain;
|
||||||
|
}
|
||||||
|
.drawer-title {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: $text-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.drawer-menu {
|
||||||
|
border-right: none !important;
|
||||||
|
padding: 8px 0;
|
||||||
|
:deep(.ant-menu-item) {
|
||||||
|
margin: 4px 8px;
|
||||||
|
border-radius: 8px;
|
||||||
|
height: 48px;
|
||||||
|
line-height: 48px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.admin-mobile-header {
|
||||||
|
flex-shrink: 0;
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
z-index: 100;
|
||||||
|
background: white;
|
||||||
|
padding: 0 16px;
|
||||||
|
height: 56px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.06);
|
||||||
|
border-bottom: 1px solid $border-color;
|
||||||
|
.menu-trigger {
|
||||||
|
font-size: 22px;
|
||||||
|
color: $text-secondary;
|
||||||
|
padding: 8px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.mobile-title {
|
||||||
|
font-size: 17px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: $text-color;
|
||||||
|
}
|
||||||
|
.user-avatar {
|
||||||
|
background: linear-gradient(135deg, $primary-color 0%, $primary-dark 100%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.admin-layout :deep(.ant-layout-sider) {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@ -178,7 +178,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, computed, watch, onMounted, onUnmounted, shallowRef } from 'vue';
|
import { ref, computed, watch, shallowRef } from 'vue';
|
||||||
import { useRouter, useRoute } from 'vue-router';
|
import { useRouter, useRoute } from 'vue-router';
|
||||||
import { message } from 'ant-design-vue';
|
import { message } from 'ant-design-vue';
|
||||||
import {
|
import {
|
||||||
@ -196,15 +196,16 @@ import {
|
|||||||
MenuOutlined,
|
MenuOutlined,
|
||||||
} from '@ant-design/icons-vue';
|
} from '@ant-design/icons-vue';
|
||||||
import { useUserStore } from '@/stores/user';
|
import { useUserStore } from '@/stores/user';
|
||||||
|
import { useBreakpoints } from '@/composables/useBreakpoints';
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const userStore = useUserStore();
|
const userStore = useUserStore();
|
||||||
|
const { isMobile } = useBreakpoints();
|
||||||
|
|
||||||
const collapsed = ref(false);
|
const collapsed = ref(false);
|
||||||
const selectedKeys = ref(['dashboard']);
|
const selectedKeys = ref(['dashboard']);
|
||||||
const notifications = ref(0);
|
const notifications = ref(0);
|
||||||
const isMobile = ref(false);
|
|
||||||
const drawerVisible = ref(false);
|
const drawerVisible = ref(false);
|
||||||
|
|
||||||
const userName = computed(() => userStore.user?.name || '家长');
|
const userName = computed(() => userStore.user?.name || '家长');
|
||||||
@ -232,13 +233,9 @@ const navItems = [
|
|||||||
{ key: 'children', icon: shallowRef(TeamOutlined), text: '孩子' },
|
{ key: 'children', icon: shallowRef(TeamOutlined), text: '孩子' },
|
||||||
];
|
];
|
||||||
|
|
||||||
// 检测屏幕宽度
|
watch(isMobile, (mobile) => {
|
||||||
const checkMobile = () => {
|
if (!mobile) drawerVisible.value = false;
|
||||||
isMobile.value = window.innerWidth < 768;
|
});
|
||||||
if (!isMobile.value) {
|
|
||||||
drawerVisible.value = false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// 根据路由设置选中的菜单
|
// 根据路由设置选中的菜单
|
||||||
watch(
|
watch(
|
||||||
@ -286,14 +283,6 @@ const handleLogout = () => {
|
|||||||
router.push('/login');
|
router.push('/login');
|
||||||
};
|
};
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
checkMobile();
|
|
||||||
window.addEventListener('resize', checkMobile);
|
|
||||||
});
|
|
||||||
|
|
||||||
onUnmounted(() => {
|
|
||||||
window.removeEventListener('resize', checkMobile);
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<a-layout class="school-layout">
|
<a-layout class="school-layout">
|
||||||
<a-layout-sider
|
<a-layout-sider
|
||||||
|
v-if="!isMobile"
|
||||||
v-model:collapsed="collapsed"
|
v-model:collapsed="collapsed"
|
||||||
:trigger="null"
|
:trigger="null"
|
||||||
collapsible
|
collapsible
|
||||||
@ -118,8 +119,58 @@
|
|||||||
</div>
|
</div>
|
||||||
</a-layout-sider>
|
</a-layout-sider>
|
||||||
|
|
||||||
|
<a-drawer
|
||||||
|
v-if="isMobile"
|
||||||
|
v-model:open="drawerVisible"
|
||||||
|
placement="left"
|
||||||
|
:closable="false"
|
||||||
|
:width="280"
|
||||||
|
class="school-drawer"
|
||||||
|
:body-style="{ padding: 0 }"
|
||||||
|
>
|
||||||
|
<template #title>
|
||||||
|
<div class="drawer-header">
|
||||||
|
<img src="/logo.png" alt="Logo" class="drawer-logo" />
|
||||||
|
<span class="drawer-title">管理后台</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<a-menu
|
||||||
|
v-model:selectedKeys="selectedKeys"
|
||||||
|
v-model:openKeys="openKeys"
|
||||||
|
mode="inline"
|
||||||
|
theme="light"
|
||||||
|
@click="handleDrawerMenuClick"
|
||||||
|
class="drawer-menu"
|
||||||
|
>
|
||||||
|
<a-menu-item key="dashboard"><template #icon><DashboardOutlined /></template><span>数据概览</span></a-menu-item>
|
||||||
|
<a-sub-menu key="staff"><template #icon><TeamOutlined /></template><template #title>人员管理</template>
|
||||||
|
<a-menu-item key="teachers"><template #icon><SolutionOutlined /></template><span>教师管理</span></a-menu-item>
|
||||||
|
<a-menu-item key="students"><template #icon><UserOutlined /></template><span>学生管理</span></a-menu-item>
|
||||||
|
<a-menu-item key="parents"><template #icon><IdcardOutlined /></template><span>家长管理</span></a-menu-item>
|
||||||
|
<a-menu-item key="classes"><template #icon><HomeOutlined /></template><span>班级管理</span></a-menu-item>
|
||||||
|
</a-sub-menu>
|
||||||
|
<a-sub-menu key="teaching"><template #icon><BookOutlined /></template><template #title>教学管理</template>
|
||||||
|
<a-menu-item key="courses"><template #icon><ReadOutlined /></template><span>课程管理</span></a-menu-item>
|
||||||
|
<a-menu-item key="school-courses"><template #icon><FolderAddOutlined /></template><span>校本课程包</span></a-menu-item>
|
||||||
|
<a-menu-item key="schedule"><template #icon><CalendarOutlined /></template><span>课程排期</span></a-menu-item>
|
||||||
|
<a-menu-item key="tasks"><template #icon><CheckSquareOutlined /></template><span>阅读任务</span></a-menu-item>
|
||||||
|
<a-menu-item key="task-templates"><template #icon><CopyOutlined /></template><span>任务模板</span></a-menu-item>
|
||||||
|
<a-menu-item key="feedback"><template #icon><MessageOutlined /></template><span>课程反馈</span></a-menu-item>
|
||||||
|
</a-sub-menu>
|
||||||
|
<a-sub-menu key="data"><template #icon><BarChartOutlined /></template><template #title>数据中心</template>
|
||||||
|
<a-menu-item key="reports"><template #icon><FileTextOutlined /></template><span>数据报告</span></a-menu-item>
|
||||||
|
<a-menu-item key="growth"><template #icon><FileImageOutlined /></template><span>成长档案</span></a-menu-item>
|
||||||
|
</a-sub-menu>
|
||||||
|
<a-sub-menu key="system"><template #icon><SettingOutlined /></template><template #title>系统管理</template>
|
||||||
|
<a-menu-item key="packages"><template #icon><GiftOutlined /></template><span>套餐管理</span></a-menu-item>
|
||||||
|
<a-menu-item key="operation-logs"><template #icon><HistoryOutlined /></template><span>操作日志</span></a-menu-item>
|
||||||
|
<a-menu-item key="settings"><template #icon><ToolOutlined /></template><span>系统设置</span></a-menu-item>
|
||||||
|
</a-sub-menu>
|
||||||
|
</a-menu>
|
||||||
|
</a-drawer>
|
||||||
|
|
||||||
<a-layout class="school-layout-right">
|
<a-layout class="school-layout-right">
|
||||||
<a-layout-header class="school-header">
|
<a-layout-header v-if="!isMobile" class="school-header">
|
||||||
<div class="header-left">
|
<div class="header-left">
|
||||||
<MenuUnfoldOutlined
|
<MenuUnfoldOutlined
|
||||||
v-if="collapsed"
|
v-if="collapsed"
|
||||||
@ -165,7 +216,26 @@
|
|||||||
</div>
|
</div>
|
||||||
</a-layout-header>
|
</a-layout-header>
|
||||||
|
|
||||||
<a-layout-content class="school-content">
|
<a-layout-header v-if="isMobile" class="school-mobile-header">
|
||||||
|
<MenuOutlined class="menu-trigger" @click="drawerVisible = true" />
|
||||||
|
<span class="mobile-title">少儿智慧阅读</span>
|
||||||
|
<a-dropdown>
|
||||||
|
<a-space class="user-info" style="cursor: pointer;">
|
||||||
|
<a-avatar :size="32" class="user-avatar">
|
||||||
|
<template #icon><UserOutlined /></template>
|
||||||
|
</a-avatar>
|
||||||
|
</a-space>
|
||||||
|
<template #overlay>
|
||||||
|
<a-menu @click="handleUserMenuClick">
|
||||||
|
<a-menu-item key="profile"><UserOutlined /> 个人信息</a-menu-item>
|
||||||
|
<a-menu-divider />
|
||||||
|
<a-menu-item key="logout"><LogoutOutlined /> 退出登录</a-menu-item>
|
||||||
|
</a-menu>
|
||||||
|
</template>
|
||||||
|
</a-dropdown>
|
||||||
|
</a-layout-header>
|
||||||
|
|
||||||
|
<a-layout-content :class="['school-content', { 'school-content-mobile': isMobile }]">
|
||||||
<router-view />
|
<router-view />
|
||||||
</a-layout-content>
|
</a-layout-content>
|
||||||
</a-layout>
|
</a-layout>
|
||||||
@ -190,6 +260,7 @@ import {
|
|||||||
MessageOutlined,
|
MessageOutlined,
|
||||||
MenuUnfoldOutlined,
|
MenuUnfoldOutlined,
|
||||||
MenuFoldOutlined,
|
MenuFoldOutlined,
|
||||||
|
MenuOutlined,
|
||||||
BellOutlined,
|
BellOutlined,
|
||||||
LogoutOutlined,
|
LogoutOutlined,
|
||||||
DownOutlined,
|
DownOutlined,
|
||||||
@ -204,19 +275,26 @@ import {
|
|||||||
FolderAddOutlined,
|
FolderAddOutlined,
|
||||||
} from '@ant-design/icons-vue';
|
} from '@ant-design/icons-vue';
|
||||||
import { useUserStore } from '@/stores/user';
|
import { useUserStore } from '@/stores/user';
|
||||||
|
import { useBreakpoints } from '@/composables/useBreakpoints';
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const userStore = useUserStore();
|
const userStore = useUserStore();
|
||||||
|
const { isMobile } = useBreakpoints();
|
||||||
|
|
||||||
const collapsed = ref(false);
|
const collapsed = ref(false);
|
||||||
const selectedKeys = ref(['dashboard']);
|
const selectedKeys = ref(['dashboard']);
|
||||||
const openKeys = ref<string[]>(['staff', 'teaching', 'data', 'system']);
|
const openKeys = ref<string[]>(['staff', 'teaching', 'data', 'system']);
|
||||||
const notifications = ref(0);
|
const notifications = ref(0);
|
||||||
|
const drawerVisible = ref(false);
|
||||||
|
|
||||||
const userName = computed(() => userStore.user?.name || '管理员');
|
const userName = computed(() => userStore.user?.name || '管理员');
|
||||||
const tenantName = computed(() => userStore.user?.tenantName || '');
|
const tenantName = computed(() => userStore.user?.tenantName || '');
|
||||||
|
|
||||||
|
watch(isMobile, (mobile) => {
|
||||||
|
if (!mobile) drawerVisible.value = false;
|
||||||
|
});
|
||||||
|
|
||||||
// 路由到菜单的映射关系
|
// 路由到菜单的映射关系
|
||||||
const routeToMenuMap: Record<string, { key: string; parentKey: string }> = {
|
const routeToMenuMap: Record<string, { key: string; parentKey: string }> = {
|
||||||
'/school/teachers': { key: 'teachers', parentKey: 'staff' },
|
'/school/teachers': { key: 'teachers', parentKey: 'staff' },
|
||||||
@ -261,11 +339,22 @@ const handleMenuClick = ({ key }: { key: string | number }) => {
|
|||||||
} else if (keyStr === 'operation-logs') {
|
} else if (keyStr === 'operation-logs') {
|
||||||
router.push('/school/operation-logs');
|
router.push('/school/operation-logs');
|
||||||
} else if (!['staff', 'teaching', 'data', 'system'].includes(keyStr)) {
|
} else if (!['staff', 'teaching', 'data', 'system'].includes(keyStr)) {
|
||||||
// 只有点击叶子菜单项才跳转
|
|
||||||
router.push(`/school/${keyStr}`);
|
router.push(`/school/${keyStr}`);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleDrawerMenuClick = ({ key }: { key: string | number }) => {
|
||||||
|
const keyStr = String(key);
|
||||||
|
if (keyStr === 'packages') {
|
||||||
|
router.push('/school/package');
|
||||||
|
} else if (keyStr === 'operation-logs') {
|
||||||
|
router.push('/school/operation-logs');
|
||||||
|
} else if (!['staff', 'teaching', 'data', 'system'].includes(keyStr)) {
|
||||||
|
router.push(`/school/${keyStr}`);
|
||||||
|
drawerVisible.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const handleUserMenuClick = ({ key }: { key: string | number }) => {
|
const handleUserMenuClick = ({ key }: { key: string | number }) => {
|
||||||
const keyStr = String(key);
|
const keyStr = String(key);
|
||||||
if (keyStr === 'logout') {
|
if (keyStr === 'logout') {
|
||||||
@ -500,5 +589,51 @@ $bg-light: #FAFAFA;
|
|||||||
border-radius: 12px;
|
border-radius: 12px;
|
||||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
|
overflow-x: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.school-content-mobile {
|
||||||
|
margin: 12px;
|
||||||
|
padding: 16px;
|
||||||
|
border-radius: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.school-drawer {
|
||||||
|
.drawer-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
.drawer-logo { width: 36px; height: 36px; object-fit: contain; }
|
||||||
|
.drawer-title { font-size: 16px; font-weight: 600; color: $text-color; }
|
||||||
|
}
|
||||||
|
.drawer-menu {
|
||||||
|
border-right: none !important;
|
||||||
|
padding: 8px 0;
|
||||||
|
:deep(.ant-menu-item),
|
||||||
|
:deep(.ant-menu-submenu-title) { margin: 4px 8px; border-radius: 8px; height: 48px; line-height: 48px; }
|
||||||
|
:deep(.ant-menu-sub) .ant-menu-item { height: 44px; line-height: 44px; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.school-mobile-header {
|
||||||
|
flex-shrink: 0;
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
z-index: 100;
|
||||||
|
background: white;
|
||||||
|
padding: 0 16px;
|
||||||
|
height: 56px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.06);
|
||||||
|
border-bottom: 1px solid $border-color;
|
||||||
|
.menu-trigger { font-size: 22px; color: $text-secondary; padding: 8px; cursor: pointer; }
|
||||||
|
.mobile-title { font-size: 17px; font-weight: 600; color: $text-color; }
|
||||||
|
.user-avatar { background: linear-gradient(135deg, $primary-color 0%, $primary-dark 100%); }
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.school-layout :deep(.ant-layout-sider) { display: none; }
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<a-layout class="teacher-layout">
|
<a-layout class="teacher-layout">
|
||||||
<a-layout-sider
|
<a-layout-sider
|
||||||
|
v-if="!isMobile"
|
||||||
v-model:collapsed="collapsed"
|
v-model:collapsed="collapsed"
|
||||||
:trigger="null"
|
:trigger="null"
|
||||||
collapsible
|
collapsible
|
||||||
@ -64,8 +65,42 @@
|
|||||||
</div>
|
</div>
|
||||||
</a-layout-sider>
|
</a-layout-sider>
|
||||||
|
|
||||||
|
<a-drawer
|
||||||
|
v-if="isMobile"
|
||||||
|
v-model:open="drawerVisible"
|
||||||
|
placement="left"
|
||||||
|
:closable="false"
|
||||||
|
:width="280"
|
||||||
|
class="teacher-drawer"
|
||||||
|
:body-style="{ padding: 0 }"
|
||||||
|
>
|
||||||
|
<template #title>
|
||||||
|
<div class="drawer-header">
|
||||||
|
<img src="/logo.png" alt="Logo" class="drawer-logo" />
|
||||||
|
<span class="drawer-title">服务平台</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<a-menu
|
||||||
|
v-model:selectedKeys="selectedKeys"
|
||||||
|
mode="inline"
|
||||||
|
theme="light"
|
||||||
|
@click="handleDrawerMenuClick"
|
||||||
|
class="drawer-menu"
|
||||||
|
>
|
||||||
|
<a-menu-item key="dashboard"><template #icon><HomeOutlined /></template><span>首页</span></a-menu-item>
|
||||||
|
<a-menu-item key="classes"><template #icon><TeamOutlined /></template><span>我的班级</span></a-menu-item>
|
||||||
|
<a-menu-item key="courses"><template #icon><BookOutlined /></template><span>课程中心</span></a-menu-item>
|
||||||
|
<a-menu-item key="school-courses"><template #icon><FolderAddOutlined /></template><span>校本课程包</span></a-menu-item>
|
||||||
|
<a-menu-item key="lessons"><template #icon><CalendarOutlined /></template><span>上课记录</span></a-menu-item>
|
||||||
|
<a-menu-item key="schedule"><template #icon><ScheduleOutlined /></template><span>我的课表</span></a-menu-item>
|
||||||
|
<a-menu-item key="tasks"><template #icon><CheckSquareOutlined /></template><span>阅读任务</span></a-menu-item>
|
||||||
|
<a-menu-item key="feedback"><template #icon><FileTextOutlined /></template><span>课程反馈</span></a-menu-item>
|
||||||
|
<a-menu-item key="growth"><template #icon><CameraOutlined /></template><span>成长档案</span></a-menu-item>
|
||||||
|
</a-menu>
|
||||||
|
</a-drawer>
|
||||||
|
|
||||||
<a-layout class="teacher-layout-right">
|
<a-layout class="teacher-layout-right">
|
||||||
<a-layout-header class="teacher-header">
|
<a-layout-header v-if="!isMobile" class="teacher-header">
|
||||||
<div class="header-left">
|
<div class="header-left">
|
||||||
<MenuUnfoldOutlined
|
<MenuUnfoldOutlined
|
||||||
v-if="collapsed"
|
v-if="collapsed"
|
||||||
@ -111,7 +146,26 @@
|
|||||||
</div>
|
</div>
|
||||||
</a-layout-header>
|
</a-layout-header>
|
||||||
|
|
||||||
<a-layout-content class="teacher-content">
|
<a-layout-header v-if="isMobile" class="teacher-mobile-header">
|
||||||
|
<MenuOutlined class="menu-trigger" @click="drawerVisible = true" />
|
||||||
|
<span class="mobile-title">少儿智慧阅读</span>
|
||||||
|
<a-dropdown>
|
||||||
|
<a-space class="user-info" style="cursor: pointer;">
|
||||||
|
<a-avatar :size="32" class="user-avatar">
|
||||||
|
<template #icon><UserOutlined /></template>
|
||||||
|
</a-avatar>
|
||||||
|
</a-space>
|
||||||
|
<template #overlay>
|
||||||
|
<a-menu @click="handleUserMenuClick">
|
||||||
|
<a-menu-item key="profile"><UserOutlined /> 个人信息</a-menu-item>
|
||||||
|
<a-menu-divider />
|
||||||
|
<a-menu-item key="logout"><LogoutOutlined /> 退出登录</a-menu-item>
|
||||||
|
</a-menu>
|
||||||
|
</template>
|
||||||
|
</a-dropdown>
|
||||||
|
</a-layout-header>
|
||||||
|
|
||||||
|
<a-layout-content :class="['teacher-content', { 'teacher-content-mobile': isMobile }]">
|
||||||
<router-view />
|
<router-view />
|
||||||
</a-layout-content>
|
</a-layout-content>
|
||||||
</a-layout>
|
</a-layout>
|
||||||
@ -130,6 +184,7 @@ import {
|
|||||||
FileTextOutlined,
|
FileTextOutlined,
|
||||||
MenuUnfoldOutlined,
|
MenuUnfoldOutlined,
|
||||||
MenuFoldOutlined,
|
MenuFoldOutlined,
|
||||||
|
MenuOutlined,
|
||||||
BellOutlined,
|
BellOutlined,
|
||||||
UserOutlined,
|
UserOutlined,
|
||||||
LogoutOutlined,
|
LogoutOutlined,
|
||||||
@ -140,18 +195,25 @@ import {
|
|||||||
FolderAddOutlined,
|
FolderAddOutlined,
|
||||||
} from '@ant-design/icons-vue';
|
} from '@ant-design/icons-vue';
|
||||||
import { useUserStore } from '@/stores/user';
|
import { useUserStore } from '@/stores/user';
|
||||||
|
import { useBreakpoints } from '@/composables/useBreakpoints';
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const userStore = useUserStore();
|
const userStore = useUserStore();
|
||||||
|
const { isMobile } = useBreakpoints();
|
||||||
|
|
||||||
const collapsed = ref(false);
|
const collapsed = ref(false);
|
||||||
const selectedKeys = ref(['dashboard']);
|
const selectedKeys = ref(['dashboard']);
|
||||||
const notifications = ref(0);
|
const notifications = ref(0);
|
||||||
|
const drawerVisible = ref(false);
|
||||||
|
|
||||||
const userName = computed(() => userStore.user?.name || '教师');
|
const userName = computed(() => userStore.user?.name || '教师');
|
||||||
const tenantName = computed(() => userStore.user?.tenantName || '');
|
const tenantName = computed(() => userStore.user?.tenantName || '');
|
||||||
|
|
||||||
|
watch(isMobile, (mobile) => {
|
||||||
|
if (!mobile) drawerVisible.value = false;
|
||||||
|
});
|
||||||
|
|
||||||
// 根据路由设置选中的菜单
|
// 根据路由设置选中的菜单
|
||||||
watch(
|
watch(
|
||||||
() => route.path,
|
() => route.path,
|
||||||
@ -183,6 +245,13 @@ const handleMenuClick = ({ key }: { key: string | number }) => {
|
|||||||
router.push(`/teacher/${key}`);
|
router.push(`/teacher/${key}`);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleDrawerMenuClick = ({ key }: { key: string | number }) => {
|
||||||
|
const keyStr = String(key);
|
||||||
|
if (!['dashboard', 'classes', 'courses', 'school-courses', 'lessons', 'schedule', 'tasks', 'feedback', 'growth'].includes(keyStr)) return;
|
||||||
|
router.push(`/teacher/${keyStr}`);
|
||||||
|
drawerVisible.value = false;
|
||||||
|
};
|
||||||
|
|
||||||
const handleUserMenuClick = ({ key }: { key: string | number }) => {
|
const handleUserMenuClick = ({ key }: { key: string | number }) => {
|
||||||
const keyStr = String(key);
|
const keyStr = String(key);
|
||||||
if (keyStr === 'logout') {
|
if (keyStr === 'logout') {
|
||||||
@ -380,5 +449,49 @@ $bg-light: #FAFAFA;
|
|||||||
border-radius: 12px;
|
border-radius: 12px;
|
||||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
|
overflow-x: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.teacher-content-mobile {
|
||||||
|
margin: 12px;
|
||||||
|
padding: 16px;
|
||||||
|
border-radius: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.teacher-drawer {
|
||||||
|
.drawer-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
.drawer-logo { width: 36px; height: 36px; object-fit: contain; }
|
||||||
|
.drawer-title { font-size: 16px; font-weight: 600; color: $text-color; }
|
||||||
|
}
|
||||||
|
.drawer-menu {
|
||||||
|
border-right: none !important;
|
||||||
|
padding: 8px 0;
|
||||||
|
:deep(.ant-menu-item) { margin: 4px 8px; border-radius: 8px; height: 48px; line-height: 48px; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.teacher-mobile-header {
|
||||||
|
flex-shrink: 0;
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
z-index: 100;
|
||||||
|
background: white;
|
||||||
|
padding: 0 16px;
|
||||||
|
height: 56px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.06);
|
||||||
|
border-bottom: 1px solid $border-color;
|
||||||
|
.menu-trigger { font-size: 22px; color: $text-secondary; padding: 8px; cursor: pointer; }
|
||||||
|
.mobile-title { font-size: 17px; font-weight: 600; color: $text-color; }
|
||||||
|
.user-avatar { background: linear-gradient(135deg, $primary-color 0%, $primary-dark 100%); }
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.teacher-layout :deep(.ant-layout-sider) { display: none; }
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user