Files
hzdyjs/docs/开发文档/移动端开发规范总结.md
T
2025-11-28 21:46:23 +08:00

26 KiB
Raw Blame History

移动端开发规范总结文档

📋 文档概述

文档目的: 为智慧校园移动端开发提供完整的规范性指导文档
适用项目: /mobile/ 目录下的 Vue 2 + uni-app + uview-ui 项目
最后更新: 2025-01-03 更新技术栈信息
维护人员: wuzilong


🎯 项目技术架构总览

核心技术栈

{
    "基础框架": "uni-app 2.x + Vue 2.6.14",
    "UI组件库": "uview-ui 2.0.38 (Vue 2版本)",
    "开发语言": "JavaScript (ES6+) + TypeScript 4.5.5",
    "样式预处理": "Sass 1.90.0",
    "构建工具": "Vue CLI 5.0.8",
    "代码规范": "ESLint",
    "状态管理": "Vuex 3.2.0"
}

项目特色

  • 🎨 现代化设计: CSS 变量设计系统 + 响应式布局
  • ♿ 无障碍访问: 完整的 ARIA 支持和键盘导航
  • 🎭 用户体验: 动画效果、手势识别、触觉反馈
  • 🚀 性能优化: 智能缓存、组件懒加载、图片优化
  • 📱 多端支持: H5、微信小程序、App、鸿蒙全平台

📁 项目结构规范

标准目录结构

mobile/
├── src/                          # 源码目录
│   ├── components/               # 通用组件
│   │   ├── NavBar.vue           # 自定义导航栏
│   │   ├── Card.vue             # 通用卡片组件
│   │   ├── ServiceItem.vue      # 服务项组件
│   │   ├── StatCard.vue         # 统计卡片组件
│   │   └── GridLayout.vue       # 网格布局组件
│   ├── pages/                   # 页面文件
│   │   ├── index/               # 首页 (底部导航)
│   │   ├── workbench/           # 办公中心 (底部导航)
│   │   └── profile/             # 我的页面 (底部导航)
│   ├── utils/                   # 工具类库
│   │   ├── request.js           # 网络请求工具
│   │   ├── sse.js              # SSE实时数据
│   │   ├── device.js           # 设备检测
│   │   ├── animations.js       # 动画工具
│   │   ├── gestures.js         # 手势识别
│   │   └── accessibility.js    # 无障碍访问
│   ├── static/                 # 静态资源
│   │   ├── tabbar/             # 底部导航图标
│   │   └── fonts/              # 字体文件
│   ├── App.vue                 # 应用入口
│   ├── main.js                 # 项目入口
│   ├── pages.json              # 页面路由配置
│   ├── manifest.json           # 应用配置
│   └── uni.scss               # 全局样式变量
├── package.json                # 项目配置
├── vite.config.js             # Vite构建配置
├── eslint.config.cjs          # ESLint配置
└── README.md                  # 项目说明

文件命名规范

  • 页面文件: kebab-case (如: index.vue)
  • 组件文件: PascalCase (如: NavBar.vue, ServiceItem.vue)
  • 工具文件: camelCase (如: request.js, animations.js)
  • 静态资源: kebab-case (如: home-active.svg)

🎨 页面开发规范

标准页面模板

<template>
    <view class="page-container">
        <!-- 自定义导航栏 -->
        <NavBar
            :title="pageTitle"
            :show-refresh="true"
            :show-search="true"
            @refresh="handleRefresh"
            @search="handleSearch"
        />

        <!-- 主要内容区域 -->
        <scroll-view class="main-content" scroll-y enhanced :show-scrollbar="false">
            <!-- 页面内容 -->
            <view class="content">
                <!-- 使用uView-plus组件 -->
                <u-cell-group>
                    <u-cell title="示例项目"></u-cell>
                </u-cell-group>
            </view>

            <!-- 底部间距 -->
            <view class="bottom-spacer"></view>
        </scroll-view>
    </view>
</template>

<script>
import NavBar from '@/components/NavBar.vue';

