shop-front-end/doc/开发指南.md

10 KiB

开发指南

开发环境搭建

1. 环境准备

# 安装 Node.js (版本 16.0+)
# 下载地址: https://nodejs.org/

# 安装 pnpm
npm install -g pnpm

# 验证安装
node --version
pnpm --version

2. 项目初始化

# 克隆项目
git clone <repository-url>
cd shop-front-end

# 安装依赖
pnpm install

# 启动开发服务器
pnpm dev

3. 开发工具配置

推荐使用 VSCode 并安装以下插件:

  • Vue Language Features (Volar)
  • TypeScript Vue Plugin (Volar)
  • ESLint
  • Prettier
  • Stylelint

代码规范

1. 命名规范

文件命名

  • 组件文件: PascalCase.vue
  • 工具文件: camelCase.ts
  • 配置文件: kebab-case.ts
  • 样式文件: kebab-case.scss

变量命名

// 正确
const userName = '张三'
const MAX_COUNT = 100
const userList = []

// 避免
const username = '张三'  // 驼峰
const max_count = 100   // 下划线

组件命名

<!-- 正确 -->
<template>
  <UserProfile />
  <GoodsList />
</template>

<!-- 避免 -->
<template>
  <user-profile />
  <goods-list />
</template>

2. 代码结构

Vue 组件结构

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

<script setup lang="ts">
// 导入
import { ref, onMounted } from 'vue'
import { getUserInfo } from '@/api/user'

// 类型定义
interface User {
  id: number
  name: string
}

// 响应式数据
const userInfo = ref<User>()
const loading = ref(false)

// 方法
const fetchUser = async () => {
  loading.value = true
  try {
    const { data } = await getUserInfo()
    userInfo.value = data
  } catch (error) {
    console.error(error)
  } finally {
    loading.value = false
  }
}

// 生命周期
onMounted(() => {
  fetchUser()
})
</script>

<style scoped lang="scss">
/* 样式 */
</style>

3. TypeScript 规范

类型定义

// 接口定义
interface Goods {
  id: number
  name: string
  price: number
  stock: number
}

// 类型别名
type GoodsStatus = 'available' | 'sold_out' | 'disabled'

// 枚举
enum OrderStatus {
  PENDING = 1,
  PROCESSING = 2,
  COMPLETED = 3
}

函数定义

