896 lines
18 KiB
Markdown
896 lines
18 KiB
Markdown
# 学业邑规划 - 小程序开发规范
|
||
|
||
本文档定义了小程序前端的开发规范和编码标准,所有开发工作必须遵循这些规范。
|
||
|
||
---
|
||
|
||
## 一、命名规范
|
||
|
||
### 1.1 文件命名
|
||
|
||
| 类型 | 规范 | 示例 |
|
||
|------|------|------|
|
||
| 页面文件夹 | kebab-case | `assessment-info/`, `order-list/` |
|
||
| 页面文件 | index.vue | `pages/login/index.vue` |
|
||
| 组件文件 | PascalCase | `UserAvatar.vue`, `OrderCard.vue` |
|
||
| 组合式函数 | use + camelCase | `useAuth.ts`, `usePayment.ts` |
|
||
| 工具函数 | camelCase | `format.ts`, `validate.ts` |
|
||
| 类型定义 | camelCase + .d.ts | `user.d.ts`, `api.d.ts` |
|
||
| 样式文件 | kebab-case | `variables.scss`, `common.scss` |
|
||
|
||
### 1.2 代码命名
|
||
|
||
| 类型 | 规范 | 示例 |
|
||
|------|------|------|
|
||
| 组件名 | PascalCase | `UserAvatar`, `OrderCard` |
|
||
| 变量/函数 | camelCase | `userInfo`, `handleSubmit` |
|
||
| 常量 | UPPER_SNAKE_CASE | `API_BASE_URL`, `MAX_PAGE_SIZE` |
|
||
| 类型/接口 | PascalCase | `UserInfo`, `OrderStatus` |
|
||
| 枚举 | PascalCase | `OrderStatus`, `UserLevel` |
|
||
| CSS 类名 | kebab-case | `user-avatar`, `order-card` |
|
||
| 事件名 | on + 动词 | `onClick`, `onSubmit`, `onChange` |
|
||
| Props | camelCase | `userId`, `showHeader` |
|
||
| Emits | kebab-case | `@update:value`, `@item-click` |
|
||
|
||
### 1.3 命名示例
|
||
|
||
```typescript
|
||
// ✅ 建议
|
||
const userInfo = ref<UserInfo | null>(null)
|
||
const isLoading = ref(false)
|
||
const orderList = ref<OrderItem[]>([])
|
||
|
||
function handleLogin() { }
|
||
function fetchUserInfo() { }
|
||
function formatPrice(price: number) { }
|
||
|
||
// ❌ 不建议
|
||
const user_info = ref(null) // 不使用下划线
|
||
const data = ref([]) // 命名太模糊
|
||
const info = ref(null) // 命名太模糊
|
||
function login() { } // 缺少动词前缀
|
||
```
|
||
|
||
---
|
||
|
||
## 二、项目结构规范
|
||
|
||
### 2.1 页面结构
|
||
|
||
每个页面文件夹包含:
|
||
|
||
```
|
||
pages/assessment/info/
|
||
├── index.vue # 页面主文件
|
||
├── components/ # 页面私有组件(可选)
|
||
│ └── InfoForm.vue
|
||
└── composables/ # 页面私有组合式函数(可选)
|
||
└── useInfoForm.ts
|
||
```
|
||
|
||
### 2.2 组件分类
|
||
|
||
```
|
||
components/
|
||
├── common/ # 通用组件(与业务无关)
|
||
│ ├── AppButton.vue # 按钮
|
||
│ ├── AppModal.vue # 弹窗
|
||
│ ├── AppEmpty.vue # 空状态
|
||
│ ├── AppLoading.vue # 加载
|
||
│ └── AppNavbar.vue # 导航栏
|
||
├── business/ # 业务组件(与业务相关)
|
||
│ ├── UserAvatar.vue # 用户头像
|
||
│ ├── OrderCard.vue # 订单卡片
|
||
│ ├── AssessmentCard.vue # 测评卡片
|
||
│ └── PlannerCard.vue # 规划师卡片
|
||
└── layout/ # 布局组件
|
||
└── TabBar.vue # 底部导航
|
||
```
|
||
|
||
---
|
||
|
||
## 三、Vue 组件规范
|
||
|
||
### 3.1 组件结构
|
||
|
||
```vue
|
||
<script setup lang="ts">
|
||
/**
|
||
* 组件名称
|
||
* @description 组件描述
|
||
*/
|
||
|
||
// 1. 导入
|
||
import { ref, computed, onMounted } from 'vue'
|
||
import type { UserInfo } from '@/types/user'
|
||
import { useUserStore } from '@/stores/user'
|
||
import { getUserInfo } from '@/api/user'
|
||
|
||
// 2. Props 定义
|
||
interface Props {
|
||
/** 用户ID */
|
||
userId: number
|
||
/** 是否显示头像 */
|
||
showAvatar?: boolean
|
||
}
|
||
|
||
const props = withDefaults(defineProps<Props>(), {
|
||
showAvatar: true
|
||
})
|
||
|
||
// 3. Emits 定义
|
||
interface Emits {
|
||
(e: 'click', user: UserInfo): void
|
||
(e: 'update:value', value: string): void
|
||
}
|
||
|
||
const emit = defineEmits<Emits>()
|
||
|
||
// 4. 响应式状态
|
||
const loading = ref(false)
|
||
const userInfo = ref<UserInfo | null>(null)
|
||
|
||
// 5. 计算属性
|
||
const displayName = computed(() => {
|
||
return userInfo.value?.nickname || '未知用户'
|
||
})
|
||
|
||
// 6. 方法
|
||
async function fetchData() {
|
||
loading.value = true
|
||
try {
|
||
userInfo.value = await getUserInfo(props.userId)
|
||
} finally {
|
||
loading.value = false
|
||
}
|
||
}
|
||
|
||
function handleClick() {
|
||
if (userInfo.value) {
|
||
emit('click', userInfo.value)
|
||
}
|
||
}
|
||
|
||
// 7. 生命周期
|
||
onMounted(() => {
|
||
fetchData()
|
||
})
|
||
|
||
// 8. 暴露方法(可选)
|
||
defineExpose({
|
||
refresh: fetchData
|
||
})
|
||
</script>
|
||
|
||
<template>
|
||
<view class="user-card" @click="handleClick">
|
||
<image
|
||
v-if="showAvatar"
|
||
class="user-card__avatar"
|
||
:src="userInfo?.avatar"
|
||
/>
|
||
<text class="user-card__name">{{ displayName }}</text>
|
||
</view>
|
||
</template>
|
||
|
||
<style lang="scss" scoped>
|
||
.user-card {
|
||
display: flex;
|
||
align-items: center;
|
||
padding: 24rpx;
|
||
|
||
&__avatar {
|
||
width: 80rpx;
|
||
height: 80rpx;
|
||
border-radius: 50%;
|
||
}
|
||
|
||
&__name {
|
||
margin-left: 16rpx;
|
||
font-size: 28rpx;
|
||
color: $text-color;
|
||
}
|
||
}
|
||
</style>
|
||
```
|
||
|
||
### 3.2 Props 规范
|
||
|
||
```typescript
|
||
// ✅ 建议:使用 TypeScript 接口定义
|
||
interface Props {
|
||
/** 用户ID(必填) */
|
||
userId: number
|
||
/** 标题 */
|
||
title?: string
|
||
/** 是否显示 */
|
||
visible?: boolean
|
||
/** 列表数据 */
|
||
list?: OrderItem[]
|
||
}
|
||
|
||
const props = withDefaults(defineProps<Props>(), {
|
||
title: '默认标题',
|
||
visible: false,
|
||
list: () => []
|
||
})
|
||
|
||
// ❌ 不建议:使用运行时声明
|
||
const props = defineProps({
|
||
userId: {
|
||
type: Number,
|
||
required: true
|
||
}
|
||
})
|
||
```
|
||
|
||
### 3.3 Emits 规范
|
||
|
||
```typescript
|
||
// ✅ 建议:使用 TypeScript 接口定义
|
||
interface Emits {
|
||
(e: 'submit', data: FormData): void
|
||
(e: 'cancel'): void
|
||
(e: 'update:visible', value: boolean): void
|
||
}
|
||
|
||
const emit = defineEmits<Emits>()
|
||
|
||
// 触发事件
|
||
emit('submit', formData)
|
||
emit('update:visible', false)
|
||
```
|
||
|
||
---
|
||
|
||
## 四、TypeScript 规范
|
||
|
||
### 4.1 类型定义
|
||
|
||
```typescript
|
||
// types/user.d.ts
|
||
|
||
/** 用户等级 */
|
||
export enum UserLevel {
|
||
/** 普通用户 */
|
||
Normal = 1,
|
||
/** 合伙人 */
|
||
Partner = 2,
|
||
/** 渠道合伙人 */
|
||
ChannelPartner = 3
|
||
}
|
||
|
||
/** 用户信息 */
|
||
export interface UserInfo {
|
||
/** 用户ID */
|
||
id: number
|
||
/** 用户UID */
|
||
uid: string
|
||
/** 昵称 */
|
||
nickname: string
|
||
/** 头像 */
|
||
avatar: string
|
||
/** 手机号 */
|
||
phone: string
|
||
/** 用户等级 */
|
||
userLevel: UserLevel
|
||
/** 创建时间 */
|
||
createTime: string
|
||
}
|
||
|
||
/** 登录请求参数 */
|
||
export interface LoginRequest {
|
||
/** 微信 code */
|
||
code: string
|
||
/** 加密手机号数据 */
|
||
encryptedData: string
|
||
/** 加密向量 */
|
||
iv: string
|
||
}
|
||
|
||
/** 登录响应 */
|
||
export interface LoginResponse {
|
||
/** 访问令牌 */
|
||
token: string
|
||
/** 刷新令牌 */
|
||
refreshToken: string
|
||
/** 用户信息 */
|
||
userInfo: UserInfo
|
||
}
|
||
```
|
||
|
||
### 4.2 API 类型
|
||
|
||
```typescript
|
||
// types/api.d.ts
|
||
|
||
/** 通用响应结构 */
|
||
export interface ApiResponse<T = any> {
|
||
/** 状态码 */
|
||
code: number
|
||
/** 提示信息 */
|
||
message: string
|
||
/** 业务数据 */
|
||
data: T
|
||
}
|
||
|
||
/** 分页请求参数 */
|
||
export interface PageRequest {
|
||
/** 页码 */
|
||
page?: number
|
||
/** 每页数量 */
|
||
pageSize?: number
|
||
}
|
||
|
||
/** 分页响应 */
|
||
export interface PageResponse<T> {
|
||
/** 列表数据 */
|
||
list: T[]
|
||
/** 总数 */
|
||
total: number
|
||
/** 当前页 */
|
||
page: number
|
||
/** 每页数量 */
|
||
pageSize: number
|
||
/** 总页数 */
|
||
totalPages: number
|
||
}
|
||
```
|
||
|
||
### 4.3 类型使用
|
||
|
||
```typescript
|
||
// ✅ 建议:明确类型
|
||
const userInfo = ref<UserInfo | null>(null)
|
||
const orderList = ref<OrderItem[]>([])
|
||
const loading = ref<boolean>(false)
|
||
|
||
// ✅ 函数参数和返回值类型
|
||
function formatPrice(price: number): string {
|
||
return `¥${price.toFixed(2)}`
|
||
}
|
||
|
||
async function fetchOrders(params: PageRequest): Promise<PageResponse<OrderItem>> {
|
||
const res = await request.get<PageResponse<OrderItem>>('/api/order/getList', { params })
|
||
return res.data
|
||
}
|
||
|
||
// ❌ 不建议:使用 any
|
||
const data: any = {}
|
||
function handleData(data: any) { }
|
||
```
|
||
|
||
---
|
||
|
||
## 五、API 请求规范
|
||
|
||
### 5.1 请求封装
|
||
|
||
```typescript
|
||
// api/request.ts
|
||
import type { ApiResponse } from '@/types/api'
|
||
|
||
const BASE_URL = import.meta.env.VITE_API_BASE_URL
|
||
|
||
interface RequestOptions {
|
||
url: string
|
||
method?: 'GET' | 'POST'
|
||
data?: any
|
||
params?: any
|
||
showLoading?: boolean
|
||
showError?: boolean
|
||
}
|
||
|
||
class Request {
|
||
private getToken(): string {
|
||
return uni.getStorageSync('token') || ''
|
||
}
|
||
|
||
async request<T = any>(options: RequestOptions): Promise<ApiResponse<T>> {
|
||
const { url, method = 'GET', data, params, showLoading = true, showError = true } = options
|
||
|
||
if (showLoading) {
|
||
uni.showLoading({ title: '加载中...' })
|
||
}
|
||
|
||
try {
|
||
const res = await uni.request({
|
||
url: BASE_URL + url,
|
||
method,
|
||
data: method === 'POST' ? data : undefined,
|
||
header: {
|
||
'Content-Type': 'application/json',
|
||
'Authorization': `Bearer ${this.getToken()}`
|
||
}
|
||
})
|
||
|
||
const result = res.data as ApiResponse<T>
|
||
|
||
if (result.code !== 0) {
|
||
if (showError) {
|
||
uni.showToast({ title: result.message, icon: 'none' })
|
||
}
|
||
throw new Error(result.message)
|
||
}
|
||
|
||
return result
|
||
} finally {
|
||
if (showLoading) {
|
||
uni.hideLoading()
|
||
}
|
||
}
|
||
}
|
||
|
||
get<T = any>(url: string, options?: Partial<RequestOptions>) {
|
||
return this.request<T>({ url, method: 'GET', ...options })
|
||
}
|
||
|
||
post<T = any>(url: string, data?: any, options?: Partial<RequestOptions>) {
|
||
return this.request<T>({ url, method: 'POST', data, ...options })
|
||
}
|
||
}
|
||
|
||
export const request = new Request()
|
||
```
|
||
|
||
### 5.2 API 模块
|
||
|
||
```typescript
|
||
// api/user.ts
|
||
import { request } from './request'
|
||
import type { LoginRequest, LoginResponse, UserInfo } from '@/types/user'
|
||
|
||
/**
|
||
* 微信登录
|
||
*/
|
||
export function login(data: LoginRequest) {
|
||
return request.post<LoginResponse>('/api/user/login', data)
|
||
}
|
||
|
||
/**
|
||
* 获取用户信息
|
||
*/
|
||
export function getUserProfile() {
|
||
return request.get<UserInfo>('/api/user/getProfile')
|
||
}
|
||
|
||
/**
|
||
* 更新用户信息
|
||
*/
|
||
export function updateProfile(data: Partial<UserInfo>) {
|
||
return request.post<boolean>('/api/user/updateProfile', data)
|
||
}
|
||
```
|
||
|
||
### 5.3 API 调用
|
||
|
||
```typescript
|
||
// 在组件中使用
|
||
import { getUserProfile } from '@/api/user'
|
||
|
||
async function fetchUserInfo() {
|
||
try {
|
||
const res = await getUserProfile()
|
||
userInfo.value = res.data
|
||
} catch (error) {
|
||
console.error('获取用户信息失败', error)
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 六、状态管理规范
|
||
|
||
### 6.1 Store 定义
|
||
|
||
```typescript
|
||
// stores/user.ts
|
||
import { defineStore } from 'pinia'
|
||
import { ref, computed } from 'vue'
|
||
import type { UserInfo } from '@/types/user'
|
||
import { getUserProfile, login } from '@/api/user'
|
||
|
||
export const useUserStore = defineStore('user', () => {
|
||
// 状态
|
||
const token = ref<string>('')
|
||
const userInfo = ref<UserInfo | null>(null)
|
||
|
||
// 计算属性
|
||
const isLoggedIn = computed(() => !!token.value)
|
||
const isPartner = computed(() => (userInfo.value?.userLevel ?? 0) >= 2)
|
||
|
||
// 方法
|
||
async function loginAction(code: string, encryptedData: string, iv: string) {
|
||
const res = await login({ code, encryptedData, iv })
|
||
token.value = res.data.token
|
||
userInfo.value = res.data.userInfo
|
||
uni.setStorageSync('token', res.data.token)
|
||
}
|
||
|
||
async function fetchUserInfo() {
|
||
if (!token.value) return
|
||
const res = await getUserProfile()
|
||
userInfo.value = res.data
|
||
}
|
||
|
||
function logout() {
|
||
token.value = ''
|
||
userInfo.value = null
|
||
uni.removeStorageSync('token')
|
||
}
|
||
|
||
// 初始化
|
||
function init() {
|
||
token.value = uni.getStorageSync('token') || ''
|
||
if (token.value) {
|
||
fetchUserInfo()
|
||
}
|
||
}
|
||
|
||
return {
|
||
token,
|
||
userInfo,
|
||
isLoggedIn,
|
||
isPartner,
|
||
loginAction,
|
||
fetchUserInfo,
|
||
logout,
|
||
init
|
||
}
|
||
})
|
||
```
|
||
|
||
### 6.2 Store 使用
|
||
|
||
```typescript
|
||
// 在组件中使用
|
||
import { useUserStore } from '@/stores/user'
|
||
|
||
const userStore = useUserStore()
|
||
|
||
// 访问状态
|
||
const { userInfo, isLoggedIn } = storeToRefs(userStore)
|
||
|
||
// 调用方法
|
||
await userStore.loginAction(code, encryptedData, iv)
|
||
userStore.logout()
|
||
```
|
||
|
||
---
|
||
|
||
## 七、样式规范
|
||
|
||
### 7.1 变量定义
|
||
|
||
```scss
|
||
// styles/variables.scss
|
||
|
||
// 主题色
|
||
$primary-color: #4A90E2;
|
||
$primary-color-light: #6BA3E8;
|
||
$primary-color-dark: #3A7BC8;
|
||
|
||
// 文字颜色
|
||
$text-color: #333333;
|
||
$text-color-secondary: #666666;
|
||
$text-color-placeholder: #999999;
|
||
$text-color-disabled: #CCCCCC;
|
||
|
||
// 背景色
|
||
$bg-color: #F5F5F5;
|
||
$bg-color-white: #FFFFFF;
|
||
$bg-color-gray: #F8F8F8;
|
||
|
||
// 边框
|
||
$border-color: #EEEEEE;
|
||
$border-radius: 8rpx;
|
||
$border-radius-lg: 16rpx;
|
||
|
||
// 间距
|
||
$spacing-xs: 8rpx;
|
||
$spacing-sm: 16rpx;
|
||
$spacing-md: 24rpx;
|
||
$spacing-lg: 32rpx;
|
||
$spacing-xl: 48rpx;
|
||
|
||
// 字体大小
|
||
$font-size-xs: 20rpx;
|
||
$font-size-sm: 24rpx;
|
||
$font-size-md: 28rpx;
|
||
$font-size-lg: 32rpx;
|
||
$font-size-xl: 36rpx;
|
||
|
||
// 安全区域
|
||
$safe-area-bottom: env(safe-area-inset-bottom);
|
||
```
|
||
|
||
### 7.2 BEM 命名
|
||
|
||
```scss
|
||
// ✅ 建议:使用 BEM 命名
|
||
.order-card {
|
||
// Block
|
||
padding: $spacing-md;
|
||
background: $bg-color-white;
|
||
|
||
// Element
|
||
&__header {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
}
|
||
|
||
&__title {
|
||
font-size: $font-size-lg;
|
||
color: $text-color;
|
||
}
|
||
|
||
&__status {
|
||
font-size: $font-size-sm;
|
||
color: $text-color-secondary;
|
||
}
|
||
|
||
&__content {
|
||
margin-top: $spacing-sm;
|
||
}
|
||
|
||
&__footer {
|
||
margin-top: $spacing-md;
|
||
text-align: right;
|
||
}
|
||
|
||
// Modifier
|
||
&--active {
|
||
border-color: $primary-color;
|
||
}
|
||
|
||
&--disabled {
|
||
opacity: 0.5;
|
||
}
|
||
}
|
||
```
|
||
|
||
### 7.3 响应式单位
|
||
|
||
```scss
|
||
// ✅ 使用 rpx 作为主要单位
|
||
.container {
|
||
padding: 24rpx;
|
||
font-size: 28rpx;
|
||
border-radius: 8rpx;
|
||
}
|
||
|
||
// ✅ 固定尺寸使用 px
|
||
.icon {
|
||
width: 24px;
|
||
height: 24px;
|
||
}
|
||
|
||
// ❌ 不建议混用单位
|
||
.box {
|
||
padding: 12px 24rpx; // 不要混用
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 八、页面开发流程
|
||
|
||
### 8.1 开发步骤
|
||
|
||
1. **查看设计图** - 确认页面布局和交互
|
||
2. **定义类型** - 在 `types/` 下定义相关类型
|
||
3. **编写 API** - 在 `api/` 下编写接口调用
|
||
4. **创建页面** - 在 `pages/` 下创建页面文件夹
|
||
5. **配置路由** - 在 `pages.json` 中添加页面配置
|
||
6. **编写组件** - 抽取可复用组件
|
||
7. **编写样式** - 使用 BEM 命名,引用变量
|
||
8. **测试验证** - 在模拟器和真机测试
|
||
|
||
### 8.2 页面配置
|
||
|
||
```json
|
||
// pages.json
|
||
{
|
||
"pages": [
|
||
{
|
||
"path": "pages/index/index",
|
||
"style": {
|
||
"navigationBarTitleText": "首页",
|
||
"navigationStyle": "custom"
|
||
}
|
||
},
|
||
{
|
||
"path": "pages/assessment/info/index",
|
||
"style": {
|
||
"navigationBarTitleText": "填写信息"
|
||
}
|
||
}
|
||
],
|
||
"tabBar": {
|
||
"color": "#999999",
|
||
"selectedColor": "#4A90E2",
|
||
"backgroundColor": "#FFFFFF",
|
||
"list": [
|
||
{
|
||
"pagePath": "pages/index/index",
|
||
"text": "首页",
|
||
"iconPath": "static/icons/tab-home.png",
|
||
"selectedIconPath": "static/icons/tab-home-active.png"
|
||
},
|
||
{
|
||
"pagePath": "pages/team/index",
|
||
"text": "团队",
|
||
"iconPath": "static/icons/tab-team.png",
|
||
"selectedIconPath": "static/icons/tab-team-active.png"
|
||
},
|
||
{
|
||
"pagePath": "pages/mine/index",
|
||
"text": "我的",
|
||
"iconPath": "static/icons/tab-mine.png",
|
||
"selectedIconPath": "static/icons/tab-mine-active.png"
|
||
}
|
||
]
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 九、常用工具函数
|
||
|
||
### 9.1 格式化
|
||
|
||
```typescript
|
||
// utils/format.ts
|
||
|
||
/**
|
||
* 格式化价格
|
||
*/
|
||
export function formatPrice(price: number): string {
|
||
return `¥${price.toFixed(2)}`
|
||
}
|
||
|
||
/**
|
||
* 格式化日期
|
||
*/
|
||
export function formatDate(date: string | Date, format = 'YYYY-MM-DD'): string {
|
||
const d = new Date(date)
|
||
const year = d.getFullYear()
|
||
const month = String(d.getMonth() + 1).padStart(2, '0')
|
||
const day = String(d.getDate()).padStart(2, '0')
|
||
const hour = String(d.getHours()).padStart(2, '0')
|
||
const minute = String(d.getMinutes()).padStart(2, '0')
|
||
|
||
return format
|
||
.replace('YYYY', String(year))
|
||
.replace('MM', month)
|
||
.replace('DD', day)
|
||
.replace('HH', hour)
|
||
.replace('mm', minute)
|
||
}
|
||
|
||
/**
|
||
* 格式化手机号(隐藏中间4位)
|
||
*/
|
||
export function formatPhone(phone: string): string {
|
||
return phone.replace(/(\d{3})\d{4}(\d{4})/, '$1****$2')
|
||
}
|
||
```
|
||
|
||
### 9.2 验证
|
||
|
||
```typescript
|
||
// utils/validate.ts
|
||
|
||
/**
|
||
* 验证手机号
|
||
*/
|
||
export function isValidPhone(phone: string): boolean {
|
||
return /^1[3-9]\d{9}$/.test(phone)
|
||
}
|
||
|
||
/**
|
||
* 验证邀请码(5位大写字母)
|
||
*/
|
||
export function isValidInviteCode(code: string): boolean {
|
||
return /^[A-Z]{5}$/.test(code)
|
||
}
|
||
```
|
||
|
||
### 9.3 存储
|
||
|
||
```typescript
|
||
// utils/storage.ts
|
||
|
||
const TOKEN_KEY = 'token'
|
||
const USER_INFO_KEY = 'userInfo'
|
||
|
||
export const storage = {
|
||
getToken(): string {
|
||
return uni.getStorageSync(TOKEN_KEY) || ''
|
||
},
|
||
|
||
setToken(token: string): void {
|
||
uni.setStorageSync(TOKEN_KEY, token)
|
||
},
|
||
|
||
removeToken(): void {
|
||
uni.removeStorageSync(TOKEN_KEY)
|
||
},
|
||
|
||
getUserInfo<T>(): T | null {
|
||
const data = uni.getStorageSync(USER_INFO_KEY)
|
||
return data ? JSON.parse(data) : null
|
||
},
|
||
|
||
setUserInfo<T>(info: T): void {
|
||
uni.setStorageSync(USER_INFO_KEY, JSON.stringify(info))
|
||
},
|
||
|
||
clear(): void {
|
||
uni.clearStorageSync()
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 十、注意事项
|
||
|
||
### 10.1 性能优化
|
||
|
||
- 图片使用 CDN 地址,避免本地大图
|
||
- 列表使用虚拟滚动或分页加载
|
||
- 避免在 `onShow` 中频繁请求数据
|
||
- 使用 `v-if` 而非 `v-show` 控制大组件显示
|
||
|
||
### 10.2 兼容性
|
||
|
||
- 使用 `rpx` 作为主要单位
|
||
- 避免使用不支持的 CSS 属性
|
||
- 测试 iOS 和 Android 两端表现
|
||
- 注意安全区域适配
|
||
|
||
### 10.3 安全
|
||
|
||
- 敏感数据不存储在本地
|
||
- Token 过期自动刷新或跳转登录
|
||
- 支付相关操作需二次确认
|
||
- 用户输入需做 XSS 过滤
|
||
|
||
---
|
||
|
||
## 十一、Git 提交规范
|
||
|
||
### 11.1 提交信息格式
|
||
|
||
```
|
||
<type>(<scope>): <subject>
|
||
|
||
<body>
|
||
```
|
||
|
||
### 11.2 Type 类型
|
||
|
||
| Type | 说明 |
|
||
|------|------|
|
||
| feat | 新功能 |
|
||
| fix | 修复 bug |
|
||
| docs | 文档更新 |
|
||
| style | 代码格式(不影响功能) |
|
||
| refactor | 重构 |
|
||
| perf | 性能优化 |
|
||
| test | 测试 |
|
||
| chore | 构建/工具 |
|
||
|
||
### 11.3 示例
|
||
|
||
```
|
||
feat(assessment): 添加测评答题页面
|
||
|
||
- 实现题目列表展示
|
||
- 实现选项选择交互
|
||
- 添加提交答案功能
|
||
```
|