入职第一件事,老板让我制定前端开发规范

目录

  1. 项目概述
  2. 技术栈规范
  3. 项目结构规范
  4. 代码编写规范
  5. 组件开发规范
  6. 状态管理规范
  7. 路由管理规范
  8. API 接口规范
  9. 样式编写规范
  10. 国际化规范
  11. 测试规范
  12. 构建部署规范
  13. Git 工作流规范
  14. 文档编写规范

项目概述

项目定位

后台管理系统是一个基于 Vue 3 + TypeScript + Vite 的现代化前端应用,主要用于xx业务的管理和操作。

核心特性

  • 🚀 基于 Vue 3 Composition API
  • 💪 TypeScript 全面支持
  • ⚡ Vite 构建工具
  • 🎨 Element Plus UI 组件库
  • 🌍 完整的国际化支持
  • 📱 响应式设计
  • 🔒 完善的权限控制
  • 🧪 单元测试覆盖

技术栈规范

核心技术栈

{
  "framework": "Vue 3.3+",
  "language": "TypeScript 5.0+",
  "buildTool": "Vite 4.0+",
  "uiLibrary": "Element Plus 2.3+",
  "stateManagement": "Pinia 2.1+",
  "router": "Vue Router 4.2+",
  "i18n": "Vue I18n 9.2+",
  "httpClient": "Axios 1.4+",
  "cssFramework": "Tailwind CSS 3.3+",
  "testing": "Vitest + Vue Test Utils",
  "linting": "ESLint + Prettier"
}

开发工具要求

  • Node.js: >= 18.0.0
  • 包管理器: npm >= 9.0.0 或 yarn >= 1.22.0
  • 编辑器: VS Code (推荐)
  • 浏览器: Chrome >= 90, Firefox >= 88, Safari >= 14

VS Code 扩展推荐

{
  "recommendations": [
    "Vue.volar",
    "Vue.vscode-typescript-vue-plugin",
    "bradlc.vscode-tailwindcss",
    "esbenp.prettier-vscode",
    "dbaeumer.vscode-eslint",
    "lokalise.i18n-ally",
    "ms-vscode.vscode-typescript-next"
  ]
}

项目结构规范

目录结构

cmclink-ibs-web/
├── public/                     # 静态资源
│   ├── favicon.ico
│   └── index.html
├── src/                        # 源代码
│   ├── api/                    # API 接口
│   │   ├── modules/            # 按模块分类的接口
│   │   │   ├── auth.ts
│   │   │   ├── booking.ts
│   │   │   └── user.ts
│   │   ├── types/              # API 类型定义
│   │   └── index.ts            # API 统一导出
│   ├── assets/                 # 静态资源
│   │   ├── images/
│   │   ├── icons/
│   │   └── styles/
│   ├── components/             # 公共组件
│   │   ├── Common/             # 通用组件
│   │   ├── Business/           # 业务组件
│   │   └── Layout/             # 布局组件
│   ├── composables/            # 组合式函数
│   │   ├── useAuth.ts
│   │   ├── useTable.ts
│   │   └── useForm.ts
│   ├── directives/             # 自定义指令
│   ├── i18n/                   # 国际化
│   │   ├── zh/                 # 中文语言包
│   │   ├── en/                 # 英文语言包
│   │   └── index.ts            # i18n 配置
│   ├── router/                 # 路由配置
│   │   ├── modules/            # 路由模块
│   │   ├── guards/             # 路由守卫
│   │   └── index.ts
│   ├── store/                  # 状态管理
│   │   ├── modules/            # store 模块
│   │   └── index.ts
│   ├── styles/                 # 样式文件
│   │   ├── variables.scss      # 变量定义
│   │   ├── mixins.scss         # 混入
│   │   └── global.scss         # 全局样式
│   ├── types/                  # 类型定义
│   │   ├── api.ts              # API 类型
│   │   ├── common.ts           # 通用类型
│   │   └── business.ts         # 业务类型
│   ├── utils/                  # 工具函数
│   │   ├── request.ts          # 请求封装
│   │   ├── storage.ts          # 存储工具
│   │   ├── validators.ts       # 验证工具
│   │   └── formatters.ts       # 格式化工具
│   ├── views/                  # 页面组件
│   │   ├── auth/               # 认证相关页面
│   │   ├── booking/            # 订舱相关页面
│   │   ├── dashboard/          # 仪表盘
│   │   └── system/             # 系统管理
│   ├── App.vue                 # 根组件
│   └── main.ts                 # 入口文件
├── tests/                      # 测试文件
│   ├── unit/                   # 单元测试
│   ├── integration/            # 集成测试
│   └── utils/                  # 测试工具
├── docs/                       # 项目文档
├── .env.development            # 开发环境变量
├── .env.production             # 生产环境变量
├── .eslintrc.js                # ESLint 配置
├── .prettierrc                 # Prettier 配置
├── tailwind.config.js          # Tailwind 配置
├── tsconfig.json               # TypeScript 配置
├── vite.config.ts              # Vite 配置
└── package.json                # 项目配置

