hookehuyr

refactor(layout): 移除遗留布局目录并更新相关文档

- 删除 src/layouts/AppLayout.vue,统一使用 src/components/layout/AppLayout.vue
- 从组件类型声明中移除 VanTabbar 和 VanTabbarItem
- 更新 myClassPage.vue 导入以使用新的布局路径
- 同步更新 README、COMPONENTS 和 VUE_CODE_STYLE_GUIDE 中的文档说明
......@@ -53,10 +53,9 @@ src/
- 文档与代码存在不一致:README/CLAUDE 中的组件目录、路径描述与实际目录不一致
- 依赖与工程规范不够统一:同时存在 pnpm/yarn/npm 的 lock 文件,容易引起依赖漂移
- 构建产物体积偏大:首屏 main chunk 已压到 500kB 内;仍有 video.js / pdf.js 等功能性依赖 chunk 超 500kB,但仅在对应功能触发时按需加载
- 状态来源较多:localStorage + contexts + axios 默认头并存,一致性风险偏高
- 全局屏蔽 warnHandler:[/src/main.js](file:///Users/huyirui/program/itomix/git/mlaj/src/main.js) 会吞掉 Vue 警告,可能掩盖潜在问题
- 目录存在历史遗留:[/src/layouts](file:///Users/huyirui/program/itomix/git/mlaj/src/layouts)[/src/components/layout](file:///Users/huyirui/program/itomix/git/mlaj/src/components/layout) 并存,建议后续清理归一
- 布局目录已归一:统一使用 [/src/components/layout](file:///Users/huyirui/program/itomix/git/mlaj/src/components/layout),已移除 /src/layouts
## 文档索引
......
......@@ -204,7 +204,7 @@ Vue 官方建议在 SFC + Composition API 场景使用 `<script setup>`,因为
4) 清理“重复/并存”的实现:避免同名组件在不同目录各自演进
- 目标:减少认知负担与误用风险(例如存在两个 `AppLayout`
- 涉及文件:[layouts/AppLayout.vue](file:///Users/huyirui/program/itomix/git/mlaj/src/layouts/AppLayout.vue)[components/layout/AppLayout.vue](file:///Users/huyirui/program/itomix/git/mlaj/src/components/layout/AppLayout.vue)
- 涉及文件:[AppLayout.vue](file:///Users/huyirui/program/itomix/git/mlaj/src/components/layout/AppLayout.vue)(已完成归一,移除 /src/layouts)
5) 降低“语法形态”混用成本:能不用 JSX 就别用
......
......@@ -22,4 +22,4 @@
## 备注
- 历史遗留目录:[/src/layouts](file:///Users/huyirui/program/itomix/git/mlaj/src/layouts)[/src/components/layout](file:///Users/huyirui/program/itomix/git/mlaj/src/components/layout) 并存,建议后续合并归一
- 布局目录已归一:统一使用 [/src/components/layout](file:///Users/huyirui/program/itomix/git/mlaj/src/components/layout),已移除 /src/layouts
......
......@@ -95,8 +95,6 @@ declare module 'vue' {
VanSwipe: typeof import('vant/es')['Swipe']
VanSwipeItem: typeof import('vant/es')['SwipeItem']
VanTab: typeof import('vant/es')['Tab']
VanTabbar: typeof import('vant/es')['Tabbar']
VanTabbarItem: typeof import('vant/es')['TabbarItem']
VanTabs: typeof import('vant/es')['Tabs']
VanTag: typeof import('vant/es')['Tag']
VanTimePicker: typeof import('vant/es')['TimePicker']
......
<!-- src/layouts/AppLayout.vue -->
<template>
<div class="app-layout">
<!-- Header -->
<header class="app-header" v-if="hasTitle">
<div v-if="showBack" class="header-back" @click="goBack">
<van-icon name="arrow-left" size="20" />
</div>
<h1 class="header-title">{{ title }}</h1>
<div class="header-right">
<slot name="header-right"></slot>
</div>
</header>
<!-- Main Content -->
<main class="app-content" :class="{ 'has-bottom-nav': showBottomNav, 'no-header': !hasTitle }">
<slot></slot>
</main>
<!-- Bottom Navigation -->
<van-tabbar v-if="showBottomNav" route safe-area-inset-bottom>
<van-tabbar-item to="/home" icon="home-o">首页</van-tabbar-item>
<van-tabbar-item to="/courses" icon="orders-o">课程</van-tabbar-item>
<van-tabbar-item to="/activities" icon="friends-o">活动</van-tabbar-item>
<van-tabbar-item to="/community" icon="chat-o">社区</van-tabbar-item>
<van-tabbar-item to="/profile" icon="user-o">我的</van-tabbar-item>
</van-tabbar>
</div>
</template>
<script>
import { ref, onMounted, computed } from 'vue'
import { useRouter, useRoute } from 'vue-router'
export default {
name: 'AppLayout',
props: {
title: {
type: String,
default: '美乐爱觉教育'
},
showBack: {
type: Boolean,
default: false
},
showBottomNav: {
type: Boolean,
default: true
},
hasTitle: {
type: Boolean,
default: true
},
},
setup(props) {
const router = useRouter()
const route = useRoute()
const goBack = () => {
if (window.history.length > 1) {
router.back()
} else {
router.push('/')
}
}
return {
goBack,
}
}
}
</script>
<style scoped>
.app-layout {
display: flex;
flex-direction: column;
min-height: 100vh;
background-color: var(--background-color);
}
.app-header {
position: sticky;
top: 0;
z-index: 100;
display: flex;
align-items: center;
justify-content: center;
height: 46px;
padding: 0 16px;
background-color: var(--white);
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.1);
}
.header-back {
position: absolute;
left: 16px;
display: flex;
align-items: center;
justify-content: center;
}
.header-title {
font-size: 18px;
font-weight: 600;
margin: 0;
}
.header-right {
position: absolute;
right: 16px;
display: flex;
align-items: center;
}
.app-content {
flex: 1;
overflow-y: auto;
padding-bottom: 20px;
-webkit-overflow-scrolling: touch;
}
.app-content.has-bottom-nav {
padding-bottom: 50px;
}
.app-content.no-header {
padding-top: 0;
}
</style>
......@@ -166,8 +166,7 @@
<script setup>
import { ref, computed, onMounted, onUnmounted } from 'vue'
import { useRouter } from 'vue-router'
import AppLayout from '@/layouts/AppLayout.vue'
import { useRouter, useRoute } from 'vue-router'
import { useTitle } from '@vueuse/core';
import { useAuth } from '@/contexts/auth'
import CourseGroupCascader from '@/components/courses/CourseGroupCascader.vue'
......