本次更新
小程序完整实现登录注册功能,且与后端完成接口对接,包括:
- 双模式表单的设计与实现
- 全面的前端验证机制
- 网络请求的封装与拦截器设计
- 用户状态的管理与持久化
- 完善的错误处理机制
- 用户体验的优化措施
关键代码解析
主要涉及到以下三个代码文件
一、整体架构分析
技术栈关联性:
层级 | 主要技术 | 关联组件 |
---|---|---|
界面层 | Vue3 + Uni-app | login.vue 模板/样式 |
网络层 | Uni.request + 拦截器 | http.ts 封装 |
状态层 | Pinia + 持久化插件 | member.ts store定义 |
桥梁 | 组合式API | 各层间数据传递 |
架构组件详解
1. 前端界面层 (UI Layer)
- 核心文件:
login.vue
- 功能模块:
- 响应式双模式表单(登录/注册一键切换)
- 基于正则表达式的实时输入验证
- 中国风
UI
设计(渐变线条/传统配色) - 加载状态管理(按钮禁用/loading动画)
2. 网络请求层 (Network Layer)
- 核心机制:
BaseURL
自动补全:非http
开头的请求自动拼接http://localhost:8080
- 智能Token管理:从
Pinia store
自动获取并注入Authorization头 - 双拦截器设计:同时拦截
request
和uploadFile
请求
3. 状态管理层 (State Management)
-
数据流:
登录成功 → 更新Pinia状态 → 自动持久化到Storage → 下次启动自动恢复
4. 完整数据流示例
二、前端界面实现
1. 登录/注册表单组件
在login.vue
文件中,我们实现了一个双模式表单,可以在登录和注册状态间切换:
<view v-if="!isRegister" class="form-box">
<!-- 登录表单内容 -->
</view>
<view v-else class="form-box">
<!-- 注册表单内容 -->
</view>
登录表单包含:
- 用户名输入框
- 密码输入框
- 登录按钮
- 切换到注册的链接
注册表单包含:
- 用户名输入框
- 密码输入框
- 确认密码输入框
- 邮箱输入框
- 昵称输入框
- 注册按钮
- 切换到登录的链接
2. 表单验证机制
前端实现了实时表单验证功能,在用户输入时即时反馈:
// 验证用户名格式
validateUsername(username) {
return /^\S{5,16}$/.test(username)
},
// 验证密码格式
validatePassword(password) {
return /^\S{5,16}$/.test(password)
},
// 验证密码是否匹配
validatePasswordMatch(password, confirmPassword) {
return password === confirmPassword
}
验证规则:
- 用户名:5-16位非空字符
- 密码:5-16位非空字符
- 确认密码:必须与密码一致
- 邮箱:必须符合邮箱格式
三、网络请求层实现
1. HTTP拦截器
在http.ts
中实现了请求拦截器,主要功能包括:
const httpInterceptor = {
invoke(options: UniApp.RequestOptions) {
// 1. 非 http 开头需拼接地址
if (!options.url.startsWith('http')) {
options.url = baseURL + options.url
}
// 2. 请求超时设置
options.timeout = 10000
// 3. 添加小程序端请求头标识
options.header = {
...options.header,
'source-client': 'miniapp',
}
// 4. 添加 token 请求头标识
const memberStore = useMemberStore()
const token = memberStore.profile?.token
if (token) {
options.header.Authorization = token
}
},
}
2. 统一的请求处理
封装了http
函数处理所有请求的响应和错误:
export const http = <T>(options: UniApp.RequestOptions) => {
return new Promise<T>((resolve, reject) => {
uni.request({
...options,
success(res) {
// 处理各种状态码
if (res.statusCode >= 200 && res.statusCode < 300) {
const data = res.data as ResultData<T>
if (data.code === 200) {
resolve(data.data)
} else if (data.code === 401) {
handleUnauthorized()
reject(data)
} else {
uni.showToast({
icon: 'none',
title: data.message || '业务错误',
})
reject(data)
}
}
// 其他状态码处理...
},
fail(err) {
uni.showToast({
icon: 'none',
title: '网络错误,请检查网络连接',
})
reject(err)
},
})
})
}
四、状态管理实现
使用Pinia
进行状态管理,在member.ts
中定义了用户状态:
export const useMemberStore = defineStore(
'member',
() => {
// 用户信息
const profile = ref<any>()
// 保存用户信息
const setProfile = (val: any) => {
profile.value = val
}
// 清理用户信息
const clearProfile = () => {
profile.value = undefined
}
return {
profile,
setProfile,
clearProfile,
}
},
// 持久化配置
{
persist: {
storage: {
getItem(key) {
return uni.getStorageSync(key)
},
setItem(key, value) {
uni.setStorageSync(key, value)
},
},
},
},
)
五、登录功能实现细节
1. 登录流程
时序图关键节点解释
-
步骤4-5:网络层会自动添加
Authorization
头(如果已有token)// http.ts拦截器 if (token) { options.header.Authorization = token }
-
步骤7-8:采用
uni.setStorageSync
双写保证可靠性// login.vue中的处理 memberStore.setProfile({ token: res }) uni.setStorageSync('token', res) // 双重保险
-
步骤10:智能跳转逻辑避免页面栈混乱
// 判断逻辑优先保障用户体验
2. 前端验证阶段
-
验证规则:
// login.vue中的验证方法 validateUsername(username) { return /^\S{5,16}$/.test(username) // 非空字符5-16位 }
3. 网络请求阶段
-
请求示例:
await post('/user/login', { username: 'testuser', password: '123456' })
4. 令牌存储阶段
-
存储逻辑:
// member.ts中的action setProfile(val: any) { this.profile = val // 响应式更新 // 通过persist插件自动持久化 }
5. 跳转逻辑判断
-
实现代码:
const pages = getCurrentPages() if (pages.length > 1) { uni.navigateBack() // 返回 } else { uni.switchTab({ // 首页 url: '/pages/index/index' }) }
6. 异常处理分支
异常类型 | 处理方式 | 用户提示 |
---|---|---|
用户名格式错误 | 阻止提交,显示行内提示 | “用户名需5-16位非空字符” |
密码格式错误 | 阻止提交,显示行内提示 | “密码需5-16位非空字符” |
用户不存在 | 网络层拦截,显示Toast | “用户不存在” |
密码错误 | 网络层拦截,显示Toast | “密码错误” |
网络连接失败 | fail回调处理,显示Toast | “网络异常,请检查连接” |
服务端错误 | 根据statusCode处理 | “服务繁忙,请稍后重试(code)” |
六、注册功能实现细节
1. 注册流程
2. 表单验证阶段
-
验证逻辑示例:
// login.vue中的验证方法 validatePasswordMatch(password, confirmPassword) { return password === confirmPassword // 密码一致性检查 }
3. 网络请求阶段
-
请求示例:
await post('/user/register', { username: 'newuser', password: '123456', confirmPassword: '123456', email: 'user@example.com', nickname: '昵称' })
4. 注册后处理阶段
-
自动填充实现:
this.loginForm.username = this.registerForm.username this.registerForm = { username: '', password: '', // 其他字段重置... } this.isRegister = false
5. 异常处理场景
异常类型 | 检测方式 | 用户反馈方式 |
---|---|---|
用户名格式不符 | 前端正则校验 /\S{5,16}/ | 输入框下方红色文字提示 |
密码不一致 | 比较password和confirmPassword字段 | 确认密码框下方提示 |
邮箱格式错误 | 检查是否包含@符号 | 邮箱输入框下方提示 |
用户名已存在 | 后端返回1001错误码 | 顶部Toast提示"用户名已存在" |
网络请求超时 | 拦截器10s超时机制 | Toast提示"网络连接超时" |
6. 与登录流程的差异点
-
多字段协同验证:
-
成功后的特殊处理:
// 注册特有的表单处理 this.loginForm.username = this.registerForm.username // 用户名自动传递
-
更复杂的验证逻辑:
// 注册时需要验证更多字段 if (!this.registerForm.nickname.trim()) { this.showErrorToast('请输入昵称') return }
七、安全与优化措施
-
Token存储安全:
- 使用Pinia持久化插件将token存储在本地
- 每次请求自动携带token
-
401未授权处理:
function handleUnauthorized() { const memberStore = useMemberStore() memberStore.clearProfile() uni.navigateTo({ url: '/pages/login/login' }) uni.showToast({ icon: 'none', title: '登录已过期,请重新登录', }) }
-
请求超时设置:
options.timeout = 10000
-
网络错误处理:
fail(err) { uni.showToast({ icon: 'none', title: '网络错误,请检查网络连接', }) reject(err) }
八、用户体验优化
-
表单切换动画:
- 使用v-if/v-else实现表单切换
- 清空密码字段确保安全
-
加载状态反馈:
<button :disabled="isLogging" :loading="isLogging"> {{ isLogging ? '登录中...' : '登 录' }} </button>
-
输入实时验证:
@input="validateInput('username')"
-
成功后的自动跳转:
setTimeout(() => { const pages = getCurrentPages() if (pages.length > 1) { uni.navigateBack() } else { uni.switchTab({ url: '/pages/index/index' }) } }, 1500)