文件命名规范

  • 组件文件: PascalCase (如 UserProfile.vue)
  • 页面文件: PascalCase (如 BookingList.vue)
  • 工具文件: camelCase (如 formatters.ts)
  • 常量文件: UPPER_SNAKE_CASE (如 API_CONSTANTS.ts)
  • 类型文件: camelCase (如 userTypes.ts)
  • 样式文件: kebab-case (如 user-profile.scss)

代码编写规范

TypeScript 规范

1. 类型定义

// ✅ 推荐:使用 interface 定义对象类型
interface User {
  id: number;
  name: string;
  email: string;
  avatar?: string;
  createdAt: Date;
}

// ✅ 推荐:使用 type 定义联合类型
type UserStatus = 'active' | 'inactive' | 'pending';

// ✅ 推荐:使用泛型提高复用性
interface ApiResponse<T> {
  code: number;
  message: string;
  data: T;
}

// ✅ 推荐:使用枚举定义常量
enum UserRole {
  ADMIN = 'admin',
  USER = 'user',
  GUEST = 'guest'
}

2. 函数定义

// ✅ 推荐:明确的参数和返回值类型
function formatUser(user: User): FormattedUser {
  return {
    id: user.id,
    displayName: user.name,
    emailAddress: user.email
  };
}

// ✅ 推荐:使用箭头函数和类型推断
const calculateTotal = (items: OrderItem[]): number => {
  return items.reduce((sum, item) => sum + item.price * item.quantity, 0);
};

// ✅ 推荐:异步函数的类型定义
async function fetchUserData(id: number): Promise<User> {
  const response = await api.get<ApiResponse<User>>(`/users/${id}`);
  return response.data.data;
}

3. 组件 Props 类型

// ✅ 推荐:使用 interface 定义 Props
interface UserCardProps {
  user: User;
  showAvatar?: boolean;
  onEdit?: (user: User) => void;
  onDelete?: (id: number) => void;
}

// ✅ 推荐:使用 withDefaults 设置默认值
const props = withDefaults(defineProps<UserCardProps>(), {
  showAvatar: true
});

Vue 3 Composition API 规范

1. 组件结构

<template>
  <!-- 模板内容 -->
</template>

<script setup lang="ts">
// 1. 导入依赖
import { ref, computed, watch, onMounted } from 'vue';
import { useI18n } from 'vue-i18n';
import { useRouter } from 'vue-router';

// 2. 类型定义
interface Props {
  // props 类型
}

interface Emits {
  // emits 类型
}

// 3. Props 和 Emits
const props = defineProps<Props>();
const emit = defineEmits<Emits>();

// 4. 组合式函数
const { t } = useI18n();
const router = useRouter();

// 5. 响应式数据
const loading = ref(false);
const formData = ref({});

// 6. 计算属性
const computedValue = computed(() => {
  // 计算逻辑
});

// 7. 方法定义
const handleSubmit = async () => {
  // 方法实现
};

// 8. 监听器
watch(
  () => props.value,
  (newValue) => {
    // 监听逻辑
  }
);

// 9. 生命周期
onMounted(() => {
  // 初始化逻辑
});

// 10. 暴露给模板的内容
defineExpose({
  // 暴露的方法或属性
});
</script>

<style scoped>
/* 组件样式 */
</style>

2. 响应式数据规范

// ✅ 推荐:使用 ref 定义基础类型
const count = ref(0);
const message = ref('');
const isVisible = ref(false);