export default {
    name: 'PageName',
    components: {
        NavBar,
    },
    data() {
        return {
            pageTitle: '页面标题',
            // 页面数据
        };
    },
    onLoad() {
        // 页面加载逻辑
        this.initPage();
    },
    onShow() {
        // 页面显示逻辑
    },
    onUnload() {
        // 页面卸载时清理定时器等
        if (this.timer) {
            clearInterval(this.timer);
        }
    },
    methods: {
        initPage() {
            // 初始化页面逻辑
        },
        handleRefresh() {
            // 刷新逻辑
            uni.showLoading({ title: '刷新中...' });
            setTimeout(() => {
                uni.hideLoading();
                uni.showToast({ title: '刷新成功', icon: 'success' });
            }, 1000);
        },
        handleSearch() {
            // 搜索逻辑
        },
    },
};
</script>

<style lang="scss" scoped>
.page-container {
    min-height: 100vh;
    background-color: var(--background-color);
    display: flex;
    flex-direction: column;
}

.main-content {
    flex: 1;
    padding-bottom: calc(50px + env(safe-area-inset-bottom));
}

.content {
    padding: var(--spacing-md);
}

.bottom-spacer {
    height: 20px;
}
</style>

页面开发要点

✅ 必须遵循的规范

  1. 导航栏配置: 使用navigationStyle: "custom",采用自定义 NavBar 组件
  2. 安全区域适配: 使用env(safe-area-inset-bottom)处理底部安全区域
  3. scroll-view 容器: 使用enhanced属性提升滚动性能
  4. 生命周期管理: 正确使用onLoad、onShow、onUnload
  5. 定时器清理: 在onUnload中清理所有定时器和监听器

🎨 样式规范

// 使用CSS变量系统
.page-container {
    background-color: var(--background-color);
    color: var(--text-primary);
}

// 使用工具类
.flex-center {
    display: flex;
    align-items: center;
    justify-content: center;
}

// 使用统一间距
.mt-md {
    margin-top: var(--spacing-md);
}
.p-lg {
    padding: var(--spacing-lg);
}

🧩 组件开发规范

通用组件模板

<template>
    <view class="component-wrapper" :class="computedClass">
        <!-- 组件内容 -->
        <slot></slot>
    </view>
</template>

<script>
/**
 * 智慧校园通用组件
 * @property {String} customClass 自定义样式类
 * @property {Boolean} disabled 是否禁用
 * @example <custom-component :disabled="false">内容</custom-component>
 */
export default {
    name: 'CustomComponent',
    props: {
        customClass: {
            type: String,
            default: '',
        },
        disabled: {
            type: Boolean,
            default: false,
        },
    },
    computed: {
        computedClass() {
            return {
                disabled: this.disabled,
                [this.customClass]: !!this.customClass,
            };
        },
    },
    methods: {
        handleClick() {
            if (this.disabled) return;

            this.$emit('click');
            // 触觉反馈
            uni.vibrateShort();
        },
    },
};
</script>

<style lang="scss" scoped>
.component-wrapper {
    transition: var(--transition-fast);

    &.disabled {
        opacity: 0.5;
        pointer-events: none;
    }
}
</style>

组件开发要点

✅ 必须遵循的规范

  1. 组件命名: 使用 PascalCase,具有语义化的名称
  2. 属性定义: 提供完整的 props 配置,包含类型和默认值
  3. 事件抛出: 使用$emit向父组件传递事件
  4. 样式作用域: 使用scoped避免样式污染
  5. 无障碍支持: 添加适当的 ARIA 标签和键盘支持
  6. 触觉反馈: 在交互操作中添加uni.vibrateShort()

🎯 组件分类

  • 基础组件: NavBar、Card、Button 等
  • 业务组件: ServiceItem、StatCard 等
  • 布局组件: GridLayout 等
  • 工具组件: Loading、Toast 等

🌐 API 接口规范

网络请求工具

// utils/request.js
const baseURL =
    process.env.NODE_ENV === 'development'
        ? 'http://localhost:3000/api'
        : 'https://your-api-domain.com/api';

