Files
hzdyjs/.cursor/cursorrules-mobile.md
T
zlwu 249b1dfeb4 feat: 完善项目开发规范和测试文档体系
📝 主要改进:
• Cursor配置模块化:拆分为前端、后端、移动端、文档、API测试5个专门规范文件
• API文档重新组织:按业务模块拆分到docs/开发文档/API/目录下
• 新增维修分类功能完整测试方案和自动化测试脚本
• 后端热启动脚本完善,支持Linux/Windows双平台
• 强化文档同步更新规范,确保代码与文档一致性

🔧 技术改进:
• 优化Cursor AI工作流程和代码生成质量
• 完善API测试规范(AES+RSA双重加密登录方法)
• 提升开发效率和代码质量标准化程度

�� 测试覆盖:
• 维修分类管理功能端到端测试验证通过
• 包含页面加载、CRUD操作、搜索功能等核心测试用例

更新时间:2025-07-01
2025-07-01 17:53:05 +08:00

14 KiB

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 组件和样式变量

页面模板

<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>

组件开发规范

<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 对接规范

请求封装

// 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 接口定义

// 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 结构

// 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,
    },
});

用户模块示例

// 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 配置

{
    "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. 样式规范

全局样式

// 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;
}

组件样式规范

// 组件样式示例
.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. 工具函数规范

通用工具函数

// 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;
}

存储工具

// 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. 权限管理

登录状态检查

// 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;
}

路由守卫

// 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. 错误处理

全局错误处理

// 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. 性能优化

图片优化

// 图片懒加载
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,
        });
    });
}

数据缓存

// 数据缓存管理
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. 测试规范

单元测试

// 使用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');
    });
});

页面测试

// 页面功能测试
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('请输入用户名');
    });
});