// ✅ 推荐:使用 reactive 定义对象
const formData = reactive({
  name: '',
  email: '',
  age: 0
});

// ✅ 推荐:使用 computed 定义计算属性
const fullName = computed(() => {
  return `${formData.firstName} ${formData.lastName}`;
});

// ✅ 推荐:使用 readonly 保护数据
const readonlyData = readonly(formData);

错误处理规范

// ✅ 推荐:统一的错误处理
try {
  const result = await apiCall();
  // 处理成功结果
} catch (error) {
  // 记录错误
  console.error('API call failed:', error);
  
  // 显示用户友好的错误信息
  if (error instanceof ApiError) {
    showError(error.message);
  } else {
    showError(t('error.unexpectedError'));
  }
}

// ✅ 推荐:自定义错误类
class ApiError extends Error {
  constructor(
    message: string,
    public code: string,
    public status: number
  ) {
    super(message);
    this.name = 'ApiError';
  }
}

组件开发规范

组件分类

  1. 基础组件 (Base Components)

    • 纯 UI 组件,无业务逻辑
    • 高度可复用
    • 命名以 Base 开头
  2. 通用组件 (Common Components)

    • 包含少量业务逻辑
    • 跨模块复用
    • 放在 components/Common/ 目录
  3. 业务组件 (Business Components)

    • 特定业务逻辑
    • 模块内复用
    • 放在 components/Business/ 目录
  4. 页面组件 (Page Components)

    • 页面级组件
    • 不可复用
    • 放在 views/ 目录

组件开发示例

<!-- components/Common/DataTable.vue -->
<template>
  <div class="data-table">
    <div v-if="showHeader" class="table-header">
      <slot name="header">
        <h3>{{ title }}</h3>
      </slot>
    </div>
    
    <el-table
      :data="data"
      :loading="loading"
      v-bind="$attrs"
      @selection-change="handleSelectionChange"
    >
      <slot />
    </el-table>
    
    <div v-if="showPagination" class="table-pagination">
      <el-pagination
        v-model:current-page="currentPage"
        v-model:page-size="pageSize"
        :total="total"
        @size-change="handleSizeChange"
        @current-change="handleCurrentChange"
      />
    </div>
  </div>
</template>

<script setup lang="ts">
import { computed } from 'vue';

interface Props {
  data: any[];
  loading?: boolean;
  title?: string;
  showHeader?: boolean;
  showPagination?: boolean;
  total?: number;
  pageSize?: number;
  currentPage?: number;
}

interface Emits {
  'selection-change': [selection: any[]];
  'size-change': [size: number];
  'current-change': [page: number];
}

const props = withDefaults(defineProps<Props>(), {
  loading: false,
  showHeader: true,
  showPagination: true,
  pageSize: 20,
  currentPage: 1
});

const emit = defineEmits<Emits>();

// 事件处理
const handleSelectionChange = (selection: any[]) => {
  emit('selection-change', selection);
};

const handleSizeChange = (size: number) => {
  emit('size-change', size);
};

const handleCurrentChange = (page: number) => {
  emit('current-change', page);
};
</script>

<style scoped>
.data-table {
  @apply bg-white rounded-lg shadow;
}

.table-header {
  @apply p-4 border-b border-gray-200;
}

.table-pagination {
  @apply p-4 flex justify-end;
}
</style>

组件文档规范

<!--
组件名称: DataTable
组件描述: 通用数据表格组件,支持分页、选择、自定义列等功能

使用示例:
<DataTable
  :data="tableData"
  :loading="loading"
  title="用户列表"
  @selection-change="handleSelectionChange"
>
  <el-table-column prop="name" label="姓名" />
  <el-table-column prop="email" label="邮箱" />
</DataTable>

Props:
- data: 表格数据数组
- loading: 加载状态
- title: 表格标题
- showHeader: 是否显示头部
- showPagination: 是否显示分页

Emits:
- selection-change: 选择项变化事件
- size-change: 分页大小变化事件
- current-change: 当前页变化事件

Slots:
- default: 表格列定义
- header: 自定义头部内容
-->

状态管理规范

Pinia Store 结构

// store/modules/user.ts
import { defineStore } from 'pinia';
import { ref, computed } from 'vue';
import type { User, UserProfile } from '@/types/user';
import { userApi } from '@/api/modules/user';