// 统一请求方法
export const request = (options) => {
    return new Promise((resolve, reject) => {
        uni.request({
            url: baseURL + options.url,
            method: options.method || 'GET',
            data: options.data || {},
            header: {
                'Content-Type': 'application/json',
                Authorization: getToken(),
                ...options.header,
            },
            success: (res) => {
                if (res.statusCode === 200) {
                    resolve(res.data);
                } else {
                    reject(res);
                }
            },
            fail: (error) => {
                reject(error);
            },
        });
    });
};

// API方法封装
export const api = {
    // 用户登录
    login: (data) => request({ url: '/auth/login', method: 'POST', data }),
    // 获取用户信息
    getUserInfo: () => request({ url: '/user/info' }),
    // 获取首页数据
    getHomeData: () => request({ url: '/home/data' }),
};

智能缓存策略

// 缓存策略配置
export const cacheStrategy = {
    short: 5 * 60, // 短期缓存 (5分钟)
    medium: 30 * 60, // 中期缓存 (30分钟)
    long: 24 * 60 * 60, // 长期缓存 (24小时)
};

// 数据缓存工具
export const storage = {
    set: (key, value, expire) => {
        try {
            const data = {
                value,
                timestamp: Date.now(),
                expire: expire ? Date.now() + expire * 1000 : null,
            };
            uni.setStorageSync(key, JSON.stringify(data));
        } catch (e) {
            console.error('缓存设置失败:', e);
        }
    },

    get: (key) => {
        try {
            const data = uni.getStorageSync(key);
            if (!data) return null;

            const parsed = JSON.parse(data);

            // 检查是否过期
            if (parsed.expire && Date.now() > parsed.expire) {
                uni.removeStorageSync(key);
                return null;
            }

            return parsed.value;
        } catch (e) {
            console.error('缓存获取失败:', e);
            return null;
        }
    },
};

API 调用最佳实践

✅ 推荐做法

// 带缓存的API调用
export const api = {
    getUserInfo: () => {
        const cacheKey = 'userInfo';
        const cached = storage.get(cacheKey);

        if (cached) {
            return Promise.resolve(cached);
        }

        return request({
            url: '/user/info',
        }).then(data => {
            storage.set(cacheKey, data, cacheStrategy.medium);
            return data;
        });
    },
};

// 在页面中使用
methods: {
    async loadUserInfo() {
        try {
            const userInfo = await api.getUserInfo();
            this.userInfo = userInfo;
        } catch (error) {
            uni.showToast({
                title: '获取用户信息失败',
                icon: 'none'
            });
        }
    }
}

🎭 用户体验规范

动画效果规范

CSS 动画类

// 全局动画类 (uni.scss)
.fade-in {
    animation: fadeIn var(--transition-medium);
}

.slide-up {
    animation: slideUp var(--transition-medium);
}

@keyframes fadeIn {
    from {
        opacity: 0;
    }
    to {
        opacity: 1;
    }
}

@keyframes slideUp {
    from {
        opacity: 0;
        transform: translateY(20px);
    }
    to {
        opacity: 1;
        transform: translateY(0);
    }
}

JavaScript 动画工具

// utils/animations.js
export const animationUtils = {
    fadeIn: (element) => {
        element.style.animation = 'fadeIn 0.3s ease';
    },

    slideUp: (element) => {
        element.style.animation = 'slideUp 0.3s ease';
    },

    scale: (element) => {
        element.style.animation = 'scale 0.2s ease';
    },
};

手势识别规范

// utils/gestures.js
export const gestureManager = {
    init: (element, callbacks) => {
        let startX, startY, startTime;

        element.addEventListener('touchstart', (e) => {
            startX = e.touches[0].clientX;
            startY = e.touches[0].clientY;
            startTime = Date.now();
        });

        element.addEventListener('touchend', (e) => {
            const endX = e.changedTouches[0].clientX;
            const endY = e.changedTouches[0].clientY;
            const deltaX = endX - startX;
            const deltaY = endY - startY;

            // 判断手势类型
            if (Math.abs(deltaX) > Math.abs(deltaY) && Math.abs(deltaX) > 50) {
                if (deltaX > 0) {
                    callbacks.swipeRight && callbacks.swipeRight();
                } else {
                    callbacks.swipeLeft && callbacks.swipeLeft();
                }
            }
        });
    },
};

