feat: 完善项目开发规范和测试文档体系
📝 主要改进: • Cursor配置模块化:拆分为前端、后端、移动端、文档、API测试5个专门规范文件 • API文档重新组织:按业务模块拆分到docs/开发文档/API/目录下 • 新增维修分类功能完整测试方案和自动化测试脚本 • 后端热启动脚本完善,支持Linux/Windows双平台 • 强化文档同步更新规范,确保代码与文档一致性 🔧 技术改进: • 优化Cursor AI工作流程和代码生成质量 • 完善API测试规范(AES+RSA双重加密登录方法) • 提升开发效率和代码质量标准化程度 �� 测试覆盖: • 维修分类管理功能端到端测试验证通过 • 包含页面加载、CRUD操作、搜索功能等核心测试用例 更新时间:2025-07-01
This commit is contained in:
1 parent
7a5b9d1bea
commit
249b1dfeb4
44 files changed
+14027
-2852
No files matched your search
@@ -0,0 +1,714 @@
|
||||
# Cursor Rules - 移动端开发规范
|
||||
|
||||
## 适用范围
|
||||
|
||||
- **项目目录**: `mobile/` (基于 uniapp + uView UI)
|
||||
- **开发语言**: JavaScript/TypeScript
|
||||
- **平台支持**: H5、小程序、APP
|
||||
|
||||
---
|
||||
|
||||
# 移动端开发规范 (mobile/)
|
||||
|
||||
## 1. 技术栈规范
|
||||
|
||||
### 核心技术
|
||||
|
||||
- **框架**: uni-app
|
||||
- **UI 组件库**: uView UI
|
||||
- **开发语言**: JavaScript/TypeScript
|
||||
- **状态管理**: Vuex
|
||||
- **HTTP 客户端**: uni.request 封装
|
||||
|
||||
## 2. 项目结构规范
|
||||
|
||||
### 移动端目录结构
|
||||
|
||||
```
|
||||
mobile/
|
||||
├── src/
|
||||
│ ├── api/ # API接口定义
|
||||
│ ├── components/ # 通用组件
|
||||
│ ├── pages/ # 页面文件
|
||||
│ │ ├── index/ # 首页
|
||||
│ │ ├── login/ # 登录页
|
||||
│ │ ├── mine/ # 个人中心
|
||||
│ │ └── workbench/ # 工作台
|
||||
│ ├── static/ # 静态资源
|
||||
│ ├── utils/ # 工具函数
|
||||
│ ├── App.vue # 应用入口
|
||||
│ ├── config.js # 配置文件
|
||||
│ └── main.js # 主入口文件
|
||||
├── pages.json # 页面配置
|
||||
├── manifest.json # 应用配置
|
||||
└── uni.scss # 全局样式
|
||||
```
|
||||
|
||||
## 3. 开发规范
|
||||
|
||||
### 页面开发规范
|
||||
|
||||
- **页面命名**: 使用小写字母和连字符 (如: user-list)
|
||||
- **组件命名**: 使用 PascalCase (如: UserCard)
|
||||
- **API 调用**: 统一使用封装的 request 工具
|
||||
- **样式规范**: 使用 uView UI 组件和样式变量
|
||||
|
||||
### 页面模板
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<view class="page-container">
|
||||
<u-navbar title="页面标题" @leftClick="handleBack"></u-navbar>
|
||||
<!-- 页面内容 -->
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
// 页面数据
|
||||
};
|
||||
},
|
||||
onLoad() {
|
||||
// 页面加载逻辑
|
||||
},
|
||||
methods: {
|
||||
handleBack() {
|
||||
uni.navigateBack();
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.page-container {
|
||||
// 页面样式
|
||||
}
|
||||
</style>
|
||||
```
|
||||
|
||||
### 组件开发规范
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<view class="component-wrapper">
|
||||
<!-- 组件内容 -->
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'ComponentName',
|
||||
props: {
|
||||
// 组件属性
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
// 组件数据
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
// 组件方法
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.component-wrapper {
|
||||
// 组件样式
|
||||
}
|
||||
</style>
|
||||
```
|
||||
|
||||
## 4. 与后端 API 对接规范
|
||||
|
||||
### 请求封装
|
||||
|
||||
```javascript
|
||||
// utils/request.js
|
||||
const request = (options) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
uni.request({
|
||||
url: getApp().globalData.baseUrl + options.url,
|
||||
method: options.method || 'GET',
|
||||
data: options.data || {},
|
||||
header: {
|
||||
Authorization: uni.getStorageSync('token'),
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
success: (res) => {
|
||||
if (res.data.code === 200) {
|
||||
resolve(res.data);
|
||||
} else {
|
||||
reject(res.data);
|
||||
}
|
||||
},
|
||||
fail: (err) => {
|
||||
reject(err);
|
||||
},
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
export default request;
|
||||
```
|
||||
|
||||
### API 接口定义
|
||||
|
||||
```javascript
|
||||
// api/user.js
|
||||
import request from '@/utils/request';
|
||||
|
||||
export const userApi = {
|
||||
// 用户登录
|
||||
login(data) {
|
||||
return request({
|
||||
url: '/auth/login',
|
||||
method: 'POST',
|
||||
data,
|
||||
});
|
||||
},
|
||||
|
||||
// 获取用户信息
|
||||
getUserInfo() {
|
||||
return request({
|
||||
url: '/system/user/getInfo',
|
||||
method: 'GET',
|
||||
});
|
||||
},
|
||||
|
||||
// 用户列表
|
||||
getUserList(params) {
|
||||
return request({
|
||||
url: '/system/user/list',
|
||||
method: 'GET',
|
||||
data: params,
|
||||
});
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
## 5. 状态管理规范
|
||||
|
||||
### Vuex Store 结构
|
||||
|
||||
```javascript
|
||||
// store/index.js
|
||||
import Vue from 'vue';
|
||||
import Vuex from 'vuex';
|
||||
import user from './modules/user';
|
||||
|
||||
Vue.use(Vuex);
|
||||
|
||||
export default new Vuex.Store({
|
||||
modules: {
|
||||
user,
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
### 用户模块示例
|
||||
|
||||
```javascript
|
||||
// store/modules/user.js
|
||||
const state = {
|
||||
token: uni.getStorageSync('token') || '',
|
||||
userInfo: {},
|
||||
};
|
||||
|
||||
const mutations = {
|
||||
SET_TOKEN(state, token) {
|
||||
state.token = token;
|
||||
uni.setStorageSync('token', token);
|
||||
},
|
||||
|
||||
SET_USER_INFO(state, userInfo) {
|
||||
state.userInfo = userInfo;
|
||||
},
|
||||
};
|
||||
|
||||
const actions = {
|
||||
// 用户登录
|
||||
login({ commit }, loginForm) {
|
||||
return new Promise((resolve, reject) => {
|
||||
userApi
|
||||
.login(loginForm)
|
||||
.then((res) => {
|
||||
commit('SET_TOKEN', res.data.access_token);
|
||||
resolve(res);
|
||||
})
|
||||
.catch((error) => {
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
// 获取用户信息
|
||||
getUserInfo({ commit }) {
|
||||
return new Promise((resolve, reject) => {
|
||||
userApi
|
||||
.getUserInfo()
|
||||
.then((res) => {
|
||||
commit('SET_USER_INFO', res.data);
|
||||
resolve(res);
|
||||
})
|
||||
.catch((error) => {
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
export default {
|
||||
namespaced: true,
|
||||
state,
|
||||
mutations,
|
||||
actions,
|
||||
};
|
||||
```
|
||||
|
||||
## 6. 页面配置规范
|
||||
|
||||
### pages.json 配置
|
||||
|
||||
```json
|
||||
{
|
||||
"pages": [
|
||||
{
|
||||
"path": "pages/index/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "首页"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/login/login",
|
||||
"style": {
|
||||
"navigationBarTitleText": "登录",
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
}
|
||||
],
|
||||
"globalStyle": {
|
||||
"navigationBarTextStyle": "black",
|
||||
"navigationBarTitleText": "校园管理",
|
||||
"navigationBarBackgroundColor": "#F8F8F8",
|
||||
"backgroundColor": "#F8F8F8"
|
||||
},
|
||||
"tabBar": {
|
||||
"color": "#7A7E83",
|
||||
"selectedColor": "#3cc51f",
|
||||
"borderStyle": "black",
|
||||
"backgroundColor": "#ffffff",
|
||||
"list": [
|
||||
{
|
||||
"pagePath": "pages/index/index",
|
||||
"iconPath": "static/tabbar/home.png",
|
||||
"selectedIconPath": "static/tabbar/home-active.png",
|
||||
"text": "首页"
|
||||
},
|
||||
{
|
||||
"pagePath": "pages/workbench/workbench",
|
||||
"iconPath": "static/tabbar/workbench.png",
|
||||
"selectedIconPath": "static/tabbar/workbench-active.png",
|
||||
"text": "工作台"
|
||||
},
|
||||
{
|
||||
"pagePath": "pages/mine/mine",
|
||||
"iconPath": "static/tabbar/mine.png",
|
||||
"selectedIconPath": "static/tabbar/mine-active.png",
|
||||
"text": "我的"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 7. 样式规范
|
||||
|
||||
### 全局样式
|
||||
|
||||
```scss
|
||||
// uni.scss
|
||||
$primary-color: #007aff;
|
||||
$success-color: #4cd964;
|
||||
$warning-color: #f0ad4e;
|
||||
$error-color: #dd524d;
|
||||
|
||||
$text-color: #333333;
|
||||
$text-color-light: #666666;
|
||||
$text-color-lighter: #999999;
|
||||
|
||||
$border-color: #e5e5e5;
|
||||
$background-color: #f5f5f5;
|
||||
|
||||
// 通用样式
|
||||
.container {
|
||||
padding: 20rpx;
|
||||
}
|
||||
|
||||
.flex {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.flex-center {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.text-center {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.text-right {
|
||||
text-align: right;
|
||||
}
|
||||
```
|
||||
|
||||
### 组件样式规范
|
||||
|
||||
```scss
|
||||
// 组件样式示例
|
||||
.user-card {
|
||||
background: #fff;
|
||||
border-radius: 10rpx;
|
||||
padding: 20rpx;
|
||||
margin-bottom: 20rpx;
|
||||
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.1);
|
||||
|
||||
.avatar {
|
||||
width: 80rpx;
|
||||
height: 80rpx;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.info {
|
||||
flex: 1;
|
||||
margin-left: 20rpx;
|
||||
|
||||
.name {
|
||||
font-size: 32rpx;
|
||||
color: $text-color;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.desc {
|
||||
font-size: 28rpx;
|
||||
color: $text-color-light;
|
||||
margin-top: 10rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 8. 工具函数规范
|
||||
|
||||
### 通用工具函数
|
||||
|
||||
```javascript
|
||||
// utils/index.js
|
||||
|
||||
// 格式化日期
|
||||
export function formatDate(date, format = 'YYYY-MM-DD HH:mm:ss') {
|
||||
// 实现日期格式化逻辑
|
||||
}
|
||||
|
||||
// 防抖函数
|
||||
export function debounce(func, wait) {
|
||||
let timeout;
|
||||
return function executedFunction(...args) {
|
||||
const later = () => {
|
||||
clearTimeout(timeout);
|
||||
func(...args);
|
||||
};
|
||||
clearTimeout(timeout);
|
||||
timeout = setTimeout(later, wait);
|
||||
};
|
||||
}
|
||||
|
||||
// 节流函数
|
||||
export function throttle(func, limit) {
|
||||
let inThrottle;
|
||||
return function () {
|
||||
const args = arguments;
|
||||
const context = this;
|
||||
if (!inThrottle) {
|
||||
func.apply(context, args);
|
||||
inThrottle = true;
|
||||
setTimeout(() => (inThrottle = false), limit);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// 深拷贝
|
||||
export function deepClone(obj) {
|
||||
if (obj === null) return null;
|
||||
if (typeof obj !== 'object') return obj;
|
||||
if (obj.constructor === Date) return new Date(obj);
|
||||
if (obj.constructor === RegExp) return new RegExp(obj);
|
||||
|
||||
var newObj = new obj.constructor();
|
||||
for (var key in obj) {
|
||||
if (obj.hasOwnProperty(key)) {
|
||||
newObj[key] = deepClone(obj[key]);
|
||||
}
|
||||
}
|
||||
return newObj;
|
||||
}
|
||||
```
|
||||
|
||||
### 存储工具
|
||||
|
||||
```javascript
|
||||
// utils/storage.js
|
||||
|
||||
// 同步存储
|
||||
export function setStorageSync(key, data) {
|
||||
try {
|
||||
uni.setStorageSync(key, data);
|
||||
} catch (e) {
|
||||
console.error('存储数据失败', e);
|
||||
}
|
||||
}
|
||||
|
||||
export function getStorageSync(key) {
|
||||
try {
|
||||
return uni.getStorageSync(key);
|
||||
} catch (e) {
|
||||
console.error('获取存储数据失败', e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export function removeStorageSync(key) {
|
||||
try {
|
||||
uni.removeStorageSync(key);
|
||||
} catch (e) {
|
||||
console.error('删除存储数据失败', e);
|
||||
}
|
||||
}
|
||||
|
||||
// 异步存储
|
||||
export function setStorage(key, data) {
|
||||
return new Promise((resolve, reject) => {
|
||||
uni.setStorage({
|
||||
key,
|
||||
data,
|
||||
success: resolve,
|
||||
fail: reject,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export function getStorage(key) {
|
||||
return new Promise((resolve, reject) => {
|
||||
uni.getStorage({
|
||||
key,
|
||||
success: (res) => resolve(res.data),
|
||||
fail: reject,
|
||||
});
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
## 9. 权限管理
|
||||
|
||||
### 登录状态检查
|
||||
|
||||
```javascript
|
||||
// utils/auth.js
|
||||
|
||||
// 检查登录状态
|
||||
export function checkLogin() {
|
||||
const token = uni.getStorageSync('token');
|
||||
return !!token;
|
||||
}
|
||||
|
||||
// 跳转到登录页
|
||||
export function toLogin() {
|
||||
uni.navigateTo({
|
||||
url: '/pages/login/login',
|
||||
});
|
||||
}
|
||||
|
||||
// 页面权限检查中间件
|
||||
export function requireAuth() {
|
||||
if (!checkLogin()) {
|
||||
toLogin();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
```
|
||||
|
||||
### 路由守卫
|
||||
|
||||
```javascript
|
||||
// main.js
|
||||
import { requireAuth } from '@/utils/auth';
|
||||
|
||||
// 页面跳转拦截器
|
||||
const originalNavigateTo = uni.navigateTo;
|
||||
uni.navigateTo = function (options) {
|
||||
// 需要登录的页面列表
|
||||
const authPages = ['/pages/workbench/workbench', '/pages/mine/mine'];
|
||||
|
||||
if (authPages.includes(options.url) && !requireAuth()) {
|
||||
return;
|
||||
}
|
||||
|
||||
return originalNavigateTo.call(this, options);
|
||||
};
|
||||
```
|
||||
|
||||
## 10. 错误处理
|
||||
|
||||
### 全局错误处理
|
||||
|
||||
```javascript
|
||||
// utils/errorHandler.js
|
||||
|
||||
// 统一错误处理
|
||||
export function handleError(error) {
|
||||
console.error('Error:', error);
|
||||
|
||||
let message = '系统异常,请稍后重试';
|
||||
|
||||
if (error.code) {
|
||||
switch (error.code) {
|
||||
case 401:
|
||||
message = '登录已过期,请重新登录';
|
||||
// 跳转到登录页
|
||||
toLogin();
|
||||
break;
|
||||
case 403:
|
||||
message = '没有权限访问';
|
||||
break;
|
||||
case 404:
|
||||
message = '请求的资源不存在';
|
||||
break;
|
||||
case 500:
|
||||
message = '服务器内部错误';
|
||||
break;
|
||||
default:
|
||||
message = error.msg || message;
|
||||
}
|
||||
}
|
||||
|
||||
uni.showToast({
|
||||
title: message,
|
||||
icon: 'none',
|
||||
duration: 2000,
|
||||
});
|
||||
}
|
||||
|
||||
// 网络错误处理
|
||||
export function handleNetworkError() {
|
||||
uni.showToast({
|
||||
title: '网络连接异常',
|
||||
icon: 'none',
|
||||
duration: 2000,
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
## 11. 性能优化
|
||||
|
||||
### 图片优化
|
||||
|
||||
```javascript
|
||||
// 图片懒加载
|
||||
export function lazyLoadImage(selector) {
|
||||
const query = uni.createSelectorQuery();
|
||||
query
|
||||
.selectAll(selector)
|
||||
.boundingClientRect((data) => {
|
||||
data.forEach((item) => {
|
||||
if (item.top < uni.getSystemInfoSync().windowHeight) {
|
||||
// 加载图片
|
||||
}
|
||||
});
|
||||
})
|
||||
.exec();
|
||||
}
|
||||
|
||||
// 图片压缩
|
||||
export function compressImage(src, quality = 0.8) {
|
||||
return new Promise((resolve, reject) => {
|
||||
uni.compressImage({
|
||||
src,
|
||||
quality,
|
||||
success: resolve,
|
||||
fail: reject,
|
||||
});
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
### 数据缓存
|
||||
|
||||
```javascript
|
||||
// 数据缓存管理
|
||||
class DataCache {
|
||||
constructor() {
|
||||
this.cache = new Map();
|
||||
this.timeout = 5 * 60 * 1000; // 5分钟
|
||||
}
|
||||
|
||||
set(key, data) {
|
||||
this.cache.set(key, {
|
||||
data,
|
||||
timestamp: Date.now(),
|
||||
});
|
||||
}
|
||||
|
||||
get(key) {
|
||||
const item = this.cache.get(key);
|
||||
if (!item) return null;
|
||||
|
||||
if (Date.now() - item.timestamp > this.timeout) {
|
||||
this.cache.delete(key);
|
||||
return null;
|
||||
}
|
||||
|
||||
return item.data;
|
||||
}
|
||||
|
||||
clear() {
|
||||
this.cache.clear();
|
||||
}
|
||||
}
|
||||
|
||||
export const dataCache = new DataCache();
|
||||
```
|
||||
|
||||
## 12. 测试规范
|
||||
|
||||
### 单元测试
|
||||
|
||||
```javascript
|
||||
// 使用Jest进行单元测试
|
||||
describe('utils/index', () => {
|
||||
test('formatDate should format date correctly', () => {
|
||||
const date = new Date('2023-01-01 12:00:00');
|
||||
const result = formatDate(date, 'YYYY-MM-DD');
|
||||
expect(result).toBe('2023-01-01');
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
### 页面测试
|
||||
|
||||
```javascript
|
||||
// 页面功能测试
|
||||
describe('Login Page', () => {
|
||||
test('should show error when username is empty', async () => {
|
||||
// 模拟登录表单提交
|
||||
const wrapper = mount(LoginPage);
|
||||
await wrapper.find('.login-btn').trigger('tap');
|
||||
|
||||
expect(wrapper.find('.error-message').text()).toBe('请输入用户名');
|
||||
});
|
||||
});
|
||||
```
|
||||
Reference in new issue
Block a user