# Telegram 管理系统开发规范文档 > **版本**: v2.0.0 > **更新时间**: 2024-01-20 > **维护者**: 开发团队 ## 📋 文档目录 - [开发环境配置](#开发环境配置) - [代码规范](#代码规范) - [项目结构规范](#项目结构规范) - [组件开发规范](#组件开发规范) - [API开发规范](#api开发规范) - [状态管理规范](#状态管理规范) - [路由规范](#路由规范) - [样式规范](#样式规范) - [测试规范](#测试规范) - [Git工作流](#git工作流) - [代码审查](#代码审查) - [性能优化规范](#性能优化规范) --- ## 🛠️ 开发环境配置 ### 必备工具 #### 1. 基础开发工具 ```bash # Node.js版本管理 nvm install 18.19.0 nvm use 18.19.0 # 包管理器 npm install -g pnpm@8.15.0 # Git配置 git config --global user.name "Your Name" git config --global user.email "your.email@company.com" git config --global init.defaultBranch main ``` #### 2. VSCode插件配置 推荐安装以下插件: ```json { "recommendations": [ "vue.volar", "vue.vscode-typescript-vue-plugin", "bradlc.vscode-tailwindcss", "esbenp.prettier-vscode", "dbaeumer.vscode-eslint", "ms-vscode.vscode-typescript-next", "formulahendry.auto-rename-tag", "christian-kohler.path-intellisense", "ms-vscode.vscode-json", "ms-playwright.playwright" ] } ``` #### 3. VSCode设置 ```json { "editor.codeActionsOnSave": { "source.fixAll.eslint": true, "source.organizeImports": true }, "editor.formatOnSave": true, "editor.defaultFormatter": "esbenp.prettier-vscode", "typescript.preferences.importModuleSpecifier": "relative", "typescript.suggest.autoImports": true, "vue.inlayHints.missingProps": true, "vue.inlayHints.inlineHandlerLeading": true, "vue.inlayHints.optionsWrapper": true } ``` ### 项目环境配置 #### 1. 环境变量设置 ```bash # 开发环境配置 cp .env.development.example .env.development # 本地环境配置 cp .env.local.example .env.local ``` #### 2. 依赖安装 ```bash # 安装项目依赖 pnpm install # 安装Playwright浏览器 pnpm test:install # 验证环境 pnpm typecheck pnpm lint pnpm test:unit ``` --- ## 📝 代码规范 ### TypeScript规范 #### 1. 类型定义 ```typescript // ✅ 推荐:使用interface定义对象类型 interface UserInfo { id: string; username: string; email: string; profile?: UserProfile; } // ✅ 推荐:使用type定义联合类型 type UserStatus = 'active' | 'inactive' | 'suspended'; // ✅ 推荐:使用泛型增强类型安全 interface ApiResponse { success: boolean; data: T; message: string; } // ❌ 避免:使用any类型 const userData: any = {}; // 不推荐 // ✅ 推荐:使用具体类型 const userData: UserInfo = { id: '123', username: 'admin', email: 'admin@example.com', }; ``` #### 2. 函数定义 ```typescript // ✅ 推荐:明确的参数和返回类型 async function fetchUserData(userId: string): Promise { const response = await api.get>(`/users/${userId}`); return response.data.data; } // ✅ 推荐:使用类型守卫 function isValidUser(user: unknown): user is UserInfo { return ( typeof user === 'object' && user !== null && typeof (user as UserInfo).id === 'string' && typeof (user as UserInfo).username === 'string' ); } // ✅ 推荐:解构参数使用类型注解 function updateUser({ id, username, email, }: Pick): Promise { // 实现逻辑 } ``` #### 3. 组合式API类型定义 ```typescript // ✅ 推荐:为组合式函数定义返回类型 interface UseUserReturn { userInfo: Ref; loading: Ref; error: Ref; fetchUser: (id: string) => Promise; updateUser: (data: Partial) => Promise; } function useUser(): UseUserReturn { const userInfo = ref(null); const loading = ref(false); const error = ref(null); const fetchUser = async (id: string): Promise => { // 实现逻辑 }; const updateUser = async (data: Partial): Promise => { // 实现逻辑 }; return { userInfo, loading, error, fetchUser, updateUser, }; } ``` ### ESLint规范 #### 1. 基础规则 ```javascript // .eslintrc.js module.exports = { extends: [ '@vben/eslint-config', 'plugin:vue/vue3-recommended', '@vue/eslint-config-typescript', '@vue/eslint-config-prettier', ], rules: { // 变量命名 camelcase: ['error', { properties: 'never' }], 'no-var': 'error', 'prefer-const': 'error', // 函数规范 'prefer-arrow-callback': 'error', 'arrow-spacing': 'error', 'no-unused-vars': 'off', '@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }], // Vue特定规则 'vue/component-name-in-template-casing': ['error', 'PascalCase'], 'vue/define-props-declaration': ['error', 'type-based'], 'vue/define-emits-declaration': ['error', 'type-based'], 'vue/no-v-html': 'warn', // TypeScript特定规则 '@typescript-eslint/explicit-function-return-type': 'off', '@typescript-eslint/explicit-module-boundary-types': 'off', '@typescript-eslint/no-explicit-any': 'warn', '@typescript-eslint/prefer-nullish-coalescing': 'error', '@typescript-eslint/prefer-optional-chain': 'error', }, }; ``` ### Prettier规范 ```javascript // .prettierrc.js module.exports = { semi: true, singleQuote: true, trailingComma: 'es5', tabWidth: 2, useTabs: false, printWidth: 100, bracketSpacing: true, arrowParens: 'avoid', endOfLine: 'lf', vueIndentScriptAndStyle: false, htmlWhitespaceSensitivity: 'ignore', }; ``` ### 命名规范 #### 1. 文件命名 ```bash # ✅ 推荐:组件使用PascalCase UserProfile.vue AccountList.vue MessageTemplate.vue # ✅ 推荐:工具函数使用kebab-case user-utils.ts api-client.ts validation-rules.ts # ✅ 推荐:页面文件使用kebab-case user-list.vue account-management.vue private-message.vue # ✅ 推荐:类型定义文件 types/user.ts types/api.ts types/common.ts ``` #### 2. 变量和函数命名 ```typescript // ✅ 推荐:变量使用camelCase const userName = 'admin'; const isUserActive = true; const userAccountList = []; // ✅ 推荐:常量使用SCREAMING_SNAKE_CASE const API_BASE_URL = 'https://api.example.com'; const MAX_RETRY_COUNT = 3; const DEFAULT_PAGE_SIZE = 20; // ✅ 推荐:函数使用动词开头的camelCase function getUserInfo() {} function validateUserInput() {} function handleUserLogin() {} // ✅ 推荐:布尔值使用is/has/can/should开头 const isLoading = false; const hasPermission = true; const canEdit = false; const shouldUpdate = true; // ✅ 推荐:事件处理函数使用handle/on开头 const handleClick = () => {}; const onUserSelect = () => {}; const handleFormSubmit = () => {}; ``` #### 3. 组件命名 ```typescript // ✅ 推荐:组件使用PascalCase export default defineComponent({ name: 'UserProfileCard', // ... }); // ✅ 推荐:组件props使用camelCase interface Props { userId: string; showAvatar?: boolean; onUserClick?: (user: UserInfo) => void; } // ✅ 推荐:组件emits使用kebab-case const emit = defineEmits<{ 'user-select': [user: UserInfo]; 'status-change': [status: UserStatus]; }>(); ``` --- ## 🏗️ 项目结构规范 ### 目录结构 ``` src/ ├── api/ # API接口层 │ ├── modules/ # 按模块分组的API │ │ ├── auth.ts │ │ ├── user.ts │ │ └── message.ts │ ├── types/ # API相关类型定义 │ └── client.ts # API客户端配置 ├── assets/ # 静态资源 │ ├── images/ │ ├── icons/ │ └── styles/ ├── components/ # 全局组件 │ ├── UI/ # 基础UI组件 │ ├── Business/ # 业务组件 │ └── Layout/ # 布局组件 ├── composables/ # 组合式函数 │ ├── useUser.ts │ ├── usePermission.ts │ └── useApi.ts ├── directives/ # 自定义指令 ├── hooks/ # React风格的hooks(向前兼容) ├── layouts/ # 布局组件 ├── locales/ # 国际化资源 │ ├── lang/ │ │ ├── zh-CN/ │ │ └── en-US/ │ └── index.ts ├── router/ # 路由配置 │ ├── modules/ │ ├── guards/ │ └── index.ts ├── stores/ # 状态管理 │ ├── modules/ │ │ ├── user.ts │ │ ├── auth.ts │ │ └── permission.ts │ └── index.ts ├── types/ # 全局类型定义 │ ├── api.ts │ ├── user.ts │ └── global.ts ├── utils/ # 工具函数 │ ├── request.ts │ ├── validation.ts │ └── format.ts ├── views/ # 页面组件 │ ├── auth/ │ ├── dashboard/ │ └── user/ ├── App.vue └── main.ts ``` ### 文件命名约定 #### 1. 组件文件 ```bash # 页面组件 - PascalCase UserList.vue AccountManagement.vue PrivateMessageSend.vue # 业务组件 - PascalCase UserCard.vue MessageTemplate.vue StatisticsChart.vue # 基础组件 - 带前缀的PascalCase BaseButton.vue BaseTable.vue BaseForm.vue ``` #### 2. 工具文件 ```bash # 工具函数 - kebab-case api-client.ts user-utils.ts validation-rules.ts date-format.ts # 类型定义 - kebab-case user-types.ts api-types.ts common-types.ts # 常量文件 - kebab-case api-constants.ts app-constants.ts ``` #### 3. 存储和组合式函数 ```bash # Store文件 - camelCase userStore.ts authStore.ts permissionStore.ts # 组合式函数 - camelCase with use前缀 useUser.ts useAuth.ts usePermission.ts useApi.ts ``` --- ## 🧩 组件开发规范 ### Vue 3 组合式API规范 #### 1. 组件结构 ```vue ``` #### 2. 组合式函数规范 ```typescript // composables/useUser.ts import { ref, computed, type Ref } from 'vue'; import type { UserInfo, CreateUserData, UpdateUserData } from '@/types/user'; import { userApi } from '@/api/modules/user'; export interface UseUserOptions { immediate?: boolean; } export interface UseUserReturn { // 状态 userList: Ref; currentUser: Ref; loading: Ref; error: Ref; // 计算属性 activeUsers: ComputedRef; userCount: ComputedRef; // 方法 fetchUsers: (params?: UserListParams) => Promise; fetchUser: (id: string) => Promise; createUser: (data: CreateUserData) => Promise; updateUser: (id: string, data: UpdateUserData) => Promise; deleteUser: (id: string) => Promise; refresh: () => Promise; } export function useUser(options: UseUserOptions = {}): UseUserReturn { // 响应式状态 const userList = ref([]); const currentUser = ref(null); const loading = ref(false); const error = ref(null); // 计算属性 const activeUsers = computed(() => userList.value.filter((user) => user.status === 'active'), ); const userCount = computed(() => userList.value.length); // 私有方法 const setError = (message: string) => { error.value = message; console.error('[useUser]', message); }; const clearError = () => { error.value = null; }; // 公共方法 const fetchUsers = async (params?: UserListParams): Promise => { try { loading.value = true; clearError(); const response = await userApi.getList(params); userList.value = response.data.items; } catch (err) { setError(err instanceof Error ? err.message : 'Failed to fetch users'); } finally { loading.value = false; } }; const fetchUser = async (id: string): Promise => { try { loading.value = true; clearError(); const response = await userApi.getById(id); currentUser.value = response.data; return response.data; } catch (err) { setError(err instanceof Error ? err.message : 'Failed to fetch user'); return null; } finally { loading.value = false; } }; const createUser = async (data: CreateUserData): Promise => { try { loading.value = true; clearError(); const response = await userApi.create(data); userList.value.push(response.data); return response.data; } catch (err) { setError(err instanceof Error ? err.message : 'Failed to create user'); throw err; } finally { loading.value = false; } }; const updateUser = async ( id: string, data: UpdateUserData, ): Promise => { try { loading.value = true; clearError(); const response = await userApi.update(id, data); const index = userList.value.findIndex((user) => user.id === id); if (index !== -1) { userList.value[index] = response.data; } return response.data; } catch (err) { setError(err instanceof Error ? err.message : 'Failed to update user'); throw err; } finally { loading.value = false; } }; const deleteUser = async (id: string): Promise => { try { loading.value = true; clearError(); await userApi.delete(id); const index = userList.value.findIndex((user) => user.id === id); if (index !== -1) { userList.value.splice(index, 1); } } catch (err) { setError(err instanceof Error ? err.message : 'Failed to delete user'); throw err; } finally { loading.value = false; } }; const refresh = async (): Promise => { await fetchUsers(); }; // 初始化 if (options.immediate) { fetchUsers(); } return { // 状态 userList, currentUser, loading, error, // 计算属性 activeUsers, userCount, // 方法 fetchUsers, fetchUser, createUser, updateUser, deleteUser, refresh, }; } ``` #### 3. 组件通信规范 ```typescript // 父子组件通信 - Props和Emits // 父组件 // 子组件 interface Props { users: UserInfo[]; loading?: boolean; } interface Emits { (e: 'user-select', user: UserInfo): void; (e: 'user-delete', id: string): void; } // 兄弟组件通信 - 使用Store const userStore = useUserStore(); const selectedUser = computed(() => userStore.selectedUser); // 跨层级通信 - Provide/Inject // 祖先组件 provide('user-context', { currentUser, permissions, updateUser }); // 后代组件 const userContext = inject('user-context'); ``` --- ## 🌐 API开发规范 ### API客户端配置 ```typescript // api/client.ts import axios, { type AxiosInstance, type AxiosRequestConfig } from 'axios'; import { useAuthStore } from '@/stores/modules/auth'; import { message } from 'ant-design-vue'; class ApiClient { private instance: AxiosInstance; constructor() { this.instance = axios.create({ baseURL: import.meta.env.VITE_API_URL, timeout: 10000, headers: { 'Content-Type': 'application/json', }, }); this.setupInterceptors(); } private setupInterceptors() { // 请求拦截器 this.instance.interceptors.request.use( (config) => { const authStore = useAuthStore(); const token = authStore.token; if (token) { config.headers.Authorization = `Bearer ${token}`; } // 添加请求ID用于追踪 config.headers['X-Request-ID'] = this.generateRequestId(); return config; }, (error) => { console.error('[API Request Error]', error); return Promise.reject(error); }, ); // 响应拦截器 this.instance.interceptors.response.use( (response) => { return response.data; }, (error) => { return this.handleError(error); }, ); } private handleError(error: any) { const { response } = error; if (!response) { message.error('网络连接失败,请检查网络设置'); return Promise.reject(new Error('Network Error')); } const { status, data } = response; switch (status) { case 401: this.handleUnauthorized(); break; case 403: message.error('权限不足,无法访问该资源'); break; case 404: message.error('请求的资源不存在'); break; case 422: this.handleValidationError(data); break; case 500: message.error('服务器内部错误,请稍后重试'); break; default: message.error(data?.message || '请求失败,请稍后重试'); } return Promise.reject(error); } private handleUnauthorized() { const authStore = useAuthStore(); authStore.logout(); // 重定向到登录页 if (window.location.pathname !== '/login') { window.location.href = '/login'; } } private handleValidationError(data: any) { if (data?.errors && Array.isArray(data.errors)) { data.errors.forEach((error: any) => { message.error(`${error.field}: ${error.message}`); }); } else { message.error(data?.message || '数据验证失败'); } } private generateRequestId(): string { return `req_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; } // HTTP方法封装 get(url: string, config?: AxiosRequestConfig): Promise { return this.instance.get(url, config); } post( url: string, data?: any, config?: AxiosRequestConfig, ): Promise { return this.instance.post(url, data, config); } put( url: string, data?: any, config?: AxiosRequestConfig, ): Promise { return this.instance.put(url, data, config); } patch( url: string, data?: any, config?: AxiosRequestConfig, ): Promise { return this.instance.patch(url, data, config); } delete(url: string, config?: AxiosRequestConfig): Promise { return this.instance.delete(url, config); } } export const apiClient = new ApiClient(); ``` ### API模块定义 ```typescript // api/modules/user.ts import { apiClient } from '../client'; import type { UserInfo, UserListParams, UserListResponse, CreateUserData, UpdateUserData, } from '@/types/user'; export const userApi = { /** * 获取用户列表 */ getList(params?: UserListParams): Promise { return apiClient.get('/users', { params }); }, /** * 获取用户详情 */ getById(id: string): Promise<{ data: UserInfo }> { return apiClient.get(`/users/${id}`); }, /** * 创建用户 */ create(data: CreateUserData): Promise<{ data: UserInfo }> { return apiClient.post('/users', data); }, /** * 更新用户 */ update(id: string, data: UpdateUserData): Promise<{ data: UserInfo }> { return apiClient.put(`/users/${id}`, data); }, /** * 删除用户 */ delete(id: string): Promise { return apiClient.delete(`/users/${id}`); }, /** * 批量操作用户 */ batchAction( action: string, userIds: string[], data?: any, ): Promise<{ data: { success: number; failed: number } }> { return apiClient.post('/users/batch', { action, userIds, data, }); }, /** * 上传用户头像 */ uploadAvatar(id: string, file: File): Promise<{ data: { url: string } }> { const formData = new FormData(); formData.append('avatar', file); return apiClient.post(`/users/${id}/avatar`, formData, { headers: { 'Content-Type': 'multipart/form-data', }, }); }, }; ``` ### API类型定义 ```typescript // types/user.ts export interface UserInfo { id: string; username: string; email: string; profile: UserProfile; roles: string[]; status: UserStatus; createdAt: string; updatedAt: string; } export interface UserProfile { firstName: string; lastName: string; avatar?: string; phone?: string; bio?: string; } export type UserStatus = 'active' | 'inactive' | 'suspended'; export interface UserListParams { page?: number; pageSize?: number; sortBy?: string; sortOrder?: 'asc' | 'desc'; filters?: { status?: UserStatus; roles?: string[]; username?: string; email?: string; createdAt?: { start?: string; end?: string; }; }; } export interface UserListResponse { success: boolean; data: { items: UserInfo[]; pagination: { page: number; pageSize: number; total: number; totalPages: number; }; }; } export interface CreateUserData { username: string; email: string; password: string; profile: Omit; roles?: string[]; } export interface UpdateUserData { username?: string; email?: string; profile?: Partial; roles?: string[]; status?: UserStatus; } ``` --- ## 📦 状态管理规范 ### Pinia Store规范 ```typescript // stores/modules/user.ts import { defineStore } from 'pinia'; import { ref, computed } from 'vue'; import type { UserInfo, UserListParams } from '@/types/user'; import { userApi } from '@/api/modules/user'; export const useUserStore = defineStore('user', () => { // ==================== 状态定义 ==================== const userList = ref([]); const currentUser = ref(null); const selectedUsers = ref([]); const loading = ref(false); const error = ref(null); // 分页信息 const pagination = ref({ page: 1, pageSize: 20, total: 0, totalPages: 0, }); // 筛选条件 const filters = ref({}); // ==================== 计算属性 ==================== const activeUsers = computed(() => userList.value.filter((user) => user.status === 'active'), ); const userCount = computed(() => userList.value.length); const hasSelectedUsers = computed(() => selectedUsers.value.length > 0); const selectedUserIds = computed(() => selectedUsers.value.map((user) => user.id), ); // ==================== 私有方法 ==================== const setError = (message: string) => { error.value = message; console.error('[UserStore]', message); }; const clearError = () => { error.value = null; }; // ==================== Actions ==================== /** * 获取用户列表 */ const fetchUsers = async (params?: UserListParams) => { try { loading.value = true; clearError(); const mergedParams = { ...params, filters: { ...filters.value, ...params?.filters }, }; const response = await userApi.getList(mergedParams); userList.value = response.data.items; pagination.value = response.data.pagination; } catch (err) { setError(err instanceof Error ? err.message : 'Failed to fetch users'); throw err; } finally { loading.value = false; } }; /** * 获取用户详情 */ const fetchUser = async (id: string): Promise => { try { loading.value = true; clearError(); const response = await userApi.getById(id); currentUser.value = response.data; return response.data; } catch (err) { setError(err instanceof Error ? err.message : 'Failed to fetch user'); return null; } finally { loading.value = false; } }; /** * 创建用户 */ const createUser = async (data: CreateUserData): Promise => { try { loading.value = true; clearError(); const response = await userApi.create(data); // 如果当前页面能容纳新用户,则添加到列表 if (userList.value.length < pagination.value.pageSize) { userList.value.push(response.data); } // 更新总数 pagination.value.total += 1; return response.data; } catch (err) { setError(err instanceof Error ? err.message : 'Failed to create user'); throw err; } finally { loading.value = false; } }; /** * 更新用户 */ const updateUser = async ( id: string, data: UpdateUserData, ): Promise => { try { loading.value = true; clearError(); const response = await userApi.update(id, data); // 更新列表中的用户 const index = userList.value.findIndex((user) => user.id === id); if (index !== -1) { userList.value[index] = response.data; } // 更新当前用户 if (currentUser.value?.id === id) { currentUser.value = response.data; } return response.data; } catch (err) { setError(err instanceof Error ? err.message : 'Failed to update user'); throw err; } finally { loading.value = false; } }; /** * 删除用户 */ const deleteUser = async (id: string): Promise => { try { loading.value = true; clearError(); await userApi.delete(id); // 从列表中移除 const index = userList.value.findIndex((user) => user.id === id); if (index !== -1) { userList.value.splice(index, 1); } // 从选中列表中移除 const selectedIndex = selectedUsers.value.findIndex( (user) => user.id === id, ); if (selectedIndex !== -1) { selectedUsers.value.splice(selectedIndex, 1); } // 清除当前用户 if (currentUser.value?.id === id) { currentUser.value = null; } // 更新总数 pagination.value.total -= 1; } catch (err) { setError(err instanceof Error ? err.message : 'Failed to delete user'); throw err; } finally { loading.value = false; } }; /** * 批量删除用户 */ const batchDeleteUsers = async (userIds: string[]): Promise => { try { loading.value = true; clearError(); const response = await userApi.batchAction('delete', userIds); // 从列表中移除成功删除的用户 userList.value = userList.value.filter( (user) => !userIds.includes(user.id), ); // 清空选中列表 selectedUsers.value = []; // 更新总数 pagination.value.total -= response.data.success; } catch (err) { setError( err instanceof Error ? err.message : 'Failed to batch delete users', ); throw err; } finally { loading.value = false; } }; /** * 选择用户 */ const selectUser = (user: UserInfo) => { const index = selectedUsers.value.findIndex((u) => u.id === user.id); if (index === -1) { selectedUsers.value.push(user); } }; /** * 取消选择用户 */ const unselectUser = (userId: string) => { const index = selectedUsers.value.findIndex((user) => user.id === userId); if (index !== -1) { selectedUsers.value.splice(index, 1); } }; /** * 切换用户选择状态 */ const toggleUserSelection = (user: UserInfo) => { const index = selectedUsers.value.findIndex((u) => u.id === user.id); if (index === -1) { selectedUsers.value.push(user); } else { selectedUsers.value.splice(index, 1); } }; /** * 全选/取消全选 */ const toggleSelectAll = () => { if (selectedUsers.value.length === userList.value.length) { selectedUsers.value = []; } else { selectedUsers.value = [...userList.value]; } }; /** * 清空选择 */ const clearSelection = () => { selectedUsers.value = []; }; /** * 更新筛选条件 */ const updateFilters = (newFilters: UserListParams['filters']) => { filters.value = { ...filters.value, ...newFilters }; }; /** * 重置筛选条件 */ const resetFilters = () => { filters.value = {}; }; /** * 刷新当前页面 */ const refresh = async () => { await fetchUsers({ page: pagination.value.page, pageSize: pagination.value.pageSize, filters: filters.value, }); }; /** * 重置所有状态 */ const resetState = () => { userList.value = []; currentUser.value = null; selectedUsers.value = []; loading.value = false; error.value = null; pagination.value = { page: 1, pageSize: 20, total: 0, totalPages: 0, }; filters.value = {}; }; return { // 状态 userList, currentUser, selectedUsers, loading, error, pagination, filters, // 计算属性 activeUsers, userCount, hasSelectedUsers, selectedUserIds, // Actions fetchUsers, fetchUser, createUser, updateUser, deleteUser, batchDeleteUsers, selectUser, unselectUser, toggleUserSelection, toggleSelectAll, clearSelection, updateFilters, resetFilters, refresh, resetState, }; }); ``` ### Store组合使用 ```typescript // stores/index.ts import type { App } from 'vue'; import { createPinia } from 'pinia'; // 创建pinia实例 export const pinia = createPinia(); // 插件安装 export function setupStore(app: App) { app.use(pinia); } // 导出所有store export { useAuthStore } from './modules/auth'; export { useUserStore } from './modules/user'; export { usePermissionStore } from './modules/permission'; export { useSettingStore } from './modules/setting'; ``` --- ## 🛣️ 路由规范 ### 路由配置 ```typescript // router/modules/user.ts import type { RouteRecordRaw } from 'vue-router'; const userRoutes: RouteRecordRaw[] = [ { path: '/user', name: 'User', redirect: '/user/list', component: () => import('@/layouts/default/index.vue'), meta: { title: 'route.user.title', icon: 'mdi:account-group', orderNo: 10, requiresAuth: true, permissions: ['user:read'], }, children: [ { path: 'list', name: 'UserList', component: () => import('@/views/user/list/index.vue'), meta: { title: 'route.user.list', icon: 'mdi:account-multiple', permissions: ['user:read'], keepAlive: true, }, }, { path: 'create', name: 'UserCreate', component: () => import('@/views/user/create/index.vue'), meta: { title: 'route.user.create', icon: 'mdi:account-plus', permissions: ['user:create'], hideInMenu: true, }, }, { path: 'edit/:id', name: 'UserEdit', component: () => import('@/views/user/edit/index.vue'), meta: { title: 'route.user.edit', permissions: ['user:update'], hideInMenu: true, activeMenu: '/user/list', }, }, { path: 'detail/:id', name: 'UserDetail', component: () => import('@/views/user/detail/index.vue'), meta: { title: 'route.user.detail', permissions: ['user:read'], hideInMenu: true, activeMenu: '/user/list', }, }, ], }, ]; export default userRoutes; ``` ### 路由守卫 ```typescript // router/guards/permission.ts import type { Router } from 'vue-router'; import { useAuthStore } from '@/stores/modules/auth'; import { usePermissionStore } from '@/stores/modules/permission'; export function setupPermissionGuard(router: Router) { router.beforeEach(async (to, from, next) => { const authStore = useAuthStore(); const permissionStore = usePermissionStore(); // 1. 检查登录状态 if (to.meta.requiresAuth && !authStore.isLoggedIn) { next({ path: '/login', query: { redirect: to.fullPath }, }); return; } // 2. 检查权限 if (to.meta.permissions && to.meta.permissions.length > 0) { const hasPermission = to.meta.permissions.some((permission: string) => permissionStore.hasPermission(permission), ); if (!hasPermission) { next({ path: '/403' }); return; } } // 3. 动态加载用户权限(如果还未加载) if (authStore.isLoggedIn && !permissionStore.isPermissionsLoaded) { try { await permissionStore.loadPermissions(); // 重新检查权限 if (to.meta.permissions && to.meta.permissions.length > 0) { const hasPermission = to.meta.permissions.some((permission: string) => permissionStore.hasPermission(permission), ); if (!hasPermission) { next({ path: '/403' }); return; } } } catch (error) { console.error('Failed to load permissions:', error); next({ path: '/login' }); return; } } next(); }); } ``` ### 动态路由 ```typescript // router/dynamic.ts import type { RouteRecordRaw } from 'vue-router'; import { usePermissionStore } from '@/stores/modules/permission'; /** * 根据权限过滤路由 */ export function filterRoutesByPermission( routes: RouteRecordRaw[], permissions: string[], ): RouteRecordRaw[] { return routes.filter((route) => { // 检查当前路由权限 if (route.meta?.permissions) { const hasPermission = route.meta.permissions.some((permission: string) => permissions.includes(permission), ); if (!hasPermission) { return false; } } // 递归处理子路由 if (route.children) { route.children = filterRoutesByPermission(route.children, permissions); } return true; }); } /** * 生成动态路由 */ export async function generateDynamicRoutes(): Promise { const permissionStore = usePermissionStore(); // 获取用户权限 const permissions = permissionStore.permissions; // 导入所有路由模块 const routeModules = import.meta.glob('./modules/*.ts', { eager: true }); let routes: RouteRecordRaw[] = []; // 合并所有路由 Object.values(routeModules).forEach((module: any) => { const moduleRoutes = module.default || module; if (Array.isArray(moduleRoutes)) { routes = routes.concat(moduleRoutes); } }); // 根据权限过滤路由 return filterRoutesByPermission(routes, permissions); } ``` --- ## 🎨 样式规范 ### CSS架构 采用 `TailwindCSS + SCSS` 的混合方案: ```scss // styles/variables.scss // 颜色系统 :root { // 主色调 --color-primary: #1890ff; --color-primary-light: #40a9ff; --color-primary-dark: #096dd9; // 功能色 --color-success: #52c41a; --color-warning: #faad14; --color-error: #f5222d; --color-info: #1890ff; // 中性色 --color-text-primary: #262626; --color-text-secondary: #595959; --color-text-disabled: #bfbfbf; // 背景色 --color-bg-primary: #ffffff; --color-bg-secondary: #fafafa; --color-bg-tertiary: #f5f5f5; // 边框色 --color-border-primary: #d9d9d9; --color-border-secondary: #f0f0f0; // 阴影 --shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.05); --shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.1); --shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.1); // 圆角 --radius-sm: 4px; --radius-md: 6px; --radius-lg: 8px; --radius-xl: 12px; // 间距 --spacing-xs: 4px; --spacing-sm: 8px; --spacing-md: 16px; --spacing-lg: 24px; --spacing-xl: 32px; --spacing-2xl: 48px; } // 暗色主题 [data-theme='dark'] { --color-bg-primary: #141414; --color-bg-secondary: #1f1f1f; --color-bg-tertiary: #262626; --color-text-primary: #ffffff; --color-text-secondary: #a6a6a6; --color-border-primary: #434343; --color-border-secondary: #303030; } ``` ### 组件样式规范 ```scss // 使用BEM命名规范 + TailwindCSS工具类 .user-profile-card { @apply rounded-lg border border-gray-200 bg-white shadow-sm; // 组件状态 &--loading { @apply pointer-events-none opacity-50; } &--disabled { @apply bg-gray-50 text-gray-400; } // 子元素 &__header { @apply flex items-center justify-between border-b border-gray-100 p-4; } &__title { @apply text-lg font-semibold text-gray-900; } &__subtitle { @apply text-sm text-gray-500; } &__body { @apply p-4; } &__actions { @apply flex justify-end space-x-2 border-t border-gray-100 p-4; } // 修饰符 &--compact { .user-profile-card__header, .user-profile-card__body, .user-profile-card__actions { @apply p-2; } } // 响应式 @screen md { @apply p-6; } // 暗色主题 @apply dark:border-gray-700 dark:bg-gray-800; &__title { @apply dark:text-gray-100; } &__subtitle { @apply dark:text-gray-400; } } ``` ### 全局样式 ```scss // styles/global.scss // 重置样式 *, *::before, *::after { box-sizing: border-box; } html { line-height: 1.15; -webkit-text-size-adjust: 100%; } body { margin: 0; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif; font-size: 14px; line-height: 1.5; color: var(--color-text-primary); background-color: var(--color-bg-primary); } // 工具类 .text-ellipsis { @apply truncate; } .text-ellipsis-2 { display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; } .text-ellipsis-3 { display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden; } // 滚动条样式 .scrollbar-thin { scrollbar-width: thin; scrollbar-color: var(--color-border-primary) transparent; &::-webkit-scrollbar { width: 6px; height: 6px; } &::-webkit-scrollbar-track { background: transparent; } &::-webkit-scrollbar-thumb { background-color: var(--color-border-primary); border-radius: 3px; } &::-webkit-scrollbar-thumb:hover { background-color: var(--color-text-disabled); } } // 动画 .fade-enter-active, .fade-leave-active { transition: opacity 0.3s ease; } .fade-enter-from, .fade-leave-to { opacity: 0; } .slide-up-enter-active, .slide-up-leave-active { transition: all 0.3s ease; } .slide-up-enter-from { transform: translateY(20px); opacity: 0; } .slide-up-leave-to { transform: translateY(-20px); opacity: 0; } ``` ### TailwindCSS配置 ```javascript // tailwind.config.js module.exports = { content: ['./index.html', './src/**/*.{vue,js,ts,jsx,tsx}'], darkMode: ['class', '[data-theme="dark"]'], theme: { extend: { colors: { primary: { 50: '#e6f7ff', 100: '#bae7ff', 200: '#91d5ff', 300: '#69c0ff', 400: '#40a9ff', 500: '#1890ff', 600: '#096dd9', 700: '#0050b3', 800: '#003a8c', 900: '#002766', }, gray: { 50: '#fafafa', 100: '#f5f5f5', 200: '#f0f0f0', 300: '#d9d9d9', 400: '#bfbfbf', 500: '#8c8c8c', 600: '#595959', 700: '#434343', 800: '#262626', 900: '#1f1f1f', }, }, fontFamily: { sans: [ '-apple-system', 'BlinkMacSystemFont', 'Segoe UI', 'Roboto', 'Helvetica Neue', 'Arial', 'sans-serif', ], mono: [ 'SFMono-Regular', 'Menlo', 'Monaco', 'Consolas', 'Liberation Mono', 'Courier New', 'monospace', ], }, spacing: { 18: '4.5rem', 88: '22rem', }, animation: { 'fade-in': 'fadeIn 0.3s ease-in-out', 'slide-up': 'slideUp 0.3s ease-out', 'bounce-in': 'bounceIn 0.6s ease-out', }, keyframes: { fadeIn: { '0%': { opacity: '0' }, '100%': { opacity: '1' }, }, slideUp: { '0%': { transform: 'translateY(20px)', opacity: '0' }, '100%': { transform: 'translateY(0)', opacity: '1' }, }, bounceIn: { '0%': { transform: 'scale(0.3)', opacity: '0' }, '50%': { transform: 'scale(1.05)' }, '70%': { transform: 'scale(0.9)' }, '100%': { transform: 'scale(1)', opacity: '1' }, }, }, }, }, plugins: [ require('@tailwindcss/forms'), require('@tailwindcss/typography'), require('@tailwindcss/aspect-ratio'), ], }; ``` --- ## 🧪 测试规范 ### 单元测试规范 ```typescript // tests/unit/composables/useUser.test.ts import { describe, it, expect, vi, beforeEach } from 'vitest'; import { useUser } from '@/composables/useUser'; import { userApi } from '@/api/modules/user'; // Mock API vi.mock('@/api/modules/user', () => ({ userApi: { getList: vi.fn(), getById: vi.fn(), create: vi.fn(), update: vi.fn(), delete: vi.fn(), }, })); describe('useUser', () => { beforeEach(() => { vi.clearAllMocks(); }); it('should initialize with default values', () => { const { userList, loading, error } = useUser(); expect(userList.value).toEqual([]); expect(loading.value).toBe(false); expect(error.value).toBeNull(); }); it('should fetch users successfully', async () => { const mockUsers = [ { id: '1', username: 'user1', email: 'user1@test.com' }, { id: '2', username: 'user2', email: 'user2@test.com' }, ]; vi.mocked(userApi.getList).mockResolvedValue({ success: true, data: { items: mockUsers, pagination: { page: 1, pageSize: 20, total: 2, totalPages: 1 }, }, }); const { userList, fetchUsers, loading } = useUser(); await fetchUsers(); expect(loading.value).toBe(false); expect(userList.value).toEqual(mockUsers); expect(userApi.getList).toHaveBeenCalledOnce(); }); it('should handle fetch users error', async () => { const errorMessage = 'Failed to fetch users'; vi.mocked(userApi.getList).mockRejectedValue(new Error(errorMessage)); const { fetchUsers, error } = useUser(); await expect(fetchUsers()).rejects.toThrow(errorMessage); expect(error.value).toBe(errorMessage); }); it('should create user successfully', async () => { const newUser = { id: '3', username: 'user3', email: 'user3@test.com' }; const createData = { username: 'user3', email: 'user3@test.com', password: '123456', }; vi.mocked(userApi.create).mockResolvedValue({ success: true, data: newUser, }); const { userList, createUser } = useUser(); const result = await createUser(createData); expect(result).toEqual(newUser); expect(userList.value).toContain(newUser); expect(userApi.create).toHaveBeenCalledWith(createData); }); }); ``` ### 组件测试规范 ```typescript // tests/unit/components/UserCard.test.ts import { describe, it, expect, vi } from 'vitest'; import { mount } from '@vue/test-utils'; import UserCard from '@/components/UserCard.vue'; import type { UserInfo } from '@/types/user'; const mockUser: UserInfo = { id: '1', username: 'testuser', email: 'test@example.com', profile: { firstName: 'Test', lastName: 'User', avatar: 'https://example.com/avatar.jpg', }, roles: ['user'], status: 'active', createdAt: '2024-01-01T00:00:00Z', updatedAt: '2024-01-01T00:00:00Z', }; describe('UserCard', () => { it('should render user information correctly', () => { const wrapper = mount(UserCard, { props: { userInfo: mockUser, }, }); expect(wrapper.find('.user-card__username').text()).toBe(mockUser.username); expect(wrapper.find('.user-card__email').text()).toBe(mockUser.email); expect(wrapper.find('.user-card__status').text()).toContain('active'); }); it('should emit user-click when card is clicked', async () => { const wrapper = mount(UserCard, { props: { userInfo: mockUser, }, }); await wrapper.find('.user-card').trigger('click'); expect(wrapper.emitted('user-click')).toBeTruthy(); expect(wrapper.emitted('user-click')?.[0]).toEqual([mockUser]); }); it('should not show actions when readonly is true', () => { const wrapper = mount(UserCard, { props: { userInfo: mockUser, readonly: true, }, }); expect(wrapper.find('.user-card__actions').exists()).toBe(false); }); it('should show actions when readonly is false', () => { const wrapper = mount(UserCard, { props: { userInfo: mockUser, readonly: false, showActions: true, }, }); expect(wrapper.find('.user-card__actions').exists()).toBe(true); }); it('should handle null user info gracefully', () => { const wrapper = mount(UserCard, { props: { userInfo: null, }, }); expect(wrapper.find('.user-card__username').text()).toBe('Unknown'); }); }); ``` ### E2E测试规范 ```typescript // tests/e2e/user-management.test.ts import { test, expect } from '@playwright/test'; test.describe('用户管理功能', () => { test.beforeEach(async ({ page }) => { // 登录 await page.goto('/login'); await page.fill('[data-testid="username-input"]', 'admin'); await page.fill('[data-testid="password-input"]', '111111'); await page.click('[data-testid="login-button"]'); await page.waitForURL('/dashboard'); // 导航到用户管理页面 await page.click('[data-testid="menu-user"]'); await page.click('[data-testid="menu-user-list"]'); await page.waitForURL('/user/list'); }); test('应该显示用户列表', async ({ page }) => { // 等待用户列表加载 await page.waitForSelector('[data-testid="user-table"]'); // 检查表格是否显示 const table = page.locator('[data-testid="user-table"]'); await expect(table).toBeVisible(); // 检查表格头部 await expect(page.locator('th')).toContainText([ '用户名', '邮箱', '状态', '创建时间', '操作', ]); // 检查是否有用户数据 const rows = page.locator('tbody tr'); await expect(rows).toHaveCountGreaterThan(0); }); test('应该能够搜索用户', async ({ page }) => { // 输入搜索关键词 await page.fill('[data-testid="search-input"]', 'admin'); await page.click('[data-testid="search-button"]'); // 等待搜索结果 await page.waitForTimeout(1000); // 检查搜索结果 const userRows = page.locator('tbody tr'); const firstUserName = userRows.first().locator('td').nth(1); await expect(firstUserName).toContainText('admin'); }); test('应该能够创建新用户', async ({ page }) => { // 点击添加用户按钮 await page.click('[data-testid="add-user-button"]'); // 填写用户信息 await page.fill('[data-testid="username-input"]', 'newuser'); await page.fill('[data-testid="email-input"]', 'newuser@example.com'); await page.fill('[data-testid="password-input"]', '123456'); await page.fill('[data-testid="firstName-input"]', 'New'); await page.fill('[data-testid="lastName-input"]', 'User'); // 提交表单 await page.click('[data-testid="submit-button"]'); // 等待成功消息 await expect(page.locator('.ant-message-success')).toBeVisible(); // 检查用户是否已添加到列表 await page.waitForTimeout(1000); const userRows = page.locator('tbody tr'); await expect(userRows).toContainText('newuser'); }); test('应该能够编辑用户', async ({ page }) => { // 点击第一个用户的编辑按钮 await page.click('tbody tr:first-child [data-testid="edit-button"]'); // 修改用户信息 await page.fill('[data-testid="firstName-input"]', 'Updated'); await page.fill('[data-testid="lastName-input"]', 'Name'); // 提交更改 await page.click('[data-testid="submit-button"]'); // 等待成功消息 await expect(page.locator('.ant-message-success')).toBeVisible(); // 返回列表页面 await page.click('[data-testid="back-button"]'); // 检查更改是否生效 const firstUserRow = page.locator('tbody tr:first-child'); await expect(firstUserRow).toContainText('Updated Name'); }); test('应该能够删除用户', async ({ page }) => { // 获取删除前的用户数量 const initialCount = await page.locator('tbody tr').count(); // 点击第一个用户的删除按钮 await page.click('tbody tr:first-child [data-testid="delete-button"]'); // 确认删除 await page.click('[data-testid="confirm-delete-button"]'); // 等待成功消息 await expect(page.locator('.ant-message-success')).toBeVisible(); // 检查用户数量是否减少 await page.waitForTimeout(1000); const finalCount = await page.locator('tbody tr').count(); expect(finalCount).toBe(initialCount - 1); }); }); ``` ### 测试配置 ```typescript // vitest.config.ts import { defineConfig } from 'vitest/config'; import vue from '@vitejs/plugin-vue'; import { resolve } from 'path'; export default defineConfig({ plugins: [vue()], test: { globals: true, environment: 'jsdom', setupFiles: ['./tests/setup.ts'], coverage: { provider: 'v8', reporter: ['text', 'json', 'html'], exclude: [ 'node_modules/', 'tests/', '**/*.d.ts', '**/*.config.*', 'build/', 'dist/', ], }, }, resolve: { alias: { '@': resolve(__dirname, './src'), }, }, }); ``` --- ## 🔀 Git工作流 ### 分支策略 采用 Git Flow 分支策略: ```bash # 主要分支 main # 生产环境分支 develop # 开发环境分支 # 支持分支 feature/* # 功能分支 release/* # 发布分支 hotfix/* # 热修复分支 ``` ### 分支命名规范 ```bash # 功能分支 feature/user-management feature/message-template feature/api-optimization # 修复分支 bugfix/login-validation bugfix/table-pagination # 热修复分支 hotfix/security-patch hotfix/performance-issue # 发布分支 release/v2.1.0 release/v2.2.0 ``` ### 提交信息规范 采用 Conventional Commits 规范: ```bash # 格式 [optional scope]: [optional body] [optional footer(s)] # 类型说明 feat: 新功能 fix: 修复bug docs: 文档更新 style: 格式调整(不影响代码运行) refactor: 重构代码 perf: 性能优化 test: 测试相关 build: 构建系统相关 ci: CI配置相关 chore: 其他修改 # 示例 feat(user): add user profile management Add comprehensive user profile management functionality including: - User profile editing - Avatar upload - Profile validation - Permission checks Closes #123 ``` ### Git Hooks配置 ```javascript // .husky/pre-commit #!/usr/bin/env sh . "$(dirname -- "$0")/_/husky.sh" # 代码检查 pnpm lint-staged # 类型检查 pnpm typecheck ``` ```javascript // .husky/commit-msg #!/usr/bin/env sh . "$(dirname -- "$0")/_/husky.sh" # 提交信息验证 pnpm commitlint --edit $1 ``` ```json // lint-staged.config.js module.exports = { '*.{js,jsx,ts,tsx,vue}': [ 'eslint --fix', 'prettier --write', 'git add' ], '*.{css,scss,less}': [ 'stylelint --fix', 'prettier --write', 'git add' ], '*.{json,md}': [ 'prettier --write', 'git add' ] }; ``` --- ## 👀 代码审查 ### 审查清单 #### 功能性审查 - [ ] **功能完整性**: 功能是否按需求完整实现 - [ ] **边界条件**: 是否处理了边界情况和异常情况 - [ ] **错误处理**: 是否有适当的错误处理和用户提示 - [ ] **性能影响**: 是否会对系统性能产生负面影响 - [ ] **安全性**: 是否存在安全漏洞或风险 #### 代码质量审查 - [ ] **代码规范**: 是否遵循项目的代码规范和最佳实践 - [ ] **命名规范**: 变量、函数、类名是否清晰且有意义 - [ ] **代码复用**: 是否有重复的代码可以提取复用 - [ ] **注释质量**: 复杂逻辑是否有适当的注释说明 - [ ] **类型安全**: TypeScript类型定义是否完整和准确 #### 架构设计审查 - [ ] **设计模式**: 是否使用了合适的设计模式 - [ ] **组件设计**: 组件职责是否单一,是否可复用 - [ ] **API设计**: API接口设计是否合理和一致 - [ ] **状态管理**: 状态管理是否合理,是否有不必要的状态 - [ ] **依赖关系**: 模块间依赖是否合理,是否有循环依赖 #### 测试覆盖审查 - [ ] **单元测试**: 是否有足够的单元测试覆盖 - [ ] **集成测试**: 关键功能是否有集成测试 - [ ] **测试质量**: 测试用例是否覆盖了主要场景和边界情况 - [ ] **测试维护**: 测试代码是否易于维护和理解 ### 审查流程 1. **自检阶段** - 提交前自我审查代码 - 运行所有测试确保通过 - 检查代码规范和类型检查 2. **同伴审查** - 指定至少一名同伴进行代码审查 - 审查者需要理解业务需求和技术方案 - 提供建设性的反馈和建议 3. **技术负责人审查** - 涉及架构变更需要技术负责人审查 - 检查技术方案的合理性和可维护性 - 评估对系统整体的影响 4. **合并前检查** - 确保所有CI检查通过 - 解决所有审查意见 - 更新相关文档 --- ## ⚡ 性能优化规范 ### 前端性能优化 #### 1. 构建优化 ```typescript // vite.config.ts export default defineConfig({ build: { rollupOptions: { output: { // 代码分割 manualChunks: { vendor: ['vue', 'vue-router', 'pinia'], antd: ['ant-design-vue'], utils: ['lodash-es', 'dayjs'], }, }, }, // 启用压缩 minify: 'terser', terserOptions: { compress: { drop_console: true, drop_debugger: true, pure_funcs: ['console.log'], }, }, }, // 开发服务器优化 server: { hmr: { overlay: false, }, }, // 预构建优化 optimizeDeps: { include: ['vue', 'vue-router', 'pinia', 'ant-design-vue', '@vueuse/core'], }, }); ``` #### 2. 组件优化 ```vue ``` #### 3. 图片优化 ```typescript // utils/image.ts /** * 图片懒加载指令 */ export const vLazyLoad = { mounted(el: HTMLImageElement, binding: DirectiveBinding) { const observer = new IntersectionObserver((entries) => { entries.forEach((entry) => { if (entry.isIntersecting) { const img = entry.target as HTMLImageElement; img.src = binding.value; img.onload = () => { img.classList.add('loaded'); }; observer.unobserve(img); } }); }); observer.observe(el); }, }; /** * 图片压缩 */ export function compressImage( file: File, quality: number = 0.8, maxWidth: number = 1920, ): Promise { return new Promise((resolve) => { const canvas = document.createElement('canvas'); const ctx = canvas.getContext('2d')!; const img = new Image(); img.onload = () => { const { width, height } = img; const ratio = Math.min(maxWidth / width, maxWidth / height); canvas.width = width * ratio; canvas.height = height * ratio; ctx.drawImage(img, 0, 0, canvas.width, canvas.height); canvas.toBlob(resolve as BlobCallback, 'image/jpeg', quality); }; img.src = URL.createObjectURL(file); }); } ``` #### 4. 缓存策略 ```typescript // utils/cache.ts class CacheManager { private cache = new Map(); set(key: string, data: any, ttl: number = 5 * 60 * 1000) { const expiry = Date.now() + ttl; this.cache.set(key, { data, expiry }); } get(key: string): T | null { const item = this.cache.get(key); if (!item) return null; if (Date.now() > item.expiry) { this.cache.delete(key); return null; } return item.data; } clear() { this.cache.clear(); } // 清理过期缓存 cleanup() { const now = Date.now(); for (const [key, value] of this.cache.entries()) { if (now > value.expiry) { this.cache.delete(key); } } } } export const cacheManager = new CacheManager(); // 定期清理过期缓存 setInterval( () => { cacheManager.cleanup(); }, 5 * 60 * 1000, ); // 每5分钟清理一次 ``` ### 性能监控 ```typescript // utils/performance.ts class PerformanceMonitor { private metrics: Map = new Map(); // 开始性能监控 start(name: string): () => void { const startTime = performance.now(); return () => { const endTime = performance.now(); const duration = endTime - startTime; this.record(name, duration); }; } // 记录性能数据 record(name: string, value: number) { if (!this.metrics.has(name)) { this.metrics.set(name, []); } const values = this.metrics.get(name)!; values.push(value); // 只保留最近100条记录 if (values.length > 100) { values.shift(); } } // 获取性能统计 getStats(name: string) { const values = this.metrics.get(name); if (!values || values.length === 0) return null; const sorted = [...values].sort((a, b) => a - b); const len = sorted.length; return { count: len, min: sorted[0], max: sorted[len - 1], avg: values.reduce((a, b) => a + b, 0) / len, p50: sorted[Math.floor(len * 0.5)], p90: sorted[Math.floor(len * 0.9)], p95: sorted[Math.floor(len * 0.95)], }; } // 获取所有统计数据 getAllStats() { const stats: Record = {}; for (const name of this.metrics.keys()) { stats[name] = this.getStats(name); } return stats; } // 发送性能数据到服务器 async report() { const stats = this.getAllStats(); try { await fetch('/api/performance/report', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ timestamp: Date.now(), url: window.location.href, userAgent: navigator.userAgent, stats, }), }); } catch (error) { console.warn('Failed to report performance metrics:', error); } } } export const performanceMonitor = new PerformanceMonitor(); // 定期上报性能数据 setInterval(() => { performanceMonitor.report(); }, 60 * 1000); // 每分钟上报一次 ``` --- ## 📚 总结 本开发规范文档涵盖了Telegram管理系统开发的各个方面,包括: ### 🎯 核心规范 - **开发环境**: 统一的开发工具和配置 - **代码规范**: TypeScript、ESLint、Prettier配置 - **项目结构**: 清晰的目录组织和文件命名 ### 🛠️ 技术规范 - **组件开发**: Vue 3组合式API最佳实践 - **API开发**: 统一的接口调用和错误处理 - **状态管理**: Pinia状态管理规范 - **路由管理**: 动态路由和权限控制 ### 🎨 质量保证 - **样式规范**: TailwindCSS + SCSS混合方案 - **测试规范**: 单元测试、组件测试、E2E测试 - **Git工作流**: 分支策略和提交规范 - **代码审查**: 完整的审查流程和清单 ### ⚡ 性能优化 - **构建优化**: Vite配置和代码分割 - **运行时优化**: 懒加载、虚拟滚动、缓存策略 - **性能监控**: 性能指标收集和上报 遵循这些规范将确保代码质量、提高开发效率、降低维护成本。所有团队成员都应该熟悉并严格执行这些规范。 --- **维护说明**: 本规范文档会随着项目发展不断更新和完善,请定期查看最新版本。如有疑问或建议,请联系技术团队。