feat: 登录页
This commit is contained in:
parent
02f9c4ec4c
commit
03b516afa7
|
|
@ -8,11 +8,10 @@ VITE_WX_APPID = 'wxa2abb91f64032a2b'
|
||||||
VITE_APP_PUBLIC_BASE=/
|
VITE_APP_PUBLIC_BASE=/
|
||||||
|
|
||||||
# 登录页面
|
# 登录页面
|
||||||
VITE_LOGIN_URL = '/pages/login/index'
|
VITE_LOGIN_URL = '/pages/auth/index'
|
||||||
# 第一个请求地址
|
|
||||||
VITE_SERVER_BASEURL = 'https://ukw0y1.laf.run'
|
|
||||||
|
|
||||||
VITE_UPLOAD_BASEURL = 'https://ukw0y1.laf.run/upload'
|
VITE_SERVER_BASEURL = 'http://mapi.xianglexue.com/api/v1'
|
||||||
|
VITE_UPLOAD_BASEURL = 'http://mapi.xianglexue.com/api/v1'
|
||||||
|
|
||||||
# h5是否需要配置代理
|
# h5是否需要配置代理
|
||||||
VITE_APP_PROXY=false
|
VITE_APP_PROXY=false
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
# 变量必须以 VITE_ 为前缀才能暴露给外部读取
|
# 变量必须以 VITE_ 为前缀才能暴露给外部读取
|
||||||
NODE_ENV = 'development'
|
NODE_ENV = 'development'
|
||||||
# 是否去除console 和 debugger
|
# 是否去除console 和 debugger
|
||||||
VITE_DELETE_CONSOLE = true
|
VITE_DELETE_CONSOLE = false
|
||||||
# 是否开启sourcemap
|
# 是否开启sourcemap
|
||||||
VITE_SHOW_SOURCEMAP = false
|
VITE_SHOW_SOURCEMAP = false
|
||||||
|
|
|
||||||
82
src/App.vue
82
src/App.vue
|
|
@ -1,25 +1,91 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { onHide, onLaunch, onShow } from '@dcloudio/uni-app'
|
import { onHide, onLaunch, onShow } from '@dcloudio/uni-app'
|
||||||
import { navigateToInterceptor } from '@/router/interceptor'
|
import { navigateToInterceptor } from '@/router/interceptor'
|
||||||
|
import { useUserStore } from '@/store/user'
|
||||||
import { tabbarStore } from './tabbar/store'
|
import { tabbarStore } from './tabbar/store'
|
||||||
import 'abortcontroller-polyfill/dist/abortcontroller-polyfill-only'
|
import 'abortcontroller-polyfill/dist/abortcontroller-polyfill-only'
|
||||||
|
|
||||||
|
const userStore = useUserStore()
|
||||||
|
|
||||||
|
// 不需要登录验证的页面路径
|
||||||
|
const publicPages = [
|
||||||
|
'/pages/auth/splash',
|
||||||
|
'/pages/auth/index',
|
||||||
|
]
|
||||||
|
|
||||||
|
// 检查是否为公开页面
|
||||||
|
function isPublicPage(path: string) {
|
||||||
|
return publicPages.some(page => path.includes(page.replace('/pages/', '')))
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理登录状态检查和页面跳转
|
||||||
|
function handlePageNavigation(targetPath?: string) {
|
||||||
|
const isLoggedIn = userStore.isLogin && userStore.accessToken
|
||||||
|
|
||||||
|
// 如果未登录且不是公开页面,跳转到加载页
|
||||||
|
if (!isLoggedIn && targetPath && !isPublicPage(targetPath)) {
|
||||||
|
console.log('用户未登录,跳转到加载页')
|
||||||
|
uni.reLaunch({
|
||||||
|
url: '/pages/auth/index',
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果已登录且访问的是登录相关页面,跳转到首页
|
||||||
|
if (isLoggedIn && targetPath && isPublicPage(targetPath)) {
|
||||||
|
console.log('用户已登录,跳转到首页')
|
||||||
|
uni.reLaunch({
|
||||||
|
url: '/pages/index/index',
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 正常处理页面跳转
|
||||||
|
if (targetPath) {
|
||||||
|
navigateToInterceptor.invoke({ url: targetPath })
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// 根据登录状态决定默认跳转页面
|
||||||
|
const defaultUrl = isLoggedIn ? '/pages/index/index' : '/pages/auth/splash'
|
||||||
|
navigateToInterceptor.invoke({ url: defaultUrl })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
onLaunch((options) => {
|
onLaunch((options) => {
|
||||||
// 处理直接进入页面路由的情况:如h5直接输入路由、微信小程序分享后进入等
|
// 处理直接进入页面路由的情况:如h5直接输入路由、微信小程序分享后进入等
|
||||||
// https://github.com/unibest-tech/unibest/issues/192
|
// https://github.com/unibest-tech/unibest/issues/192
|
||||||
console.log('App Launch', options)
|
console.log('App Launch', options)
|
||||||
if (options?.path) {
|
|
||||||
navigateToInterceptor.invoke({ url: `/${options.path}` })
|
const targetPath = options?.path ? `/${options.path}` : undefined
|
||||||
}
|
|
||||||
else {
|
// 延迟执行以确保 store 已初始化
|
||||||
navigateToInterceptor.invoke({ url: '/' })
|
setTimeout(() => {
|
||||||
}
|
handlePageNavigation(targetPath)
|
||||||
// 处理直接进入路由非首页时,tabbarIndex 不正确的问题
|
// 处理直接进入路由非首页时,tabbarIndex 不正确的问题
|
||||||
tabbarStore.setAutoCurIdx(options.path)
|
tabbarStore.setAutoCurIdx(options.path)
|
||||||
|
}, 100)
|
||||||
})
|
})
|
||||||
|
|
||||||
onShow((options) => {
|
onShow((options) => {
|
||||||
console.log('App Show', options)
|
console.log('App Show', options)
|
||||||
|
|
||||||
|
// 每次显示应用时检查登录状态
|
||||||
|
const isLoggedIn = userStore.isLogin && userStore.accessToken
|
||||||
|
if (!isLoggedIn) {
|
||||||
|
// 检查当前页面是否为公开页面
|
||||||
|
const pages = getCurrentPages()
|
||||||
|
const currentPage = pages[pages.length - 1]
|
||||||
|
const currentRoute = currentPage?.route || ''
|
||||||
|
|
||||||
|
if (!isPublicPage(`/${currentRoute}`)) {
|
||||||
|
console.log('应用显示时检测到未登录,跳转到登录页')
|
||||||
|
uni.reLaunch({
|
||||||
|
url: '/pages/auth/index',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
onHide(() => {
|
onHide(() => {
|
||||||
console.log('App Hide')
|
console.log('App Hide')
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,287 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import type { LoginRequest } from '@/service/types'
|
||||||
|
import { computed, reactive, ref } from 'vue'
|
||||||
|
import VerificationCode from './VerificationCode.vue'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
mode: 'login' | 'reset'
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Emits {
|
||||||
|
(e: 'login', data: LoginRequest): void
|
||||||
|
(e: 'reset', data: { phone: string, code: string, password: string }): void
|
||||||
|
(e: 'switchMode', mode: 'login' | 'reset'): void
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = defineProps<Props>()
|
||||||
|
const emit = defineEmits<Emits>()
|
||||||
|
|
||||||
|
// 表单数据
|
||||||
|
const loginForm = reactive<LoginRequest>({
|
||||||
|
username: '',
|
||||||
|
password: '',
|
||||||
|
})
|
||||||
|
|
||||||
|
const resetForm = reactive({
|
||||||
|
phone: '',
|
||||||
|
code: '',
|
||||||
|
password: '',
|
||||||
|
confirmPassword: '',
|
||||||
|
})
|
||||||
|
|
||||||
|
// 状态管理
|
||||||
|
const loading = ref(false)
|
||||||
|
const agreed = ref(false)
|
||||||
|
|
||||||
|
// 计算属性
|
||||||
|
const isLogin = computed(() => props.mode === 'login')
|
||||||
|
const isReset = computed(() => props.mode === 'reset')
|
||||||
|
|
||||||
|
// 表单验证
|
||||||
|
function validateLoginForm() {
|
||||||
|
if (!loginForm.username) {
|
||||||
|
uni.showToast({
|
||||||
|
title: '请输入手机号',
|
||||||
|
icon: 'none',
|
||||||
|
})
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!/^1[3-9]\d{9}$/.test(loginForm.username)) {
|
||||||
|
uni.showToast({
|
||||||
|
title: '请输入正确的手机号',
|
||||||
|
icon: 'none',
|
||||||
|
})
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!loginForm.password) {
|
||||||
|
uni.showToast({
|
||||||
|
title: '请输入密码',
|
||||||
|
icon: 'none',
|
||||||
|
})
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (loginForm.password.length < 6) {
|
||||||
|
uni.showToast({
|
||||||
|
title: '密码不能少于6位',
|
||||||
|
icon: 'none',
|
||||||
|
})
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
function validateResetForm() {
|
||||||
|
if (!resetForm.phone) {
|
||||||
|
uni.showToast({
|
||||||
|
title: '请输入手机号',
|
||||||
|
icon: 'none',
|
||||||
|
})
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!/^1[3-9]\d{9}$/.test(resetForm.phone)) {
|
||||||
|
uni.showToast({
|
||||||
|
title: '请输入正确的手机号',
|
||||||
|
icon: 'none',
|
||||||
|
})
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!resetForm.code) {
|
||||||
|
uni.showToast({
|
||||||
|
title: '请输入验证码',
|
||||||
|
icon: 'none',
|
||||||
|
})
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!resetForm.password) {
|
||||||
|
uni.showToast({
|
||||||
|
title: '请输入新密码',
|
||||||
|
icon: 'none',
|
||||||
|
})
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (resetForm.password.length < 8) {
|
||||||
|
uni.showToast({
|
||||||
|
title: '密码不能少于8位',
|
||||||
|
icon: 'none',
|
||||||
|
})
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (resetForm.password !== resetForm.confirmPassword) {
|
||||||
|
uni.showToast({
|
||||||
|
title: '两次密码输入不一致',
|
||||||
|
icon: 'none',
|
||||||
|
})
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!agreed.value) {
|
||||||
|
uni.showToast({
|
||||||
|
title: '请先同意用户协议',
|
||||||
|
icon: 'none',
|
||||||
|
})
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// 事件处理
|
||||||
|
function handleSubmit() {
|
||||||
|
if (isLogin.value) {
|
||||||
|
if (validateLoginForm()) {
|
||||||
|
emit('login', loginForm)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (validateResetForm()) {
|
||||||
|
emit('reset', {
|
||||||
|
phone: resetForm.phone,
|
||||||
|
code: resetForm.code,
|
||||||
|
password: resetForm.password,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleSwitchMode() {
|
||||||
|
const newMode = isLogin.value ? 'reset' : 'login'
|
||||||
|
emit('switchMode', newMode)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证码回调
|
||||||
|
function handleCodeUpdate(code: string) {
|
||||||
|
resetForm.code = code
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<view class="min-h-40vh w-full p-8 pt-4">
|
||||||
|
<!-- 表单标题 -->
|
||||||
|
<view class="mb-4 text-center">
|
||||||
|
<text class="text-xl text-gray-900 font-bold">{{ isLogin ? '登录' : '重置密码' }}</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 表单内容 -->
|
||||||
|
<view class="space-y-4">
|
||||||
|
<!-- 登录表单 -->
|
||||||
|
<view v-if="isLogin" class="mb-24 space-y-4">
|
||||||
|
<!-- 手机号输入 -->
|
||||||
|
<view class="space-y-2">
|
||||||
|
<text class="block text-sm text-gray-700 font-medium">手机号</text>
|
||||||
|
<wd-input
|
||||||
|
v-model="loginForm.username"
|
||||||
|
placeholder="请输入手机号"
|
||||||
|
type="number"
|
||||||
|
:maxlength="11"
|
||||||
|
clearable
|
||||||
|
class="w-full"
|
||||||
|
/>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 密码输入 -->
|
||||||
|
<view class="space-y-2">
|
||||||
|
<text class="block text-sm text-gray-700 font-medium">密码</text>
|
||||||
|
<wd-input
|
||||||
|
v-model="loginForm.password"
|
||||||
|
type="text"
|
||||||
|
show-password
|
||||||
|
placeholder="请输入密码"
|
||||||
|
clearable
|
||||||
|
class="w-full"
|
||||||
|
/>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 重置密码表单 -->
|
||||||
|
<view v-else class="space-y-2">
|
||||||
|
<!-- 手机号输入 -->
|
||||||
|
<view class="space-y-2">
|
||||||
|
<text class="block text-sm text-gray-700 font-medium">电话号码</text>
|
||||||
|
<wd-input
|
||||||
|
v-model="resetForm.phone"
|
||||||
|
placeholder="请输入手机号"
|
||||||
|
type="number"
|
||||||
|
:maxlength="11"
|
||||||
|
clearable
|
||||||
|
class="w-full"
|
||||||
|
/>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 验证码输入 -->
|
||||||
|
<verification-code
|
||||||
|
:phone="resetForm.phone"
|
||||||
|
@update:code="handleCodeUpdate"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<!-- 新密码输入 -->
|
||||||
|
<view class="space-y-2">
|
||||||
|
<text class="block text-sm text-gray-700 font-medium">新密码</text>
|
||||||
|
<wd-input
|
||||||
|
v-model="resetForm.password"
|
||||||
|
type="text"
|
||||||
|
show-password
|
||||||
|
placeholder="请输入新密码"
|
||||||
|
clearable
|
||||||
|
class="w-full"
|
||||||
|
/>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 确认密码输入 -->
|
||||||
|
<view class="space-y-2">
|
||||||
|
<text class="block text-sm text-gray-700 font-medium">确认密码</text>
|
||||||
|
<wd-input
|
||||||
|
v-model="resetForm.confirmPassword"
|
||||||
|
type="text"
|
||||||
|
show-password
|
||||||
|
placeholder="请再次输入密码"
|
||||||
|
clearable
|
||||||
|
class="w-full"
|
||||||
|
/>
|
||||||
|
<!-- 密码提示 -->
|
||||||
|
<text class="block text-0.6rem text-gray-500">
|
||||||
|
密码应不少于8位,至少包含数字、字母,且不能包含中文。
|
||||||
|
</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 用户协议 -->
|
||||||
|
<view class="flex items-center space-x-1" @click="agreed = !agreed">
|
||||||
|
<wd-checkbox :model-value="agreed" class="mt-2" />
|
||||||
|
<text class="text-0.6rem text-gray-600">
|
||||||
|
我已阅读并同意《用户协议》、《隐私政策》,以及《个人信息处理规则》
|
||||||
|
</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 表单按钮 -->
|
||||||
|
<view class="mt-4 space-y-4">
|
||||||
|
<!-- 提交按钮 -->
|
||||||
|
<wd-button
|
||||||
|
type="primary"
|
||||||
|
size="large"
|
||||||
|
block
|
||||||
|
:loading="loading"
|
||||||
|
class="rounded-lg !h-12"
|
||||||
|
@click="handleSubmit"
|
||||||
|
>
|
||||||
|
{{ isLogin ? '登录' : '重置密码' }}
|
||||||
|
</wd-button>
|
||||||
|
|
||||||
|
<!-- 切换模式 -->
|
||||||
|
<view class="text-center">
|
||||||
|
<text class="cursor-pointer text-sm text-blue-600" @click="handleSwitchMode">
|
||||||
|
{{ isLogin ? '忘记密码?' : '返回登录' }}
|
||||||
|
</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
@ -0,0 +1,117 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, watch } from 'vue'
|
||||||
|
import { authGetCodeUsingGet } from '@/service'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
phone: string
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Emits {
|
||||||
|
(e: 'update:code', code: string): void
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = defineProps<Props>()
|
||||||
|
const emit = defineEmits<Emits>()
|
||||||
|
|
||||||
|
// 状态管理
|
||||||
|
const code = ref('')
|
||||||
|
const disabled = ref(false)
|
||||||
|
const countdown = ref(0)
|
||||||
|
const codeText = ref('获取验证码')
|
||||||
|
|
||||||
|
// 监听验证码变化
|
||||||
|
watch(code, (newCode) => {
|
||||||
|
emit('update:code', newCode)
|
||||||
|
})
|
||||||
|
|
||||||
|
// 发送验证码
|
||||||
|
async function sendCode() {
|
||||||
|
if (!props.phone) {
|
||||||
|
uni.showToast({
|
||||||
|
title: '请输入手机号',
|
||||||
|
icon: 'none',
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!/^1[3-9]\d{9}$/.test(props.phone)) {
|
||||||
|
uni.showToast({
|
||||||
|
title: '请输入正确的手机号',
|
||||||
|
icon: 'none',
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await authGetCodeUsingGet({
|
||||||
|
params: { phone: props.phone },
|
||||||
|
})
|
||||||
|
|
||||||
|
uni.showToast({
|
||||||
|
title: '验证码已发送',
|
||||||
|
icon: 'success',
|
||||||
|
})
|
||||||
|
|
||||||
|
// 开始倒计时
|
||||||
|
startCountdown()
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
console.error('发送验证码失败:', error)
|
||||||
|
uni.showToast({
|
||||||
|
title: '发送失败,请重试',
|
||||||
|
icon: 'none',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 开始倒计时
|
||||||
|
function startCountdown() {
|
||||||
|
disabled.value = true
|
||||||
|
countdown.value = 60
|
||||||
|
|
||||||
|
const timer = setInterval(() => {
|
||||||
|
countdown.value--
|
||||||
|
codeText.value = `${countdown.value}s后重发`
|
||||||
|
|
||||||
|
if (countdown.value <= 0) {
|
||||||
|
clearInterval(timer)
|
||||||
|
disabled.value = false
|
||||||
|
codeText.value = '获取验证码'
|
||||||
|
}
|
||||||
|
}, 1000)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 重新获取验证码的提示
|
||||||
|
function handleNoCode() {
|
||||||
|
uni.showModal({
|
||||||
|
title: '提示',
|
||||||
|
content: '如果您收不到验证码,请检查手机号是否正确,或联系客服获取帮助。',
|
||||||
|
showCancel: false,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<view class="space-y-2">
|
||||||
|
<text class="block text-sm text-gray-700 font-medium">验证码</text>
|
||||||
|
<view class="flex space-x-3">
|
||||||
|
<wd-input
|
||||||
|
v-model="code"
|
||||||
|
placeholder="请输入验证码"
|
||||||
|
type="number"
|
||||||
|
:maxlength="6"
|
||||||
|
clearable
|
||||||
|
class="flex-1"
|
||||||
|
/>
|
||||||
|
<wd-button
|
||||||
|
type="primary"
|
||||||
|
size="small"
|
||||||
|
:disabled="disabled"
|
||||||
|
class="h-10 whitespace-nowrap px-4 text-xs"
|
||||||
|
@click="sendCode"
|
||||||
|
>
|
||||||
|
{{ codeText }}
|
||||||
|
</wd-button>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
@ -68,6 +68,8 @@ const httpInterceptor = {
|
||||||
if (currentToken) {
|
if (currentToken) {
|
||||||
options.header.Authorization = `Bearer ${currentToken}`
|
options.header.Authorization = `Bearer ${currentToken}`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return options
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -72,6 +72,14 @@
|
||||||
"style": {
|
"style": {
|
||||||
"navigationBarTitleText": "Vue Query 请求演示"
|
"navigationBarTitleText": "Vue Query 请求演示"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "pages/auth/index",
|
||||||
|
"type": "page",
|
||||||
|
"name": "auth",
|
||||||
|
"style": {
|
||||||
|
"navigationStyle": "custom"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"subPackages": [
|
"subPackages": [
|
||||||
|
|
@ -89,4 +97,4 @@
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,140 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import type { LoginRequest } from '@/service/types'
|
||||||
|
import { ref } from 'vue'
|
||||||
|
import AuthForm from '@/components/auth/AuthForm.vue'
|
||||||
|
import { sysUsersResetPasswordUsingPost } from '@/service/xitongyonghu'
|
||||||
|
import { useUserStore } from '@/store/user'
|
||||||
|
|
||||||
|
const userStore = useUserStore()
|
||||||
|
|
||||||
|
// 当前模式:登录或重置密码
|
||||||
|
const currentMode = ref<'login' | 'reset'>('login')
|
||||||
|
const loading = ref(false)
|
||||||
|
|
||||||
|
// 处理登录
|
||||||
|
async function handleLogin(data: LoginRequest) {
|
||||||
|
try {
|
||||||
|
loading.value = true
|
||||||
|
await userStore.login(data)
|
||||||
|
|
||||||
|
uni.showToast({
|
||||||
|
title: '登录成功',
|
||||||
|
icon: 'success',
|
||||||
|
})
|
||||||
|
|
||||||
|
// 登录成功,跳转到首页
|
||||||
|
setTimeout(() => {
|
||||||
|
uni.reLaunch({
|
||||||
|
url: '/pages/index/index',
|
||||||
|
})
|
||||||
|
}, 1500)
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
console.error('登录失败:', error)
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理重置密码
|
||||||
|
async function handleReset(data: { phone: string, code: string, password: string }) {
|
||||||
|
try {
|
||||||
|
loading.value = true
|
||||||
|
|
||||||
|
// 调用重置密码API
|
||||||
|
await sysUsersResetPasswordUsingPost({
|
||||||
|
body: {
|
||||||
|
phone: data.phone,
|
||||||
|
verify_code: data.code,
|
||||||
|
new_password: data.password,
|
||||||
|
confirm_password: data.password,
|
||||||
|
username: data.phone, // 使用手机号作为用户名
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
uni.showToast({
|
||||||
|
title: '密码重置成功',
|
||||||
|
icon: 'success',
|
||||||
|
})
|
||||||
|
|
||||||
|
// 延迟切换到登录模式
|
||||||
|
setTimeout(() => {
|
||||||
|
currentMode.value = 'login'
|
||||||
|
}, 1500)
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
console.error('重置密码失败:', error)
|
||||||
|
uni.showToast({
|
||||||
|
title: '重置失败,请重试',
|
||||||
|
icon: 'none',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 切换模式
|
||||||
|
function handleSwitchMode(mode: 'login' | 'reset') {
|
||||||
|
currentMode.value = mode
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<view class="relative h-screen w-full overflow-hidden from-blue-500 via-purple-500 to-blue-600 bg-gradient-to-br">
|
||||||
|
<!-- 背景图片 -->
|
||||||
|
<image
|
||||||
|
class="absolute inset-0 z-0 h-full w-full opacity-80"
|
||||||
|
src="https://images.unsplash.com/photo-1557804506-669a67965ba0?ixlib=rb-4.0.3&auto=format&fit=crop&w=1974&q=80"
|
||||||
|
mode="aspectFill"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<!-- 渐变蒙版 -->
|
||||||
|
<view class="absolute inset-0 z-1 from-blue-500/90 via-purple-500/80 to-blue-600/90 bg-gradient-to-br" />
|
||||||
|
|
||||||
|
<!-- 主要内容 -->
|
||||||
|
<view class="relative z-2 h-full flex flex-col">
|
||||||
|
<!-- Logo 区域 -->
|
||||||
|
<view class="flex flex-1 flex-col items-center justify-center px-8">
|
||||||
|
<view class="relative mb-4">
|
||||||
|
<view class="h-12 w-12 flex items-center justify-center rounded-3xl from-red-500 to-red-600 bg-gradient-to-br shadow-xl">
|
||||||
|
<text class="text-3xl text-white font-bold">享</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="text-center">
|
||||||
|
<text class="mb-2 block text-4xl text-white font-bold drop-shadow-lg">象乐学</text>
|
||||||
|
<text class="mb-4 block text-base text-white/90 drop-shadow">【教师版】</text>
|
||||||
|
<text class="block text-sm text-white/80 drop-shadow">乐学未来,向学而行</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 认证表单区域 - 全宽底部 -->
|
||||||
|
<view class="w-full rounded-t-2xl bg-white shadow-2xl">
|
||||||
|
<auth-form
|
||||||
|
:mode="currentMode"
|
||||||
|
@login="handleLogin"
|
||||||
|
@reset="handleReset"
|
||||||
|
@switch-mode="handleSwitchMode"
|
||||||
|
/>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 装饰元素 -->
|
||||||
|
<view class="pointer-events-none absolute inset-0 z-1">
|
||||||
|
<view class="absolute left-1/10 top-1/5 h-20 w-20 animate-pulse rounded-full bg-white/10" />
|
||||||
|
<view class="animation-delay-2s absolute right-1/6 top-3/5 h-12 w-12 animate-pulse rounded-full bg-white/10" />
|
||||||
|
<view class="animation-delay-4s absolute left-1/12 top-3/4 h-28 w-28 animate-pulse rounded-full bg-white/10" />
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<route lang="json5">
|
||||||
|
{
|
||||||
|
"name": "auth",
|
||||||
|
"style": {
|
||||||
|
"navigationStyle": "custom",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</route>
|
||||||
Loading…
Reference in New Issue