shop-web/doc/开发指南.md

529 lines
11 KiB
Markdown
Raw Permalink Normal View History

# 开发指南
## 开发环境设置
### 1. 环境要求
- **Node.js**: 20.x 或 22+
- **包管理器**: pnpm 9.x 或 10+
- **编辑器**: Visual Studio Code (推荐)
- **浏览器**: Chrome 或 Safari (移动端调试)
### 2. 推荐 VS Code 插件
- Vue Language Features (Volar)
- TypeScript Vue Plugin (Volar)
- ESLint
- UnoCSS
- Auto Rename Tag
- GitLens
### 3. 项目初始化
```bash
# 克隆项目
git clone <repository-url>
# 安装依赖
pnpm i
# 启动开发服务器
pnpm dev
```
## 项目架构
### 目录结构详解
```
src/
├── common/ # 通用模块
│ ├── apis/ # API 接口定义
│ │ ├── users/ # 用户相关接口
│ │ ├── shop/ # 店铺相关接口
│ │ ├── cabinet/ # 柜机相关接口
│ │ ├── approval/ # 审批相关接口
│ │ └── ab98/ # AB98系统接口
│ ├── assets/ # 静态资源
│ │ └── styles/ # 样式文件
│ ├── composables/ # 组合式函数
│ │ ├── useDark.ts # 暗黑模式
│ │ ├── useWatermark.ts # 水印功能
│ │ └── useGrayscaleAndColorblind.ts # 无障碍模式
│ ├── components/ # 通用组件
│ └── utils/ # 工具函数
│ ├── cache/ # 缓存工具
│ ├── permission.ts # 权限工具
│ └── wx.ts # 微信工具
├── layout/ # 布局组件
│ ├── index.vue # 主布局
│ ├── components/
│ │ ├── NavBar.vue # 导航栏
│ │ ├── Tabbar.vue # 标签栏
│ │ └── Footer.vue # 底部
├── pages/ # 页面组件
│ ├── product/ # 商品相关页面
│ ├── order/ # 订单相关页面
│ ├── cabinet/ # 柜机相关页面
│ ├── approval/ # 审批相关页面
│ ├── me/ # 个人中心
│ └── error/ # 错误页面
├── pinia/ # 状态管理
│ ├── index.ts # Pinia 实例
│ └── stores/ # Store 定义
│ ├── user.ts # 用户状态
│ ├── ab98-user.ts # AB98用户状态
│ ├── wx.ts # 微信状态
│ ├── product.ts # 商品状态
│ ├── cart.ts # 购物车状态
│ ├── order.ts # 订单状态
│ └── approval.ts # 审批状态
├── router/ # 路由配置
│ ├── index.ts # 路由定义
│ ├── guard.ts # 路由守卫
│ └── whitelist.ts # 白名单
├── http/ # HTTP 请求
│ └── axios.ts # Axios 配置
└── plugins/ # 插件配置
├── index.ts # 插件安装
├── permission-directive.ts # 权限指令
└── console.ts # 控制台工具
```
## 开发规范
### 1. 代码风格
#### TypeScript 规范
- 使用严格模式,避免 `any` 类型
- 为所有函数和变量提供明确的类型定义
- 使用接口定义复杂的数据结构
```typescript
// ✅ 推荐
interface UserInfo {
id: number;
name: string;
email: string;
}
const getUserInfo = async (id: number): Promise<UserInfo> => {
// ...
}
// ❌ 避免
const getUserInfo = async (id: any): Promise<any> => {
// ...
}
```
#### Vue 组件规范
- 使用 script setup 语法
- 组件名使用 PascalCase
- Props 和 Emits 使用 TypeScript 定义
```vue
<script setup lang="ts">
interface Props {
title: string;
count?: number;
}
interface Emits {
(e: 'update:count', value: number): void;
}
const props = defineProps<Props>();
const emit = defineEmits<Emits>();
const handleClick = () => {
emit('update:count', (props.count || 0) + 1);
};
</script>
<template>
<div class="my-component">
<h3>{{ title }}</h3>
<button @click="handleClick">
点击 {{ count }}
</button>
</div>
</template>
```
### 2. 状态管理规范
#### Store 定义
- Store 名使用 camelCase
- 使用组合式 API 风格
- 提供清晰的类型定义
```typescript
import { pinia } from "@/pinia";
export const useProductStore = defineStore("product", () => {
// State
const products = ref<Product[]>([]);
const loading = ref(false);
// Getters
const availableProducts = computed(() =>
products.value.filter(p => p.stock > 0)
);
// Actions
const fetchProducts = async () => {
loading.value = true;
try {
const response = await getProductListApi();
if (response.code === 0) {
products.value = response.data.products;
}
} finally {
loading.value = false;
}
};
return {
products,
loading,
availableProducts,
fetchProducts
};
});
```
### 3. API 开发规范
#### API 文件结构
```typescript
// src/common/apis/product/index.ts
import { request } from "@/http/axios";
import type { ProductListResponse, ProductDetailResponse } from "./type";
export const getProductListApi = (params?: { page?: number; size?: number }) => {
return request<ProductListResponse>({
url: "/api/v1/product/list",
method: "GET",
params
});
};
export const getProductDetailApi = (id: number) => {
return request<ProductDetailResponse>({
url: `/api/v1/product/${id}`,
method: "GET"
});
};
```
#### 类型定义文件
```typescript
// src/common/apis/product/type.ts
export interface Product {
id: number;
name: string;
price: number;
image: string;
description: string;
stock: number;
category: string;
}
export interface ProductListResponse {
products: Product[];
total: number;
page: number;
size: number;
}
export interface ProductDetailResponse extends Product {
specifications: Specification[];
images: string[];
details: string;
}
```
### 4. 路由配置规范
#### 路由定义
```typescript
// src/router/index.ts
export const routes: RouteRecordRaw[] = [
{
path: "/product/:id",
component: () => import("@/pages/product/ProductDetail.vue"),
name: "ProductDetail",
meta: {
title: "商品详情",
keepAlive: true,
layout: {
navBar: {
showNavBar: true,
showLeftArrow: true
},
tabbar: {
showTabbar: false
}
},
requiresAuth: true
}
}
];
```
### 5. 样式开发规范
#### UnoCSS 使用
- 优先使用预设的原子类
- 自定义规则在 `uno.config.ts` 中定义
- 使用属性化模式un-前缀)
```vue
<template>
<div class="product-card un-p-4 un-bg-white un-rounded-lg un-shadow-sm">
<img
:src="product.image"
class="un-w-full un-h-40 un-object-cover un-rounded"
/>
<h3 class="un-text-lg un-font-medium un-mt-2 un-text-gray-900">
{{ product.name }}
</h3>
<p class="un-text-primary un-font-bold un-mt-1">
¥{{ product.price }}
</p>
</div>
</template>
```
#### CSS 变量
`src/common/assets/styles/variables.css` 中定义主题变量:
```css
:root {
--mobvue-primary-color: #1989fa;
--mobvue-bg-color: #ffffff;
--mobvue-text-color: #323233;
}
html.dark {
--mobvue-primary-color: #2d8cf0;
--mobvue-bg-color: #1a1a1a;
--mobvue-text-color: #e5e5e5;
}
```
## 功能开发指南
### 1. 添加新页面
#### 步骤 1: 创建页面组件
```vue
<!-- src/pages/example/ExamplePage.vue -->
<script setup lang="ts">
import { useRoute } from "vue-router";
const route = useRoute();
// 页面逻辑...
</script>
<template>
<div class="example-page">
<h1>示例页面</h1>
<!-- 页面内容 -->
</div>
</template>
<style scoped>
.example-page {
padding: 16px;
}
</style>
```
#### 步骤 2: 配置路由
```typescript
// src/router/index.ts
export const routes: RouteRecordRaw[] = [
{
path: "/example",
component: () => import("@/pages/example/ExamplePage.vue"),
name: "ExamplePage",
meta: {
title: "示例页面",
layout: {
navBar: {
showNavBar: true,
showLeftArrow: true
}
}
}
}
];
```
### 2. 添加新 Store
```typescript
// src/pinia/stores/example.ts
import { pinia } from "@/pinia";
export const useExampleStore = defineStore("example", () => {
const data = ref<string[]>([]);
const loading = ref(false);
const fetchData = async () => {
loading.value = true;
try {
// API 调用...
} finally {
loading.value = false;
}
};
return {
data,
loading,
fetchData
};
});
```
### 3. 添加新 API
```typescript
// src/common/apis/example/index.ts
import { request } from "@/http/axios";
import type { ExampleResponse } from "./type";
export const getExampleDataApi = () => {
return request<ExampleResponse>({
url: "/api/v1/example/data",
method: "GET"
});
};
// src/common/apis/example/type.ts
export interface ExampleResponse {
data: string[];
}
```
## 调试技巧
### 1. 移动端调试
- 使用 Chrome DevTools 的设备模拟
- 启用移动端触摸模拟
- 测试不同屏幕尺寸
### 2. 微信调试
- 使用微信开发者工具
- 配置正确的回调域名
- 检查 openid 获取逻辑
### 3. 状态调试
- 使用 Vue DevTools
- 查看 Pinia Store 状态
- 监控组件生命周期
## 性能优化
### 1. 代码分割
- 路由级别的懒加载
- 组件按需导入
- 第三方库独立打包
### 2. 图片优化
- 使用 WebP 格式
- 实现懒加载
- 压缩图片大小
### 3. 缓存策略
- 合理使用浏览器缓存
- API 响应缓存
- 组件 keep-alive
## 测试指南
### 1. 单元测试
```typescript
// tests/example.test.ts
import { describe, it, expect } from "vitest";
import { mount } from "@vue/test-utils";
import ExampleComponent from "@/components/ExampleComponent.vue";
describe("ExampleComponent", () => {
it("renders correctly", () => {
const wrapper = mount(ExampleComponent, {
props: {
title: "Test Title"
}
});
expect(wrapper.text()).toContain("Test Title");
});
});
```
### 2. E2E 测试
- 使用 Playwright 或 Cypress
- 模拟用户操作流程
- 测试关键业务路径
## 部署指南
### 1. 环境变量配置
```env
# .env.production
VITE_BASE_URL=https://api.example.com
VITE_PUBLIC_PATH=/
VITE_ROUTER_HISTORY=hash
```
### 2. 构建优化
```bash
# 生产环境构建
pnpm build
# 预发布环境构建
pnpm build:staging
```
### 3. 部署检查清单
- [ ] 环境变量配置正确
- [ ] 静态资源路径正确
- [ ] API 接口可访问
- [ ] 路由配置正确
- [ ] 移动端适配正常
## 常见问题解决
### 1. 微信认证失败
- 检查回调域名配置
- 验证 corpid 和 secret
- 查看网络请求日志
### 2. 路由跳转问题
- 检查路由守卫逻辑
- 验证权限配置
- 查看路由历史模式
### 3. 样式显示异常
- 检查 UnoCSS 配置
- 验证 CSS 变量定义
- 测试不同主题模式
## 贡献指南
### 1. 代码提交
- 遵循提交信息规范
- 一个功能一个提交
- 提供清晰的提交描述
### 2. 代码审查
- 检查代码规范
- 验证功能完整性
- 测试边界情况
### 3. 文档更新
- 更新相关文档
- 添加使用示例
- 记录变更内容