无障碍访问规范

// utils/accessibility.js
export const accessibilityUtils = {
    // 设置焦点
    setFocus: (element) => {
        element.focus();
    },

    // 添加ARIA标签
    addAriaLabel: (element, label) => {
        element.setAttribute('aria-label', label);
    },

    // 键盘导航支持
    enableKeyboardNavigation: (container) => {
        container.addEventListener('keydown', (e) => {
            switch (e.key) {
                case 'Tab':
                    // Tab导航逻辑
                    break;
                case 'Enter':
                case ' ':
                    // 激活元素
                    e.target.click();
                    break;
                case 'Escape':
                    // 取消操作
                    break;
            }
        });
    },
};

📱 页面配置规范

pages.json 标准配置

{
    "pages": [
        {
            "path": "pages/index/index",
            "style": {
                "navigationBarTitleText": "首页",
                "navigationStyle": "custom"
            }
        },
        {
            "path": "pages/workbench/index",
            "style": {
                "navigationBarTitleText": "办公中心",
                "navigationStyle": "custom"
            }
        },
        {
            "path": "pages/profile/index",
            "style": {
                "navigationBarTitleText": "我的",
                "navigationStyle": "custom"
            }
        }
    ],
    "globalStyle": {
        "navigationBarTextStyle": "white",
        "navigationBarTitleText": "智慧校园",
        "navigationBarBackgroundColor": "rgb(93, 135, 255)",
        "backgroundColor": "#f8f9fa"
    },
    "tabBar": {
        "color": "#999999",
        "selectedColor": "rgb(93, 135, 255)",
        "borderStyle": "white",
        "backgroundColor": "#ffffff",
        "height": "60px",
        "fontSize": "12px",
        "iconWidth": "22px",
        "iconHeight": "22px",
        "list": [
            {
                "pagePath": "pages/index/index",
                "text": "首页",
                "iconPath": "static/tabbar/home.svg",
                "selectedIconPath": "static/tabbar/home-active.svg"
            },
            {
                "pagePath": "pages/workbench/index",
                "text": "办公中心",
                "iconPath": "static/tabbar/workbench.svg",
                "selectedIconPath": "static/tabbar/workbench-active.svg"
            },
            {
                "pagePath": "pages/profile/index",
                "text": "我的",
                "iconPath": "static/tabbar/profile.svg",
                "selectedIconPath": "static/tabbar/profile-active.svg"
            }
        ]
    }
}

manifest.json 应用配置

{
    "name": "智慧校园",
    "appid": "",
    "description": "基于Vue3+uniapp的智慧校园移动应用",
    "versionName": "1.0.0",
    "versionCode": "100",
    "transformPx": false,
    "vueVersion": "3",
    "app-plus": {
        "usingComponents": true,
        "nvueStyleCompiler": "uni-app",
        "compilerVersion": 3,
        "splashscreen": {
            "alwaysShowBeforeRender": true,
            "waiting": true,
            "autoclose": true,
            "delay": 0
        },
        "distribute": {
            "android": {
                "permissions": [
                    "<uses-permission android:name=\"android.permission.VIBRATE\"/>",
                    "<uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\"/>",
                    "<uses-permission android:name=\"android.permission.ACCESS_WIFI_STATE\"/>",
                    "<uses-permission android:name=\"android.permission.CAMERA\"/>",
                    "<uses-permission android:name=\"android.permission.FLASHLIGHT\"/>"
                ]
            }
        }
    },
    "mp-weixin": {
        "appid": "",
        "setting": {
            "urlCheck": false
        },
        "usingComponents": true
    },
    "uniStatistics": {
        "enable": false
    }
}

🚀 性能优化规范

代码优化策略

组件懒加载

