|
...
|
...
|
@@ -6,32 +6,18 @@ |
|
|
|
<!-- 优化:为了兼容微信小程序等跨端环境,原生 SVG 转换为 Base64 嵌入标准 image 标签中,确保完美渲染 -->
|
|
|
|
<image class="logo-img" :src="logoSvgBase64" mode="aspectFit" />
|
|
|
|
</view>
|
|
|
|
<view class="brand-title">FitFlow 悦动健身</view>
|
|
|
|
<view class="brand-title">自己练</view>
|
|
|
|
<view class="brand-slogan">健康极简 · 开启你的蜕变时刻</view>
|
|
|
|
</view>
|
|
|
|
|
|
|
|
<!-- 2. 登录方式 Tab 切换 -->
|
|
|
|
<view class="tab-container">
|
|
|
|
<view class="tab-item" :class="{ active: activeTab === 'sms' }" @click="switchTab('sms')">
|
|
|
|
<u-icon
|
|
|
|
name="phone"
|
|
|
|
size="18"
|
|
|
|
:color="activeTab === 'sms' ? '#0a1931' : '#7a8290'"
|
|
|
|
class="tab-icon"
|
|
|
|
/>
|
|
|
|
<u-icon name="phone" size="18" :color="activeTab === 'sms' ? '#0a1931' : '#7a8290'" class="tab-icon" />
|
|
|
|
<text>免密登录</text>
|
|
|
|
</view>
|
|
|
|
<view
|
|
|
|
class="tab-item"
|
|
|
|
:class="{ active: activeTab === 'password' }"
|
|
|
|
@click="switchTab('password')"
|
|
|
|
>
|
|
|
|
<u-icon
|
|
|
|
name="lock"
|
|
|
|
size="18"
|
|
|
|
:color="activeTab === 'password' ? '#0a1931' : '#7a8290'"
|
|
|
|
class="tab-icon"
|
|
|
|
/>
|
|
|
|
<view class="tab-item" :class="{ active: activeTab === 'password' }" @click="switchTab('password')">
|
|
|
|
<u-icon name="lock" size="18" :color="activeTab === 'password' ? '#0a1931' : '#7a8290'" class="tab-icon" />
|
|
|
|
<text>密码登录</text>
|
|
|
|
</view>
|
|
|
|
</view>
|
|
...
|
...
|
@@ -43,14 +29,8 @@ |
|
|
|
<text class="form-label">手机号码</text>
|
|
|
|
<view class="input-box">
|
|
|
|
<u-icon name="phone" size="20" color="#a1a8b3" class="input-icon" />
|
|
|
|
<input
|
|
|
|
type="number"
|
|
|
|
v-model="phoneNumber"
|
|
|
|
placeholder="请输入您的手机号"
|
|
|
|
placeholder-style="color: #a1a8b3"
|
|
|
|
maxlength="11"
|
|
|
|
class="native-input"
|
|
|
|
/>
|
|
|
|
<input type="number" v-model="phoneNumber" placeholder="请输入您的手机号" placeholder-style="color: #a1a8b3"
|
|
|
|
maxlength="11" class="native-input" />
|
|
|
|
</view>
|
|
|
|
</view>
|
|
|
|
|
|
...
|
...
|
@@ -60,21 +40,11 @@ |
|
|
|
<view class="code-row">
|
|
|
|
<view class="input-box flex-1">
|
|
|
|
<u-icon name="chat" size="20" color="#a1a8b3" class="input-icon" />
|
|
|
|
<input
|
|
|
|
type="number"
|
|
|
|
v-model="verifyCode"
|
|
|
|
placeholder="6位验证码"
|
|
|
|
placeholder-style="color: #a1a8b3"
|
|
|
|
maxlength="6"
|
|
|
|
class="native-input"
|
|
|
|
/>
|
|
|
|
<input type="number" v-model="verifyCode" placeholder="6位验证码" placeholder-style="color: #a1a8b3"
|
|
|
|
maxlength="6" class="native-input" />
|
|
|
|
</view>
|
|
|
|
<!-- 获取验证码按钮 -->
|
|
|
|
<view
|
|
|
|
class="code-btn"
|
|
|
|
:class="{ disabled: countdown > 0 || isSending }"
|
|
|
|
@click="handleGetCode"
|
|
|
|
>
|
|
|
|
<view class="code-btn" :class="{ disabled: countdown > 0 || isSending }" @click="handleGetCode">
|
|
|
|
<text>{{ countdown > 0 ? `${countdown}s 后重试` : '获取验证码' }}</text>
|
|
|
|
</view>
|
|
|
|
</view>
|
|
...
|
...
|
@@ -85,14 +55,8 @@ |
|
|
|
<text class="form-label">登录密码</text>
|
|
|
|
<view class="input-box">
|
|
|
|
<u-icon name="lock" size="20" color="#a1a8b3" class="input-icon" />
|
|
|
|
<input
|
|
|
|
type="password"
|
|
|
|
v-model="password"
|
|
|
|
placeholder="请输入您的密码"
|
|
|
|
placeholder-style="color: #a1a8b3"
|
|
|
|
class="native-input"
|
|
|
|
:password="true"
|
|
|
|
/>
|
|
|
|
<input type="password" v-model="password" placeholder="请输入您的密码" placeholder-style="color: #a1a8b3"
|
|
|
|
class="native-input" :password="true" />
|
|
|
|
</view>
|
|
|
|
</view>
|
|
|
|
</view>
|
|
...
|
...
|
@@ -121,439 +85,439 @@ |
|
|
|
</template>
|
|
|
|
|
|
|
|
<script setup>
|
|
|
|
import { ref, onUnmounted } from 'vue';
|
|
|
|
import sheep from '@/sheep';
|
|
|
|
import AuthUtil from '@/sheep/api/member/auth';
|
|
|
|
import { onHide } from '@dcloudio/uni-app';
|
|
|
|
|
|
|
|
// 用于跨端兼容渲染的 Base64 编码火焰 SVG (绿渐变)
|
|
|
|
const logoSvgBase64 =
|
|
|
|
'data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><defs><linearGradient id="g" x1="0%" y1="0%" x2="100%" y2="100%"><stop offset="0%" stop-color="%238fc31f" /><stop offset="100%" stop-color="%2300b074" /></linearGradient></defs><path d="M12 2C12 2 6 7.5 6 13C6 16.8 9.1 20 12 20C14.9 20 18 16.8 18 13C18 7.5 12 2 12 2ZM12 16C10.3 16 9 14.7 9 13C9 10.5 12 7.5 12 7.5C12 7.5 15 10.5 15 13C15 14.7 13.7 16 12 16Z" fill="url(%23g)" /></svg>';
|
|
|
|
|
|
|
|
const activeTab = ref('sms'); // sms: 免密, password: 密码
|
|
|
|
const phoneNumber = ref('');
|
|
|
|
const verifyCode = ref('');
|
|
|
|
const password = ref('');
|
|
|
|
const isAgreed = ref(false);
|
|
|
|
|
|
|
|
// 防抖及加载动画状态变量
|
|
|
|
const isSending = ref(false);
|
|
|
|
const isSubmitting = ref(false);
|
|
|
|
|
|
|
|
// 验证码倒计时逻辑
|
|
|
|
const countdown = ref(0);
|
|
|
|
let timer = null;
|
|
|
|
|
|
|
|
// 验证手机号码格式
|
|
|
|
const validatePhone = (phone) => {
|
|
|
|
return /^1[3-9]\d{9}$/.test(phone);
|
|
|
|
};
|
|
|
|
|
|
|
|
// 切换Tab,同时清理另一个 Tab 的输入
|
|
|
|
const switchTab = (type) => {
|
|
|
|
activeTab.value = type;
|
|
|
|
verifyCode.value = '';
|
|
|
|
password.value = '';
|
|
|
|
};
|
|
|
|
|
|
|
|
// 发送验证码
|
|
|
|
const handleGetCode = async () => {
|
|
|
|
if (countdown.value > 0 || isSending.value) return;
|
|
|
|
|
|
|
|
// 前置逻辑校验
|
|
|
|
if (!phoneNumber.value) {
|
|
|
|
uni.showToast({ title: '请输入手机号码', icon: 'none' });
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (!validatePhone(phoneNumber.value)) {
|
|
|
|
uni.showToast({ title: '请输入正确的11位手机号码', icon: 'none' });
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
import { ref, onUnmounted } from 'vue';
|
|
|
|
import sheep from '@/sheep';
|
|
|
|
import AuthUtil from '@/sheep/api/member/auth';
|
|
|
|
import { onHide } from '@dcloudio/uni-app';
|
|
|
|
|
|
|
|
// 用于跨端兼容渲染的 Base64 编码火焰 SVG (绿渐变)
|
|
|
|
const logoSvgBase64 =
|
|
|
|
'data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><defs><linearGradient id="g" x1="0%" y1="0%" x2="100%" y2="100%"><stop offset="0%" stop-color="%238fc31f" /><stop offset="100%" stop-color="%2300b074" /></linearGradient></defs><path d="M12 2C12 2 6 7.5 6 13C6 16.8 9.1 20 12 20C14.9 20 18 16.8 18 13C18 7.5 12 2 12 2ZM12 16C10.3 16 9 14.7 9 13C9 10.5 12 7.5 12 7.5C12 7.5 15 10.5 15 13C15 14.7 13.7 16 12 16Z" fill="url(%23g)" /></svg>';
|
|
|
|
|
|
|
|
const activeTab = ref('sms'); // sms: 免密, password: 密码
|
|
|
|
const phoneNumber = ref('');
|
|
|
|
const verifyCode = ref('');
|
|
|
|
const password = ref('');
|
|
|
|
const isAgreed = ref(false);
|
|
|
|
|
|
|
|
// 防抖及加载动画状态变量
|
|
|
|
const isSending = ref(false);
|
|
|
|
const isSubmitting = ref(false);
|
|
|
|
|
|
|
|
// 验证码倒计时逻辑
|
|
|
|
const countdown = ref(0);
|
|
|
|
let timer = null;
|
|
|
|
|
|
|
|
// 验证手机号码格式
|
|
|
|
const validatePhone = (phone) => {
|
|
|
|
return /^1[3-9]\d{9}$/.test(phone);
|
|
|
|
};
|
|
|
|
|
|
|
|
// 切换Tab,同时清理另一个 Tab 的输入
|
|
|
|
const switchTab = (type) => {
|
|
|
|
activeTab.value = type;
|
|
|
|
verifyCode.value = '';
|
|
|
|
password.value = '';
|
|
|
|
};
|
|
|
|
|
|
|
|
// 发送验证码
|
|
|
|
const handleGetCode = async () => {
|
|
|
|
if (countdown.value > 0 || isSending.value) return;
|
|
|
|
|
|
|
|
// 前置逻辑校验
|
|
|
|
if (!phoneNumber.value) {
|
|
|
|
uni.showToast({ title: '请输入手机号码', icon: 'none' });
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (!validatePhone(phoneNumber.value)) {
|
|
|
|
uni.showToast({ title: '请输入正确的11位手机号码', icon: 'none' });
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
isSending.value = true;
|
|
|
|
try {
|
|
|
|
// 成功发送验证码请求
|
|
|
|
const res = await AuthUtil.sendSmsCode({ phone: phoneNumber.value });
|
|
|
|
|
|
|
|
uni.showToast({ title: '验证码已发送', icon: 'success' });
|
|
|
|
countdown.value = 60;
|
|
|
|
timer = setInterval(() => {
|
|
|
|
countdown.value--;
|
|
|
|
if (countdown.value <= 0) {
|
|
|
|
clearInterval(timer);
|
|
|
|
timer = null;
|
|
|
|
}
|
|
|
|
}, 1000);
|
|
|
|
} catch (err) {
|
|
|
|
console.error('发送验证码失败异常:', err);
|
|
|
|
} finally {
|
|
|
|
isSending.value = false;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
isSending.value = true;
|
|
|
|
try {
|
|
|
|
// 成功发送验证码请求
|
|
|
|
const res = await AuthUtil.sendSmsCode({ phone: phoneNumber.value });
|
|
|
|
|
|
|
|
uni.showToast({ title: '验证码已发送', icon: 'success' });
|
|
|
|
countdown.value = 60;
|
|
|
|
timer = setInterval(() => {
|
|
|
|
countdown.value--;
|
|
|
|
if (countdown.value <= 0) {
|
|
|
|
clearInterval(timer);
|
|
|
|
timer = null;
|
|
|
|
}
|
|
|
|
}, 1000);
|
|
|
|
} catch (err) {
|
|
|
|
console.error('发送验证码失败异常:', err);
|
|
|
|
} finally {
|
|
|
|
isSending.value = false;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
// 登录按钮提交逻辑
|
|
|
|
const handleLogin = async () => {
|
|
|
|
if (isSubmitting.value) return;
|
|
|
|
// 登录按钮提交逻辑
|
|
|
|
const handleLogin = async () => {
|
|
|
|
if (isSubmitting.value) return;
|
|
|
|
|
|
|
|
// 1. 用户协议验证
|
|
|
|
if (!isAgreed.value) {
|
|
|
|
uni.showToast({ title: '请先阅读并同意用户协议及隐私政策', icon: 'none' });
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// 2. 基础手机号格式验证
|
|
|
|
if (!phoneNumber.value) {
|
|
|
|
uni.showToast({ title: '请输入手机号码', icon: 'none' });
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (!validatePhone(phoneNumber.value)) {
|
|
|
|
uni.showToast({ title: '请输入正确的手机号码', icon: 'none' });
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// 1. 用户协议验证
|
|
|
|
if (!isAgreed.value) {
|
|
|
|
uni.showToast({ title: '请先阅读并同意用户协议及隐私政策', icon: 'none' });
|
|
|
|
// 3. Tab特定表单验证并请求
|
|
|
|
if (activeTab.value === 'sms') {
|
|
|
|
if (!verifyCode.value) {
|
|
|
|
uni.showToast({ title: '请输入验证码', icon: 'none' });
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// 2. 基础手机号格式验证
|
|
|
|
if (!phoneNumber.value) {
|
|
|
|
uni.showToast({ title: '请输入手机号码', icon: 'none' });
|
|
|
|
if (verifyCode.value.length < 4) {
|
|
|
|
uni.showToast({ title: '请输入完整的验证码', icon: 'none' });
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (!validatePhone(phoneNumber.value)) {
|
|
|
|
uni.showToast({ title: '请输入正确的手机号码', icon: 'none' });
|
|
|
|
await performLogin('sms');
|
|
|
|
} else {
|
|
|
|
if (!password.value) {
|
|
|
|
uni.showToast({ title: '请输入密码', icon: 'none' });
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// 3. Tab特定表单验证并请求
|
|
|
|
if (activeTab.value === 'sms') {
|
|
|
|
if (!verifyCode.value) {
|
|
|
|
uni.showToast({ title: '请输入验证码', icon: 'none' });
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (verifyCode.value.length < 4) {
|
|
|
|
uni.showToast({ title: '请输入完整的验证码', icon: 'none' });
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
await performLogin('sms');
|
|
|
|
await performLogin('password');
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
// 执行最终请求与状态写入
|
|
|
|
const performLogin = async (type) => {
|
|
|
|
isSubmitting.value = true;
|
|
|
|
uni.showLoading({ title: '正在登录...', mask: true });
|
|
|
|
|
|
|
|
try {
|
|
|
|
let res = null;
|
|
|
|
if (type === 'sms') {
|
|
|
|
// 修复:原代码使用的 AuthApi 变更为导入的 AuthUtil 服务
|
|
|
|
res = await AuthUtil.loginBySms({
|
|
|
|
phone: phoneNumber.value,
|
|
|
|
code: verifyCode.value,
|
|
|
|
});
|
|
|
|
} else {
|
|
|
|
if (!password.value) {
|
|
|
|
uni.showToast({ title: '请输入密码', icon: 'none' });
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
await performLogin('password');
|
|
|
|
res = await AuthUtil.loginByPhonePassword({
|
|
|
|
phone: phoneNumber.value,
|
|
|
|
password: password.value,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
// 执行最终请求与状态写入
|
|
|
|
const performLogin = async (type) => {
|
|
|
|
isSubmitting.value = true;
|
|
|
|
uni.showLoading({ title: '正在登录...', mask: true });
|
|
|
|
|
|
|
|
try {
|
|
|
|
let res = null;
|
|
|
|
if (type === 'sms') {
|
|
|
|
// 修复:原代码使用的 AuthApi 变更为导入的 AuthUtil 服务
|
|
|
|
res = await AuthUtil.loginBySms({
|
|
|
|
phone: phoneNumber.value,
|
|
|
|
code: verifyCode.value,
|
|
|
|
});
|
|
|
|
} else {
|
|
|
|
res = await AuthUtil.loginByPhonePassword({
|
|
|
|
phone: phoneNumber.value,
|
|
|
|
password: password.value,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
const authData = res.data || {};
|
|
|
|
const authData = res.data || {};
|
|
|
|
|
|
|
|
if (authData && authData.accessToken) {
|
|
|
|
// 1. 取得芋道商城 Pinia 内置的 user Store
|
|
|
|
const userStore = sheep.$store('user');
|
|
|
|
if (authData && authData.accessToken) {
|
|
|
|
// 1. 取得芋道商城 Pinia 内置的 user Store
|
|
|
|
const userStore = sheep.$store('user');
|
|
|
|
|
|
|
|
// 2. 写入 Token 到 Pinia 及持久态 LocalStorage
|
|
|
|
userStore.setToken(authData.accessToken, authData.refreshToken || '');
|
|
|
|
// 2. 写入 Token 到 Pinia 及持久态 LocalStorage
|
|
|
|
userStore.setToken(authData.accessToken, authData.refreshToken || '');
|
|
|
|
|
|
|
|
// 3. 登录成功后,主动拉取一次用户信息同步至本地状态
|
|
|
|
await userStore.getInfo();
|
|
|
|
// 3. 登录成功后,主动拉取一次用户信息同步至本地状态
|
|
|
|
await userStore.getInfo();
|
|
|
|
|
|
|
|
uni.showToast({
|
|
|
|
title: '登录成功',
|
|
|
|
icon: 'success',
|
|
|
|
});
|
|
|
|
uni.showToast({
|
|
|
|
title: '登录成功',
|
|
|
|
icon: 'success',
|
|
|
|
});
|
|
|
|
|
|
|
|
// 4. 跳转至目标页面
|
|
|
|
setTimeout(() => {
|
|
|
|
uni.switchTab({
|
|
|
|
url: '/pages/xunji/xunji',
|
|
|
|
});
|
|
|
|
}, 100);
|
|
|
|
} else {
|
|
|
|
uni.showToast({
|
|
|
|
title: res.msg || '登录失败,未获取到有效凭证',
|
|
|
|
icon: 'none',
|
|
|
|
// 4. 跳转至目标页面
|
|
|
|
setTimeout(() => {
|
|
|
|
uni.switchTab({
|
|
|
|
url: '/pages/xunji/xunji',
|
|
|
|
});
|
|
|
|
}
|
|
|
|
} catch (err) {
|
|
|
|
console.error('登录逻辑异常:', err);
|
|
|
|
} finally {
|
|
|
|
isSubmitting.value = false;
|
|
|
|
uni.hideLoading();
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
// 忘记密码跳转
|
|
|
|
const goToForgetPassword = () => {
|
|
|
|
uni.navigateTo({
|
|
|
|
url: '/pages7/pages/index/reset-password',
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
// 协议详情跳转
|
|
|
|
const goToAgreement = (type) => {
|
|
|
|
const url =
|
|
|
|
type === 'service'
|
|
|
|
? '/pages/public/richtext?id=service'
|
|
|
|
: '/pages/public/richtext?id=privacy';
|
|
|
|
uni.navigateTo({
|
|
|
|
url,
|
|
|
|
fail: () => {
|
|
|
|
uni.showToast({
|
|
|
|
title: `跳转至${type === 'service' ? '用户协议' : '隐私政策'}`,
|
|
|
|
icon: 'none',
|
|
|
|
});
|
|
|
|
},
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
// 页面卸载生命周期:清除定时器,规避潜在的内存泄露
|
|
|
|
onHide(() => {
|
|
|
|
if (timer) {
|
|
|
|
clearInterval(timer);
|
|
|
|
timer = null;
|
|
|
|
}, 100);
|
|
|
|
} else {
|
|
|
|
uni.showToast({
|
|
|
|
title: res.msg || '登录失败,未获取到有效凭证',
|
|
|
|
icon: 'none',
|
|
|
|
});
|
|
|
|
}
|
|
|
|
} catch (err) {
|
|
|
|
console.error('登录逻辑异常:', err);
|
|
|
|
} finally {
|
|
|
|
isSubmitting.value = false;
|
|
|
|
uni.hideLoading();
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
// 忘记密码跳转
|
|
|
|
const goToForgetPassword = () => {
|
|
|
|
uni.navigateTo({
|
|
|
|
url: '/pages7/pages/index/reset-password?isModify=false',
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
// 协议详情跳转
|
|
|
|
const goToAgreement = (type) => {
|
|
|
|
const url =
|
|
|
|
type === 'service'
|
|
|
|
? '/pages/public/richtext?id=service'
|
|
|
|
: '/pages/public/richtext?id=privacy';
|
|
|
|
uni.navigateTo({
|
|
|
|
url,
|
|
|
|
fail: () => {
|
|
|
|
uni.showToast({
|
|
|
|
title: `跳转至${type === 'service' ? '用户协议' : '隐私政策'}`,
|
|
|
|
icon: 'none',
|
|
|
|
});
|
|
|
|
},
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
// 页面卸载生命周期:清除定时器,规避潜在的内存泄露
|
|
|
|
onHide(() => {
|
|
|
|
if (timer) {
|
|
|
|
clearInterval(timer);
|
|
|
|
timer = null;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
</script>
|
|
|
|
|
|
|
|
<style lang="scss" scoped>
|
|
|
|
.login-wrapper {
|
|
|
|
height: 100vh;
|
|
|
|
// #ifdef H5
|
|
|
|
height: calc(100vh - 44px);
|
|
|
|
// #endif
|
|
|
|
background-color: #ffffff;
|
|
|
|
padding: 80rpx 50rpx;
|
|
|
|
.login-wrapper {
|
|
|
|
height: 100vh;
|
|
|
|
// #ifdef H5
|
|
|
|
height: calc(100vh - 44px);
|
|
|
|
// #endif
|
|
|
|
background-color: #ffffff;
|
|
|
|
padding: 80rpx 50rpx;
|
|
|
|
box-sizing: border-box;
|
|
|
|
|
|
|
|
// 1. Logo
|
|
|
|
.logo-section {
|
|
|
|
display: flex;
|
|
|
|
flex-direction: column;
|
|
|
|
align-items: center;
|
|
|
|
margin-bottom: 70rpx;
|
|
|
|
margin-top: 40rpx;
|
|
|
|
|
|
|
|
.logo-box {
|
|
|
|
width: 140rpx;
|
|
|
|
height: 140rpx;
|
|
|
|
border-radius: 40rpx;
|
|
|
|
background: #ffffff;
|
|
|
|
border: 3rpx solid #d2ee9e;
|
|
|
|
box-shadow: 0 16rpx 40rpx rgba(111, 214, 32, 0.12);
|
|
|
|
display: flex;
|
|
|
|
justify-content: center;
|
|
|
|
align-items: center;
|
|
|
|
margin-bottom: 30rpx;
|
|
|
|
|
|
|
|
.logo-img {
|
|
|
|
width: 76rpx;
|
|
|
|
height: 76rpx;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
.brand-title {
|
|
|
|
font-size: 46rpx;
|
|
|
|
font-weight: bold;
|
|
|
|
color: #0a1931;
|
|
|
|
letter-spacing: 2rpx;
|
|
|
|
}
|
|
|
|
|
|
|
|
.brand-slogan {
|
|
|
|
font-size: 26rpx;
|
|
|
|
color: #9097a3;
|
|
|
|
margin-top: 12rpx;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// 2. Tab
|
|
|
|
.tab-container {
|
|
|
|
height: 96rpx;
|
|
|
|
background-color: #f1f3f7;
|
|
|
|
border-radius: 20rpx;
|
|
|
|
display: flex;
|
|
|
|
padding: 8rpx;
|
|
|
|
box-sizing: border-box;
|
|
|
|
margin-bottom: 50rpx;
|
|
|
|
|
|
|
|
// 1. Logo
|
|
|
|
.logo-section {
|
|
|
|
.tab-item {
|
|
|
|
flex: 1;
|
|
|
|
display: flex;
|
|
|
|
flex-direction: column;
|
|
|
|
justify-content: center;
|
|
|
|
align-items: center;
|
|
|
|
margin-bottom: 70rpx;
|
|
|
|
margin-top: 40rpx;
|
|
|
|
|
|
|
|
.logo-box {
|
|
|
|
width: 140rpx;
|
|
|
|
height: 140rpx;
|
|
|
|
border-radius: 40rpx;
|
|
|
|
background: #ffffff;
|
|
|
|
border: 3rpx solid #d2ee9e;
|
|
|
|
box-shadow: 0 16rpx 40rpx rgba(111, 214, 32, 0.12);
|
|
|
|
display: flex;
|
|
|
|
justify-content: center;
|
|
|
|
align-items: center;
|
|
|
|
margin-bottom: 30rpx;
|
|
|
|
font-size: 28rpx;
|
|
|
|
color: #7a8290;
|
|
|
|
font-weight: 500;
|
|
|
|
transition: all 0.2s ease;
|
|
|
|
border-radius: 16rpx;
|
|
|
|
|
|
|
|
.logo-img {
|
|
|
|
width: 76rpx;
|
|
|
|
height: 76rpx;
|
|
|
|
}
|
|
|
|
.tab-icon {
|
|
|
|
margin-right: 12rpx;
|
|
|
|
}
|
|
|
|
|
|
|
|
.brand-title {
|
|
|
|
font-size: 46rpx;
|
|
|
|
font-weight: bold;
|
|
|
|
&.active {
|
|
|
|
background-color: #ffffff;
|
|
|
|
color: #0a1931;
|
|
|
|
letter-spacing: 2rpx;
|
|
|
|
font-weight: bold;
|
|
|
|
box-shadow: 0 4rpx 16rpx rgba(10, 25, 49, 0.05);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
.brand-slogan {
|
|
|
|
// 3. Form
|
|
|
|
.form-container {
|
|
|
|
.form-item-wrapper {
|
|
|
|
margin-bottom: 36rpx;
|
|
|
|
|
|
|
|
.form-label {
|
|
|
|
font-size: 26rpx;
|
|
|
|
color: #9097a3;
|
|
|
|
margin-top: 12rpx;
|
|
|
|
color: #0a1931;
|
|
|
|
font-weight: bold;
|
|
|
|
display: block;
|
|
|
|
margin-bottom: 16rpx;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// 2. Tab
|
|
|
|
.tab-container {
|
|
|
|
height: 96rpx;
|
|
|
|
background-color: #f1f3f7;
|
|
|
|
border-radius: 20rpx;
|
|
|
|
display: flex;
|
|
|
|
padding: 8rpx;
|
|
|
|
box-sizing: border-box;
|
|
|
|
margin-bottom: 50rpx;
|
|
|
|
|
|
|
|
.tab-item {
|
|
|
|
flex: 1;
|
|
|
|
.input-box {
|
|
|
|
height: 104rpx;
|
|
|
|
background-color: #f4f6fa;
|
|
|
|
border: 2rpx solid #e1e6eb;
|
|
|
|
border-radius: 24rpx;
|
|
|
|
display: flex;
|
|
|
|
justify-content: center;
|
|
|
|
align-items: center;
|
|
|
|
font-size: 28rpx;
|
|
|
|
color: #7a8290;
|
|
|
|
font-weight: 500;
|
|
|
|
transition: all 0.2s ease;
|
|
|
|
border-radius: 16rpx;
|
|
|
|
padding: 0 30rpx;
|
|
|
|
box-sizing: border-box;
|
|
|
|
|
|
|
|
.tab-icon {
|
|
|
|
margin-right: 12rpx;
|
|
|
|
.input-icon {
|
|
|
|
margin-right: 16rpx;
|
|
|
|
}
|
|
|
|
|
|
|
|
&.active {
|
|
|
|
background-color: #ffffff;
|
|
|
|
.native-input {
|
|
|
|
flex: 1;
|
|
|
|
height: 100%;
|
|
|
|
font-size: 28rpx;
|
|
|
|
color: #0a1931;
|
|
|
|
font-weight: bold;
|
|
|
|
box-shadow: 0 4rpx 16rpx rgba(10, 25, 49, 0.05);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// 3. Form
|
|
|
|
.form-container {
|
|
|
|
.form-item-wrapper {
|
|
|
|
margin-bottom: 36rpx;
|
|
|
|
// 验证码一栏的双列布局
|
|
|
|
.code-row {
|
|
|
|
display: flex;
|
|
|
|
align-items: center;
|
|
|
|
|
|
|
|
.form-label {
|
|
|
|
font-size: 26rpx;
|
|
|
|
color: #0a1931;
|
|
|
|
font-weight: bold;
|
|
|
|
display: block;
|
|
|
|
margin-bottom: 16rpx;
|
|
|
|
.flex-1 {
|
|
|
|
flex: 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
.input-box {
|
|
|
|
.code-btn {
|
|
|
|
width: 220rpx;
|
|
|
|
height: 104rpx;
|
|
|
|
background-color: #f4f6fa;
|
|
|
|
border: 2rpx solid #e1e6eb;
|
|
|
|
border-radius: 24rpx;
|
|
|
|
background-color: #f9fdf2;
|
|
|
|
border: 2rpx solid #d0ee9c;
|
|
|
|
display: flex;
|
|
|
|
justify-content: center;
|
|
|
|
align-items: center;
|
|
|
|
padding: 0 30rpx;
|
|
|
|
box-sizing: border-box;
|
|
|
|
|
|
|
|
.input-icon {
|
|
|
|
margin-right: 16rpx;
|
|
|
|
}
|
|
|
|
|
|
|
|
.native-input {
|
|
|
|
flex: 1;
|
|
|
|
height: 100%;
|
|
|
|
font-size: 28rpx;
|
|
|
|
color: #0a1931;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// 验证码一栏的双列布局
|
|
|
|
.code-row {
|
|
|
|
display: flex;
|
|
|
|
align-items: center;
|
|
|
|
margin-left: 20rpx;
|
|
|
|
font-size: 26rpx;
|
|
|
|
color: #8fc31f;
|
|
|
|
font-weight: bold;
|
|
|
|
transition: all 0.2s ease;
|
|
|
|
|
|
|
|
.flex-1 {
|
|
|
|
flex: 1;
|
|
|
|
&:active {
|
|
|
|
opacity: 0.8;
|
|
|
|
}
|
|
|
|
|
|
|
|
.code-btn {
|
|
|
|
width: 220rpx;
|
|
|
|
height: 104rpx;
|
|
|
|
border-radius: 24rpx;
|
|
|
|
background-color: #f9fdf2;
|
|
|
|
border: 2rpx solid #d0ee9c;
|
|
|
|
display: flex;
|
|
|
|
justify-content: center;
|
|
|
|
align-items: center;
|
|
|
|
margin-left: 20rpx;
|
|
|
|
font-size: 26rpx;
|
|
|
|
color: #8fc31f;
|
|
|
|
font-weight: bold;
|
|
|
|
transition: all 0.2s ease;
|
|
|
|
|
|
|
|
&:active {
|
|
|
|
opacity: 0.8;
|
|
|
|
}
|
|
|
|
|
|
|
|
&.disabled {
|
|
|
|
color: #b0b8c4;
|
|
|
|
background-color: #f4f6fa;
|
|
|
|
border-color: #e1e6eb;
|
|
|
|
pointer-events: none; // 禁用点击以避免多余的触发
|
|
|
|
}
|
|
|
|
&.disabled {
|
|
|
|
color: #b0b8c4;
|
|
|
|
background-color: #f4f6fa;
|
|
|
|
border-color: #e1e6eb;
|
|
|
|
pointer-events: none; // 禁用点击以避免多余的触发
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
.forget-password {
|
|
|
|
text-align: right;
|
|
|
|
margin-top: 20rpx;
|
|
|
|
font-size: 24rpx;
|
|
|
|
color: rgb(220, 54, 46);
|
|
|
|
}
|
|
|
|
.forget-password {
|
|
|
|
text-align: right;
|
|
|
|
margin-top: 20rpx;
|
|
|
|
font-size: 24rpx;
|
|
|
|
color: rgb(220, 54, 46);
|
|
|
|
}
|
|
|
|
|
|
|
|
// 4. Agreement
|
|
|
|
.agreement-container {
|
|
|
|
// 4. Agreement
|
|
|
|
.agreement-container {
|
|
|
|
display: flex;
|
|
|
|
align-items: flex-start;
|
|
|
|
margin-top: 40rpx;
|
|
|
|
margin-bottom: 50rpx;
|
|
|
|
padding: 0 6rpx;
|
|
|
|
|
|
|
|
.checkbox-box {
|
|
|
|
width: 32rpx;
|
|
|
|
height: 32rpx;
|
|
|
|
border: 2rpx solid #c2c9d3;
|
|
|
|
border-radius: 8rpx;
|
|
|
|
margin-right: 16rpx;
|
|
|
|
margin-top: 4rpx;
|
|
|
|
display: flex;
|
|
|
|
align-items: flex-start;
|
|
|
|
margin-top: 40rpx;
|
|
|
|
margin-bottom: 50rpx;
|
|
|
|
padding: 0 6rpx;
|
|
|
|
|
|
|
|
.checkbox-box {
|
|
|
|
width: 32rpx;
|
|
|
|
height: 32rpx;
|
|
|
|
border: 2rpx solid #c2c9d3;
|
|
|
|
border-radius: 8rpx;
|
|
|
|
margin-right: 16rpx;
|
|
|
|
margin-top: 4rpx;
|
|
|
|
display: flex;
|
|
|
|
justify-content: center;
|
|
|
|
align-items: center;
|
|
|
|
transition: all 0.15s ease;
|
|
|
|
box-sizing: border-box;
|
|
|
|
justify-content: center;
|
|
|
|
align-items: center;
|
|
|
|
transition: all 0.15s ease;
|
|
|
|
box-sizing: border-box;
|
|
|
|
|
|
|
|
&.checked {
|
|
|
|
border-color: #00b074;
|
|
|
|
background-color: #00b074;
|
|
|
|
}
|
|
|
|
&.checked {
|
|
|
|
border-color: #00b074;
|
|
|
|
background-color: #00b074;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
.agreement-text {
|
|
|
|
flex: 1;
|
|
|
|
font-size: 24rpx;
|
|
|
|
color: #7a8290;
|
|
|
|
line-height: 1.5;
|
|
|
|
.agreement-text {
|
|
|
|
flex: 1;
|
|
|
|
font-size: 24rpx;
|
|
|
|
color: #7a8290;
|
|
|
|
line-height: 1.5;
|
|
|
|
|
|
|
|
.link-text {
|
|
|
|
color: #8fc31f;
|
|
|
|
font-weight: 500;
|
|
|
|
}
|
|
|
|
.link-text {
|
|
|
|
color: #8fc31f;
|
|
|
|
font-weight: 500;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// 5. Submit Button
|
|
|
|
.submit-btn {
|
|
|
|
height: 104rpx;
|
|
|
|
border-radius: 24rpx;
|
|
|
|
background: linear-gradient(135deg, #79d621 0%, #00b074 100%);
|
|
|
|
box-shadow: 0 16rpx 40rpx rgba(0, 176, 116, 0.25);
|
|
|
|
display: flex;
|
|
|
|
justify-content: center;
|
|
|
|
align-items: center;
|
|
|
|
color: #ffffff;
|
|
|
|
font-size: 32rpx;
|
|
|
|
font-weight: bold;
|
|
|
|
letter-spacing: 2rpx;
|
|
|
|
transition: all 0.2s ease;
|
|
|
|
|
|
|
|
&:active {
|
|
|
|
transform: scale(0.98);
|
|
|
|
opacity: 0.9;
|
|
|
|
}
|
|
|
|
// 5. Submit Button
|
|
|
|
.submit-btn {
|
|
|
|
height: 104rpx;
|
|
|
|
border-radius: 24rpx;
|
|
|
|
background: linear-gradient(135deg, #79d621 0%, #00b074 100%);
|
|
|
|
box-shadow: 0 16rpx 40rpx rgba(0, 176, 116, 0.25);
|
|
|
|
display: flex;
|
|
|
|
justify-content: center;
|
|
|
|
align-items: center;
|
|
|
|
color: #ffffff;
|
|
|
|
font-size: 32rpx;
|
|
|
|
font-weight: bold;
|
|
|
|
letter-spacing: 2rpx;
|
|
|
|
transition: all 0.2s ease;
|
|
|
|
|
|
|
|
&:active {
|
|
|
|
transform: scale(0.98);
|
|
|
|
opacity: 0.9;
|
|
|
|
}
|
|
|
|
|
|
|
|
&.disabled-btn {
|
|
|
|
opacity: 0.75;
|
|
|
|
pointer-events: none; // 阻止后续的多余点击
|
|
|
|
}
|
|
|
|
&.disabled-btn {
|
|
|
|
opacity: 0.75;
|
|
|
|
pointer-events: none; // 阻止后续的多余点击
|
|
|
|
}
|
|
|
|
|
|
|
|
.btn-arrow {
|
|
|
|
margin-left: 10rpx;
|
|
|
|
}
|
|
|
|
.btn-arrow {
|
|
|
|
margin-left: 10rpx;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
</style> |
...
|
...
|
|