export const useUserStore = defineStore('user', () => {
  // State
  const currentUser = ref<User | null>(null);
  const userProfile = ref<UserProfile | null>(null);
  const loading = ref(false);
  
  // Getters
  const isLoggedIn = computed(() => !!currentUser.value);
  const userName = computed(() => currentUser.value?.name || '');
  const userRole = computed(() => currentUser.value?.role || 'guest');
  
  // Actions
  const login = async (credentials: LoginCredentials) => {
    loading.value = true;
    try {
      const response = await userApi.login(credentials);
      currentUser.value = response.data.user;
      // 存储 token
      localStorage.setItem('token', response.data.token);
      return response;
    } catch (error) {
      throw error;
    } finally {
      loading.value = false;
    }
  };
  
  const logout = async () => {
    try {
      await userApi.logout();
    } finally {
      currentUser.value = null;
      userProfile.value = null;
      localStorage.removeItem('token');
    }
  };
  
  const fetchProfile = async () => {
    if (!currentUser.value) return;
    
    loading.value = true;
    try {
      const response = await userApi.getProfile(currentUser.value.id);
      userProfile.value = response.data;
    } catch (error) {
      console.error('Failed to fetch user profile:', error);
    } finally {
      loading.value = false;
    }
  };
  
  const updateProfile = async (data: Partial<UserProfile>) => {
    if (!userProfile.value) return;
    
    loading.value = true;
    try {
      const response = await userApi.updateProfile(userProfile.value.id, data);
      userProfile.value = { ...userProfile.value, ...response.data };
      return response;
    } catch (error) {
      throw error;
    } finally {
      loading.value = false;
    }
  };
  
  // 重置状态
  const $reset = () => {
    currentUser.value = null;
    userProfile.value = null;
    loading.value = false;
  };
  
  return {
    // State
    currentUser,
    userProfile,
    loading,
    
    // Getters
    isLoggedIn,
    userName,
    userRole,
    
    // Actions
    login,
    logout,
    fetchProfile,
    updateProfile,
    $reset
  };
});

Store 使用规范

<script setup lang="ts">
import { useUserStore } from '@/store/modules/user';
import { storeToRefs } from 'pinia';

// ✅ 推荐:使用 storeToRefs 保持响应性
const userStore = useUserStore();
const { currentUser, loading, isLoggedIn } = storeToRefs(userStore);
const { login, logout } = userStore;

// ✅ 推荐:在组件中调用 store actions
const handleLogin = async (credentials: LoginCredentials) => {
  try {
    await login(credentials);
    // 登录成功后的处理
  } catch (error) {
    // 错误处理
  }
};
</script>

路由管理规范

路由配置结构

// router/modules/booking.ts
import type { RouteRecordRaw } from 'vue-router';

const bookingRoutes: RouteRecordRaw[] = [
  {
    path: '/booking',
    name: 'Booking',
    component: () => import('@/views/booking/index.vue'),
    meta: {
      titleKey: 'router.booking.title',
      icon: 'booking',
      requiresAuth: true,
      permissions: ['booking:read']
    },
    children: [
      {
        path: '',
        redirect: '/booking/list'
      },
      {
        path: 'list',
        name: 'BookingList',
        component: () => import('@/views/booking/BookingList.vue'),
        meta: {
          titleKey: 'router.booking.list',
          keepAlive: true
        }
      },
      {
        path: 'create',
        name: 'BookingCreate',
        component: () => import('@/views/booking/BookingCreate.vue'),
        meta: {
          titleKey: 'router.booking.create'
        }
      },
      {
        path: ':id/edit',
        name: 'BookingEdit',
        component: () => import('@/views/booking/BookingEdit.vue'),
        meta: {
          titleKey: 'router.booking.edit'
        },
        props: true
      }
    ]
  }
];

export default bookingRoutes;

路由守卫

// router/guards/auth.ts
import type { NavigationGuardNext, RouteLocationNormalized } from 'vue-router';
import { useUserStore } from '@/store/modules/user';
import { showError } from '@/utils/message';

