You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
10 KiB
10 KiB
03-用户认证页面
目标
实现登录和注册页面,完成用户认证功能。
UI 设计参考
参考设计稿:
files/ui/登录页.png
页面布局
| 区域 | 设计要点 |
|---|---|
| 顶部 | 绿色渐变背景 (#10B981 → #2EC4B6) + 医疗插图 |
| Logo | "AI健康助手" 标题 + "您的专属健康管理伙伴" |
| 表单 | 白色圆角卡片 (16px),内边距 24px |
| 输入框 | 灰色背景 #F3F4F6,圆角 24px |
| 主按钮 | 绿色 #10B981,全宽,圆角 24px |
| 底部 | 注册/登录切换链接 |
配色规范
$primary: #10B981; // 主色调
$primary-dark: #059669; // 深色(hover)
$bg-page: #F5F5F5; // 页面背景
$bg-input: #F3F4F6; // 输入框背景
$text-main: #1F2937; // 主文字
$text-sub: #6B7280; // 次文字
$text-hint: #9CA3AF; // 提示文字
前置要求
- 路由和布局已配置
- 后端认证接口可用
实施步骤
步骤 1:创建认证 API
创建 src/api/auth.ts:
import request from './request'
export interface LoginRequest {
phone: string
password: string
}
export interface RegisterRequest {
phone: string
password: string
nickname?: string
}
export interface AuthResponse {
token: string
user_id: number
nickname: string
survey_completed: boolean
}
export const login = (data: LoginRequest): Promise<AuthResponse> => {
return request.post('/auth/login', data)
}
export const register = (data: RegisterRequest): Promise<AuthResponse> => {
return request.post('/auth/register', data)
}
步骤 2:创建登录页面
创建 src/views/auth/Login.vue:
<template>
<div class="login-page">
<el-form
ref="formRef"
:model="form"
:rules="rules"
label-position="top"
@submit.prevent="handleLogin"
>
<el-form-item label="手机号" prop="phone">
<el-input
v-model="form.phone"
placeholder="请输入手机号"
prefix-icon="Phone"
size="large"
/>
</el-form-item>
<el-form-item label="密码" prop="password">
<el-input
v-model="form.password"
type="password"
placeholder="请输入密码"
prefix-icon="Lock"
size="large"
show-password
/>
</el-form-item>
<el-form-item>
<el-button
type="primary"
size="large"
:loading="loading"
style="width: 100%"
native-type="submit"
>
登录
</el-button>
</el-form-item>
<div class="form-footer">
<span>还没有账号?</span>
<router-link to="/auth/register">立即注册</router-link>
</div>
</el-form>
</div>
</template>
<script setup lang="ts">
import { ref, reactive } from 'vue'
import { useRouter, useRoute } from 'vue-router'
import { ElMessage, type FormInstance, type FormRules } from 'element-plus'
import { useUserStore } from '@/stores/user'
import { login } from '@/api/auth'
import { getUserProfile } from '@/api/user'
const router = useRouter()
const route = useRoute()
const userStore = useUserStore()
const formRef = ref<FormInstance>()
const loading = ref(false)
const form = reactive({
phone: '',
password: '',
})
const rules: FormRules = {
phone: [
{ required: true, message: '请输入手机号', trigger: 'blur' },
{ pattern: /^1[3-9]\d{9}$/, message: '手机号格式不正确', trigger: 'blur' },
],
password: [
{ required: true, message: '请输入密码', trigger: 'blur' },
{ min: 6, message: '密码至少6位', trigger: 'blur' },
],
}
const handleLogin = async () => {
const valid = await formRef.value?.validate()
if (!valid) return
loading.value = true
try {
const res = await login(form)
// 保存 token 和用户信息
userStore.setToken(res.token)
userStore.setUserInfo({
id: res.user_id,
nickname: res.nickname,
survey_completed: res.survey_completed,
})
// 获取完整用户信息
const userInfo = await getUserProfile()
userStore.setUserInfo(userInfo)
ElMessage.success('登录成功')
// 跳转到目标页面或首页
const redirect = route.query.redirect as string
if (res.survey_completed) {
router.push(redirect || '/')
} else {
router.push('/survey')
}
} catch (error) {
// 错误已在拦截器中处理
} finally {
loading.value = false
}
}
</script>
<style scoped>
.login-page {
padding: 20px 0;
}
.form-footer {
text-align: center;
font-size: 14px;
color: #666;
}
.form-footer a {
color: #409eff;
text-decoration: none;
margin-left: 8px;
}
.form-footer a:hover {
text-decoration: underline;
}
</style>
步骤 3:创建注册页面
创建 src/views/auth/Register.vue:
<template>
<div class="register-page">
<el-form
ref="formRef"
:model="form"
:rules="rules"
label-position="top"
@submit.prevent="handleRegister"
>
<el-form-item label="手机号" prop="phone">
<el-input
v-model="form.phone"
placeholder="请输入手机号"
prefix-icon="Phone"
size="large"
/>
</el-form-item>
<el-form-item label="昵称" prop="nickname">
<el-input
v-model="form.nickname"
placeholder="请输入昵称(选填)"
prefix-icon="User"
size="large"
/>
</el-form-item>
<el-form-item label="密码" prop="password">
<el-input
v-model="form.password"
type="password"
placeholder="请输入密码(至少6位)"
prefix-icon="Lock"
size="large"
show-password
/>
</el-form-item>
<el-form-item label="确认密码" prop="confirmPassword">
<el-input
v-model="form.confirmPassword"
type="password"
placeholder="请再次输入密码"
prefix-icon="Lock"
size="large"
show-password
/>
</el-form-item>
<el-form-item>
<el-checkbox v-model="form.agreement">
我已阅读并同意
<a href="javascript:;" @click.stop="showAgreement">《用户协议》</a>
和
<a href="javascript:;" @click.stop="showPrivacy">《隐私政策》</a>
</el-checkbox>
</el-form-item>
<el-form-item>
<el-button
type="primary"
size="large"
:loading="loading"
:disabled="!form.agreement"
style="width: 100%"
native-type="submit"
>
注册
</el-button>
</el-form-item>
<div class="form-footer">
<span>已有账号?</span>
<router-link to="/auth/login">立即登录</router-link>
</div>
</el-form>
</div>
</template>
<script setup lang="ts">
import { ref, reactive } from 'vue'
import { useRouter } from 'vue-router'
import { ElMessage, ElMessageBox, type FormInstance, type FormRules } from 'element-plus'
import { useUserStore } from '@/stores/user'
import { register } from '@/api/auth'
const router = useRouter()
const userStore = useUserStore()
const formRef = ref<FormInstance>()
const loading = ref(false)
const form = reactive({
phone: '',
nickname: '',
password: '',
confirmPassword: '',
agreement: false,
})
const validateConfirmPassword = (rule: any, value: string, callback: any) => {
if (value !== form.password) {
callback(new Error('两次输入的密码不一致'))
} else {
callback()
}
}
const rules: FormRules = {
phone: [
{ required: true, message: '请输入手机号', trigger: 'blur' },
{ pattern: /^1[3-9]\d{9}$/, message: '手机号格式不正确', trigger: 'blur' },
],
password: [
{ required: true, message: '请输入密码', trigger: 'blur' },
{ min: 6, message: '密码至少6位', trigger: 'blur' },
],
confirmPassword: [
{ required: true, message: '请再次输入密码', trigger: 'blur' },
{ validator: validateConfirmPassword, trigger: 'blur' },
],
}
const handleRegister = async () => {
const valid = await formRef.value?.validate()
if (!valid) return
if (!form.agreement) {
ElMessage.warning('请先同意用户协议和隐私政策')
return
}
loading.value = true
try {
const res = await register({
phone: form.phone,
password: form.password,
nickname: form.nickname || undefined,
})
// 保存 token
userStore.setToken(res.token)
userStore.setUserInfo({
id: res.user_id,
nickname: res.nickname,
survey_completed: false,
})
ElMessage.success('注册成功,请完成健康调查')
router.push('/survey')
} catch (error) {
// 错误已在拦截器中处理
} finally {
loading.value = false
}
}
const showAgreement = () => {
ElMessageBox.alert(
'本应用仅提供健康咨询建议,不构成医疗诊断。如有不适,请及时就医。',
'用户协议',
{ confirmButtonText: '我知道了' }
)
}
const showPrivacy = () => {
ElMessageBox.alert(
'我们重视您的隐私,您的健康信息将被加密存储,不会泄露给第三方。',
'隐私政策',
{ confirmButtonText: '我知道了' }
)
}
</script>
<style scoped>
.register-page {
padding: 20px 0;
}
.el-checkbox {
height: auto;
line-height: 1.5;
}
.el-checkbox a {
color: #409eff;
}
.form-footer {
text-align: center;
font-size: 14px;
color: #666;
}
.form-footer a {
color: #409eff;
text-decoration: none;
margin-left: 8px;
}
</style>
需要创建的文件清单
| 文件路径 | 说明 |
|---|---|
src/api/auth.ts |
认证 API |
src/views/auth/Login.vue |
登录页面 |
src/views/auth/Register.vue |
注册页面 |
功能说明
登录页面
- 手机号+密码登录
- 表单验证
- 登录成功后:
- 已完成调查 → 跳转首页
- 未完成调查 → 跳转健康调查页
注册页面
- 手机号+密码注册
- 可选昵称
- 密码确认
- 用户协议确认
- 注册成功后跳转健康调查页
验收标准
- 登录表单验证正常
- 注册表单验证正常
- 登录成功保存 Token
- 注册成功自动登录
- 路由跳转逻辑正确
预计耗时
20-25 分钟
下一步
完成后进入 03-Web前端开发/04-健康调查页面.md