重构认证系统和订单支付功能,新增邮箱验证、密码重置及支付流程
This commit is contained in:
862
docs/开发规范和最佳实践.md
Normal file
862
docs/开发规范和最佳实践.md
Normal file
@@ -0,0 +1,862 @@
|
||||
# 解班客项目开发规范和最佳实践
|
||||
|
||||
## 📋 文档概述
|
||||
|
||||
本文档制定了解班客项目的开发规范、编码标准和最佳实践,旨在提高代码质量、团队协作效率和项目可维护性。
|
||||
|
||||
### 文档目标
|
||||
- 建立统一的代码规范和编码标准
|
||||
- 规范开发流程和团队协作方式
|
||||
- 提高代码质量和可维护性
|
||||
- 确保项目的长期稳定发展
|
||||
|
||||
## 🎯 开发原则
|
||||
|
||||
### 核心原则
|
||||
1. **可读性优先**:代码应该易于理解和维护
|
||||
2. **一致性**:遵循统一的编码风格和命名规范
|
||||
3. **简洁性**:避免过度设计,保持代码简洁
|
||||
4. **可测试性**:编写易于测试的代码
|
||||
5. **安全性**:始终考虑安全因素
|
||||
6. **性能意识**:在保证功能的前提下优化性能
|
||||
|
||||
### SOLID原则
|
||||
- **S** - 单一职责原则(Single Responsibility Principle)
|
||||
- **O** - 开闭原则(Open/Closed Principle)
|
||||
- **L** - 里氏替换原则(Liskov Substitution Principle)
|
||||
- **I** - 接口隔离原则(Interface Segregation Principle)
|
||||
- **D** - 依赖倒置原则(Dependency Inversion Principle)
|
||||
|
||||
## 📁 项目结构规范
|
||||
|
||||
### 后端项目结构
|
||||
```
|
||||
backend/
|
||||
├── src/
|
||||
│ ├── controllers/ # 控制器层
|
||||
│ │ ├── admin/ # 管理员控制器
|
||||
│ │ └── user/ # 用户控制器
|
||||
│ ├── models/ # 数据模型层
|
||||
│ ├── routes/ # 路由层
|
||||
│ │ ├── admin/ # 管理员路由
|
||||
│ │ └── user/ # 用户路由
|
||||
│ ├── middleware/ # 中间件
|
||||
│ ├── services/ # 业务逻辑层
|
||||
│ ├── utils/ # 工具函数
|
||||
│ ├── config/ # 配置文件
|
||||
│ └── validators/ # 数据验证
|
||||
├── tests/ # 测试文件
|
||||
│ ├── unit/ # 单元测试
|
||||
│ ├── integration/ # 集成测试
|
||||
│ └── fixtures/ # 测试数据
|
||||
├── docs/ # API文档
|
||||
├── scripts/ # 脚本文件
|
||||
└── package.json
|
||||
```
|
||||
|
||||
### 前端项目结构
|
||||
```
|
||||
frontend/
|
||||
├── src/
|
||||
│ ├── components/ # 公共组件
|
||||
│ │ ├── common/ # 通用组件
|
||||
│ │ └── business/ # 业务组件
|
||||
│ ├── views/ # 页面组件
|
||||
│ │ ├── admin/ # 管理员页面
|
||||
│ │ └── user/ # 用户页面
|
||||
│ ├── stores/ # Pinia状态管理
|
||||
│ ├── composables/ # 组合式函数
|
||||
│ ├── utils/ # 工具函数
|
||||
│ ├── api/ # API接口
|
||||
│ ├── router/ # 路由配置
|
||||
│ ├── assets/ # 静态资源
|
||||
│ └── styles/ # 样式文件
|
||||
├── public/ # 公共资源
|
||||
├── tests/ # 测试文件
|
||||
└── package.json
|
||||
```
|
||||
frontend/
|
||||
├── src/
|
||||
│ ├── components/ # 公共组件
|
||||
│ │ ├── common/ # 通用组件
|
||||
│ │ └── business/ # 业务组件
|
||||
│ ├── views/ # 页面视图
|
||||
│ │ ├── user/ # 用户相关页面
|
||||
│ │ ├── animal/ # 动物相关页面
|
||||
│ │ └── admin/ # 管理页面
|
||||
│ ├── stores/ # 状态管理
|
||||
│ ├── router/ # 路由配置
|
||||
│ ├── utils/ # 工具函数
|
||||
│ ├── api/ # API接口
|
||||
│ ├── assets/ # 静态资源
|
||||
│ │ ├── images/ # 图片资源
|
||||
│ │ ├── styles/ # 样式文件
|
||||
│ │ └── icons/ # 图标资源
|
||||
│ └── composables/ # 组合式函数
|
||||
├── public/ # 公共文件
|
||||
├── tests/ # 测试文件
|
||||
└── package.json
|
||||
```
|
||||
|
||||
## 🔤 命名规范
|
||||
|
||||
### 文件和目录命名
|
||||
- **文件名**: 使用小写字母和连字符 (`kebab-case`)
|
||||
```
|
||||
✅ user-management.js
|
||||
✅ animal-list.vue
|
||||
❌ UserManagement.js
|
||||
❌ animalList.vue
|
||||
```
|
||||
|
||||
- **目录名**: 使用小写字母和连字符
|
||||
```
|
||||
✅ user-management/
|
||||
✅ api-docs/
|
||||
❌ UserManagement/
|
||||
❌ apiDocs/
|
||||
```
|
||||
|
||||
### 变量和函数命名
|
||||
|
||||
#### JavaScript/Node.js
|
||||
- **变量**: 使用驼峰命名法 (`camelCase`)
|
||||
- **常量**: 使用大写字母和下划线 (`UPPER_SNAKE_CASE`)
|
||||
- **函数**: 使用驼峰命名法,动词开头
|
||||
- **类**: 使用帕斯卡命名法 (`PascalCase`)
|
||||
|
||||
```javascript
|
||||
// ✅ 正确示例
|
||||
const userName = 'john';
|
||||
const MAX_RETRY_COUNT = 3;
|
||||
const API_BASE_URL = 'https://api.example.com';
|
||||
|
||||
function getUserById(id) { }
|
||||
function createAnimalRecord(data) { }
|
||||
|
||||
class UserService { }
|
||||
class AnimalController { }
|
||||
|
||||
// ❌ 错误示例
|
||||
const user_name = 'john';
|
||||
const maxretrycount = 3;
|
||||
function GetUserById(id) { }
|
||||
class userService { }
|
||||
```
|
||||
|
||||
#### Vue.js组件
|
||||
- **组件名**: 使用帕斯卡命名法
|
||||
- **Props**: 使用驼峰命名法
|
||||
- **事件**: 使用kebab-case
|
||||
|
||||
```vue
|
||||
<!-- ✅ 正确示例 -->
|
||||
<template>
|
||||
<UserProfile
|
||||
:user-data="userData"
|
||||
@user-updated="handleUserUpdate"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'UserProfile',
|
||||
props: {
|
||||
userData: Object,
|
||||
isEditable: Boolean
|
||||
},
|
||||
emits: ['user-updated', 'profile-changed']
|
||||
}
|
||||
</script>
|
||||
```
|
||||
|
||||
### 数据库命名
|
||||
- **表名**: 使用复数形式,下划线分隔
|
||||
- **字段名**: 使用下划线分隔
|
||||
- **索引名**: 使用 `idx_` 前缀
|
||||
- **外键名**: 使用 `fk_` 前缀
|
||||
|
||||
```sql
|
||||
-- ✅ 正确示例
|
||||
CREATE TABLE users (
|
||||
id INT PRIMARY KEY,
|
||||
user_name VARCHAR(50),
|
||||
email_address VARCHAR(100),
|
||||
created_at TIMESTAMP,
|
||||
updated_at TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE INDEX idx_users_email ON users(email_address);
|
||||
ALTER TABLE adoptions ADD CONSTRAINT fk_adoptions_user_id
|
||||
FOREIGN KEY (user_id) REFERENCES users(id);
|
||||
```
|
||||
|
||||
## 💻 代码风格规范
|
||||
|
||||
### JavaScript/Node.js代码规范
|
||||
|
||||
#### 基本格式
|
||||
```javascript
|
||||
// ✅ 使用2个空格缩进
|
||||
if (condition) {
|
||||
doSomething();
|
||||
}
|
||||
|
||||
// ✅ 使用单引号
|
||||
const message = 'Hello World';
|
||||
|
||||
// ✅ 对象和数组的格式
|
||||
const user = {
|
||||
name: 'John',
|
||||
age: 30,
|
||||
email: 'john@example.com'
|
||||
};
|
||||
|
||||
const animals = [
|
||||
'dog',
|
||||
'cat',
|
||||
'bird'
|
||||
];
|
||||
|
||||
// ✅ 函数声明
|
||||
function calculateAge(birthDate) {
|
||||
const today = new Date();
|
||||
const birth = new Date(birthDate);
|
||||
return today.getFullYear() - birth.getFullYear();
|
||||
}
|
||||
|
||||
// ✅ 箭头函数
|
||||
const getFullName = (firstName, lastName) => `${firstName} ${lastName}`;
|
||||
```
|
||||
|
||||
#### 注释规范
|
||||
```javascript
|
||||
/**
|
||||
* 获取用户信息
|
||||
* @param {number} userId - 用户ID
|
||||
* @param {Object} options - 查询选项
|
||||
* @param {boolean} options.includeProfile - 是否包含个人资料
|
||||
* @returns {Promise<Object>} 用户信息对象
|
||||
* @throws {Error} 当用户不存在时抛出错误
|
||||
*/
|
||||
async function getUserInfo(userId, options = {}) {
|
||||
// 验证用户ID
|
||||
if (!userId || typeof userId !== 'number') {
|
||||
throw new Error('Invalid user ID');
|
||||
}
|
||||
|
||||
// 查询用户基本信息
|
||||
const user = await User.findById(userId);
|
||||
|
||||
if (!user) {
|
||||
throw new Error('User not found');
|
||||
}
|
||||
|
||||
// 如果需要包含个人资料
|
||||
if (options.includeProfile) {
|
||||
user.profile = await UserProfile.findByUserId(userId);
|
||||
}
|
||||
|
||||
return user;
|
||||
}
|
||||
```
|
||||
|
||||
#### 错误处理
|
||||
```javascript
|
||||
// ✅ 使用try-catch处理异步错误
|
||||
async function createUser(userData) {
|
||||
try {
|
||||
// 验证输入数据
|
||||
const validatedData = validateUserData(userData);
|
||||
|
||||
// 创建用户
|
||||
const user = await User.create(validatedData);
|
||||
|
||||
// 记录日志
|
||||
logger.info('User created successfully', { userId: user.id });
|
||||
|
||||
return user;
|
||||
} catch (error) {
|
||||
// 记录错误日志
|
||||
logger.error('Failed to create user', { error: error.message, userData });
|
||||
|
||||
// 重新抛出错误
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// ✅ 使用自定义错误类
|
||||
class ValidationError extends Error {
|
||||
constructor(message, field) {
|
||||
super(message);
|
||||
this.name = 'ValidationError';
|
||||
this.field = field;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Vue.js代码规范
|
||||
|
||||
#### 组件结构
|
||||
```vue
|
||||
<template>
|
||||
<!-- 模板内容 -->
|
||||
<div class="user-profile">
|
||||
<div class="user-profile__header">
|
||||
<h2 class="user-profile__title">{{ user.name }}</h2>
|
||||
</div>
|
||||
|
||||
<div class="user-profile__content">
|
||||
<UserAvatar
|
||||
:src="user.avatar"
|
||||
:alt="user.name"
|
||||
@click="handleAvatarClick"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref, computed, onMounted } from 'vue';
|
||||
import { useUserStore } from '@/stores/user';
|
||||
import UserAvatar from '@/components/common/UserAvatar.vue';
|
||||
|
||||
export default {
|
||||
name: 'UserProfile',
|
||||
|
||||
components: {
|
||||
UserAvatar
|
||||
},
|
||||
|
||||
props: {
|
||||
userId: {
|
||||
type: Number,
|
||||
required: true
|
||||
},
|
||||
editable: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
|
||||
emits: ['profile-updated', 'avatar-changed'],
|
||||
|
||||
setup(props, { emit }) {
|
||||
// 响应式数据
|
||||
const user = ref(null);
|
||||
const loading = ref(false);
|
||||
|
||||
// 计算属性
|
||||
const displayName = computed(() => {
|
||||
return user.value ? user.value.name : 'Unknown User';
|
||||
});
|
||||
|
||||
// 方法
|
||||
const loadUser = async () => {
|
||||
loading.value = true;
|
||||
try {
|
||||
user.value = await userStore.fetchUser(props.userId);
|
||||
} catch (error) {
|
||||
console.error('Failed to load user:', error);
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const handleAvatarClick = () => {
|
||||
if (props.editable) {
|
||||
emit('avatar-changed');
|
||||
}
|
||||
};
|
||||
|
||||
// 生命周期
|
||||
onMounted(() => {
|
||||
loadUser();
|
||||
});
|
||||
|
||||
// 返回模板需要的数据和方法
|
||||
return {
|
||||
user,
|
||||
loading,
|
||||
displayName,
|
||||
handleAvatarClick
|
||||
};
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.user-profile {
|
||||
padding: 20px;
|
||||
|
||||
&__header {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
&__title {
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
&__content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
```
|
||||
|
||||
#### CSS/SCSS规范
|
||||
```scss
|
||||
// ✅ 使用BEM命名规范
|
||||
.animal-card {
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 8px;
|
||||
padding: 16px;
|
||||
|
||||
&__header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
&__title {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
&__status {
|
||||
padding: 4px 8px;
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
|
||||
&--available {
|
||||
background-color: #e8f5e8;
|
||||
color: #2d8f2d;
|
||||
}
|
||||
|
||||
&--adopted {
|
||||
background-color: #fff3cd;
|
||||
color: #856404;
|
||||
}
|
||||
}
|
||||
|
||||
&__content {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
&__actions {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
// ✅ 使用CSS变量
|
||||
:root {
|
||||
--primary-color: #007bff;
|
||||
--success-color: #28a745;
|
||||
--warning-color: #ffc107;
|
||||
--danger-color: #dc3545;
|
||||
--font-family: 'Helvetica Neue', Arial, sans-serif;
|
||||
}
|
||||
```
|
||||
|
||||
## 🧪 测试规范
|
||||
|
||||
### 测试文件命名
|
||||
- 单元测试: `*.test.js` 或 `*.spec.js`
|
||||
- 集成测试: `*.integration.test.js`
|
||||
- E2E测试: `*.e2e.test.js`
|
||||
|
||||
### 测试结构
|
||||
```javascript
|
||||
// ✅ 测试文件示例
|
||||
describe('UserService', () => {
|
||||
let userService;
|
||||
let mockDatabase;
|
||||
|
||||
beforeEach(() => {
|
||||
mockDatabase = createMockDatabase();
|
||||
userService = new UserService(mockDatabase);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
mockDatabase.reset();
|
||||
});
|
||||
|
||||
describe('createUser', () => {
|
||||
it('should create user with valid data', async () => {
|
||||
// Arrange
|
||||
const userData = {
|
||||
name: 'John Doe',
|
||||
email: 'john@example.com'
|
||||
};
|
||||
|
||||
// Act
|
||||
const result = await userService.createUser(userData);
|
||||
|
||||
// Assert
|
||||
expect(result).toBeDefined();
|
||||
expect(result.id).toBeTruthy();
|
||||
expect(result.name).toBe(userData.name);
|
||||
expect(result.email).toBe(userData.email);
|
||||
});
|
||||
|
||||
it('should throw error with invalid email', async () => {
|
||||
// Arrange
|
||||
const userData = {
|
||||
name: 'John Doe',
|
||||
email: 'invalid-email'
|
||||
};
|
||||
|
||||
// Act & Assert
|
||||
await expect(userService.createUser(userData))
|
||||
.rejects
|
||||
.toThrow('Invalid email format');
|
||||
});
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
### 测试覆盖率要求
|
||||
- **单元测试覆盖率**: ≥ 80%
|
||||
- **集成测试覆盖率**: ≥ 60%
|
||||
- **关键业务逻辑**: 100%
|
||||
|
||||
## 📝 文档规范
|
||||
|
||||
### API文档
|
||||
使用OpenAPI 3.0规范编写API文档:
|
||||
|
||||
```yaml
|
||||
# ✅ API文档示例
|
||||
paths:
|
||||
/api/v1/users/{id}:
|
||||
get:
|
||||
summary: 获取用户信息
|
||||
description: 根据用户ID获取用户详细信息
|
||||
parameters:
|
||||
- name: id
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: integer
|
||||
description: 用户ID
|
||||
responses:
|
||||
'200':
|
||||
description: 成功获取用户信息
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/User'
|
||||
'404':
|
||||
description: 用户不存在
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Error'
|
||||
```
|
||||
|
||||
### 代码注释
|
||||
```javascript
|
||||
/**
|
||||
* 动物认领服务类
|
||||
* 处理动物认领相关的业务逻辑
|
||||
*/
|
||||
class AdoptionService {
|
||||
/**
|
||||
* 创建认领申请
|
||||
* @param {Object} adoptionData - 认领申请数据
|
||||
* @param {number} adoptionData.userId - 申请人ID
|
||||
* @param {number} adoptionData.animalId - 动物ID
|
||||
* @param {string} adoptionData.reason - 认领原因
|
||||
* @param {Object} adoptionData.contact - 联系方式
|
||||
* @returns {Promise<Object>} 认领申请对象
|
||||
* @throws {ValidationError} 当数据验证失败时
|
||||
* @throws {BusinessError} 当业务规则验证失败时
|
||||
*
|
||||
* @example
|
||||
* const adoption = await adoptionService.createAdoption({
|
||||
* userId: 123,
|
||||
* animalId: 456,
|
||||
* reason: '我想给这只小狗一个温暖的家',
|
||||
* contact: { phone: '13800138000', address: '北京市朝阳区' }
|
||||
* });
|
||||
*/
|
||||
async createAdoption(adoptionData) {
|
||||
// 实现代码...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 🔒 安全规范
|
||||
|
||||
### 输入验证
|
||||
```javascript
|
||||
// ✅ 使用joi进行数据验证
|
||||
const Joi = require('joi');
|
||||
|
||||
const userSchema = Joi.object({
|
||||
name: Joi.string().min(2).max(50).required(),
|
||||
email: Joi.string().email().required(),
|
||||
password: Joi.string().min(8).pattern(/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)/).required(),
|
||||
phone: Joi.string().pattern(/^1[3-9]\d{9}$/).optional()
|
||||
});
|
||||
|
||||
// 验证用户输入
|
||||
const { error, value } = userSchema.validate(userData);
|
||||
if (error) {
|
||||
throw new ValidationError(error.details[0].message);
|
||||
}
|
||||
```
|
||||
|
||||
### SQL注入防护
|
||||
```javascript
|
||||
// ✅ 使用参数化查询
|
||||
const getUserById = async (id) => {
|
||||
const query = 'SELECT * FROM users WHERE id = ?';
|
||||
const result = await db.query(query, [id]);
|
||||
return result[0];
|
||||
};
|
||||
|
||||
// ❌ 避免字符串拼接
|
||||
const getUserById = async (id) => {
|
||||
const query = `SELECT * FROM users WHERE id = ${id}`; // 危险!
|
||||
const result = await db.query(query);
|
||||
return result[0];
|
||||
};
|
||||
```
|
||||
|
||||
### 敏感信息处理
|
||||
```javascript
|
||||
// ✅ 密码加密
|
||||
const bcrypt = require('bcrypt');
|
||||
|
||||
const hashPassword = async (password) => {
|
||||
const saltRounds = 12;
|
||||
return await bcrypt.hash(password, saltRounds);
|
||||
};
|
||||
|
||||
// ✅ 敏感信息过滤
|
||||
const sanitizeUser = (user) => {
|
||||
const { password, salt, ...safeUser } = user;
|
||||
return safeUser;
|
||||
};
|
||||
```
|
||||
|
||||
## 🚀 性能优化规范
|
||||
|
||||
### 数据库查询优化
|
||||
```javascript
|
||||
// ✅ 使用索引和限制查询
|
||||
const getAnimals = async (filters, pagination) => {
|
||||
const { page = 1, limit = 20 } = pagination;
|
||||
const offset = (page - 1) * limit;
|
||||
|
||||
const query = `
|
||||
SELECT a.*, u.name as owner_name
|
||||
FROM animals a
|
||||
LEFT JOIN users u ON a.owner_id = u.id
|
||||
WHERE a.status = ?
|
||||
ORDER BY a.created_at DESC
|
||||
LIMIT ? OFFSET ?
|
||||
`;
|
||||
|
||||
return await db.query(query, [filters.status, limit, offset]);
|
||||
};
|
||||
|
||||
// ✅ 使用缓存
|
||||
const Redis = require('redis');
|
||||
const redis = Redis.createClient();
|
||||
|
||||
const getCachedUser = async (userId) => {
|
||||
const cacheKey = `user:${userId}`;
|
||||
|
||||
// 尝试从缓存获取
|
||||
let user = await redis.get(cacheKey);
|
||||
if (user) {
|
||||
return JSON.parse(user);
|
||||
}
|
||||
|
||||
// 从数据库获取
|
||||
user = await User.findById(userId);
|
||||
|
||||
// 存入缓存,过期时间1小时
|
||||
await redis.setex(cacheKey, 3600, JSON.stringify(user));
|
||||
|
||||
return user;
|
||||
};
|
||||
```
|
||||
|
||||
### 前端性能优化
|
||||
```vue
|
||||
<template>
|
||||
<!-- ✅ 使用v-show代替v-if进行频繁切换 -->
|
||||
<div v-show="isVisible" class="content">
|
||||
<!-- ✅ 使用key优化列表渲染 -->
|
||||
<div
|
||||
v-for="animal in animals"
|
||||
:key="animal.id"
|
||||
class="animal-item"
|
||||
>
|
||||
{{ animal.name }}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref, computed, watchEffect } from 'vue';
|
||||
|
||||
export default {
|
||||
setup() {
|
||||
// ✅ 使用computed缓存计算结果
|
||||
const expensiveValue = computed(() => {
|
||||
return animals.value.filter(animal => animal.status === 'available')
|
||||
.sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt));
|
||||
});
|
||||
|
||||
// ✅ 使用防抖处理搜索
|
||||
const searchTerm = ref('');
|
||||
const debouncedSearch = debounce((term) => {
|
||||
performSearch(term);
|
||||
}, 300);
|
||||
|
||||
watchEffect(() => {
|
||||
debouncedSearch(searchTerm.value);
|
||||
});
|
||||
|
||||
return {
|
||||
expensiveValue,
|
||||
searchTerm
|
||||
};
|
||||
}
|
||||
};
|
||||
</script>
|
||||
```
|
||||
|
||||
## 📋 Git工作流规范
|
||||
|
||||
### 分支命名
|
||||
- **主分支**: `main`
|
||||
- **开发分支**: `develop`
|
||||
- **功能分支**: `feature/功能名称`
|
||||
- **修复分支**: `fix/问题描述`
|
||||
- **发布分支**: `release/版本号`
|
||||
|
||||
### 提交信息规范
|
||||
使用Conventional Commits规范:
|
||||
|
||||
```bash
|
||||
# ✅ 正确的提交信息
|
||||
feat: 添加用户认证功能
|
||||
fix: 修复动物列表分页问题
|
||||
docs: 更新API文档
|
||||
style: 统一代码格式
|
||||
refactor: 重构用户服务层
|
||||
test: 添加用户注册测试用例
|
||||
chore: 更新依赖包版本
|
||||
|
||||
# 详细提交信息示例
|
||||
feat: 添加动物认领申请功能
|
||||
|
||||
- 实现认领申请表单
|
||||
- 添加申请状态跟踪
|
||||
- 集成邮件通知功能
|
||||
- 添加相关测试用例
|
||||
|
||||
Closes #123
|
||||
```
|
||||
|
||||
### 代码审查清单
|
||||
- [ ] 代码符合项目规范
|
||||
- [ ] 功能实现正确
|
||||
- [ ] 测试用例充分
|
||||
- [ ] 文档更新完整
|
||||
- [ ] 性能影响评估
|
||||
- [ ] 安全风险评估
|
||||
- [ ] 向后兼容性检查
|
||||
|
||||
## 🛠️ 开发工具配置
|
||||
|
||||
### ESLint配置
|
||||
```json
|
||||
{
|
||||
"extends": [
|
||||
"eslint:recommended",
|
||||
"@vue/eslint-config-prettier"
|
||||
],
|
||||
"rules": {
|
||||
"indent": ["error", 2],
|
||||
"quotes": ["error", "single"],
|
||||
"semi": ["error", "always"],
|
||||
"no-console": "warn",
|
||||
"no-debugger": "error",
|
||||
"no-unused-vars": "error"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Prettier配置
|
||||
```json
|
||||
{
|
||||
"semi": true,
|
||||
"singleQuote": true,
|
||||
"tabWidth": 2,
|
||||
"trailingComma": "es5",
|
||||
"printWidth": 80,
|
||||
"bracketSpacing": true,
|
||||
"arrowParens": "avoid"
|
||||
}
|
||||
```
|
||||
|
||||
### VS Code配置
|
||||
```json
|
||||
{
|
||||
"editor.formatOnSave": true,
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.fixAll.eslint": true
|
||||
},
|
||||
"emmet.includeLanguages": {
|
||||
"vue": "html"
|
||||
},
|
||||
"files.associations": {
|
||||
"*.vue": "vue"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 📚 学习资源
|
||||
|
||||
### 官方文档
|
||||
- [Vue.js 官方文档](https://vuejs.org/)
|
||||
- [Node.js 官方文档](https://nodejs.org/)
|
||||
- [Express.js 官方文档](https://expressjs.com/)
|
||||
- [MySQL 官方文档](https://dev.mysql.com/doc/)
|
||||
|
||||
### 最佳实践
|
||||
- [JavaScript 最佳实践](https://github.com/airbnb/javascript)
|
||||
- [Vue.js 风格指南](https://vuejs.org/style-guide/)
|
||||
- [Node.js 最佳实践](https://github.com/goldbergyoni/nodebestpractices)
|
||||
|
||||
### 工具和库
|
||||
- [ESLint](https://eslint.org/) - 代码检查
|
||||
- [Prettier](https://prettier.io/) - 代码格式化
|
||||
- [Jest](https://jestjs.io/) - 测试框架
|
||||
- [Joi](https://joi.dev/) - 数据验证
|
||||
|
||||
## 🔄 规范更新
|
||||
|
||||
本规范会根据项目发展和团队反馈持续更新。如有建议或问题,请通过以下方式反馈:
|
||||
|
||||
1. 创建GitHub Issue
|
||||
2. 提交Pull Request
|
||||
3. 团队会议讨论
|
||||
|
||||
---
|
||||
|
||||
**文档版本**: v1.0.0
|
||||
**最后更新**: 2024年1月15日
|
||||
**下次审查**: 2024年4月15日
|
||||
Reference in New Issue
Block a user