export function createAuthGuard() {
  return async (
    to: RouteLocationNormalized,
    from: RouteLocationNormalized,
    next: NavigationGuardNext
  ) => {
    const userStore = useUserStore();
    
    // 检查是否需要认证
    if (to.meta.requiresAuth && !userStore.isLoggedIn) {
      showError('auth.loginRequired');
      next({ name: 'Login', query: { redirect: to.fullPath } });
      return;
    }
    
    // 检查权限
    if (to.meta.permissions && userStore.isLoggedIn) {
      const hasPermission = to.meta.permissions.some((permission: string) =>
        userStore.hasPermission(permission)
      );
      
      if (!hasPermission) {
        showError('auth.permissionDenied');
        next({ name: 'Forbidden' });
        return;
      }
    }
    
    next();
  };
}

API 接口规范

接口封装

// api/modules/booking.ts
import { request } from '@/utils/request';
import type {
  BookingListParams,
  BookingListResponse,
  BookingDetail,
  CreateBookingData,
  UpdateBookingData
} from '@/types/booking';

export const bookingApi = {
  // 获取订舱列表
  getList(params: BookingListParams): Promise<BookingListResponse> {
    return request.get('/bookings', { params });
  },
  
  // 获取订舱详情
  getDetail(id: number): Promise<BookingDetail> {
    return request.get(`/bookings/${id}`);
  },
  
  // 创建订舱
  create(data: CreateBookingData): Promise<BookingDetail> {
    return request.post('/bookings', data);
  },
  
  // 更新订舱
  update(id: number, data: UpdateBookingData): Promise<BookingDetail> {
    return request.put(`/bookings/${id}`, data);
  },
  
  // 删除订舱
  delete(id: number): Promise<void> {
    return request.delete(`/bookings/${id}`);
  },
  
  // 批量删除
  batchDelete(ids: number[]): Promise<void> {
    return request.delete('/bookings/batch', { data: { ids } });
  }
};

请求拦截器

// utils/request.ts
import axios from 'axios';
import type { AxiosRequestConfig, AxiosResponse } from 'axios';
import { useUserStore } from '@/store/modules/user';
import { showError } from '@/utils/message';
import { ApiErrorHandler } from '@/utils/api-error';

// 创建 axios 实例
const request = axios.create({
  baseURL: import.meta.env.VITE_API_BASE_URL,
  timeout: 10000,
  headers: {
    'Content-Type': 'application/json'
  }
});

// 请求拦截器
request.interceptors.request.use(
  (config: AxiosRequestConfig) => {
    const userStore = useUserStore();
    
    // 添加认证 token
    const token = localStorage.getItem('token');
    if (token) {
      config.headers = {
        ...config.headers,
        Authorization: `Bearer ${token}`
      };
    }
    
    // 添加语言标识
    const locale = localStorage.getItem('locale') || 'zh';
    config.headers = {
      ...config.headers,
      'Accept-Language': locale
    };
    
    return config;
  },
  (error) => {
    return Promise.reject(error);
  }
);

// 响应拦截器
request.interceptors.response.use(
  (response: AxiosResponse) => {
    const { code, message, data } = response.data;
    
    // 业务成功
    if (code === 200) {
      return data;
    }
    
    // 业务失败
    const error = new Error(message);
    error.code = code;
    return Promise.reject(error);
  },
  (error) => {
    // 处理 HTTP 错误
    ApiErrorHandler.handle(error);
    return Promise.reject(error);
  }
);

export { request };

样式编写规范

Tailwind CSS 使用规范

<template>
  <!-- ✅ 推荐:使用 Tailwind 原子类 -->
  <div class="flex items-center justify-between p-4 bg-white rounded-lg shadow">
    <h2 class="text-lg font-semibold text-gray-900">
      {{ title }}
    </h2>
    <button class="px-4 py-2 text-white bg-blue-600 rounded hover:bg-blue-700">
      {{ t('common.save') }}
    </button>
  </div>
  
  <!-- ✅ 推荐:复杂样式使用自定义类 -->
  <div class="user-card">
    <div class="user-avatar">
      <img :src="user.avatar" :alt="user.name" />
    </div>
    <div class="user-info">
      <h3>{{ user.name }}</h3>
      <p>{{ user.email }}</p>
    </div>
  </div>
</template>

<style scoped>
/* ✅ 推荐:使用 @apply 指令组合 Tailwind 类 */
.user-card {
  @apply flex items-center p-4 bg-white rounded-lg shadow hover:shadow-md transition-shadow;
}

