Files
yunzerwebsiteallinone/uniapp/pages/login/login.vue
T
2026-07-15 18:14:17 +08:00

469 lines
9.2 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<view class="login-page">
<view class="page-inner">
<!-- 品牌区 -->
<view class="header">
<view class="logo">
<text class="logo-char"></text>
</view>
<text class="title">欢迎回来</text>
<text class="subtitle">登录云泽开启你的旅程</text>
</view>
<!-- 登录方式切换 -->
<view class="tabs">
<view
class="tab"
:class="{ active: loginType === 'phone' }"
@tap="loginType = 'phone'"
>手机登录</view>
<view
class="tab"
:class="{ active: loginType === 'account' }"
@tap="loginType = 'account'"
>账号登录</view>
</view>
<!-- 表单 -->
<view class="form-card">
<view class="form">
<template v-if="loginType === 'phone'">
<view class="field">
<u-input
v-model="phone"
placeholder="请输入手机号"
type="number"
maxlength="11"
border="none"
color="#303133"
:customStyle="inputStyle"
:placeholderStyle="placeholderStyle"
>
<template #prefix>
<text class="prefix">+86</text>
</template>
</u-input>
</view>
<view class="field field-row">
<u-input
v-model="code"
placeholder="请输入验证码"
type="number"
maxlength="6"
border="none"
color="#303133"
:customStyle="inputStyle"
:placeholderStyle="placeholderStyle"
/>
<text
class="code-link"
:class="{ disabled: countdown > 0 }"
@tap="sendCode"
>{{ countdown > 0 ? `${countdown}s 后重发` : '获取验证码' }}</text>
</view>
</template>
<template v-else>
<view class="field">
<u-input
v-model="username"
placeholder="请输入账号"
border="none"
color="#303133"
:customStyle="inputStyle"
:placeholderStyle="placeholderStyle"
/>
</view>
<view class="field">
<u-input
v-model="password"
placeholder="请输入密码"
type="password"
border="none"
color="#303133"
:customStyle="inputStyle"
:placeholderStyle="placeholderStyle"
/>
</view>
</template>
<view class="btn-primary" @tap="handleLogin"> </view>
<view class="btn-ghost" @tap="handleTestLogin">测试登录</view>
</view>
</view>
<!-- 协议 -->
<view class="agreement" @tap="agreed = !agreed">
<view class="agree-dot" :class="{ on: agreed }">
<text v-if="agreed" class="agree-check"></text>
</view>
<text class="agree-text">
登录即表示同意
<text class="agree-link">用户协议</text>
<text class="agree-link">隐私政策</text>
</text>
</view>
<!-- 第三方 -->
<view class="oauth">
<text class="oauth-label">其他方式</text>
<view class="oauth-icons">
<view class="oauth-btn" @tap="socialLogin('wechat')">
<FaIcon name="weixin" type="brands" color="#3c9cff" :size="22" />
</view>
<view class="oauth-btn" @tap="socialLogin('qq')">
<FaIcon name="qq" type="brands" color="#3c9cff" :size="22" />
</view>
<view class="oauth-btn" @tap="socialLogin('alipay')">
<FaIcon name="alipay" type="brands" color="#3c9cff" :size="22" />
</view>
</view>
</view>
</view>
</view>
</template>
<script setup>
import { ref, onMounted } from 'vue'
import { loginSuccess, isLoggedIn } from '@/utils/auth.js'
const loginType = ref('phone')
const phone = ref('')
const code = ref('')
const username = ref('')
const password = ref('')
const agreed = ref(true)
const countdown = ref(0)
let timer = null
const inputStyle = {
backgroundColor: 'transparent',
padding: '0 8rpx',
height: '96rpx',
fontSize: '28rpx'
}
const placeholderStyle = 'color: #909399; font-size: 28rpx'
onMounted(() => {
if (isLoggedIn()) {
uni.reLaunch({ url: '/pages/dashboard/dashboard' })
}
})
function sendCode() {
if (countdown.value > 0) return
if (!/^1\d{10}$/.test(phone.value)) {
uni.showToast({ title: '请输入正确手机号', icon: 'none' })
return
}
countdown.value = 60
timer = setInterval(() => {
countdown.value--
if (countdown.value <= 0) clearInterval(timer)
}, 1000)
uni.showToast({ title: '验证码已发送', icon: 'success' })
}
function navigateAfterLogin() {
uni.showToast({ title: '登录成功', icon: 'success' })
setTimeout(() => {
uni.reLaunch({ url: '/pages/dashboard/dashboard' })
}, 500)
}
function handleTestLogin() {
loginSuccess({ nickname: '测试用户', phone: '13800000000' })
navigateAfterLogin()
}
function handleLogin() {
if (!agreed.value) {
uni.showToast({ title: '请先同意用户协议', icon: 'none' })
return
}
if (loginType.value === 'phone') {
if (!/^1\d{10}$/.test(phone.value)) {
uni.showToast({ title: '请输入正确手机号', icon: 'none' })
return
}
if (!code.value || code.value.length < 4) {
uni.showToast({ title: '请输入验证码', icon: 'none' })
return
}
loginSuccess({ phone: phone.value, nickname: '用户' + phone.value.slice(-4) })
} else {
if (!username.value.trim()) {
uni.showToast({ title: '请输入账号', icon: 'none' })
return
}
if (!password.value.trim()) {
uni.showToast({ title: '请输入密码', icon: 'none' })
return
}
loginSuccess({ nickname: username.value })
}
navigateAfterLogin()
}
function socialLogin(type) {
if (!agreed.value) {
uni.showToast({ title: '请先同意用户协议', icon: 'none' })
return
}
const names = { wechat: '微信', qq: 'QQ', alipay: '支付宝' }
loginSuccess({ nickname: names[type] + '用户' })
uni.showToast({ title: `${names[type]}登录成功`, icon: 'success' })
setTimeout(() => {
uni.reLaunch({ url: '/pages/dashboard/dashboard' })
}, 500)
}
</script>
<style lang="scss" scoped>
@import '@/src/styles/page-common.scss';
.login-page {
min-height: 100vh;
background: $color-bg-page;
}
.page-inner {
padding: 0 48rpx;
padding-top: calc(var(--status-bar-height, 44px) + 80rpx);
}
.header {
margin-bottom: 64rpx;
}
.logo {
width: 96rpx;
height: 96rpx;
border-radius: $radius-lg;
background: $color-primary-bg;
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 32rpx;
}
.logo-char {
font-size: 44rpx;
font-weight: 600;
color: $color-primary;
}
.title {
display: block;
font-size: 48rpx;
font-weight: 600;
color: $color-text;
}
.subtitle {
display: block;
font-size: 28rpx;
color: $color-text-muted;
margin-top: 12rpx;
}
.tabs {
display: flex;
background: $color-card;
border-radius: $radius-md;
padding: 6rpx;
margin-bottom: 32rpx;
box-shadow: $shadow-card;
}
.tab {
flex: 1;
text-align: center;
font-size: 28rpx;
color: $color-text-secondary;
padding: 18rpx 0;
border-radius: $radius-sm;
&.active {
color: $color-primary;
font-weight: 600;
background: $color-primary-bg;
}
}
.form-card {
@include card;
padding: 32rpx 28rpx;
display: flex;
flex-direction: column;
gap: 24rpx;
}
.form {
display: flex;
flex-direction: column;
gap: 24rpx;
}
.field {
background: $color-bg-page;
border-radius: $radius-md;
padding: 0 24rpx;
overflow: hidden;
}
.field-row {
display: flex;
align-items: center;
}
.prefix {
font-size: 28rpx;
color: $color-text;
padding-right: 20rpx;
margin-right: 20rpx;
border-right: 1rpx solid $color-border;
}
.code-link {
flex-shrink: 0;
font-size: 26rpx;
color: $color-primary;
padding-left: 16rpx;
white-space: nowrap;
&.disabled {
color: $color-text-muted;
}
}
.btn-primary {
height: 96rpx;
background: $color-primary;
border-radius: $radius-md;
display: flex;
align-items: center;
justify-content: center;
font-size: 32rpx;
font-weight: 600;
color: #fff;
margin-top: 8rpx;
&:active {
background: $color-primary-dark;
}
}
.btn-ghost {
height: 96rpx;
background: $color-card;
border: 1rpx solid $color-border;
border-radius: $radius-md;
display: flex;
align-items: center;
justify-content: center;
font-size: 28rpx;
color: $color-text-secondary;
&:active {
background: $color-bg-page;
}
}
.agreement {
display: flex;
align-items: flex-start;
gap: 12rpx;
margin-top: 32rpx;
}
.agree-dot {
width: 32rpx;
height: 32rpx;
border-radius: 50%;
border: 1rpx solid $color-border;
background: $color-card;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
margin-top: 2rpx;
&.on {
background: $color-primary;
border-color: $color-primary;
}
}
.agree-check {
font-size: 18rpx;
color: #fff;
}
.agree-text {
font-size: 24rpx;
color: $color-text-muted;
line-height: 1.7;
}
.agree-link {
color: $color-primary;
}
.oauth {
margin-top: 64rpx;
display: flex;
flex-direction: column;
align-items: center;
gap: 28rpx;
padding-bottom: 60rpx;
}
.oauth-label {
font-size: 24rpx;
color: $color-text-muted;
position: relative;
padding: 0 32rpx;
&::before,
&::after {
content: '';
position: absolute;
top: 50%;
width: 80rpx;
height: 1rpx;
background: $color-border;
}
&::before {
right: 100%;
}
&::after {
left: 100%;
}
}
.oauth-icons {
display: flex;
gap: 48rpx;
}
.oauth-btn {
width: 88rpx;
height: 88rpx;
border-radius: 50%;
background: $color-card;
box-shadow: $shadow-card;
display: flex;
align-items: center;
justify-content: center;
&:active {
background: $color-bg-page;
}
}
</style>