// 页面中使用懒加载组件
export default {
    components: {
        // 懒加载大型组件
        HeavyComponent: () => import('@/components/HeavyComponent.vue'),
        // 条件加载组件
        ConditionalComponent: () => {
            if (condition) {
                return import('@/components/ConditionalComponent.vue');
            }
            return null;
        },
    },
};

图片优化

<template>
    <!-- 响应式图片 -->
    <image
        :src="imageSrc"
        mode="aspectFit"
        lazy-load
        :fade-show="true"
        @error="handleImageError"
        @load="handleImageLoad"
    />
</template>

<script>
export default {
    methods: {
        handleImageError() {
            console.log('图片加载失败');
            // 可以设置默认图片
            this.imageSrc = '/static/default-image.png';
        },
        handleImageLoad() {
            console.log('图片加载成功');
        },
    },
};
</script>

头像鉴权加载

  • 统一从 /system/user/getInfo 获取 avatar(OSS ID)与 avatarUrl(预签名地址)。
  • 页面中优先识别 avatar 是否为 OSS ID,使用 uni.request(responseType: 'arraybuffer')访问 /resource/oss/download/{ossId} 并转换为 Blob URL/Base64,确保 Token 认证生效。
  • 若历史账号仍存储直接 URL,则回退到 avatar 或 avatarUrl 原始值,最后兜底 /static/111.png。
  • 首页、详情页等通用区域必需复用该策略,避免直接暴露预签名链接导致过期或越权。

列表优化

<template>
    <!-- 使用虚拟列表优化大数据 -->
    <recycle-list class="recycle-list" :list-data="listData" :template-key="'type'">
        <template v-slot:type1="{ item }">
            <view class="list-item">{{ item.title }}</view>
        </template>
    </recycle-list>
</template>

网络优化策略

请求优化

// 请求防抖
const debounce = (func, wait) => {
    let timeout;
    return function executedFunction(...args) {
        const later = () => {
            clearTimeout(timeout);
            func(...args);
        };
        clearTimeout(timeout);
        timeout = setTimeout(later, wait);
    };
};

// 在搜索中使用防抖
methods: {
    handleSearch: debounce(function(keyword) {
        this.searchData(keyword);
    }, 300),
}

缓存优化

// 分级缓存策略
export const cacheManager = {
    // 内存缓存(当前会话)
    memoryCache: new Map(),

    // 本地缓存(持久化)
    setCache: (key, data, expire = cacheStrategy.medium) => {
        // 内存缓存
        cacheManager.memoryCache.set(key, data);

        // 本地缓存
        storage.set(key, data, expire);
    },

    getCache: (key) => {
        // 优先从内存获取
        if (cacheManager.memoryCache.has(key)) {
            return cacheManager.memoryCache.get(key);
        }

        // 从本地存储获取
        const data = storage.get(key);
        if (data) {
            // 回写内存缓存
            cacheManager.memoryCache.set(key, data);
        }

        return data;
    },
};

🛠️ 开发工具配置

Vite 构建配置

// vite.config.js
import { defineConfig } from 'vite';
import uni from '@dcloudio/vite-plugin-uni';

export default defineConfig({
    plugins: [uni()],
    server: {
        port: 3000,
        open: true,
        host: '0.0.0.0',
    },
    build: {
        // 生产环境优化
        minify: 'terser',
        terserOptions: {
            compress: {
                drop_console: true,
                drop_debugger: true,
            },
        },
        // 代码分割
        rollupOptions: {
            output: {
                manualChunks: {
                    'uview-plus': ['uview-plus'],
                    vue: ['vue'],
                },
            },
        },
    },
    // CSS优化
    css: {
        preprocessorOptions: {
            scss: {
                additionalData: '@import "@/uni.scss";',
            },
        },
    },
});

ESLint 代码规范

// eslint.config.cjs
module.exports = [
    {
        files: ['src/**/*.{js,vue}'],
        languageOptions: {
            ecmaVersion: 2022,
            sourceType: 'module',
        },
        rules: {
            // 代码风格
            indent: ['error', 2],
            quotes: ['error', 'single'],
            semi: ['error', 'never'],

            // Vue 3 规范
            'vue/component-name-in-template-casing': ['error', 'PascalCase'],
            'vue/no-unused-components': 'error',
            'vue/no-unused-vars': 'error',

            // 性能相关
            'no-console': 'warn',
            'no-debugger': 'error',
            'no-unused-vars': 'error',
        },
    },
];

