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

639 lines
10 KiB
Markdown
Raw Permalink Normal View History

# 开发指南
## 开发环境搭建
### 1. 环境准备
```bash
# 安装 Node.js (版本 16.0+)
# 下载地址: https://nodejs.org/
# 安装 pnpm
npm install -g pnpm
# 验证安装
node --version
pnpm --version
```
### 2. 项目初始化
```bash
# 克隆项目
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`
#### 变量命名
```typescript
// 正确
const userName = '张三'
const MAX_COUNT = 100
const userList = []
// 避免
const username = '张三' // 驼峰
const max_count = 100 // 下划线
```
#### 组件命名
```vue
<!-- 正确 -->
<template>
<UserProfile />
<GoodsList />
</template>
<!-- 避免 -->
<template>
<user-profile />
<goods-list />
</template>
```
### 2. 代码结构
#### Vue 组件结构
```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 规范
#### 类型定义
```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
}
```
#### 函数定义
```typescript
// 带类型的函数
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 文件结构
```typescript
// 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. 请求封装
```typescript
// 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 定义
```vue
<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 定义
```vue
<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 定义
```typescript
// 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 使用
```vue
<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. 路由定义
```typescript
// 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. 路由守卫
```typescript
// 在路由守卫中检查权限
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 规范
```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 命名规范:
```scss
.block {}
.block__element {}
.block--modifier {}
```
## 测试规范
### 1. 单元测试
```typescript
// 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. 组件测试
```typescript
// 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. 代码分割
```typescript
// 路由懒加载
const GoodsList = () => import('@/views/shop/goods/index.vue')
```
### 2. 组件优化
```vue
<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. 图片优化
```vue
<template>
<!-- 使用懒加载 -->
<el-image
:src="imageUrl"
lazy
:preview-src-list="[imageUrl]"
/>
</template>
```
## 常见问题
### 1. 类型错误
如果遇到 TypeScript 类型错误,检查:
- 是否正确导入类型定义
- 是否正确定义接口
- 是否使用正确的类型注解
### 2. 样式不生效
检查:
- 是否使用了正确的 scoped
- 是否正确定义了样式类名
- 是否引入了必要的样式文件
### 3. 路由跳转问题
检查:
- 路由路径是否正确
- 路由守卫是否阻止了跳转
- 组件是否正确定义
---
**文档版本**: 1.0
**最后更新**: 2025-10-15