// 带类型的函数
const formatPrice = (price: number): string => {
  return ${price.toFixed(2)}`
}

// 异步函数
const fetchGoodsList = async (params: SearchParams): Promise<Goods[]> => {
  const { data } = await getGoodsList(params)
  return data
}

API 开发规范

1. API 文件结构

// src/api/shop/goods.ts
import request from '@/utils/request'

export interface Goods {
  id: number
  name: string
  price: number
  stock: number
}

export interface GoodsParams {
  page?: number
  size?: number
  keyword?: string
}

export interface GoodsListResponse {
  list: Goods[]
  total: number
}

// 获取商品列表
export const getGoodsList = (params: GoodsParams) => {
  return request.get<GoodsListResponse>('/api/goods/list', { params })
}

// 创建商品
export const createGoods = (data: Partial<Goods>) => {
  return request.post<Goods>('/api/goods', data)
}

// 更新商品
export const updateGoods = (id: number, data: Partial<Goods>) => {
  return request.put<Goods>(`/api/goods/${id}`, data)
}

// 删除商品
export const deleteGoods = (id: number) => {
  return request.delete(`/api/goods/${id}`)
}

2. 请求封装

// src/utils/request.ts
import axios from 'axios'
import { ElMessage } from 'element-plus'

const request = axios.create({
  baseURL: import.meta.env.VITE_API_BASE_URL,
  timeout: 10000
})

// 请求拦截器
request.interceptors.request.use(
  config => {
    // 添加 token
    const token = localStorage.getItem('token')
    if (token) {
      config.headers.Authorization = `Bearer ${token}`
    }
    return config
  },
  error => {
    return Promise.reject(error)
  }
)

// 响应拦截器
request.interceptors.response.use(
  response => {
    return response.data
  },
  error => {
    // 统一错误处理
    ElMessage.error(error.response?.data?.message || '请求失败')
    return Promise.reject(error)
  }
)

export default request

组件开发规范

1. 组件设计原则

单一职责

每个组件只负责一个特定的功能。

可复用性

组件应该设计为可复用的,通过 props 接收配置。

组合优于继承

通过组合多个小组件来构建复杂功能。

2. Props 定义

<script setup lang="ts">
interface Props {
  // 必填属性
  title: string
  // 可选属性
  size?: 'small' | 'medium' | 'large'
  // 带默认值
  disabled?: boolean
  // 复杂对象
  data?: Record<string, any>
}

// 定义 props
const props = withDefaults(defineProps<Props>(), {
  size: 'medium',
  disabled: false,
  data: () => ({})
})
</script>

3. Emits 定义

<script setup lang="ts">
interface Emits {
  (e: 'update:modelValue', value: string): void
  (e: 'submit', data: Record<string, any>): void
  (e: 'cancel'): void
}

// 定义 emits
const emit = defineEmits<Emits>()

const handleSubmit = () => {
  emit('submit', formData)
}

const handleCancel = () => {
  emit('cancel')
}
</script>

状态管理规范

1. Store 定义

// src/store/modules/user.ts
import { defineStore } from 'pinia'

export interface UserState {
  userInfo: User | null
  token: string | null
}

export const useUserStore = defineStore('user', {
  state: (): UserState => ({
    userInfo: null,
    token: null
  }),

  getters: {
    isLogin: state => !!state.token,
    userName: state => state.userInfo?.name || ''
  },

  actions: {
    setUserInfo(userInfo: User) {
      this.userInfo = userInfo
    },

    setToken(token: string) {
      this.token = token
    },

    async login(credentials: LoginParams) {
      const { data } = await loginApi(credentials)
      this.setToken(data.token)
      this.setUserInfo(data.user)
    },

    logout() {
      this.userInfo = null
      this.token = null
    }
  },

  persist: {
    enabled: true,
    strategies: [
      {
        key: 'user',
        storage: localStorage
      }
    ]
  }
})

2. Store 使用

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

const userStore = useUserStore()

// 使用状态
const userName = computed(() => userStore.userName)
const isLogin = computed(() => userStore.isLogin)

// 调用 action
const handleLogin = async () => {
  try {
    await userStore.login(loginForm)
    // 登录成功处理
  } catch (error) {
    // 错误处理
  }
}
</script>

路由开发规范

1. 路由定义

// src/router/modules/shop.ts
export default {
  path: '/shop',
  name: 'Shop',
  redirect: '/shop/goods',
  meta: {
    title: '店铺管理',
    icon: 'shop',
    rank: 10
  },
  children: [
    {
      path: '/shop/goods',
      name: 'Goods',
      component: () => import('@/views/shop/goods/index.vue'),
      meta: {
        title: '商品管理',
        roles: ['admin', 'shop_manager']
      }
    },
    {
      path: '/shop/orders',
      name: 'Orders',
      component: () => import('@/views/shop/orders/index.vue'),
      meta: {
        title: '订单管理',
        roles: ['admin', 'shop_manager']
      }
    }
  ]
} as RouteConfigsTable

2. 路由守卫

// 在路由守卫中检查权限
router.beforeEach((to, from, next) => {
  const userStore = useUserStore()

  // 检查登录状态
  if (!userStore.isLogin && to.path !== '/login') {
    next('/login')
    return
  }

  // 检查页面权限
  if (to.meta.roles && !hasPermission(to.meta.roles)) {
    next('/error/403')
    return
  }

  next()
})

样式开发规范

1. SCSS 规范

// 变量定义
$primary-color: #409eff;
$success-color: #67c23a;
$warning-color: #e6a23c;
$danger-color: #f56c6c;

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

// 组件样式
.goods-card {
  border: 1px solid #ebeef5;
  border-radius: 4px;
  padding: 20px;

  &__header {
    @include flex-center;
    margin-bottom: 16px;
  }

  &__title {
    font-size: 16px;
    font-weight: bold;
    color: $primary-color;
  }
}

2. CSS 类命名

使用 BEM 命名规范:

.block {}
.block__element {}
.block--modifier {}

测试规范

1. 单元测试

// tests/unit/utils/format.spec.ts
import { formatPrice } from '@/utils/format'

describe('formatPrice', () => {
  it('should format price correctly', () => {
    expect(formatPrice(100)).toBe('¥100.00')
    expect(formatPrice(99.9)).toBe('¥99.90')
  })
})

2. 组件测试

// tests/unit/components/GoodsCard.spec.ts
import { mount } from '@vue/test-utils'
import GoodsCard from '@/components/GoodsCard.vue'

describe('GoodsCard', () => {
  it('should render goods info', () => {
    const wrapper = mount(GoodsCard, {
      props: {
        goods: {
          id: 1,
          name: '测试商品',
          price: 100
        }
      }
    })

    expect(wrapper.text()).toContain('测试商品')
    expect(wrapper.text()).toContain('¥100.00')
  })
})

提交规范

1. Commit Message 格式

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

<body>

<footer>

2. 类型说明

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

3. 示例

feat(shop): 添加商品批量导入功能

- 支持 Excel 文件导入商品
- 添加导入进度显示
- 优化导入错误处理

Closes #123

性能优化

1. 代码分割

// 路由懒加载
const GoodsList = () => import('@/views/shop/goods/index.vue')

2. 组件优化

<script setup lang="ts">
// 使用 defineAsyncComponent 异步加载组件
const AsyncComponent = defineAsyncComponent(() =>
  import('@/components/HeavyComponent.vue')
)

// 使用 computed 缓存计算结果
const filteredList = computed(() => {
  return list.value.filter(item => item.status === 'active')
})
</script>

3. 图片优化

<template>
  <!-- 使用懒加载 -->
  <el-image
    :src="imageUrl"
    lazy
    :preview-src-list="[imageUrl]"
  />
</template>

常见问题

1. 类型错误

如果遇到 TypeScript 类型错误,检查:

  • 是否正确导入类型定义
  • 是否正确定义接口
  • 是否使用正确的类型注解

2. 样式不生效

检查:

  • 是否使用了正确的 scoped
  • 是否正确定义了样式类名
  • 是否引入了必要的样式文件

3. 路由跳转问题

检查:

  • 路由路径是否正确
  • 路由守卫是否阻止了跳转
  • 组件是否正确定义

文档版本: 1.0 最后更新: 2025-10-15