📋 开发命令规范

标准开发命令

# 环境准备
npm install                    # 安装依赖
npm audit fix                  # 修复安全漏洞

# 开发调试
npm run dev:h5                 # H5开发调试
npm run dev:mp-weixin          # 微信小程序开发
npm run dev:app                # App开发

# 生产构建
npm run build:h5              # H5生产构建
npm run build:mp-weixin       # 微信小程序构建
npm run build:app             # App构建

# 代码质量
npm run lint                  # 代码检查
npm run lint:fix              # 自动修复代码问题

# 清理缓存
npm run clean                 # 清理构建缓存

发布流程

# 版本发布流程
npm run lint                  # 1. 代码检查
npm run build:h5              # 2. 构建H5版本
npm run build:mp-weixin       # 3. 构建小程序版本
npm run build:app             # 4. 构建App版本

✅ 最佳实践总结

🎯 核心开发原则

1. 代码规范

  • 文件组织: 按功能模块清晰组织文件结构
  • 组件复用: 提取通用组件,避免重复代码
  • 样式管理: 使用 CSS 变量系统,便于主题切换
  • 命名规范: 采用语义化命名,提高代码可读性

2. 性能优化

  • 懒加载: 合理使用组件和路由懒加载
  • 缓存策略: 实施分级缓存,提升响应速度
  • 图片优化: 使用响应式图片和懒加载
  • 代码分割: 合理分割代码包,减少初始加载

3. 用户体验

  • 加载状态: 添加适当的加载提示
  • 错误处理: 友好的错误提示和降级方案
  • 无障碍访问: 完整的 ARIA 支持和键盘导航
  • 触觉反馈: 在交互操作中添加震动反馈

4. 兼容性保证

  • 多端测试: 确保 H5、小程序、App 各端兼容
  • 设备适配: 支持各种屏幕尺寸和分辨率
  • 版本兼容: 考虑不同系统版本的兼容性
  • 性能监控: 监控各端性能表现

🔄 开发流程

1. 需求分析 → 确定页面结构和功能
2. 组件设计 → 拆分可复用组件
3. 页面开发 → 按模板编写页面代码
4. 样式调优 → 使用统一的设计规范
5. 功能测试 → 多平台测试确保兼容性
6. 性能优化 → 优化加载速度和用户体验
7. 代码审查 → 确保代码质量和规范性
8. 文档更新 → 同步更新相关文档

🎨 设计理念

  • 现代简约: 清新简洁的界面设计
  • 一致性: 统一的交互体验和视觉风格
  • 响应式: 适配多种屏幕尺寸和设备
  • 无障碍: 友好的无障碍访问支持
  • 高性能: 流畅的动画和快速的响应

📚 相关文档

技术文档

  • 详细规范: .cursor/cursorrules-mobile.md
  • 项目说明: mobile/README.md
  • API 接口: docs/开发文档/API/

学习资源

工具支持

  • 开发工具: HBuilderX、VSCode
  • 调试工具: Vue DevTools、uni-app 调试工具
  • 构建工具: Vite、uni-cli

🔄 维护更新

更新日志

  • 2025-07-02: 初始版本,基于实际 mobile 工程代码编写
  • 规范来源: 深入分析 mobile 项目架构和代码实现
  • 维护人员: wuzilong

后续计划

  • 根据实际开发反馈持续优化规范
  • 定期检查规范与实际代码的一致性
  • 新增功能时同步更新规范文档
  • 收集团队使用反馈并改进规范

📌 重要提醒: 本规范基于实际项目代码制定,所有示例代码均可直接使用。遵循本规范能够确保代码质量,提升开发效率,并为用户提供优秀的移动端体验。

🎯 目标: 为智慧校园移动端开发提供完整、准确、实用的规范指导,确保项目的可维护性和可扩展性。