.user-avatar {
  @apply w-12 h-12 mr-4 overflow-hidden rounded-full;
}

.user-avatar img {
  @apply w-full h-full object-cover;
}

.user-info h3 {
  @apply text-lg font-semibold text-gray-900;
}

.user-info p {
  @apply text-sm text-gray-600;
}
</style>

SCSS 变量和混入

// styles/variables.scss
:root {
  // 颜色变量
  --color-primary: #3b82f6;
  --color-success: #10b981;
  --color-warning: #f59e0b;
  --color-danger: #ef4444;
  --color-info: #6b7280;
  
  // 间距变量
  --spacing-xs: 0.25rem;
  --spacing-sm: 0.5rem;
  --spacing-md: 1rem;
  --spacing-lg: 1.5rem;
  --spacing-xl: 2rem;
  
  // 字体变量
  --font-size-xs: 0.75rem;
  --font-size-sm: 0.875rem;
  --font-size-base: 1rem;
  --font-size-lg: 1.125rem;
  --font-size-xl: 1.25rem;
  
  // 阴影变量
  --shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
  --shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
  --shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1);
}

// styles/mixins.scss
// 响应式断点混入
@mixin mobile {
  @media (max-width: 767px) {
    @content;
  }
}

@mixin tablet {
  @media (min-width: 768px) and (max-width: 1023px) {
    @content;
  }
}

@mixin desktop {
  @media (min-width: 1024px) {
    @content;
  }
}

