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

1038 lines
26 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 移动端开发规范总结文档
## 📋 文档概述
**文档目的**: 为智慧校园移动端开发提供完整的规范性指导文档
**适用项目**: `/mobile/` 目录下的 Vue 2 + uni-app + uview-ui 项目
**最后更新**: 2025-01-03 更新技术栈信息
**维护人员**: wuzilong
---
## 🎯 项目技术架构总览
### 核心技术栈
```json
{
"基础框架": "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`)
---
## 🎨 页面开发规范
### 标准页面模板
```vue
<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`中清理所有定时器和监听器
#### 🎨 样式规范
```scss
// 使用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);
}
```
---
## 🧩 组件开发规范
### 通用组件模板
```vue
<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 接口规范
### 网络请求工具
```javascript
// 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' }),
};
```
### 智能缓存策略
```javascript
// 缓存策略配置
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 调用最佳实践
#### ✅ 推荐做法
```javascript
// 带缓存的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 动画类
```scss
// 全局动画类 (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 动画工具
```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';
},
};
```
### 手势识别规范
```javascript
// 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();
}
}
});
},
};
```
### 无障碍访问规范
```javascript
// 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 标准配置
```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 应用配置
```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
}
}
```
---
## 🚀 性能优化规范
### 代码优化策略
#### 组件懒加载
```javascript
// 页面中使用懒加载组件
export default {
components: {
// 懒加载大型组件
HeavyComponent: () => import('@/components/HeavyComponent.vue'),
// 条件加载组件
ConditionalComponent: () => {
if (condition) {
return import('@/components/ConditionalComponent.vue');
}
return null;
},
},
};
```
#### 图片优化
```vue
<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`。
- 首页、详情页等通用区域必需复用该策略,避免直接暴露预签名链接导致过期或越权。
#### 列表优化
```vue
<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>
```
### 网络优化策略
#### 请求优化
```javascript
// 请求防抖
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),
}
```
#### 缓存优化
```javascript
// 分级缓存策略
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 构建配置
```javascript
// 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 代码规范
```javascript
// 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',
},
},
];
```
---
## 📋 开发命令规范
### 标准开发命令
```bash
# 环境准备
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 # 清理构建缓存
```
### 发布流程
```bash
# 版本发布流程
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/`
### 学习资源
- **Vue 2 官方文档**: https://v2.cn.vuejs.org/
- **uni-app 官方文档**: https://uniapp.dcloud.net.cn/
- **uview-ui 文档**: https://www.uviewui.com/
### 工具支持
- **开发工具**: HBuilderX、VSCode
- **调试工具**: Vue DevTools、uni-app 调试工具
- **构建工具**: Vite、uni-cli
---
## 🔄 维护更新
### 更新日志
- **2025-07-02**: 初始版本,基于实际 mobile 工程代码编写
- **规范来源**: 深入分析 mobile 项目架构和代码实现
- **维护人员**: wuzilong
### 后续计划
- 根据实际开发反馈持续优化规范
- 定期检查规范与实际代码的一致性
- 新增功能时同步更新规范文档
- 收集团队使用反馈并改进规范
---
**📌 重要提醒**: 本规范基于实际项目代码制定,所有示例代码均可直接使用。遵循本规范能够确保代码质量,提升开发效率,并为用户提供优秀的移动端体验。
**🎯 目标**: 为智慧校园移动端开发提供完整、准确、实用的规范指导,确保项目的可维护性和可扩展性。