|
|
@@ -1,100 +1,106 @@
|
|
|
<template>
|
|
|
<view class="container">
|
|
|
- <!-- 品牌区 -->
|
|
|
- <view class="brand-section">
|
|
|
- <view class="brand-logo">
|
|
|
- <image class="logo" src="/static/brand-logo.svg" mode="aspectFit"></image>
|
|
|
+ <!-- 动态背景层 -->
|
|
|
+ <view class="bg-layer">
|
|
|
+ <view class="gradient-orb orb-1"></view>
|
|
|
+ <view class="gradient-orb orb-2"></view>
|
|
|
+ <view class="gradient-orb orb-3"></view>
|
|
|
+ <view class="wave-container">
|
|
|
+ <view class="wave wave-1"></view>
|
|
|
+ <view class="wave wave-2"></view>
|
|
|
</view>
|
|
|
- <text class="brand-title">欢迎登录</text>
|
|
|
- <text class="brand-subtitle">快与慢充电桩 · 智能视觉零售柜</text>
|
|
|
</view>
|
|
|
|
|
|
- <!-- 登录表单 -->
|
|
|
- <view class="form-section">
|
|
|
- <view class="form-card">
|
|
|
- <!-- 微信手机号快速登录按钮 -->
|
|
|
- <button
|
|
|
- class="wechat-login-btn"
|
|
|
- open-type="getPhoneNumber"
|
|
|
- @getphonenumber="onGetPhoneNumber"
|
|
|
- :loading="isLoading"
|
|
|
- >
|
|
|
- <text class="btn-icon">📱</text>
|
|
|
- <text class="btn-text">微信手机号快速登录</text>
|
|
|
- </button>
|
|
|
-
|
|
|
- <!-- 分隔线 -->
|
|
|
- <view class="divider">
|
|
|
- <view class="divider-line"></view>
|
|
|
- <text class="divider-text">或</text>
|
|
|
- <view class="divider-line"></view>
|
|
|
- </view>
|
|
|
-
|
|
|
- <!-- 传统账号密码登录 -->
|
|
|
- <view class="form-item">
|
|
|
- <view class="input-wrapper">
|
|
|
- <view class="input-icon">
|
|
|
- <svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
|
- <path d="M20 21V19C20 17.8954 19.1046 17 18 17H6C4.89543 17 4 17.8954 4 19V21" stroke="#8C8C8C" stroke-width="2" stroke-linecap="round"/>
|
|
|
- <circle cx="12" cy="7" r="4" stroke="#8C8C8C" stroke-width="2"/>
|
|
|
- </svg>
|
|
|
+ <!-- 主内容区 -->
|
|
|
+ <view class="main-content">
|
|
|
+ <!-- 品牌展示区 -->
|
|
|
+ <view class="brand-showcase">
|
|
|
+ <view class="logo-wrapper">
|
|
|
+ <!-- 外层装饰环 -->
|
|
|
+ <view class="logo-outer-ring">
|
|
|
+ <!-- 内层装饰环 -->
|
|
|
+ <view class="logo-inner-ring">
|
|
|
+ <!-- 光晕效果 -->
|
|
|
+ <view class="logo-glow"></view>
|
|
|
+ <!-- Logo主体 -->
|
|
|
+ <view class="logo-circle">
|
|
|
+ <image class="logo" src="/static/brand-logo.svg" mode="aspectFit"></image>
|
|
|
+ </view>
|
|
|
</view>
|
|
|
- <input
|
|
|
- class="form-input"
|
|
|
- type="number"
|
|
|
- placeholder="请输入手机号"
|
|
|
- v-model="loginForm.phone"
|
|
|
- maxlength="11"
|
|
|
- placeholder-class="input-placeholder"
|
|
|
- />
|
|
|
</view>
|
|
|
</view>
|
|
|
|
|
|
- <view class="form-item">
|
|
|
- <view class="input-wrapper">
|
|
|
- <view class="input-icon">
|
|
|
- <svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
|
- <rect x="3" y="11" width="18" height="11" rx="2" stroke="#8C8C8C" stroke-width="2"/>
|
|
|
- <path d="M7 11V7C7 4.23858 9.23858 2 12 2C14.7614 2 17 4.23858 17 7V11" stroke="#8C8C8C" stroke-width="2" stroke-linecap="round"/>
|
|
|
- </svg>
|
|
|
- </view>
|
|
|
- <input
|
|
|
- class="form-input"
|
|
|
- type="password"
|
|
|
- placeholder="请输入密码"
|
|
|
- v-model="loginForm.password"
|
|
|
- placeholder-class="input-placeholder"
|
|
|
- />
|
|
|
+ <view class="brand-text-group">
|
|
|
+ <text class="brand-subtitle">AI智能零售柜 · 即拿即走</text>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <view class="feature-tags">
|
|
|
+ <view class="tag">
|
|
|
+ <text class="tag-icon">⚡</text>
|
|
|
+ <text class="tag-text">秒级开门</text>
|
|
|
</view>
|
|
|
+ <view class="tag">
|
|
|
+ <text class="tag-icon">🔒</text>
|
|
|
+ <text class="tag-text">安全支付</text>
|
|
|
+ </view>
|
|
|
+ <view class="tag">
|
|
|
+ <text class="tag-icon">🤖</text>
|
|
|
+ <text class="tag-text">AI识别</text>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <!-- 登录卡片 -->
|
|
|
+ <view class="login-card">
|
|
|
+ <view class="card-header">
|
|
|
+ <view class="header-line"></view>
|
|
|
+ <text class="card-title">快速登录</text>
|
|
|
+ <view class="header-line"></view>
|
|
|
</view>
|
|
|
|
|
|
- <button class="login-btn" @click="onPasswordLogin" :loading="isLoading">
|
|
|
- <text class="btn-text">账号密码登录</text>
|
|
|
+ <!-- 微信手机号快速登录按钮 -->
|
|
|
+ <button
|
|
|
+ class="wechat-login-btn"
|
|
|
+ open-type="getPhoneNumber"
|
|
|
+ @getphonenumber="onGetPhoneNumber"
|
|
|
+ :loading="isLoading"
|
|
|
+ hover-class="btn-hover"
|
|
|
+ >
|
|
|
+ <view class="btn-shine"></view>
|
|
|
+ <text class="btn-text">一键登录</text>
|
|
|
</button>
|
|
|
</view>
|
|
|
|
|
|
<!-- 用户协议 -->
|
|
|
- <view class="agreement">
|
|
|
- <text class="agreement-text">登录即代表您同意</text>
|
|
|
- <text class="agreement-link">《用户协议》</text>
|
|
|
- <text class="agreement-text">和</text>
|
|
|
- <text class="agreement-link">《隐私政策》</text>
|
|
|
+ <view class="agreement-section">
|
|
|
+ <view class="agreement-card">
|
|
|
+ <text class="agreement-prefix">登录即表示您已阅读并同意</text>
|
|
|
+ <view class="agreement-links">
|
|
|
+ <text class="link-item" @tap="goToServiceAgreement">《用户服务协议》</text>
|
|
|
+ <text class="link-sep">与</text>
|
|
|
+ <text class="link-item" @tap="goToPrivacyPolicy">《隐私政策》</text>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <!-- 底部装饰 -->
|
|
|
+ <view class="footer-decoration">
|
|
|
+ <view class="dot-pattern">
|
|
|
+ <view class="dot"></view>
|
|
|
+ <view class="dot"></view>
|
|
|
+ <view class="dot"></view>
|
|
|
+ </view>
|
|
|
</view>
|
|
|
</view>
|
|
|
</view>
|
|
|
</template>
|
|
|
|
|
|
<script setup lang="ts">
|
|
|
-import { ref, reactive } from 'vue';
|
|
|
+import { ref } from 'vue';
|
|
|
import { onLoad } from '@dcloudio/uni-app';
|
|
|
-import { loginByPassword, loginByMiniappPhone } from '@/api/user';
|
|
|
+import { loginByMiniappPhone } from '@/api/user';
|
|
|
import { setToken, setUserInfo } from '@/utils/auth';
|
|
|
|
|
|
-const loginForm = reactive({
|
|
|
- phone: '',
|
|
|
- password: ''
|
|
|
-});
|
|
|
-
|
|
|
const isLoading = ref(false);
|
|
|
|
|
|
// 登录后跳转的页面
|
|
|
@@ -107,38 +113,11 @@ onLoad((options: any) => {
|
|
|
}
|
|
|
});
|
|
|
|
|
|
-const onPasswordLogin = async () => {
|
|
|
- if (!loginForm.phone || loginForm.phone.length !== 11) {
|
|
|
- uni.showToast({ title: '请输入正确的手机号', icon: 'none' });
|
|
|
- return;
|
|
|
- }
|
|
|
- if (!loginForm.password) {
|
|
|
- uni.showToast({ title: '请输入密码', icon: 'none' });
|
|
|
- return;
|
|
|
- }
|
|
|
-
|
|
|
- isLoading.value = true;
|
|
|
- uni.showLoading({ title: '登录中...' });
|
|
|
-
|
|
|
- try {
|
|
|
- const res = await loginByPassword({
|
|
|
- phone: loginForm.phone,
|
|
|
- password: loginForm.password
|
|
|
- });
|
|
|
- handleLoginSuccess(res);
|
|
|
- } catch (error) {
|
|
|
- console.error('登录失败', error);
|
|
|
- } finally {
|
|
|
- uni.hideLoading();
|
|
|
- isLoading.value = false;
|
|
|
- }
|
|
|
-};
|
|
|
-
|
|
|
-/**
|
|
|
- * 微信手机号快捷登录
|
|
|
- */
|
|
|
const onGetPhoneNumber = async (e: any) => {
|
|
|
+ console.log('[登录] 获取手机号事件:', e.detail);
|
|
|
+
|
|
|
if (e.detail.errMsg !== 'getPhoneNumber:ok') {
|
|
|
+ console.error('[登录] 获取手机号失败:', e.detail.errMsg);
|
|
|
uni.showToast({
|
|
|
title: '获取手机号失败,请重试',
|
|
|
icon: 'none'
|
|
|
@@ -151,63 +130,53 @@ const onGetPhoneNumber = async (e: any) => {
|
|
|
|
|
|
try {
|
|
|
// 获取登录code
|
|
|
+ console.log('[登录] 开始获取login code...');
|
|
|
const loginRes = await new Promise<any>((resolve, reject) => {
|
|
|
uni.login({
|
|
|
provider: 'weixin',
|
|
|
- success: resolve,
|
|
|
- fail: reject
|
|
|
+ success: (res) => {
|
|
|
+ console.log('[登录] login成功:', res);
|
|
|
+ resolve(res);
|
|
|
+ },
|
|
|
+ fail: (err) => {
|
|
|
+ console.error('[登录] login失败:', err);
|
|
|
+ reject(err);
|
|
|
+ }
|
|
|
});
|
|
|
});
|
|
|
|
|
|
+ console.log('[登录] loginRes:', loginRes);
|
|
|
+ console.log('[登录] phoneCode:', e.detail.code);
|
|
|
+
|
|
|
const { code, encryptedData, iv } = e.detail;
|
|
|
const res = await loginByMiniappPhone({
|
|
|
- code: loginRes.code, // 用于获取openid
|
|
|
- phoneCode: code // 用于获取手机号
|
|
|
+ code: loginRes.code,
|
|
|
+ phoneCode: code
|
|
|
});
|
|
|
+ console.log('[登录] 登录成功:', res);
|
|
|
handleLoginSuccess(res);
|
|
|
- } catch (error) {
|
|
|
- console.error('微信手机号登录失败', error);
|
|
|
- } finally {
|
|
|
- uni.hideLoading();
|
|
|
- isLoading.value = false;
|
|
|
- }
|
|
|
-};
|
|
|
-
|
|
|
-/**
|
|
|
- * 微信手机号快捷登录 (已禁用)
|
|
|
- */
|
|
|
-/*
|
|
|
-const onGetPhoneNumber = async (e: any) => {
|
|
|
- if (e.detail.errMsg !== 'getPhoneNumber:ok') {
|
|
|
+ } catch (error: any) {
|
|
|
+ console.error('[登录] 微信手机号登录失败', error);
|
|
|
+ console.error('[登录] 错误详情:', error.message || error);
|
|
|
uni.showToast({
|
|
|
- title: '登录失败,请重试',
|
|
|
- icon: 'none'
|
|
|
+ title: error.message || '登录失败,请重试',
|
|
|
+ icon: 'none',
|
|
|
+ duration: 3000
|
|
|
});
|
|
|
- return;
|
|
|
- }
|
|
|
- const { code, encryptedData, iv } = e.detail;
|
|
|
- uni.showLoading({ title: '登录中...' });
|
|
|
- try {
|
|
|
- const res = await loginByWechatPhone({ code, encryptedData, iv });
|
|
|
- handleLoginSuccess(res);
|
|
|
- } catch (error) {
|
|
|
- console.error('登录失败', error);
|
|
|
} finally {
|
|
|
uni.hideLoading();
|
|
|
+ isLoading.value = false;
|
|
|
}
|
|
|
};
|
|
|
-*/
|
|
|
|
|
|
const handleLoginSuccess = (res: any) => {
|
|
|
console.log('[登录成功] 收到的响应数据:', res);
|
|
|
console.log('[登录成功] token:', res.token);
|
|
|
console.log('[登录成功] userInfo:', res.userInfo);
|
|
|
|
|
|
- // 使用auth工具保存token和用户信息
|
|
|
setToken(res.token);
|
|
|
setUserInfo(res.userInfo);
|
|
|
|
|
|
- // 验证保存是否成功
|
|
|
const savedToken = uni.getStorageSync('accessToken');
|
|
|
console.log('[登录成功] 保存后验证 - token:', savedToken ? '已保存' : '保存失败');
|
|
|
|
|
|
@@ -218,12 +187,10 @@ const handleLoginSuccess = (res: any) => {
|
|
|
icon: 'success'
|
|
|
});
|
|
|
|
|
|
- // 根据redirect参数跳转
|
|
|
setTimeout(() => {
|
|
|
if (redirectUrl.value) {
|
|
|
uni.reLaunch({ url: redirectUrl.value });
|
|
|
} else {
|
|
|
- // 返回上一页或首页
|
|
|
const pages = getCurrentPages();
|
|
|
if (pages.length > 1) {
|
|
|
uni.navigateBack();
|
|
|
@@ -233,208 +200,468 @@ const handleLoginSuccess = (res: any) => {
|
|
|
}
|
|
|
}, 1000);
|
|
|
};
|
|
|
+
|
|
|
+// 跳转到用户服务协议
|
|
|
+const goToServiceAgreement = () => {
|
|
|
+ uni.navigateTo({
|
|
|
+ url: '/pages/agreement/serviceAgreement'
|
|
|
+ });
|
|
|
+};
|
|
|
+
|
|
|
+// 跳转到隐私政策
|
|
|
+const goToPrivacyPolicy = () => {
|
|
|
+ uni.navigateTo({
|
|
|
+ url: '/pages/agreement/privacyPolicy'
|
|
|
+ });
|
|
|
+};
|
|
|
</script>
|
|
|
|
|
|
<style lang="scss">
|
|
|
.container {
|
|
|
+ min-height: 100vh;
|
|
|
+ background: linear-gradient(180deg, #FFFBF0 0%, #FFF9E6 30%, #FFFFFF 70%);
|
|
|
+ position: relative;
|
|
|
+ overflow: hidden;
|
|
|
+}
|
|
|
+
|
|
|
+/* ========== 动态背景层 ========== */
|
|
|
+.bg-layer {
|
|
|
+ position: fixed;
|
|
|
+ top: 0;
|
|
|
+ left: 0;
|
|
|
+ right: 0;
|
|
|
+ bottom: 0;
|
|
|
+ z-index: 0;
|
|
|
+ pointer-events: none;
|
|
|
+ contain: layout style paint;
|
|
|
+}
|
|
|
+
|
|
|
+.gradient-orb {
|
|
|
+ position: absolute;
|
|
|
+ border-radius: 50%;
|
|
|
+ will-change: transform, opacity;
|
|
|
+ transform: translateZ(0);
|
|
|
+ backface-visibility: hidden;
|
|
|
+
|
|
|
+ &.orb-1 {
|
|
|
+ width: 500rpx;
|
|
|
+ height: 500rpx;
|
|
|
+ background: radial-gradient(circle, rgba(255, 215, 0, 0.2) 0%, transparent 70%);
|
|
|
+ top: -150rpx;
|
|
|
+ right: -100rpx;
|
|
|
+ animation: float 8s ease-in-out infinite;
|
|
|
+ }
|
|
|
+
|
|
|
+ &.orb-2 {
|
|
|
+ width: 400rpx;
|
|
|
+ height: 400rpx;
|
|
|
+ background: radial-gradient(circle, rgba(255, 193, 7, 0.15) 0%, transparent 70%);
|
|
|
+ top: 200rpx;
|
|
|
+ left: -120rpx;
|
|
|
+ animation: float 10s ease-in-out infinite reverse;
|
|
|
+ }
|
|
|
+
|
|
|
+ &.orb-3 {
|
|
|
+ width: 350rpx;
|
|
|
+ height: 350rpx;
|
|
|
+ background: radial-gradient(circle, rgba(255, 235, 59, 0.12) 0%, transparent 70%);
|
|
|
+ bottom: 200rpx;
|
|
|
+ right: -80rpx;
|
|
|
+ animation: float 12s ease-in-out infinite 2s;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.wave-container {
|
|
|
+ position: absolute;
|
|
|
+ bottom: 0;
|
|
|
+ left: 0;
|
|
|
+ right: 0;
|
|
|
+ height: 400rpx;
|
|
|
+ overflow: hidden;
|
|
|
+}
|
|
|
+
|
|
|
+.wave {
|
|
|
+ position: absolute;
|
|
|
+ bottom: 0;
|
|
|
+ left: -50%;
|
|
|
+ width: 200%;
|
|
|
+ height: 400rpx;
|
|
|
+ background: linear-gradient(180deg, transparent 0%, rgba(255, 215, 0, 0.05) 100%);
|
|
|
+ border-radius: 45% 48% 43% 47%;
|
|
|
+ animation: wave 15s linear infinite;
|
|
|
+
|
|
|
+ &.wave-2 {
|
|
|
+ display: none; /* 移除第二道波浪以提升性能 */
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+/* ========== 主内容区 ========== */
|
|
|
+.main-content {
|
|
|
+ position: relative;
|
|
|
+ z-index: 1;
|
|
|
display: flex;
|
|
|
flex-direction: column;
|
|
|
- min-height: 100vh;
|
|
|
- background: linear-gradient(180deg, $color-primary-bg 0%, $color-bg-secondary 40%);
|
|
|
- padding: 0;
|
|
|
+ padding: 0 40rpx;
|
|
|
+ padding-bottom: calc(160rpx + constant(safe-area-inset-bottom));
|
|
|
+ padding-bottom: calc(160rpx + env(safe-area-inset-bottom));
|
|
|
}
|
|
|
|
|
|
-/* ========== 品牌区 ========== */
|
|
|
-.brand-section {
|
|
|
+/* ========== 品牌展示区 ========== */
|
|
|
+.brand-showcase {
|
|
|
display: flex;
|
|
|
flex-direction: column;
|
|
|
align-items: center;
|
|
|
- padding: 120rpx 40rpx 80rpx;
|
|
|
- animation: slideDown 0.6s cubic-bezier(0.25, 0.1, 0.25, 1);
|
|
|
-
|
|
|
- .brand-logo {
|
|
|
- width: 160rpx;
|
|
|
- height: 160rpx;
|
|
|
- margin-bottom: $spacing-lg;
|
|
|
- animation: scaleIn 0.8s cubic-bezier(0.68, -0.55, 0.265, 1.55);
|
|
|
-
|
|
|
- .logo {
|
|
|
- width: 100%;
|
|
|
- height: 100%;
|
|
|
+ padding-top: 160rpx;
|
|
|
+ padding-bottom: 80rpx;
|
|
|
+ animation: slideDown 0.8s cubic-bezier(0.25, 0.1, 0.25, 1);
|
|
|
+}
|
|
|
+
|
|
|
+.logo-wrapper {
|
|
|
+ margin-bottom: 56rpx;
|
|
|
+ will-change: transform;
|
|
|
+ transform: translateZ(0);
|
|
|
+
|
|
|
+ .logo-outer-ring {
|
|
|
+ width: 280rpx;
|
|
|
+ height: 280rpx;
|
|
|
+ border-radius: 50%;
|
|
|
+ border: 2rpx solid rgba(255, 215, 0, 0.25);
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ animation: ringRotate 20s linear infinite;
|
|
|
+
|
|
|
+ .logo-inner-ring {
|
|
|
+ width: 256rpx;
|
|
|
+ height: 256rpx;
|
|
|
+ border-radius: 50%;
|
|
|
+ border: 2rpx dashed rgba(255, 215, 0, 0.3);
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ position: relative;
|
|
|
+
|
|
|
+ .logo-glow {
|
|
|
+ position: absolute;
|
|
|
+ width: 240rpx;
|
|
|
+ height: 240rpx;
|
|
|
+ border-radius: 50%;
|
|
|
+ background: radial-gradient(circle, rgba(255, 215, 0, 0.15) 0%, transparent 70%);
|
|
|
+ /* 移除filter: blur()以提升性能 */
|
|
|
+ opacity: 0.8;
|
|
|
+ animation: pulse 4s ease-in-out infinite;
|
|
|
+ }
|
|
|
+
|
|
|
+ .logo-circle {
|
|
|
+ width: 220rpx;
|
|
|
+ height: 220rpx;
|
|
|
+ background: linear-gradient(135deg, #FFFFFF 0%, #FFFEF5 100%);
|
|
|
+ border-radius: 50%;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ box-shadow:
|
|
|
+ 0 16rpx 40rpx rgba(255, 193, 7, 0.2),
|
|
|
+ inset 0 2rpx 8rpx rgba(255, 255, 255, 0.6);
|
|
|
+ padding: 28rpx;
|
|
|
+ position: relative;
|
|
|
+ z-index: 1;
|
|
|
+
|
|
|
+ .logo {
|
|
|
+ width: 100%;
|
|
|
+ height: 100%;
|
|
|
+ /* 移除drop-shadow滤镜 */
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
+}
|
|
|
|
|
|
+.brand-text-group {
|
|
|
+ text-align: center;
|
|
|
+ margin-bottom: 48rpx;
|
|
|
+
|
|
|
.brand-title {
|
|
|
- font-size: 48rpx;
|
|
|
- font-weight: 300;
|
|
|
- color: $color-text-primary;
|
|
|
- margin-bottom: $spacing-sm;
|
|
|
+ font-size: 56rpx;
|
|
|
+ font-weight: 700;
|
|
|
+ background: linear-gradient(135deg, #333333 0%, #666666 100%);
|
|
|
+ -webkit-background-clip: text;
|
|
|
+ -webkit-text-fill-color: transparent;
|
|
|
+ background-clip: text;
|
|
|
letter-spacing: 4rpx;
|
|
|
+ display: block;
|
|
|
+ margin-bottom: 16rpx;
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
.brand-subtitle {
|
|
|
- font-size: 24rpx;
|
|
|
- color: $color-text-secondary;
|
|
|
+ font-size: 26rpx;
|
|
|
+ color: #888888;
|
|
|
letter-spacing: 2rpx;
|
|
|
+ font-weight: 400;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-/* ========== 表单区 ========== */
|
|
|
-.form-section {
|
|
|
- flex: 1;
|
|
|
- padding: 0 40rpx;
|
|
|
- animation: slideUp 0.8s cubic-bezier(0.25, 0.1, 0.25, 1);
|
|
|
-}
|
|
|
-
|
|
|
-.form-card {
|
|
|
- background: $color-bg-primary;
|
|
|
- border-radius: $radius-xl;
|
|
|
- padding: $spacing-xl;
|
|
|
- box-shadow: $shadow-md;
|
|
|
- margin-bottom: $spacing-lg;
|
|
|
-}
|
|
|
-
|
|
|
-.form-item {
|
|
|
- margin-bottom: $spacing-lg;
|
|
|
+.feature-tags {
|
|
|
+ display: flex;
|
|
|
+ gap: 20rpx;
|
|
|
+ justify-content: center;
|
|
|
|
|
|
- .input-wrapper {
|
|
|
+ .tag {
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
- background: $color-bg-secondary;
|
|
|
- border-radius: $radius-lg;
|
|
|
- padding: 0 $spacing-md;
|
|
|
- transition: all $duration-fast $ease-out;
|
|
|
- border: 2rpx solid transparent;
|
|
|
-
|
|
|
- &:focus-within {
|
|
|
- background: $color-bg-primary;
|
|
|
- border-color: $color-primary;
|
|
|
- box-shadow: 0 0 0 4rpx rgba(255, 193, 7, 0.1);
|
|
|
+ gap: 8rpx;
|
|
|
+ padding: 12rpx 20rpx;
|
|
|
+ background: rgba(255, 255, 255, 0.9);
|
|
|
+ border-radius: 40rpx;
|
|
|
+ box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.06);
|
|
|
+ border: 1rpx solid rgba(255, 215, 0, 0.2);
|
|
|
+
|
|
|
+ .tag-icon {
|
|
|
+ font-size: 24rpx;
|
|
|
}
|
|
|
|
|
|
- .input-icon {
|
|
|
- width: 40rpx;
|
|
|
- height: 40rpx;
|
|
|
- margin-right: $spacing-sm;
|
|
|
- flex-shrink: 0;
|
|
|
-
|
|
|
- svg {
|
|
|
- width: 100%;
|
|
|
- height: 100%;
|
|
|
- }
|
|
|
+ .tag-text {
|
|
|
+ font-size: 22rpx;
|
|
|
+ color: #555555;
|
|
|
+ font-weight: 500;
|
|
|
}
|
|
|
+ }
|
|
|
+}
|
|
|
|
|
|
- .form-input {
|
|
|
- flex: 1;
|
|
|
- height: 96rpx;
|
|
|
- font-size: 28rpx;
|
|
|
- color: $color-text-primary;
|
|
|
- background: transparent;
|
|
|
- border: none;
|
|
|
- }
|
|
|
+/* ========== 登录卡片 ========== */
|
|
|
+.login-card {
|
|
|
+ background: rgba(255, 255, 255, 0.98);
|
|
|
+ border-radius: 32rpx;
|
|
|
+ padding: 56rpx 40rpx;
|
|
|
+ margin-bottom: 40rpx;
|
|
|
+ box-shadow:
|
|
|
+ 0 16rpx 48rpx rgba(0, 0, 0, 0.06),
|
|
|
+ inset 0 1rpx 0 rgba(255, 255, 255, 0.8);
|
|
|
+ border: 1rpx solid rgba(255, 215, 0, 0.15);
|
|
|
+ animation: slideUp 0.8s cubic-bezier(0.25, 0.1, 0.25, 1) 0.3s both;
|
|
|
+ will-change: transform, opacity;
|
|
|
+ transform: translateZ(0);
|
|
|
+}
|
|
|
|
|
|
- .input-placeholder {
|
|
|
- color: $color-text-tertiary;
|
|
|
+.card-header {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ gap: 24rpx;
|
|
|
+ margin-bottom: 60rpx;
|
|
|
+
|
|
|
+ .header-line {
|
|
|
+ width: 80rpx;
|
|
|
+ height: 2rpx;
|
|
|
+ background: linear-gradient(90deg, transparent 0%, rgba(255, 215, 0, 0.4) 100%);
|
|
|
+
|
|
|
+ &:last-child {
|
|
|
+ background: linear-gradient(90deg, rgba(255, 215, 0, 0.4) 0%, transparent 100%);
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
+ .card-title {
|
|
|
+ font-size: 30rpx;
|
|
|
+ font-weight: 600;
|
|
|
+ color: #333333;
|
|
|
+ letter-spacing: 2rpx;
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
.wechat-login-btn {
|
|
|
width: 100%;
|
|
|
- height: 96rpx;
|
|
|
- background: linear-gradient(135deg, #07C160 0%, #06AD56 100%);
|
|
|
- border-radius: $radius-lg;
|
|
|
+ min-height: 108rpx;
|
|
|
+ background: linear-gradient(135deg, #FFD700 0%, #FFC107 50%, #FFB300 100%);
|
|
|
+ border-radius: 54rpx;
|
|
|
border: none;
|
|
|
- margin-bottom: $spacing-lg;
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
justify-content: center;
|
|
|
- box-shadow: 0 4rpx 16rpx rgba(7, 193, 96, 0.3);
|
|
|
- transition: all $duration-normal $ease-out;
|
|
|
+ position: relative;
|
|
|
+ overflow: hidden;
|
|
|
+ box-shadow:
|
|
|
+ 0 12rpx 40rpx rgba(255, 193, 7, 0.35),
|
|
|
+ 0 4rpx 16rpx rgba(255, 193, 7, 0.2),
|
|
|
+ inset 0 2rpx 0 rgba(255, 255, 255, 0.3);
|
|
|
+ transition: all 0.3s cubic-bezier(0.25, 0.1, 0.25, 1);
|
|
|
|
|
|
&::after {
|
|
|
border: none;
|
|
|
}
|
|
|
|
|
|
- &:active {
|
|
|
- transform: scale(0.98);
|
|
|
- box-shadow: 0 4rpx 16rpx rgba(7, 193, 96, 0.4);
|
|
|
+ .btn-shine {
|
|
|
+ position: absolute;
|
|
|
+ top: 0;
|
|
|
+ left: -100%;
|
|
|
+ width: 100%;
|
|
|
+ height: 100%;
|
|
|
+ background: linear-gradient(
|
|
|
+ 90deg,
|
|
|
+ transparent 0%,
|
|
|
+ rgba(255, 255, 255, 0.3) 50%,
|
|
|
+ transparent 100%
|
|
|
+ );
|
|
|
+ transition: left 0.6s ease;
|
|
|
}
|
|
|
|
|
|
- .btn-icon {
|
|
|
- font-size: 32rpx;
|
|
|
- margin-right: $spacing-sm;
|
|
|
+ &.btn-hover {
|
|
|
+ transform: translateY(-4rpx) scale(1.02);
|
|
|
+ box-shadow:
|
|
|
+ 0 16rpx 48rpx rgba(255, 193, 7, 0.45),
|
|
|
+ 0 6rpx 20rpx rgba(255, 193, 7, 0.25),
|
|
|
+ inset 0 2rpx 0 rgba(255, 255, 255, 0.4);
|
|
|
+
|
|
|
+ .btn-shine {
|
|
|
+ left: 100%;
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
.btn-text {
|
|
|
- font-size: 32rpx;
|
|
|
- font-weight: 500;
|
|
|
- color: #FFFFFF;
|
|
|
+ font-size: 36rpx;
|
|
|
+ font-weight: 600;
|
|
|
+ color: #1A1A1A;
|
|
|
+ letter-spacing: 8rpx;
|
|
|
+ position: relative;
|
|
|
+ z-index: 1;
|
|
|
+ white-space: nowrap;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-.divider {
|
|
|
- display: flex;
|
|
|
- align-items: center;
|
|
|
- margin: $spacing-lg 0;
|
|
|
-
|
|
|
- .divider-line {
|
|
|
- flex: 1;
|
|
|
- height: 1rpx;
|
|
|
- background: $color-border-light;
|
|
|
- }
|
|
|
-
|
|
|
- .divider-text {
|
|
|
+/* ========== 用户协议 ========== */
|
|
|
+.agreement-section {
|
|
|
+ animation: fadeIn 1s cubic-bezier(0.25, 0.1, 0.25, 1) 0.6s both;
|
|
|
+}
|
|
|
+
|
|
|
+.agreement-card {
|
|
|
+ text-align: center;
|
|
|
+ padding: 28rpx 36rpx;
|
|
|
+ background: rgba(255, 255, 255, 0.85);
|
|
|
+ border-radius: 20rpx;
|
|
|
+ border: 1rpx solid rgba(0, 0, 0, 0.04);
|
|
|
+
|
|
|
+ .agreement-prefix {
|
|
|
font-size: 24rpx;
|
|
|
- color: $color-text-tertiary;
|
|
|
- padding: 0 $spacing-md;
|
|
|
+ color: #AAAAAA;
|
|
|
+ display: block;
|
|
|
+ margin-bottom: 8rpx;
|
|
|
+ }
|
|
|
+
|
|
|
+ .agreement-links {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ gap: 8rpx;
|
|
|
+ flex-wrap: wrap;
|
|
|
+
|
|
|
+ .link-item {
|
|
|
+ font-size: 24rpx;
|
|
|
+ color: #FFB300;
|
|
|
+ font-weight: 600;
|
|
|
+ text-decoration: underline;
|
|
|
+ text-decoration-color: rgba(255, 179, 0, 0.3);
|
|
|
+ cursor: pointer;
|
|
|
+
|
|
|
+ &:active {
|
|
|
+ opacity: 0.6;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .link-sep {
|
|
|
+ font-size: 24rpx;
|
|
|
+ color: #CCCCCC;
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-.login-btn {
|
|
|
- width: 100%;
|
|
|
- height: 96rpx;
|
|
|
- background: linear-gradient(135deg, $color-primary-light 0%, $color-primary 100%);
|
|
|
- border-radius: $radius-lg;
|
|
|
- border: none;
|
|
|
- margin-top: $spacing-xl;
|
|
|
+/* ========== 底部装饰 ========== */
|
|
|
+.footer-decoration {
|
|
|
display: flex;
|
|
|
- align-items: center;
|
|
|
justify-content: center;
|
|
|
- box-shadow: $shadow-primary;
|
|
|
- transition: all $duration-normal $ease-out;
|
|
|
+ margin-top: auto;
|
|
|
+ padding-top: 60rpx;
|
|
|
|
|
|
- &::after {
|
|
|
- border: none;
|
|
|
+ .dot-pattern {
|
|
|
+ display: flex;
|
|
|
+ gap: 12rpx;
|
|
|
+
|
|
|
+ .dot {
|
|
|
+ width: 12rpx;
|
|
|
+ height: 12rpx;
|
|
|
+ border-radius: 50%;
|
|
|
+ background: linear-gradient(135deg, #FFD700 0%, #FFC107 100%);
|
|
|
+ opacity: 0.4;
|
|
|
+ /* 移除脉冲动画以提升性能 */
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+/* ========== 动画关键帧 ========== */
|
|
|
+@keyframes float {
|
|
|
+ 0%, 100% {
|
|
|
+ transform: translateY(0) scale(1);
|
|
|
+ }
|
|
|
+ 50% {
|
|
|
+ transform: translateY(-30rpx) scale(1.05);
|
|
|
}
|
|
|
+}
|
|
|
|
|
|
- &:active {
|
|
|
- transform: scale(0.98);
|
|
|
- box-shadow: 0 4rpx 16rpx rgba(255, 193, 7, 0.3);
|
|
|
+@keyframes wave {
|
|
|
+ 0% {
|
|
|
+ transform: translateX(0) rotate(0deg);
|
|
|
}
|
|
|
+ 100% {
|
|
|
+ transform: translateX(50%) rotate(360deg);
|
|
|
+ }
|
|
|
+}
|
|
|
|
|
|
- .btn-text {
|
|
|
- font-size: 32rpx;
|
|
|
- font-weight: 500;
|
|
|
- color: $color-text-primary;
|
|
|
+@keyframes slideDown {
|
|
|
+ from {
|
|
|
+ opacity: 0;
|
|
|
+ transform: translateY(-60rpx);
|
|
|
+ }
|
|
|
+ to {
|
|
|
+ opacity: 1;
|
|
|
+ transform: translateY(0);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-/* ========== 用户协议 ========== */
|
|
|
-.agreement {
|
|
|
- text-align: center;
|
|
|
- padding: 0 $spacing-lg;
|
|
|
- animation: fadeIn 1s cubic-bezier(0.25, 0.1, 0.25, 1);
|
|
|
+@keyframes slideUp {
|
|
|
+ from {
|
|
|
+ opacity: 0;
|
|
|
+ transform: translateY(60rpx);
|
|
|
+ }
|
|
|
+ to {
|
|
|
+ opacity: 1;
|
|
|
+ transform: translateY(0);
|
|
|
+ }
|
|
|
+}
|
|
|
|
|
|
- .agreement-text {
|
|
|
- font-size: 24rpx;
|
|
|
- color: $color-text-tertiary;
|
|
|
+@keyframes fadeIn {
|
|
|
+ from {
|
|
|
+ opacity: 0;
|
|
|
+ }
|
|
|
+ to {
|
|
|
+ opacity: 1;
|
|
|
}
|
|
|
+}
|
|
|
|
|
|
- .agreement-link {
|
|
|
- font-size: 24rpx;
|
|
|
- color: $color-primary-dark;
|
|
|
- font-weight: 500;
|
|
|
+@keyframes pulse {
|
|
|
+ 0%, 100% {
|
|
|
+ opacity: 0.25;
|
|
|
+ transform: scale(1);
|
|
|
+ }
|
|
|
+ 50% {
|
|
|
+ opacity: 0.4;
|
|
|
+ transform: scale(1.05);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+@keyframes ringRotate {
|
|
|
+ from {
|
|
|
+ transform: rotate(0deg);
|
|
|
+ }
|
|
|
+ to {
|
|
|
+ transform: rotate(360deg);
|
|
|
}
|
|
|
}
|
|
|
</style>
|