// 文本省略混入
@mixin text-ellipsis {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

@mixin text-ellipsis-multiline($lines: 2) {
  display: -webkit-box;
  -webkit-line-clamp: $lines;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

// 居中混入
@mixin flex-center {
  display: flex;
  align-items: center;
  justify-content: center;
}

国际化规范

语言包组织

// i18n/zh/index.ts
import common from './common';
import auth from './auth';
import booking from './booking';
import user from './user';
import error from './error';

export default {
  common,
  auth,
  booking,
  user,
  error
};

// i18n/zh/common.ts
export default {
  // 通用操作
  save: '保存',
  cancel: '取消',
  confirm: '确认',
  delete: '删除',
  edit: '编辑',
  add: '添加',
  search: '搜索',
  reset: '重置',
  submit: '提交',
  
  // 状态
  loading: '加载中...',
  noData: '暂无数据',
  success: '操作成功',
  failed: '操作失败',
  
  // 表单
  required: '此字段为必填项',
  invalidFormat: '格式不正确',
  
  // 分页
  total: '共 {count} 条',
  pageSize: '每页显示',
  
  // 时间
  today: '今天',
  yesterday: '昨天',
  thisWeek: '本周',
  thisMonth: '本月'
};

国际化使用规范

<template>
  <!-- ✅ 推荐:使用 $t 函数 -->
  <h1>{{ $t('booking.title') }}</h1>
  
  <!-- ✅ 推荐:带参数的翻译 -->
  <p>{{ $t('common.total', { count: totalCount }) }}</p>
  
  <!-- ✅ 推荐:复数形式 -->
  <span>{{ $tc('booking.item', itemCount) }}</span>
  
  <!-- ✅ 推荐:属性中使用翻译 -->
  <el-input :placeholder="$t('user.enterName')" />
</template>

<script setup lang="ts">
import { useI18n } from 'vue-i18n';

const { t, tc, locale } = useI18n();

// ✅ 推荐:在 JavaScript 中使用翻译
const showSuccessMessage = () => {
  ElMessage.success(t('common.saveSuccess'));
};

// ✅ 推荐:动态翻译键
const getStatusText = (status: string) => {
  return t(`booking.status.${status}`);
};
</script>

测试规范

单元测试

// tests/unit/components/UserCard.test.ts
import { describe, it, expect, vi } from 'vitest';
import { mount } from '@vue/test-utils';
import UserCard from '@/components/Common/UserCard.vue';
import { createTestI18n } from '../utils/test-utils';

const mockUser = {
  id: 1,
  name: 'John Doe',
  email: 'john@example.com',
  avatar: 'https://example.com/avatar.jpg'
};

describe('UserCard', () => {
  it('should render user information correctly', () => {
    const wrapper = mount(UserCard, {
      props: {
        user: mockUser
      },
      global: {
        plugins: [createTestI18n()]
      }
    });
    
    expect(wrapper.find('.user-name').text()).toBe(mockUser.name);
    expect(wrapper.find('.user-email').text()).toBe(mockUser.email);
    expect(wrapper.find('img').attributes('src')).toBe(mockUser.avatar);
  });
  
  it('should emit edit event when edit button is clicked', async () => {
    const wrapper = mount(UserCard, {
      props: {
        user: mockUser,
        showActions: true
      },
      global: {
        plugins: [createTestI18n()]
      }
    });
    
    await wrapper.find('.edit-button').trigger('click');
    
    expect(wrapper.emitted('edit')).toBeTruthy();
    expect(wrapper.emitted('edit')?.[0]).toEqual([mockUser]);
  });
  
  it('should not show actions when showActions is false', () => {
    const wrapper = mount(UserCard, {
      props: {
        user: mockUser,
        showActions: false
      },
      global: {
        plugins: [createTestI18n()]
      }
    });
    
    expect(wrapper.find('.user-actions').exists()).toBe(false);
  });
});

集成测试

// tests/integration/booking.test.ts
import { describe, it, expect, beforeEach, vi } from 'vitest';
import { mount } from '@vue/test-utils';
import { createRouter, createWebHistory } from 'vue-router';
import BookingList from '@/views/booking/BookingList.vue';
import { createTestPinia } from '@pinia/testing';
import { createTestI18n } from '../utils/test-utils';

// Mock API
vi.mock('@/api/modules/booking', () => ({
  bookingApi: {
    getList: vi.fn().mockResolvedValue({
      data: [],
      total: 0
    })
  }
}));

describe('BookingList Integration', () => {
  let router: any;
  
  beforeEach(() => {
    router = createRouter({
      history: createWebHistory(),
      routes: [
        { path: '/booking/list', component: BookingList }
      ]
    });
  });
  
  it('should load and display booking list', async () => {
    const wrapper = mount(BookingList, {
      global: {
        plugins: [
          router,
          createTestPinia(),
          createTestI18n()
        ]
      }
    });
    
    // 等待组件加载完成
    await wrapper.vm.$nextTick();
    
    expect(wrapper.find('.booking-list').exists()).toBe(true);
  });
});

构建部署规范

环境配置

# .env.development
VITE_APP_TITLE=集运后台管理系统(开发环境)
VITE_API_BASE_URL=http://localhost:3000/api
VITE_UPLOAD_URL=http://localhost:3000/upload
VITE_APP_ENV=development

# .env.production
VITE_APP_TITLE=集运后台管理系统
VITE_API_BASE_URL=https://api.example.com
VITE_UPLOAD_URL=https://upload.example.com
VITE_APP_ENV=production

Vite 配置优化

// vite.config.ts
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
import { resolve } from 'path';
import { visualizer } from 'rollup-plugin-visualizer';

export default defineConfig(({ mode }) => {
  const isProduction = mode === 'production';
  
  return {
    plugins: [
      vue(),
      // 生产环境下生成打包分析报告
      isProduction && visualizer({
        filename: 'dist/stats.html',
        open: true
      })
    ].filter(Boolean),
    
    resolve: {
      alias: {
        '@': resolve(__dirname, 'src'),
        '~': resolve(__dirname, 'src')
      }
    },
    
    build: {
      // 生产环境优化
      minify: 'terser',
      terserOptions: {
        compress: {
          drop_console: isProduction,
          drop_debugger: isProduction
        }
      },
      
      // 代码分割
      rollupOptions: {
        output: {
          manualChunks: {
            vendor: ['vue', 'vue-router', 'pinia'],
            elementPlus: ['element-plus'],
            utils: ['axios', 'dayjs']
          }
        }
      },
      
      // 资源内联限制
      assetsInlineLimit: 4096
    },
    
    // 开发服务器配置
    server: {
      port: 3001,
      open: true,
      proxy: {
        '/api': {
          target: 'http://localhost:3000',
          changeOrigin: true,
          rewrite: (path) => path.replace(/^\/api/, '')
        }
      }
    }
  };
});

部署脚本

{
  "scripts": {
    "dev": "vite",
    "build": "vue-tsc --noEmit && vite build",
    "build:dev": "vite build --mode development",
    "build:prod": "vite build --mode production",
    "preview": "vite preview",
    "lint": "eslint src --ext .vue,.js,.ts --fix",
    "lint:style": "stylelint src/**/*.{vue,css,scss} --fix",
    "test": "vitest",
    "test:ui": "vitest --ui",
    "test:coverage": "vitest --coverage",
    "type-check": "vue-tsc --noEmit",
    "deploy:dev": "npm run build:dev && npm run upload:dev",
    "deploy:prod": "npm run build:prod && npm run upload:prod"
  }
}

Git 工作流规范

分支管理

master/main     # 主分支,用于生产环境
├── develop     # 开发分支,用于集成测试
├── feature/*   # 功能分支
├── bugfix/*    # 修复分支
├── hotfix/*    # 热修复分支
└── release/*   # 发布分支

提交信息规范

<type>(<scope>): <subject>

<body>

<footer>

Type 类型:

  • feat: 新功能
  • fix: 修复 bug
  • docs: 文档更新
  • style: 代码格式调整
  • refactor: 代码重构
  • test: 测试相关
  • chore: 构建过程或辅助工具的变动

示例:

feat(booking): add booking list pagination

- Add pagination component to booking list
- Update API to support pagination parameters
- Add loading state for better UX

Closes #123

Git Hooks

// package.json
{
  "husky": {
    "hooks": {
      "pre-commit": "lint-staged",
      "commit-msg": "commitlint -E HUSKY_GIT_PARAMS"
    }
  },
  "lint-staged": {
    "*.{vue,js,ts}": [
      "eslint --fix",
      "git add"
    ],
    "*.{vue,css,scss}": [
      "stylelint --fix",
      "git add"
    ]
  }
}

文档编写规范

README 结构

# 项目名称

项目简介和主要功能描述

## 技术栈

- Vue 3.3+
- TypeScript 5.0+
- Vite 4.0+
- Element Plus 2.3+
- Pinia 2.1+

## 快速开始

### 环境要求

- Node.js >= 18.0.0
- npm >= 9.0.0

### 安装依赖

```bash
npm install

启动开发服务器

npm run dev

构建生产版本

npm run build

项目结构

src/
├── api/          # API 接口
├── components/   # 组件
├── views/        # 页面
├── store/        # 状态管理
├── router/       # 路由配置
├── utils/        # 工具函数
└── types/        # 类型定义

开发规范

详见 开发规范文档

部署说明

详见 部署文档

贡献指南

  1. Fork 项目
  2. 创建功能分支
  3. 提交代码
  4. 创建 Pull Request

许可证

MIT License


### API 文档

```markdown
# API 接口文档

## 基础信息

- 基础 URL: `https://api.example.com`
- 认证方式: Bearer Token
- 数据格式: JSON

## 通用响应格式

```json
{
  "code": 200,
  "message": "success",
  "data": {}
}

用户相关接口

用户登录

POST /auth/login

请求参数:

参数名类型必填说明
usernamestring用户名
passwordstring密码

响应示例:

{
  "code": 200,
  "message": "登录成功",
  "data": {
    "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
    "user": {
      "id": 1,
      "username": "admin",
      "name": "管理员",
      "role": "admin"
    }
  }
}

## 总结

本开发规范文档涵盖了集运后台管理系统开发的各个方面,包括:

1. **技术栈选择**:明确了项目使用的核心技术和版本要求
2. **项目结构**:规范了目录组织和文件命名
3. **代码规范**:定义了 TypeScript、Vue 3、组件开发的最佳实践
4. **架构设计**:规范了状态管理、路由管理、API 接口的设计模式
5. **样式规范**:统一了 CSS 编写和 Tailwind 使用规范
6. **国际化**:完整的多语言支持方案
7. **测试规范**:单元测试和集成测试的编写标准
8. **构建部署**:生产环境优化和部署流程
9. **团队协作**:Git 工作流和代码审查规范
10. **文档维护**:项目文档的编写和维护标准

遵循这些规范可以确保项目代码质量、团队协作效率和系统可维护性。

---

**文档版本**: v1.0  
**最后更新**: 2024年12月  
**维护者**: 前端开发团队