移动端开发规范
本文档规定了移动端 uni-app 项目的开发规范,包括代码风格、命名规范、文件组织、API设计等方面的标准。
📋 基础规范
文件命名
- 页面文件: 使用 camelCase,如
userProfile.vue
- 组件文件: 使用 PascalCase,如
UserCard.vue
- 工具文件: 使用 camelCase,如
httpUtils.ts
- 常量文件: 使用 UPPER_SNAKE_CASE,如
API_CONSTANTS.ts
目录结构
src/
├── api/ # API接口
│ ├── business/ # 业务接口
│ └── system/ # 系统接口
├── components/ # 自定义组件
│ ├── business/ # 业务组件
│ └── common/ # 通用组件
├── composables/ # 组合式函数
├── layouts/ # 布局组件
├── pages/ # 页面文件
│ ├── auth/ # 认证相关页面
│ ├── index/ # 首页
│ └── my/ # 个人中心
├── static/ # 静态资源
├── stores/ # 状态管理
├── types/ # 类型定义
├── utils/ # 工具函数
└── wd/ # WD UI组件库
🎨 样式规范
单位使用
- 移动端尺寸: 统一使用
rpx
单位 - 边框线条: 使用
1rpx
或2rpx
- UnoCSS数值: 直接使用数值,1 = 8rpx
vue
<template>
<!-- ✅ 正确:使用rpx -->
<view class="w-750rpx h-100rpx p-20rpx">
<!-- ✅ 正确:UnoCSS数值类 -->
<view class="w-full h-12 p-4">
内容
</view>
</view>
<!-- ❌ 错误:使用px -->
<view class="width: 375px; height: 50px;">
错误示例
</view>
</template>
CSS注释规范
scss
/* ✅ 正确:使用块注释 */
.user-card {
padding: 20rpx;
margin: 16rpx;
}
// ❌ 错误:不要使用行注释
.user-card {
padding: 20rpx;
}
组件样式优先级
- WD UI组件: 优先使用WD UI组件
- UnoCSS工具类: 使用原子化CSS类
- 自定义样式: 最后考虑自定义CSS
vue
<template>
<!-- 1. 优先使用WD UI -->
<wd-button type="primary">按钮</wd-button>
<!-- 2. 使用UnoCSS -->
<view class="flex items-center justify-between p-4">
<!-- 3. 自定义样式 -->
<view class="custom-element">
内容
</view>
</view>
</template>
<style lang="scss" scoped>
.custom-element {
/* 自定义样式 */
background: linear-gradient(45deg, #ff6b6b, #feca57);
}
</style>
💻 代码规范
Vue组件规范
vue
<template>
<!-- 使用语义化标签 -->
<view class="user-profile-page">
<wd-navbar title="用户资料" />
<!-- 表单区域 -->
<view class="form-section">
<wd-cell-group title="基本信息">
<wd-input
v-model="userForm.name"
label="姓名"
placeholder="请输入姓名"
required
/>
</wd-cell-group>
</view>
<!-- 操作按钮 -->
<view class="button-section">
<wd-button
type="primary"
block
:loading="submitting"
@click="handleSubmit"
>
保存
</wd-button>
</view>
</view>
</template>
<script setup lang="ts">
// 1. 导入区域
import { ref, reactive } from 'vue'
import type { UserProfileForm } from '@/types/user'
// 2. 接口定义
interface Props {
userId?: string
}
// 3. Props定义
const props = withDefaults(defineProps<Props>(), {
userId: ''
})
// 4. 响应式数据
const submitting = ref(false)
const userForm = reactive<UserProfileForm>({
name: '',
phone: '',
email: ''
})
// 5. 计算属性
const isFormValid = computed(() => {
return userForm.name && userForm.phone
})
// 6. 方法定义
const handleSubmit = async () => {
if (!isFormValid.value) {
uni.showToast({
title: '请填写必填信息',
icon: 'none'
})
return
}
submitting.value = true
try {
// API调用
await submitUserProfile(userForm)
uni.showToast({
title: '保存成功',
icon: 'success'
})
} catch (error) {
console.error('保存失败:', error)
uni.showToast({
title: '保存失败',
icon: 'none'
})
} finally {
submitting.value = false
}
}
// 7. 生命周期
onMounted(() => {
// 初始化逻辑
})
</script>
<style lang="scss" scoped>
.user-profile-page {
min-height: 100vh;
background-color: #f8f8f8;
}
.form-section {
margin: 20rpx;
}
.button-section {
padding: 40rpx 20rpx;
}
</style>
TypeScript规范
typescript
// ✅ 正确:使用interface定义类型
interface UserInfo {
id: number
name: string
avatar?: string
createTime: string
}
// ✅ 正确:使用type定义联合类型
type UserStatus = 'active' | 'inactive' | 'pending'
// ✅ 正确:泛型使用
interface ApiResponse<T> {
code: number
data: T
message: string
}
// ✅ 正确:枚举定义
enum UserRole {
ADMIN = 'admin',
USER = 'user',
GUEST = 'guest'
}
// ✅ 正确:函数类型定义
const getUserInfo = async (id: number): Promise<UserInfo> => {
// 实现逻辑
}
📡 API规范
接口定义
typescript
// api/user/userApi.ts
import { http } from '@/utils/http'
import type { UserInfo, UpdateUserRequest } from '@/types/user'
/**
* 获取用户信息
* @param id 用户ID
*/
export const getUserInfo = (id: number): Promise<UserInfo> => {
return http.get<UserInfo>(`/api/user/${id}`)
}
/**
* 更新用户信息
* @param data 用户信息
*/
export const updateUserInfo = (data: UpdateUserRequest): Promise<void> => {
return http.put<void>('/api/user/update', data)
}
/**
* 获取用户列表
* @param params 查询参数
*/
export const getUserList = (params: {
page: number
size: number
keyword?: string
}): Promise<PageResult<UserInfo>> => {
return http.get<PageResult<UserInfo>>('/api/user/list', params)
}
错误处理
typescript
// 统一错误处理
const handleApiCall = async <T>(
apiCall: () => Promise<T>,
errorMessage = '操作失败'
): Promise<T | null> => {
try {
return await apiCall()
} catch (error) {
console.error('API调用失败:', error)
uni.showToast({
title: errorMessage,
icon: 'none'
})
return null
}
}
// 使用示例
const handleSave = async () => {
const result = await handleApiCall(
() => updateUserInfo(userForm),
'保存失败,请重试'
)
if (result) {
uni.showToast({
title: '保存成功',
icon: 'success'
})
}
}
🎣 组合式函数规范
基础模板
typescript
// composables/useUserManagement.ts
import { ref, computed } from 'vue'
import { getUserList, deleteUser } from '@/api/user/userApi'
import type { UserInfo } from '@/types/user'
export function useUserManagement() {
// 响应式状态
const users = ref<UserInfo[]>([])
const loading = ref(false)
const searchKeyword = ref('')
// 计算属性
const filteredUsers = computed(() => {
if (!searchKeyword.value) return users.value
return users.value.filter(user =>
user.name.includes(searchKeyword.value) ||
user.email.includes(searchKeyword.value)
)
})
// 方法
const loadUsers = async () => {
loading.value = true
try {
const response = await getUserList({
page: 1,
size: 20,
keyword: searchKeyword.value
})
users.value = response.records
} catch (error) {
console.error('加载用户列表失败:', error)
} finally {
loading.value = false
}
}
const removeUser = async (id: number) => {
try {
await deleteUser(id)
users.value = users.value.filter(user => user.id !== id)
uni.showToast({
title: '删除成功',
icon: 'success'
})
} catch (error) {
uni.showToast({
title: '删除失败',
icon: 'none'
})
}
}
// 返回公共接口
return {
// 状态
users: readonly(users),
loading: readonly(loading),
searchKeyword,
// 计算属性
filteredUsers,
// 方法
loadUsers,
removeUser
}
}
📱 页面开发规范
页面路由
javascript
// pages.json
{
"path": "pages/user/profile",
"style": {
"navigationBarTitleText": "用户资料",
"navigationBarBackgroundColor": "#ffffff",
"navigationBarTextStyle": "black",
"backgroundColor": "#f8f8f8"
}
}
页面传参
typescript
// 页面跳转
uni.navigateTo({
url: `/pages/user/detail?id=${userId}&name=${encodeURIComponent(userName)}`
})
// 接收参数
interface DetailPageProps {
id?: string
name?: string
}
const props = withDefaults(defineProps<DetailPageProps>(), {
id: '',
name: ''
})
// 或使用onLoad
onLoad((options) => {
const { id, name } = options
// 处理参数
})
生命周期使用
vue
<script setup>
import { onMounted, onUnmounted } from 'vue'
// ✅ 正确:使用Composition API
onMounted(() => {
console.log('页面挂载')
// 初始化逻辑
})
onUnmounted(() => {
console.log('页面销毁')
// 清理逻辑
})
// ✅ 正确:使用uni-app生命周期
onShow(() => {
console.log('页面显示')
// 页面显示时的逻辑
})
onHide(() => {
console.log('页面隐藏')
// 页面隐藏时的逻辑
})
</script>
🏪 状态管理规范
Store定义
typescript
// stores/modules/user.ts
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
import type { UserInfo } from '@/types/user'
export const useUserStore = defineStore('user', () => {
// 状态
const userInfo = ref<UserInfo | null>(null)
const token = ref('')
// 计算属性
const isLoggedIn = computed(() => !!token.value)
const userName = computed(() => userInfo.value?.name || '')
// 方法
const setUserInfo = (info: UserInfo) => {
userInfo.value = info
}
const setToken = (newToken: string) => {
token.value = newToken
// 持久化存储
uni.setStorageSync('token', newToken)
}
const logout = () => {
userInfo.value = null
token.value = ''
uni.removeStorageSync('token')
}
return {
// 状态
userInfo: readonly(userInfo),
token: readonly(token),
// 计算属性
isLoggedIn,
userName,
// 方法
setUserInfo,
setToken,
logout
}
})
🛠️ 工具函数规范
通用工具
typescript
// utils/common.ts
/**
* 防抖函数
* @param fn 要防抖的函数
* @param delay 延迟时间(毫秒)
*/
export function debounce<T extends (...args: any[]) => any>(
fn: T,
delay: number
): (...args: Parameters<T>) => void {
let timeoutId: NodeJS.Timeout
return (...args: Parameters<T>) => {
clearTimeout(timeoutId)
timeoutId = setTimeout(() => fn(...args), delay)
}
}
/**
* 节流函数
* @param fn 要节流的函数
* @param delay 间隔时间(毫秒)
*/
export function throttle<T extends (...args: any[]) => any>(
fn: T,
delay: number
): (...args: Parameters<T>) => void {
let lastCall = 0
return (...args: Parameters<T>) => {
const now = Date.now()
if (now - lastCall >= delay) {
lastCall = now
fn(...args)
}
}
}
/**
* 格式化文件大小
* @param bytes 字节数
*/
export function formatFileSize(bytes: number): string {
if (bytes === 0) return '0 B'
const k = 1024
const sizes = ['B', 'KB', 'MB', 'GB']
const i = Math.floor(Math.log(bytes) / Math.log(k))
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]
}
📝 注释规范
函数注释
typescript
/**
* 获取用户信息
* @param userId 用户ID
* @param includeProfile 是否包含详细资料
* @returns 用户信息
* @throws {ApiError} 当用户不存在时抛出错误
* @example
* ```typescript
* const user = await getUserInfo(123, true)
* console.log(user.name)
* ```
*/
export async function getUserInfo(
userId: number,
includeProfile = false
): Promise<UserInfo> {
// 实现逻辑
}
组件注释
vue
<template>
<!-- 用户卡片组件:显示用户基本信息和操作按钮 -->
<view class="user-card">
<!-- 用户头像区域 -->
<view class="avatar-section">
<wd-img :src="user.avatar" class="avatar" />
</view>
<!-- 用户信息区域 -->
<view class="info-section">
<text class="name">{{ user.name }}</text>
<text class="email">{{ user.email }}</text>
</view>
</view>
</template>
⚠️ 注意事项
平台兼容性
typescript
// ✅ 正确:检查平台支持
const platform = uni.getSystemInfoSync().platform
if (platform === 'mp-weixin') {
// 微信小程序特有功能
wx.requestSubscribeMessage({
tmplIds: ['template_id']
})
} else if (platform === 'h5') {
// H5特有功能
window.location.href = 'https://example.com'
}
// ❌ 错误:直接使用平台特有API
wx.requestSubscribeMessage({
tmplIds: ['template_id']
})
性能优化
vue
<template>
<!-- ✅ 正确:使用v-show而非v-if进行频繁切换 -->
<view v-show="isVisible" class="content">
频繁切换的内容
</view>
<!-- ✅ 正确:长列表使用虚拟滚动 -->
<wd-virtual-list
:data="longList"
:item-height="100"
:height="600"
>
<template #default="{ item }">
<UserItem :user="item" />
</template>
</wd-virtual-list>
<!-- ❌ 错误:渲染大量DOM -->
<view v-for="item in longList" :key="item.id">
{{ item.name }}
</view>
</template>
错误边界
vue
<script setup>
import { onErrorCaptured } from 'vue'
// 错误捕获
onErrorCaptured((error, instance, info) => {
console.error('组件错误:', error)
console.error('错误信息:', info)
// 上报错误
reportError(error, {
component: instance?.type?.name,
info
})
return false // 阻止错误继续传播
})
</script>
🎯 最佳实践
- 代码复用: 优先使用组合式函数实现逻辑复用
- 类型安全: 充分利用TypeScript类型检查
- 性能优化: 合理使用缓存和懒加载
- 用户体验: 及时的加载反馈和错误提示
- 无障碍: 考虑无障碍访问需求
- 测试: 重要功能编写单元测试
遵循这些规范可以确保代码质量、提升开发效率和维护性。