文档目的: 为智慧校园移动端开发提供完整的规范性指导文档
适用项目: /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"
}
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>
navigationStyle: "custom",采用自定义 NavBar 组件env(safe-area-inset-bottom)处理底部安全区域enhanced属性提升滚动性能onLoad、onShow、onUnloadonUnload中清理所有定时器和监听器// 使用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>
$emit向父组件传递事件scoped避免样式污染uni.vibrateShort()// 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调用
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'
});
}
}
}
// 全局动画类 (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);
}
}
// 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": [
{
"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"
}
]
}
}
{
"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 认证生效。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.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.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. 需求分析 → 确定页面结构和功能
2. 组件设计 → 拆分可复用组件
3. 页面开发 → 按模板编写页面代码
4. 样式调优 → 使用统一的设计规范
5. 功能测试 → 多平台测试确保兼容性
6. 性能优化 → 优化加载速度和用户体验
7. 代码审查 → 确保代码质量和规范性
8. 文档更新 → 同步更新相关文档
.cursor/cursorrules-mobile.mdmobile/README.mddocs/开发文档/API/📌 重要提醒: 本规范基于实际项目代码制定,所有示例代码均可直接使用。遵循本规范能够确保代码质量,提升开发效率,并为用户提供优秀的移动端体验。
🎯 目标: 为智慧校园移动端开发提供完整、准确、实用的规范指导,确保项目的可维护性和可扩展性。