# 解班客前端开发文档 ## 📋 概述 本文档详细介绍解班客项目前端开发的技术架构、组件设计、开发规范和最佳实践。前端采用Vue.js 3 + TypeScript + Element Plus技术栈,提供现代化的用户界面和良好的用户体验。 ## 🏗️ 技术架构 ### 核心技术栈 #### 基础框架 - **Vue.js 3.4+** - 渐进式JavaScript框架 - **TypeScript 5.0+** - 类型安全的JavaScript超集 - **Vite 5.0+** - 现代化构建工具 - **Vue Router 4** - 官方路由管理器 - **Pinia** - 状态管理库 #### UI组件库 - **Element Plus** - 基于Vue 3的组件库 - **@element-plus/icons-vue** - Element Plus图标库 - **Tailwind CSS** - 原子化CSS框架 - **SCSS** - CSS预处理器 #### 工具库 - **Axios** - HTTP客户端 - **Day.js** - 轻量级日期处理库 - **VueUse** - Vue组合式API工具集 - **Lodash-es** - JavaScript工具库 - **@vueuse/core** - Vue组合式函数集合 #### 开发工具 - **ESLint** - 代码检查工具 - **Prettier** - 代码格式化工具 - **Husky** - Git钩子工具 - **Lint-staged** - 暂存文件检查 - **Commitizen** - 规范化提交工具 ### 项目结构 ``` frontend/ ├── public/ # 静态资源 │ ├── favicon.ico │ └── index.html ├── src/ │ ├── api/ # API接口 │ │ ├── modules/ # 按模块分类的API │ │ │ ├── auth.ts # 认证相关API │ │ │ ├── user.ts # 用户相关API │ │ │ ├── animal.ts # 动物相关API │ │ │ └── adoption.ts # 认领相关API │ │ ├── request.ts # 请求拦截器 │ │ └── types.ts # API类型定义 │ ├── assets/ # 静态资源 │ │ ├── images/ # 图片资源 │ │ ├── icons/ # 图标资源 │ │ └── styles/ # 全局样式 │ │ ├── index.scss # 主样式文件 │ │ ├── variables.scss # SCSS变量 │ │ └── mixins.scss # SCSS混入 │ ├── components/ # 公共组件 │ │ ├── common/ # 通用组件 │ │ │ ├── AppHeader.vue # 应用头部 │ │ │ ├── AppFooter.vue # 应用底部 │ │ │ ├── Loading.vue # 加载组件 │ │ │ └── Pagination.vue # 分页组件 │ │ └── business/ # 业务组件 │ │ ├── AnimalCard.vue # 动物卡片 │ │ ├── UserAvatar.vue # 用户头像 │ │ └── MapView.vue # 地图组件 │ ├── composables/ # 组合式函数 │ │ ├── useAuth.ts # 认证相关 │ │ ├── useApi.ts # API调用 │ │ ├── useForm.ts # 表单处理 │ │ └── useMap.ts # 地图功能 │ ├── layouts/ # 布局组件 │ │ ├── DefaultLayout.vue # 默认布局 │ │ ├── AuthLayout.vue # 认证布局 │ │ └── AdminLayout.vue # 管理布局 │ ├── pages/ # 页面组件 │ │ ├── home/ # 首页 │ │ ├── auth/ # 认证页面 │ │ ├── animal/ # 动物相关页面 │ │ ├── user/ # 用户相关页面 │ │ └── adoption/ # 认领相关页面 │ ├── router/ # 路由配置 │ │ ├── index.ts # 主路由文件 │ │ ├── guards.ts # 路由守卫 │ │ └── routes.ts # 路由定义 │ ├── stores/ # 状态管理 │ │ ├── modules/ # 按模块分类的store │ │ │ ├── auth.ts # 认证状态 │ │ │ ├── user.ts # 用户状态 │ │ │ └── animal.ts # 动物状态 │ │ └── index.ts # Store入口 │ ├── types/ # 类型定义 │ │ ├── api.ts # API类型 │ │ ├── user.ts # 用户类型 │ │ ├── animal.ts # 动物类型 │ │ └── common.ts # 通用类型 │ ├── utils/ # 工具函数 │ │ ├── auth.ts # 认证工具 │ │ ├── format.ts # 格式化工具 │ │ ├── validate.ts # 验证工具 │ │ └── constants.ts # 常量定义 │ ├── App.vue # 根组件 │ └── main.ts # 应用入口 ├── .env.development # 开发环境变量 ├── .env.production # 生产环境变量 ├── .eslintrc.js # ESLint配置 ├── .prettierrc # Prettier配置 ├── index.html # HTML模板 ├── package.json # 项目配置 ├── tsconfig.json # TypeScript配置 └── vite.config.ts # Vite配置 ``` ## 🎨 UI设计规范 ### 设计系统 #### 色彩规范 ```scss // 主色调 $primary-color: #409EFF; // 主要品牌色 $success-color: #67C23A; // 成功色 $warning-color: #E6A23C; // 警告色 $danger-color: #F56C6C; // 危险色 $info-color: #909399; // 信息色 // 中性色 $text-primary: #303133; // 主要文字 $text-regular: #606266; // 常规文字 $text-secondary: #909399; // 次要文字 $text-placeholder: #C0C4CC; // 占位文字 // 边框色 $border-base: #DCDFE6; // 基础边框 $border-light: #E4E7ED; // 浅色边框 $border-lighter: #EBEEF5; // 更浅边框 $border-extra-light: #F2F6FC; // 极浅边框 // 背景色 $bg-color: #FFFFFF; // 基础背景 $bg-page: #F2F3F5; // 页面背景 $bg-overlay: rgba(0,0,0,0.8); // 遮罩背景 ``` #### 字体规范 ```scss // 字体大小 $font-size-extra-large: 20px; // 超大字体 $font-size-large: 18px; // 大字体 $font-size-medium: 16px; // 中等字体 $font-size-base: 14px; // 基础字体 $font-size-small: 13px; // 小字体 $font-size-extra-small: 12px; // 超小字体 // 字体粗细 $font-weight-primary: 500; // 主要字重 $font-weight-secondary: 400; // 次要字重 // 行高 $line-height-primary: 24px; // 主要行高 $line-height-secondary: 16px; // 次要行高 ``` #### 间距规范 ```scss // 间距系统 (8px基准) $spacing-xs: 4px; // 超小间距 $spacing-sm: 8px; // 小间距 $spacing-md: 16px; // 中等间距 $spacing-lg: 24px; // 大间距 $spacing-xl: 32px; // 超大间距 $spacing-xxl: 48px; // 极大间距 ``` ### 组件设计原则 #### 1. 一致性原则 - 保持视觉风格统一 - 交互行为一致 - 命名规范统一 #### 2. 可访问性原则 - 支持键盘导航 - 提供语义化标签 - 考虑屏幕阅读器 #### 3. 响应式原则 - 移动端优先设计 - 断点适配 - 弹性布局 ## 🧩 组件开发规范 ### 组件命名规范 #### 文件命名 ``` // ✅ 正确 - 使用PascalCase AnimalCard.vue UserProfile.vue SearchForm.vue // ❌ 错误 animalCard.vue user-profile.vue searchform.vue ``` #### 组件注册 ```typescript // ✅ 正确 - 组件名使用PascalCase export default defineComponent({ name: 'AnimalCard', // ... }) // 全局注册 app.component('AnimalCard', AnimalCard) ``` ### 组件结构规范 #### 标准组件模板 ```vue ``` ### Props和Emits规范 #### Props定义 ```typescript // ✅ 使用TypeScript接口定义Props interface Props { // 必需属性 userId: number // 可选属性 showAvatar?: boolean // 带默认值的属性 size?: 'small' | 'medium' | 'large' // 复杂类型 user?: User | null // 数组类型 tags?: string[] // 函数类型 onUpdate?: (value: string) => void } // 设置默认值 const props = withDefaults(defineProps(), { showAvatar: true, size: 'medium', user: null, tags: () => [], onUpdate: undefined }) ``` #### Emits定义 ```typescript // ✅ 使用TypeScript接口定义Emits interface Emits { // 简单事件 close: [] // 带参数的事件 update: [value: string] // 多参数事件 change: [id: number, value: string, meta?: any] // 对象参数事件 submit: [data: { name: string; email: string }] } const emit = defineEmits() // 触发事件 const handleSubmit = () => { emit('submit', { name: 'John', email: 'john@example.com' }) } ``` ## 🔄 状态管理 ### Pinia Store设计 #### Store结构 ```typescript // stores/modules/auth.ts import { defineStore } from 'pinia' import { ref, computed } from 'vue' import type { User, LoginForm, RegisterForm } from '@/types/user' import { authApi } from '@/api/modules/auth' export const useAuthStore = defineStore('auth', () => { // State const user = ref(null) const token = ref(localStorage.getItem('token')) const loading = ref(false) // Getters const isAuthenticated = computed(() => !!token.value && !!user.value) const userRole = computed(() => user.value?.role || 'guest') const permissions = computed(() => user.value?.permissions || []) // Actions const login = async (form: LoginForm) => { loading.value = true try { const response = await authApi.login(form) token.value = response.token user.value = response.user // 保存到localStorage localStorage.setItem('token', response.token) localStorage.setItem('user', JSON.stringify(response.user)) return response } catch (error) { console.error('Login failed:', error) throw error } finally { loading.value = false } } const register = async (form: RegisterForm) => { loading.value = true try { const response = await authApi.register(form) return response } catch (error) { console.error('Register failed:', error) throw error } finally { loading.value = false } } const logout = async () => { try { await authApi.logout() } catch (error) { console.error('Logout failed:', error) } finally { // 清除本地数据 token.value = null user.value = null localStorage.removeItem('token') localStorage.removeItem('user') } } const fetchUserInfo = async () => { if (!token.value) return try { const response = await authApi.getUserInfo() user.value = response.user localStorage.setItem('user', JSON.stringify(response.user)) } catch (error) { console.error('Fetch user info failed:', error) // 如果获取用户信息失败,可能token已过期 logout() } } const updateProfile = async (data: Partial) => { try { const response = await authApi.updateProfile(data) user.value = { ...user.value, ...response.user } localStorage.setItem('user', JSON.stringify(user.value)) return response } catch (error) { console.error('Update profile failed:', error) throw error } } // 初始化 const init = () => { const savedUser = localStorage.getItem('user') if (savedUser && token.value) { try { user.value = JSON.parse(savedUser) // 验证token有效性 fetchUserInfo() } catch (error) { console.error('Parse saved user failed:', error) logout() } } } return { // State user, token, loading, // Getters isAuthenticated, userRole, permissions, // Actions login, register, logout, fetchUserInfo, updateProfile, init } }) ``` ### 组合式函数 (Composables) #### 认证相关 ```typescript // composables/useAuth.ts import { computed } from 'vue' import { useRouter } from 'vue-router' import { useAuthStore } from '@/stores/modules/auth' import { ElMessage } from 'element-plus' export function useAuth() { const authStore = useAuthStore() const router = useRouter() // 计算属性 const isLoggedIn = computed(() => authStore.isAuthenticated) const currentUser = computed(() => authStore.user) const userRole = computed(() => authStore.userRole) // 登录方法 const login = async (form: LoginForm) => { try { await authStore.login(form) ElMessage.success('登录成功') // 重定向到之前的页面或首页 const redirect = router.currentRoute.value.query.redirect as string router.push(redirect || '/') } catch (error) { ElMessage.error('登录失败,请检查用户名和密码') throw error } } // 登出方法 const logout = async () => { try { await authStore.logout() ElMessage.success('已退出登录') router.push('/login') } catch (error) { ElMessage.error('退出登录失败') } } // 权限检查 const hasPermission = (permission: string) => { return authStore.permissions.includes(permission) } const hasRole = (role: string) => { return authStore.userRole === role } // 需要登录的操作 const requireAuth = (callback: () => void) => { if (isLoggedIn.value) { callback() } else { ElMessage.warning('请先登录') router.push('/login') } } return { isLoggedIn, currentUser, userRole, login, logout, hasPermission, hasRole, requireAuth } } ``` #### API调用 ```typescript // composables/useApi.ts import { ref, unref } from 'vue' import type { Ref } from 'vue' import { ElMessage } from 'element-plus' interface UseApiOptions { immediate?: boolean showError?: boolean showSuccess?: boolean successMessage?: string } export function useApi( apiFunction: (params?: P) => Promise, options: UseApiOptions = {} ) { const { immediate = false, showError = true, showSuccess = false, successMessage = '操作成功' } = options const data = ref(null) const loading = ref(false) const error = ref(null) const execute = async (params?: P) => { loading.value = true error.value = null try { const result = await apiFunction(params) data.value = result if (showSuccess) { ElMessage.success(successMessage) } return result } catch (err) { error.value = err as Error if (showError) { ElMessage.error(err.message || '操作失败') } throw err } finally { loading.value = false } } // 立即执行 if (immediate) { execute() } return { data, loading, error, execute } } // 使用示例 export function useAnimalList() { const { data: animals, loading, execute: fetchAnimals } = useApi( animalApi.getList, { immediate: true, showError: true } ) const { execute: deleteAnimal } = useApi( animalApi.delete, { showSuccess: true, successMessage: '删除成功' } ) return { animals, loading, fetchAnimals, deleteAnimal } } ``` ## 🛣️ 路由设计 ### 路由配置 ```typescript // router/routes.ts import type { RouteRecordRaw } from 'vue-router' export const routes: RouteRecordRaw[] = [ { path: '/', name: 'Home', component: () => import('@/layouts/DefaultLayout.vue'), children: [ { path: '', name: 'HomePage', component: () => import('@/pages/home/HomePage.vue'), meta: { title: '首页', requiresAuth: false } }, { path: '/animals', name: 'AnimalList', component: () => import('@/pages/animal/AnimalList.vue'), meta: { title: '动物列表', requiresAuth: false } }, { path: '/animals/:id', name: 'AnimalDetail', component: () => import('@/pages/animal/AnimalDetail.vue'), meta: { title: '动物详情', requiresAuth: false } } ] }, { path: '/auth', component: () => import('@/layouts/AuthLayout.vue'), children: [ { path: 'login', name: 'Login', component: () => import('@/pages/auth/Login.vue'), meta: { title: '登录', requiresAuth: false, hideForAuth: true } }, { path: 'register', name: 'Register', component: () => import('@/pages/auth/Register.vue'), meta: { title: '注册', requiresAuth: false, hideForAuth: true } } ] }, { path: '/user', component: () => import('@/layouts/DefaultLayout.vue'), meta: { requiresAuth: true }, children: [ { path: 'profile', name: 'UserProfile', component: () => import('@/pages/user/Profile.vue'), meta: { title: '个人资料' } }, { path: 'animals', name: 'UserAnimals', component: () => import('@/pages/user/Animals.vue'), meta: { title: '我的动物' } }, { path: 'adoptions', name: 'UserAdoptions', component: () => import('@/pages/user/Adoptions.vue'), meta: { title: '我的认领' } } ] }, { path: '/admin', component: () => import('@/layouts/AdminLayout.vue'), meta: { requiresAuth: true, requiresRole: 'admin' }, children: [ { path: '', name: 'AdminDashboard', component: () => import('@/pages/admin/Dashboard.vue'), meta: { title: '管理后台' } } ] }, { path: '/:pathMatch(.*)*', name: 'NotFound', component: () => import('@/pages/error/NotFound.vue'), meta: { title: '页面不存在' } } ] ``` ### 路由守卫 ```typescript // router/guards.ts import type { Router } from 'vue-router' import { useAuthStore } from '@/stores/modules/auth' import { ElMessage } from 'element-plus' export function setupRouterGuards(router: Router) { // 全局前置守卫 router.beforeEach(async (to, from, next) => { const authStore = useAuthStore() // 设置页面标题 if (to.meta.title) { document.title = `${to.meta.title} - 解班客` } // 检查是否需要认证 if (to.meta.requiresAuth) { if (!authStore.isAuthenticated) { ElMessage.warning('请先登录') next({ name: 'Login', query: { redirect: to.fullPath } }) return } // 检查角色权限 if (to.meta.requiresRole) { if (authStore.userRole !== to.meta.requiresRole) { ElMessage.error('权限不足') next({ name: 'Home' }) return } } // 检查具体权限 if (to.meta.requiresPermission) { if (!authStore.permissions.includes(to.meta.requiresPermission)) { ElMessage.error('权限不足') next({ name: 'Home' }) return } } } // 已登录用户访问登录/注册页面时重定向 if (to.meta.hideForAuth && authStore.isAuthenticated) { next({ name: 'Home' }) return } next() }) // 全局后置钩子 router.afterEach((to, from) => { // 页面切换后的处理 // 例如:埋点统计、页面加载完成事件等 }) } ``` ## 🔧 工具函数 ### 格式化工具 ```typescript // utils/format.ts import dayjs from 'dayjs' import 'dayjs/locale/zh-cn' import relativeTime from 'dayjs/plugin/relativeTime' dayjs.locale('zh-cn') dayjs.extend(relativeTime) /** * 格式化日期 */ export const formatDate = ( date: string | number | Date, format = 'YYYY-MM-DD HH:mm:ss' ): string => { return dayjs(date).format(format) } /** * 格式化相对时间 */ export const formatRelativeTime = (date: string | number | Date): string => { return dayjs(date).fromNow() } /** * 格式化文件大小 */ export const formatFileSize = (bytes: number): string => { if (bytes === 0) return '0 B' const k = 1024 const sizes = ['B', 'KB', 'MB', 'GB', 'TB'] const i = Math.floor(Math.log(bytes) / Math.log(k)) return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i] } /** * 格式化数字 */ export const formatNumber = (num: number): string => { return num.toLocaleString('zh-CN') } /** * 格式化手机号 */ export const formatPhone = (phone: string): string => { return phone.replace(/(\d{3})(\d{4})(\d{4})/, '$1****$3') } /** * 格式化金额 */ export const formatMoney = (amount: number): string => { return `¥${amount.toFixed(2)}` } ``` ### 验证工具 ```typescript // utils/validate.ts /** * 验证邮箱 */ export const isEmail = (email: string): boolean => { const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/ return emailRegex.test(email) } /** * 验证手机号 */ export const isPhone = (phone: string): boolean => { const phoneRegex = /^1[3-9]\d{9}$/ return phoneRegex.test(phone) } /** * 验证身份证号 */ export const isIdCard = (idCard: string): boolean => { const idCardRegex = /(^\d{15}$)|(^\d{18}$)|(^\d{17}(\d|X|x)$)/ return idCardRegex.test(idCard) } /** * 验证密码强度 */ export const validatePassword = (password: string): { isValid: boolean strength: 'weak' | 'medium' | 'strong' message: string } => { if (password.length < 8) { return { isValid: false, strength: 'weak', message: '密码长度至少8位' } } let score = 0 // 包含小写字母 if (/[a-z]/.test(password)) score++ // 包含大写字母 if (/[A-Z]/.test(password)) score++ // 包含数字 if (/\d/.test(password)) score++ // 包含特殊字符 if (/[!@#$%^&*(),.?":{}|<>]/.test(password)) score++ if (score < 2) { return { isValid: false, strength: 'weak', message: '密码强度太弱,请包含字母和数字' } } else if (score < 3) { return { isValid: true, strength: 'medium', message: '密码强度中等' } } else { return { isValid: true, strength: 'strong', message: '密码强度很强' } } } /** * 表单验证规则 */ export const validationRules = { required: { required: true, message: '此字段为必填项', trigger: 'blur' }, email: { validator: (rule: any, value: string, callback: Function) => { if (value && !isEmail(value)) { callback(new Error('请输入正确的邮箱地址')) } else { callback() } }, trigger: 'blur' }, phone: { validator: (rule: any, value: string, callback: Function) => { if (value && !isPhone(value)) { callback(new Error('请输入正确的手机号')) } else { callback() } }, trigger: 'blur' }, password: { validator: (rule: any, value: string, callback: Function) => { const result = validatePassword(value) if (!result.isValid) { callback(new Error(result.message)) } else { callback() } }, trigger: 'blur' } } ``` ## 🎯 性能优化 ### 代码分割 ```typescript // 路由懒加载 const routes = [ { path: '/animals', component: () => import('@/pages/animal/AnimalList.vue') } ] // 组件懒加载 const LazyComponent = defineAsyncComponent(() => import('@/components/HeavyComponent.vue')) // 条件加载 const ConditionalComponent = defineAsyncComponent({ loader: () => import('@/components/ConditionalComponent.vue'), loadingComponent: Loading, errorComponent: Error, delay: 200, timeout: 3000 }) ``` ### 缓存策略 ```typescript // 组件缓存 // API缓存 const cache = new Map() export const cachedApi = { async get(url: string, ttl = 5 * 60 * 1000) { const cached = cache.get(url) if (cached && Date.now() - cached.timestamp < ttl) { return cached.data } const data = await api.get(url) cache.set(url, { data, timestamp: Date.now() }) return data } } ``` ### 虚拟滚动 ```vue ``` ## 🧪 测试规范 ### 单元测试 ```typescript // tests/components/AnimalCard.test.ts import { describe, it, expect, vi } from 'vitest' import { mount } from '@vue/test-utils' import AnimalCard from '@/components/business/AnimalCard.vue' import type { Animal } from '@/types/animal' const mockAnimal: Animal = { id: 1, name: '小白', type: 'dog', status: 'available', description: '一只可爱的小狗', avatar: 'https://example.com/avatar.jpg' } describe('AnimalCard', () => { it('renders animal information correctly', () => { const wrapper = mount(AnimalCard, { props: { animal: mockAnimal } }) expect(wrapper.find('.animal-card__title').text()).toBe('小白') expect(wrapper.find('.animal-card__description').text()).toBe('一只可爱的小狗') expect(wrapper.find('.animal-card__image').attributes('src')).toBe(mockAnimal.avatar) }) it('emits adopt event when adopt button is clicked', async () => { const wrapper = mount(AnimalCard, { props: { animal: mockAnimal } }) await wrapper.find('.el-button').trigger('click') expect(wrapper.emitted('adopt')).toBeTruthy() expect(wrapper.emitted('adopt')[0]).toEqual([mockAnimal.id]) }) it('shows correct status', () => { const wrapper = mount(AnimalCard, { props: { animal: { ...mockAnimal, status: 'adopted' } } }) const statusElement = wrapper.find('.animal-card__status--adopted') expect(statusElement.exists()).toBe(true) expect(statusElement.text()).toBe('已认领') }) it('handles image error', async () => { const wrapper = mount(AnimalCard, { props: { animal: mockAnimal } }) await wrapper.find('.animal-card__image').trigger('error') expect(wrapper.emitted('imageError')).toBeTruthy() expect(wrapper.emitted('imageError')[0]).toEqual([mockAnimal]) }) }) ``` ### 集成测试 ```typescript // tests/pages/AnimalList.test.ts import { describe, it, expect, vi, beforeEach } from 'vitest' import { mount } from '@vue/test-utils' import { createPinia, setActivePinia } from 'pinia' import AnimalList from '@/pages/animal/AnimalList.vue' import { animalApi } from '@/api/modules/animal' // Mock API vi.mock('@/api/modules/animal', () => ({ animalApi: { getList: vi.fn() } })) describe('AnimalList', () => { beforeEach(() => { setActivePinia(createPinia()) vi.clearAllMocks() }) it('loads and displays animals on mount', async () => { const mockAnimals = [ { id: 1, name: '小白', type: 'dog', status: 'available' }, { id: 2, name: '小黑', type: 'cat', status: 'available' } ] vi.mocked(animalApi.getList).mockResolvedValue({ data: mockAnimals, total: 2 }) const wrapper = mount(AnimalList) // 等待异步操作完成 await wrapper.vm.$nextTick() expect(animalApi.getList).toHaveBeenCalled() expect(wrapper.findAll('.animal-card')).toHaveLength(2) }) it('handles search functionality', async () => { const wrapper = mount(AnimalList) const searchInput = wrapper.find('input[placeholder="搜索动物"]') await searchInput.setValue('小白') await searchInput.trigger('input') // 验证搜索参数 expect(animalApi.getList).toHaveBeenCalledWith({ keyword: '小白', page: 1, limit: 20 }) }) }) ``` ## 📱 响应式设计 ### 断点系统 ```scss // 断点定义 $breakpoints: ( xs: 0, sm: 576px, md: 768px, lg: 992px, xl: 1200px, xxl: 1400px ); // 媒体查询混入 @mixin respond-to($breakpoint) { @if map-has-key($breakpoints, $breakpoint) { @media (min-width: map-get($breakpoints, $breakpoint)) { @content; } } } // 使用示例 .container { padding: 16px; @include respond-to(md) { padding: 24px; } @include respond-to(lg) { padding: 32px; } } ``` ### 移动端适配 ```vue ``` ## 🔍 调试和开发工具 ### Vue DevTools配置 ```typescript // main.ts import { createApp } from 'vue' import App from './App.vue' const app = createApp(App) // 开发环境配置 if (import.meta.env.DEV) { // 启用Vue DevTools app.config.devtools = true // 全局错误处理 app.config.errorHandler = (err, vm, info) => { console.error('Vue Error:', err) console.error('Component:', vm) console.error('Info:', info) } // 全局警告处理 app.config.warnHandler = (msg, vm, trace) => { console.warn('Vue Warning:', msg) console.warn('Component:', vm) console.warn('Trace:', trace) } } ``` ### 开发环境配置 ```typescript // vite.config.ts import { defineConfig } from 'vite' import vue from '@vitejs/plugin-vue' import { resolve } from 'path' export default defineConfig({ plugins: [vue()], resolve: { alias: { '@': resolve(__dirname, 'src') } }, server: { port: 3000, open: true, cors: true, proxy: { '/api': { target: 'http://localhost:3001', changeOrigin: true, rewrite: (path) => path.replace(/^\/api/, '') } } }, build: { sourcemap: true, rollupOptions: { output: { manualChunks: { vendor: ['vue', 'vue-router', 'pinia'], element: ['element-plus'], utils: ['axios', 'dayjs', 'lodash-es'] } } } } }) ``` ## 📚 总结 本文档详细介绍了解班客项目前端开发的各个方面,包括技术架构、组件设计、状态管理、路由配置、性能优化等。遵循这些规范和最佳实践,可以确保代码质量、提高开发效率、增强项目的可维护性。 ### 关键要点 1. **技术选型**: Vue 3 + TypeScript + Element Plus提供现代化开发体验 2. **组件化**: 采用组合式API和单文件组件,提高代码复用性 3. **状态管理**: 使用Pinia进行状态管理,支持TypeScript 4. **路由设计**: 基于角色的权限控制和懒加载优化 5. **性能优化**: 代码分割、缓存策略、虚拟滚动等技术 6. **响应式设计**: 移动端优先,多断点适配 7. **测试覆盖**: 单元测试和集成测试保证代码质量 ### 后续计划 - 完善组件库和设计系统 - 增加更多性能优化策略 - 完善测试用例覆盖 - 添加国际化支持 - 集成更多开发工具 --- **文档版本**: v1.0.0 **最后更新**: 2024年1月15日 **维护人员**: 前端开发团队