feat:修改app注册,登录页面和流程
@ -8,8 +8,24 @@ import { getDeviceFingerprint } from "@/utils/deviceFingerprint.js";
|
|||||||
const HIDE_TIME_KEY = "app_last_hide_time";
|
const HIDE_TIME_KEY = "app_last_hide_time";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
// 全局内存级标记,配合 uni.storage 中的 needs_welcome 一起判定
|
||||||
|
// TopfansWelcome 是否展示。onLaunch 触发时重置为 false(冷启动 = 新会话),
|
||||||
|
// 进入 square 后由 handleEnterTopfans 设为 true(本次会话内不再展示)。
|
||||||
|
globalData: {
|
||||||
|
welcomeShownThisSession: false,
|
||||||
|
},
|
||||||
onLaunch: function () {
|
onLaunch: function () {
|
||||||
console.log("App Launch");
|
console.log("App Launch");
|
||||||
|
// 【TopfansWelcome 会话判定】
|
||||||
|
// 触发场景(即用户期望展示欢迎页的入口):
|
||||||
|
// 1) "后台关闭才会在打开":用户在后台杀掉进程后重新打开 → onLaunch 触发 → 新会话
|
||||||
|
// 2) "每次第一次启动":冷启动 / 全新安装后首次启动 → onLaunch 触发 → 新会话
|
||||||
|
// 不触发的场景:
|
||||||
|
// - 仅按 home 键前后台切换(onShow 而非 onLaunch)→ 标记保持原值 → 不展示
|
||||||
|
// - 用户已经点过"进入 TOPFANS 世界" → 标记为 true → 不展示
|
||||||
|
// 注:storage 中的 needs_welcome 仍保留,由登录/注册流程负责写入,
|
||||||
|
// 只有 app 销毁(卸载/清缓存)才会随本地存储一起丢失。
|
||||||
|
this.globalData.welcomeShownThisSession = false;
|
||||||
// 启动早期 bootstrap 设备指纹(spec §9.1 v2.3 — X-Device-Fingerprint)
|
// 启动早期 bootstrap 设备指纹(spec §9.1 v2.3 — X-Device-Fingerprint)
|
||||||
// getDeviceFingerprint 内部自包含 setStorageSync,但显式调一次可避免首次网络请求走临时生成
|
// getDeviceFingerprint 内部自包含 setStorageSync,但显式调一次可避免首次网络请求走临时生成
|
||||||
getDeviceFingerprint();
|
getDeviceFingerprint();
|
||||||
|
|||||||
149
frontend/components/LoginBackground.vue
Normal file
@ -0,0 +1,149 @@
|
|||||||
|
<template>
|
||||||
|
<view class="login-bg">
|
||||||
|
<!-- 状态栏占位 -->
|
||||||
|
<view class="status-bar"></view>
|
||||||
|
|
||||||
|
<view v-if="showBack" class="nav-back" @tap="goBack">
|
||||||
|
<text class="nav-back-icon">←</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 背景图片层 -->
|
||||||
|
<image
|
||||||
|
class="background-image"
|
||||||
|
src="/static/login/portal/bg.png"
|
||||||
|
mode="aspectFill"
|
||||||
|
></image>
|
||||||
|
|
||||||
|
<!-- 女孩图片(点缀) -->
|
||||||
|
<image
|
||||||
|
class="character-upper"
|
||||||
|
src="/static/login/portal/character1.png"
|
||||||
|
mode="aspectFit"
|
||||||
|
></image>
|
||||||
|
|
||||||
|
<!-- 粉色玩偶(点缀) -->
|
||||||
|
<image
|
||||||
|
class="character-lower"
|
||||||
|
src="/static/login/portal/character2.png"
|
||||||
|
mode="aspectFit"
|
||||||
|
></image>
|
||||||
|
|
||||||
|
<!-- TOPFANS 标题 -->
|
||||||
|
<view v-if="showLogo" class="title-wrapper">
|
||||||
|
<image
|
||||||
|
class="topfans-logo"
|
||||||
|
src="/static/login/portal/topfans-logo.png"
|
||||||
|
mode="aspectFit"
|
||||||
|
></image>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 内容插槽 -->
|
||||||
|
<slot></slot>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
defineProps({
|
||||||
|
showLogo: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
|
showBack: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const goBack = () => {
|
||||||
|
uni.navigateBack();
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.login-bg {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
min-height: 100vh;
|
||||||
|
height: 100vh;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-bar {
|
||||||
|
width: 100%;
|
||||||
|
height: 44rpx;
|
||||||
|
position: relative;
|
||||||
|
z-index: 10;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-back {
|
||||||
|
width: 80rpx;
|
||||||
|
height: 80rpx;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
position: absolute;
|
||||||
|
top: 96rpx;
|
||||||
|
left: 32rpx;
|
||||||
|
/* background: rgba(255,255,255,0.5);
|
||||||
|
border-radius: 50%; */
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-back-icon {
|
||||||
|
font-size: 48rpx;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 背景图:覆盖整个屏幕 */
|
||||||
|
.background-image {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
z-index: 0;
|
||||||
|
opacity: 0.68;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 上方人物图(女孩) */
|
||||||
|
.character-upper {
|
||||||
|
position: absolute;
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%) rotate(0.5deg);
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
z-index: 1;
|
||||||
|
object-fit: contain;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 下方粉色玩偶 */
|
||||||
|
.character-lower {
|
||||||
|
position: absolute;
|
||||||
|
top: 448rpx;
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%) rotate(0.5deg);
|
||||||
|
width: 135%;
|
||||||
|
height: 100%;
|
||||||
|
z-index: 2;
|
||||||
|
object-fit: contain;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* TOPFANS 标题 */
|
||||||
|
.title-wrapper {
|
||||||
|
width: 758rpx;
|
||||||
|
height: 192rpx;
|
||||||
|
position: relative;
|
||||||
|
z-index: 10;
|
||||||
|
padding-top: 30rpx;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.topfans-logo {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
/* filter: drop-shadow(0 4rpx 9rpx rgba(177, 19, 19, 0.49)); */
|
||||||
|
}
|
||||||
|
</style>
|
||||||
144
frontend/components/TopfansWelcome.vue
Normal file
@ -0,0 +1,144 @@
|
|||||||
|
<template>
|
||||||
|
<LoginBackground :show-logo="false">
|
||||||
|
<view class="welcome-container">
|
||||||
|
<!-- 主卡片:女孩 + 粉色玩偶 -->
|
||||||
|
<view class="welcome-card">
|
||||||
|
<image
|
||||||
|
class="welcome-bg"
|
||||||
|
src="/static/login/portal/welcome-bg.png"
|
||||||
|
mode="aspectFill"
|
||||||
|
></image>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 进入 TOPFANS 世界按钮 -->
|
||||||
|
<view class="enter-btn-wrapper">
|
||||||
|
<view class="enter-btn" @tap="handleEnter">
|
||||||
|
<text class="enter-btn-text">进入 TOPFANS 世界</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</LoginBackground>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import LoginBackground from "@/components/LoginBackground.vue";
|
||||||
|
|
||||||
|
const emit = defineEmits(["enter"]);
|
||||||
|
|
||||||
|
const handleEnter = () => {
|
||||||
|
emit("enter");
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.welcome-container {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
z-index: 999;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 背景 */
|
||||||
|
.bg-image {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
opacity: 0.68;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bg-character {
|
||||||
|
position: absolute;
|
||||||
|
top: 60rpx;
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%) rotate(0.5deg);
|
||||||
|
width: 128%;
|
||||||
|
height: 60%;
|
||||||
|
z-index: 1;
|
||||||
|
filter: blur(2rpx);
|
||||||
|
opacity: 0.9;
|
||||||
|
object-fit: contain;
|
||||||
|
object-position: center top;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bg-character-shadow {
|
||||||
|
position: absolute;
|
||||||
|
top: 470rpx;
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%) rotate(0.5deg);
|
||||||
|
width: 145%;
|
||||||
|
height: 50%;
|
||||||
|
z-index: 2;
|
||||||
|
filter: blur(4rpx);
|
||||||
|
opacity: 0.9;
|
||||||
|
object-fit: contain;
|
||||||
|
object-position: center bottom;
|
||||||
|
}
|
||||||
|
|
||||||
|
.topfans-logo-img {
|
||||||
|
position: absolute;
|
||||||
|
top: 100rpx;
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
width: 698rpx;
|
||||||
|
height: 150rpx;
|
||||||
|
z-index: 10;
|
||||||
|
filter: drop-shadow(0 4rpx 18rpx rgba(177, 19, 19, 0.49));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 欢迎卡片 */
|
||||||
|
.welcome-card {
|
||||||
|
position: absolute;
|
||||||
|
top: 128rpx;
|
||||||
|
left: 20rpx;
|
||||||
|
width: 710rpx;
|
||||||
|
height: 1376rpx;
|
||||||
|
border-radius: 56rpx;
|
||||||
|
border: 2rpx solid rgba(255, 255, 255, 0.15);
|
||||||
|
box-shadow: 0 0 41rpx 0 rgba(0, 0, 0, 0.31);
|
||||||
|
overflow: hidden;
|
||||||
|
z-index: 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.welcome-bg {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 进入按钮 */
|
||||||
|
.enter-btn-wrapper {
|
||||||
|
position: absolute;
|
||||||
|
left: 50%;
|
||||||
|
bottom: 160rpx;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
.enter-btn {
|
||||||
|
width: 450rpx;
|
||||||
|
height: 90rpx;
|
||||||
|
border-radius: 58rpx;
|
||||||
|
background: linear-gradient(
|
||||||
|
90deg,
|
||||||
|
rgba(255, 222, 8, 0.38) 0%,
|
||||||
|
rgba(252, 100, 102, 0.78) 64%,
|
||||||
|
rgba(244, 88, 104, 0.78) 100%
|
||||||
|
);
|
||||||
|
box-shadow: 4rpx 4rpx 8rpx 0 rgba(242, 21, 21, 0.47);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
opacity: 0.96;
|
||||||
|
}
|
||||||
|
|
||||||
|
.enter-btn-text {
|
||||||
|
font-size: 46rpx;
|
||||||
|
color: #fff9e7;
|
||||||
|
text-shadow: -2rpx 2rpx 8rpx rgba(0, 0, 0, 0.84);
|
||||||
|
font-family: "Abhaya Libre Medium", sans-serif;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -77,6 +77,24 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"path": "pages/login/portal",
|
||||||
|
"style": {
|
||||||
|
"navigationStyle": "custom",
|
||||||
|
"app-plus": {
|
||||||
|
"bounce": "none"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "pages/login/quickLogin",
|
||||||
|
"style": {
|
||||||
|
"navigationStyle": "custom",
|
||||||
|
"app-plus": {
|
||||||
|
"bounce": "none"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"path": "pages/register/register",
|
"path": "pages/register/register",
|
||||||
"style": {
|
"style": {
|
||||||
|
|||||||
@ -1,709 +1,365 @@
|
|||||||
<template>
|
<template>
|
||||||
<view class="login-container">
|
<LoginBackground :show-logo="true">
|
||||||
<!-- 背景图片层 -->
|
<!-- 内容区域 -->
|
||||||
<image class="background-image" src="/static/background/login-bg.png" mode="aspectFill"></image>
|
<view class="login-content">
|
||||||
<!-- 女孩图片 -->
|
<!-- 头像 -->
|
||||||
<image class="girl-image" src="/static/login/person-photo.png" mode="aspectFit"></image>
|
<view class="avatar-wrapper">
|
||||||
<!-- 粉色玩偶(遮挡女孩脸的下半部分) -->
|
<image
|
||||||
<image class="doll-image" src="/static/login/login-character.png" mode="aspectFit"></image>
|
class="avatar-image"
|
||||||
<view class="background-overlay"></view>
|
src="/static/login/portal/register-avatar.png"
|
||||||
|
mode="aspectFit"
|
||||||
<!-- 内容区域 -->
|
></image>
|
||||||
<view class="content-wrapper">
|
</view>
|
||||||
<!-- TOPFANS 标题 -->
|
|
||||||
<view class="title-wrapper">
|
<!-- 登录卡片 -->
|
||||||
<text class="title-text">TOPFANS</text>
|
<view class="login-card">
|
||||||
</view>
|
<!-- 标题 -->
|
||||||
<!-- 表单区域(居中部分) -->
|
<view class="card-title-wrapper">
|
||||||
<view class="form-container">
|
<text class="card-title">账号密码登录</text>
|
||||||
<view class="form-wrapper">
|
</view>
|
||||||
<!-- 手机号输入框 -->
|
|
||||||
<view class="input-wrapper">
|
<!-- 表单 -->
|
||||||
<input
|
<view class="form-area">
|
||||||
class="input-field"
|
<!-- 手机号 -->
|
||||||
type="number"
|
<view class="input-group">
|
||||||
v-model="form.phone"
|
<view class="input-outer">
|
||||||
placeholder="请输入手机号"
|
<view class="input-inner">
|
||||||
maxlength="11"
|
<input
|
||||||
placeholder-class="input-placeholder"
|
class="input-field"
|
||||||
/>
|
type="number"
|
||||||
</view>
|
v-model="form.phone"
|
||||||
|
placeholder="输入您的手机号"
|
||||||
<!-- 密码输入框 -->
|
placeholder-class="input-placeholder"
|
||||||
<view class="input-wrapper password-wrapper">
|
maxlength="11"
|
||||||
<input
|
/>
|
||||||
class="input-field"
|
</view>
|
||||||
:type="showPassword ? 'text' : 'password'"
|
</view>
|
||||||
v-model="form.password"
|
</view>
|
||||||
placeholder="请输入密码"
|
|
||||||
placeholder-class="input-placeholder"
|
<!-- 密码 -->
|
||||||
/>
|
<view class="input-group">
|
||||||
<view class="eye-icon" @click="togglePassword">
|
<view class="input-outer">
|
||||||
<text class="eye-text">{{ showPassword ? '👁️' : '👁️🗨️' }}</text>
|
<view class="input-inner">
|
||||||
</view>
|
<input
|
||||||
</view>
|
class="input-field"
|
||||||
|
:type="showPassword ? 'text' : 'password'"
|
||||||
<!-- 错误提示 -->
|
v-model="form.password"
|
||||||
<view v-if="errorMessage" class="error-message">
|
placeholder="输入您的密码"
|
||||||
<text>{{ errorMessage }}</text>
|
placeholder-class="input-placeholder"
|
||||||
</view>
|
/>
|
||||||
|
</view>
|
||||||
<!-- 登录按钮 -->
|
</view>
|
||||||
<button class="btn-primary" @click="handleLogin">登录</button>
|
</view>
|
||||||
|
|
||||||
<!-- 注册按钮 -->
|
<!-- 错误提示 -->
|
||||||
<view class="register-link" @click="goToRegister">
|
<view v-if="errorMessage" class="error-message">
|
||||||
<text>还没有账号?去注册</text>
|
<text>{{ errorMessage }}</text>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
|
||||||
|
<!-- 忘记密码 -->
|
||||||
<!-- 底部服务条款 -->
|
<view class="forgot-password-wrapper">
|
||||||
<view class="footer-text">
|
<text class="forgot-password" @tap="handleForgotPassword"
|
||||||
<view class="agreement-wrapper">
|
>忘记密码</text
|
||||||
<view class="agreement-checkbox" @click="toggleAgreement">
|
>
|
||||||
<view class="custom-checkbox" :class="{ 'checked': agreedToTerms }">
|
</view>
|
||||||
<view v-if="agreedToTerms" class="checkbox-inner"></view>
|
|
||||||
</view>
|
<!-- 分割线 -->
|
||||||
<text class="agreement-label">我已阅读并同意</text>
|
<view class="divider"></view>
|
||||||
</view>
|
|
||||||
<text class="agreement-link" @click="showAgreementModal">《Topfans用户服务协议》</text>
|
<!-- 登录按钮 -->
|
||||||
</view>
|
<view class="login-btn-wrapper">
|
||||||
</view>
|
<view class="login-btn" @tap="handleLogin">
|
||||||
</view>
|
<text class="login-btn-text">登录</text>
|
||||||
|
</view>
|
||||||
<!-- 提示弹窗 -->
|
</view>
|
||||||
<view v-if="showTipDialog" class="tip-dialog-mask" @click="closeTipDialog">
|
</view>
|
||||||
<view class="tip-dialog" @click.stop>
|
</view>
|
||||||
<view class="tip-dialog-header">
|
</LoginBackground>
|
||||||
<text class="tip-dialog-title">提示</text>
|
|
||||||
<text class="tip-dialog-close" @click="closeTipDialog">×</text>
|
|
||||||
</view>
|
|
||||||
<view class="tip-dialog-content">
|
|
||||||
<text class="tip-text">阅读并同意以下条款</text>
|
|
||||||
<text class="tip-agreement-link" @click="openAgreementFromTip">《Topfans用户服务协议》</text>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<!-- 协议全文弹窗 -->
|
|
||||||
<view v-if="showAgreementDialog" class="agreement-dialog-mask" @click="closeAgreementDialog">
|
|
||||||
<view class="agreement-dialog" @click.stop>
|
|
||||||
<view class="agreement-dialog-header">
|
|
||||||
<text class="agreement-dialog-title">Topfans用户服务协议</text>
|
|
||||||
<text class="agreement-dialog-close" @click="closeAgreementDialog">×</text>
|
|
||||||
</view>
|
|
||||||
<scroll-view class="agreement-dialog-content" scroll-y>
|
|
||||||
<text class="agreement-text">{{ agreementContent || '协议内容加载中...' }}</text>
|
|
||||||
</scroll-view>
|
|
||||||
<view class="agreement-dialog-footer">
|
|
||||||
<button class="agreement-confirm-btn" @click="agreeAndClose">我同意</button>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref } from 'vue';
|
import { ref } from "vue";
|
||||||
import { onLoad } from '@dcloudio/uni-app';
|
import { onLoad } from "@dcloudio/uni-app";
|
||||||
import { useStore } from 'vuex';
|
import { useStore } from "vuex";
|
||||||
import { validatePhone, validatePassword } from '@/utils/validator';
|
import LoginBackground from "@/components/LoginBackground.vue";
|
||||||
import { AGREEMENT_CONTENT } from '@/utils/agreement';
|
import { validatePhone, validatePassword } from "@/utils/validator";
|
||||||
import { resetAllGuides } from '@/utils/guideConfig';
|
import { resetAllGuides } from "@/utils/guideConfig";
|
||||||
|
|
||||||
const store = useStore();
|
const store = useStore();
|
||||||
|
|
||||||
// 响应式数据
|
// 响应式数据
|
||||||
const form = ref({
|
const form = ref({
|
||||||
phone: '',
|
phone: "",
|
||||||
password: ''
|
password: "",
|
||||||
});
|
});
|
||||||
const showPassword = ref(false);
|
const showPassword = ref(false);
|
||||||
const errorMessage = ref('');
|
const errorMessage = ref("");
|
||||||
const agreedToTerms = ref(false);
|
|
||||||
const agreementContent = ref('');
|
|
||||||
const showAgreementDialog = ref(false);
|
|
||||||
const showTipDialog = ref(false);
|
|
||||||
|
|
||||||
// 获取页面参数(用于显示错误信息)
|
// 获取页面参数(用于显示错误信息)
|
||||||
const getPageParams = () => {
|
const getPageParams = () => {
|
||||||
const pages = getCurrentPages()
|
const pages = getCurrentPages();
|
||||||
if (pages.length > 0) {
|
if (pages.length > 0) {
|
||||||
const currentPage = pages[pages.length - 1]
|
const currentPage = pages[pages.length - 1];
|
||||||
if (currentPage.options && currentPage.options.error) {
|
if (currentPage.options && currentPage.options.error) {
|
||||||
errorMessage.value = decodeURIComponent(currentPage.options.error)
|
errorMessage.value = decodeURIComponent(currentPage.options.error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
// 页面加载时获取错误信息
|
// 页面加载时获取错误信息
|
||||||
onLoad(() => {
|
onLoad(() => {
|
||||||
getPageParams()
|
getPageParams();
|
||||||
})
|
});
|
||||||
|
|
||||||
// 获取协议内容
|
|
||||||
const getAgreementContent = () => {
|
|
||||||
return AGREEMENT_CONTENT;
|
|
||||||
};
|
|
||||||
|
|
||||||
// 切换密码显示/隐藏
|
// 切换密码显示/隐藏
|
||||||
const togglePassword = () => {
|
const togglePassword = () => {
|
||||||
showPassword.value = !showPassword.value;
|
showPassword.value = !showPassword.value;
|
||||||
};
|
};
|
||||||
|
|
||||||
// 跳转到注册页面
|
// 跳转到注册页面
|
||||||
const goToRegister = () => {
|
const goToRegister = () => {
|
||||||
// 清除新手引导相关数据
|
resetAllGuides();
|
||||||
resetAllGuides();
|
uni.removeStorageSync("is_new_user");
|
||||||
uni.removeStorageSync('is_new_user');
|
uni.removeStorageSync("has_seen_welcome");
|
||||||
uni.removeStorageSync('has_seen_welcome');
|
uni.reLaunch({ url: "/pages/register/register" });
|
||||||
|
|
||||||
uni.navigateTo({
|
|
||||||
url: '/pages/register/register'
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// 切换协议勾选状态
|
// 忘记密码
|
||||||
const toggleAgreement = () => {
|
const handleForgotPassword = () => {
|
||||||
agreedToTerms.value = !agreedToTerms.value;
|
uni.showToast({ title: "忘记密码功能开发中", icon: "none" });
|
||||||
};
|
|
||||||
|
|
||||||
// 显示协议全文弹窗
|
|
||||||
const showAgreementModal = () => {
|
|
||||||
// 直接加载协议内容(已从模块导入)
|
|
||||||
if (!agreementContent.value) {
|
|
||||||
agreementContent.value = getAgreementContent();
|
|
||||||
}
|
|
||||||
// 显示自定义协议弹窗
|
|
||||||
showAgreementDialog.value = true;
|
|
||||||
};
|
|
||||||
|
|
||||||
// 关闭协议弹窗
|
|
||||||
const closeAgreementDialog = () => {
|
|
||||||
showAgreementDialog.value = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
// 同意并关闭弹窗
|
|
||||||
const agreeAndClose = () => {
|
|
||||||
agreedToTerms.value = true;
|
|
||||||
showAgreementDialog.value = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
// 打开提示弹窗
|
|
||||||
const openTipDialog = () => {
|
|
||||||
showTipDialog.value = true;
|
|
||||||
};
|
|
||||||
|
|
||||||
// 关闭提示弹窗
|
|
||||||
const closeTipDialog = () => {
|
|
||||||
showTipDialog.value = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
// 从提示弹窗打开协议弹窗
|
|
||||||
const openAgreementFromTip = () => {
|
|
||||||
showTipDialog.value = false;
|
|
||||||
// 延迟打开协议弹窗,确保提示弹窗先关闭
|
|
||||||
setTimeout(() => {
|
|
||||||
showAgreementModal();
|
|
||||||
}, 300);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// 登录
|
// 登录
|
||||||
const handleLogin = async () => {
|
const handleLogin = async () => {
|
||||||
// 验证是否勾选协议
|
const phoneValidation = validatePhone(form.value.phone);
|
||||||
if (!agreedToTerms.value) {
|
if (!phoneValidation.valid) {
|
||||||
openTipDialog();
|
errorMessage.value = phoneValidation.message;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 验证表单
|
const passwordValidation = validatePassword(form.value.password);
|
||||||
const phoneValidation = validatePhone(form.value.phone);
|
if (!passwordValidation.valid) {
|
||||||
if (!phoneValidation.valid) {
|
errorMessage.value = passwordValidation.message;
|
||||||
errorMessage.value = phoneValidation.message;
|
return;
|
||||||
return;
|
}
|
||||||
}
|
|
||||||
|
errorMessage.value = "";
|
||||||
const passwordValidation = validatePassword(form.value.password);
|
|
||||||
if (!passwordValidation.valid) {
|
try {
|
||||||
errorMessage.value = passwordValidation.message;
|
uni.showLoading({ title: "登录中...", mask: true });
|
||||||
return;
|
|
||||||
}
|
await store.dispatch("user/login", {
|
||||||
|
mobile: form.value.phone,
|
||||||
errorMessage.value = '';
|
password: form.value.password,
|
||||||
|
});
|
||||||
try {
|
|
||||||
// 显示加载提示
|
// 任意登录方式都标记 needs_welcome=true,让 TopfansWelcome 在登出后再登录也能展示一次
|
||||||
uni.showLoading({
|
uni.setStorageSync("needs_welcome", true);
|
||||||
title: '登录中...',
|
|
||||||
mask: true
|
uni.hideLoading();
|
||||||
});
|
uni.reLaunch({ url: "/pages/square/square" });
|
||||||
|
} catch (error) {
|
||||||
await store.dispatch('user/login', {
|
uni.hideLoading();
|
||||||
mobile: form.value.phone,
|
errorMessage.value = error.message || "登录失败,请重试";
|
||||||
password: form.value.password
|
uni.showToast({
|
||||||
});
|
title: errorMessage.value,
|
||||||
|
icon: "none",
|
||||||
uni.hideLoading();
|
duration: 2000,
|
||||||
|
});
|
||||||
// 登录成功,跳转首页
|
}
|
||||||
uni.reLaunch({
|
|
||||||
url: '/pages/square/square'
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
uni.hideLoading();
|
|
||||||
// 显示错误信息(从响应的 message 中获取)
|
|
||||||
errorMessage.value = error.message || '登录失败,请重试';
|
|
||||||
uni.showToast({
|
|
||||||
title: errorMessage.value,
|
|
||||||
icon: 'none',
|
|
||||||
duration: 2000
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.login-container {
|
.login-content {
|
||||||
position: relative;
|
position: relative;
|
||||||
width: 100%;
|
z-index: 10;
|
||||||
min-height: 100vh;
|
width: 100%;
|
||||||
height: 100vh;
|
display: flex;
|
||||||
overflow: hidden;
|
flex-direction: column;
|
||||||
display: flex;
|
align-items: center;
|
||||||
flex-direction: column;
|
padding-bottom: 80rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
.background-image {
|
/* 头像 */
|
||||||
position: absolute;
|
.avatar-wrapper {
|
||||||
top: 0;
|
width: 296rpx;
|
||||||
left: 0;
|
height: 274rpx;
|
||||||
width: 100%;
|
margin-top: 130rpx;
|
||||||
height: 100%;
|
filter: drop-shadow(2rpx 4rpx 98rpx rgba(0, 0, 0, 0.94));
|
||||||
z-index: 0;
|
z-index: 11;
|
||||||
}
|
}
|
||||||
|
|
||||||
.girl-image {
|
.avatar-image {
|
||||||
position: absolute;
|
width: 100%;
|
||||||
top: 55%;
|
height: 100%;
|
||||||
left: 50%;
|
|
||||||
transform: translateX(-50%) translateY(-50%);
|
|
||||||
width: 180%;
|
|
||||||
height: 180%;
|
|
||||||
z-index: 1;
|
|
||||||
object-fit: contain;
|
|
||||||
object-position: center center;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.doll-image {
|
/* 卡片 */
|
||||||
position: absolute;
|
.login-card {
|
||||||
bottom: -70%;
|
width: 710rpx;
|
||||||
left: 45%;
|
margin-top: -104rpx;
|
||||||
transform: translateX(-50%);
|
padding: 30rpx 104rpx 30rpx;
|
||||||
width: 150%;
|
box-sizing: border-box;
|
||||||
height: 150%;
|
border-radius: 56rpx;
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.15);
|
||||||
z-index: 2;
|
background: rgba(255, 255, 255, 0.1);
|
||||||
object-fit: contain;
|
backdrop-filter: blur(64rpx);
|
||||||
object-position: center bottom;
|
-webkit-backdrop-filter: blur(64rpx);
|
||||||
|
box-shadow: 0 0 20.5px 0 rgba(0, 0, 0, 0.31);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
z-index: 12;
|
||||||
}
|
}
|
||||||
|
|
||||||
.background-overlay {
|
/* 标题 */
|
||||||
position: absolute;
|
.card-title-wrapper {
|
||||||
top: 0;
|
position: relative;
|
||||||
left: 0;
|
width: 324rpx;
|
||||||
width: 100%;
|
height: 84rpx;
|
||||||
height: 100%;
|
display: flex;
|
||||||
background: rgba(0, 0, 0, 0.3);
|
align-items: center;
|
||||||
z-index: 3;
|
justify-content: center;
|
||||||
|
margin-bottom: 56rpx;
|
||||||
|
background:
|
||||||
|
linear-gradient(240deg, #cdaffa 9.77%, rgba(205, 175, 250, 0) 62.44%),
|
||||||
|
linear-gradient(108deg, #f7cbbf 13.91%, rgba(247, 203, 191, 0) 51.34%),
|
||||||
|
linear-gradient(297deg, #81a9f8 31.28%, rgba(129, 169, 248, 0) 90.74%),
|
||||||
|
linear-gradient(80deg, #69f4e9 39.86%, rgba(164, 252, 245, 0) 66.88%);
|
||||||
|
border-radius: 84rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
.content-wrapper {
|
.card-title {
|
||||||
position: relative;
|
position: relative;
|
||||||
z-index: 10;
|
z-index: 1;
|
||||||
width: 100%;
|
font-size: 44rpx;
|
||||||
flex: 1;
|
color: #fff9e7;
|
||||||
min-height: 100%;
|
text-shadow: -2rpx 2rpx 8rpx rgba(0, 0, 0, 0.84);
|
||||||
display: flex;
|
font-family: "Abhaya Libre Medium", sans-serif;
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
padding: 0 60rpx 60rpx 60rpx;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.title-wrapper {
|
/* 表单 */
|
||||||
width: 100%;
|
.form-area {
|
||||||
padding-top: 120rpx;
|
width: 100%;
|
||||||
margin-bottom: 80rpx;
|
display: flex;
|
||||||
text-align: center;
|
flex-direction: column;
|
||||||
position: relative;
|
gap: 32rpx;
|
||||||
flex-shrink: 0;
|
padding: 0 14rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
.form-container {
|
.input-group {
|
||||||
flex: 1;
|
width: 100%;
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
width: 100%;
|
|
||||||
max-width: 600rpx;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.title-text {
|
.input-outer {
|
||||||
font-size: 150rpx;
|
width: 100%;
|
||||||
font-weight: 600;
|
height: 74rpx;
|
||||||
letter-spacing: 12rpx;
|
border-radius: 58rpx;
|
||||||
background: linear-gradient(to right, #B52920 0%, #86D9E0 80%, #56C1FF 100%);
|
background: linear-gradient(
|
||||||
-webkit-background-clip: text;
|
90deg,
|
||||||
background-clip: text;
|
rgba(255, 222, 8, 0.06) 0%,
|
||||||
-webkit-text-fill-color: transparent;
|
rgba(252, 100, 102, 0.12) 64%,
|
||||||
display: inline-block;
|
rgba(244, 88, 104, 0.12) 100%
|
||||||
line-height: 1.2;
|
);
|
||||||
text-transform: uppercase;
|
box-shadow: 4rpx 4rpx 8rpx 0 rgba(242, 21, 21, 0.47);
|
||||||
font-family: 'TheMiladiatorRegular', sans-serif !important;
|
display: flex;
|
||||||
filter: drop-shadow(0 2rpx 4rpx rgba(0, 0, 0, 0.15));
|
align-items: center;
|
||||||
display: flex;
|
padding: 4rpx;
|
||||||
justify-content: center;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
.form-wrapper {
|
.input-inner {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
height: 60rpx;
|
||||||
|
border-radius: 58rpx;
|
||||||
.input-wrapper {
|
background: rgba(255, 255, 255, 0.65);
|
||||||
position: relative;
|
display: flex;
|
||||||
width: 100%;
|
align-items: center;
|
||||||
height: 100rpx;
|
padding: 0 24rpx;
|
||||||
margin-bottom: 40rpx;
|
box-sizing: border-box;
|
||||||
background: rgba(255, 255, 255, 0.9);
|
|
||||||
border-radius: 50rpx;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
padding: 0 40rpx;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
.password-wrapper {
|
|
||||||
padding-right: 100rpx;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.input-field {
|
.input-field {
|
||||||
flex: 1;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
font-size: 32rpx;
|
font-size: 30rpx;
|
||||||
color: #333333;
|
color: #333;
|
||||||
}
|
}
|
||||||
|
|
||||||
.input-placeholder {
|
.input-placeholder {
|
||||||
color: #999999;
|
font-size: 30rpx;
|
||||||
}
|
color: #565656;
|
||||||
|
font-family: "Abhaya Libre Medium", sans-serif;
|
||||||
.eye-icon {
|
|
||||||
position: absolute;
|
|
||||||
right: 40rpx;
|
|
||||||
width: 60rpx;
|
|
||||||
height: 60rpx;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.eye-text {
|
|
||||||
font-size: 40rpx;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 错误提示 */
|
||||||
.error-message {
|
.error-message {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding: 20rpx 0;
|
padding-left: 24rpx;
|
||||||
margin-bottom: 20rpx;
|
|
||||||
text-align: center;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.error-message text {
|
.error-message text {
|
||||||
font-size: 28rpx;
|
font-size: 24rpx;
|
||||||
color: #ff4444;
|
color: #ff6b6b;
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-primary {
|
/* 忘记密码 */
|
||||||
width: 100%;
|
.forgot-password-wrapper {
|
||||||
height: 100rpx;
|
width: 100%;
|
||||||
margin-bottom: 30rpx;
|
display: flex;
|
||||||
background: linear-gradient(165deg, #F0E4B1 0%, #F08399 50%, #B94E73 90%, #834B9E 100%);
|
justify-content: center;
|
||||||
border-radius: 50rpx;
|
/* padding: 0 18rpx; */
|
||||||
border: none;
|
margin-top: 48rpx;
|
||||||
font-size: 36rpx;
|
margin-bottom: 24rpx;
|
||||||
font-weight: bold;
|
|
||||||
color: #e6e6e6;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-primary::after {
|
.forgot-password {
|
||||||
border: none;
|
font-size: 30rpx;
|
||||||
|
color: #fff9e7;
|
||||||
|
text-shadow: -2rpx 2rpx 8rpx rgba(0, 0, 0, 0.84);
|
||||||
|
font-family: "Abhaya Libre Medium", sans-serif;
|
||||||
}
|
}
|
||||||
|
|
||||||
.register-link {
|
/* 分割线 */
|
||||||
width: 100%;
|
.divider {
|
||||||
margin-top: 40rpx;
|
width: 612rpx;
|
||||||
text-align: center;
|
height: 4rpx;
|
||||||
|
background: rgba(255, 255, 255, 0.3);
|
||||||
|
border-radius: 100rpx;
|
||||||
|
margin: 28rpx 0 24rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
.register-link text {
|
/* 登录按钮 */
|
||||||
font-size: 28rpx;
|
.login-btn-wrapper {
|
||||||
color: #e6e6e6;
|
width: 100%;
|
||||||
text-decoration: underline;
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 64rpx 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.footer-text {
|
.login-btn {
|
||||||
position: relative;
|
width: 270rpx;
|
||||||
width: 100%;
|
height: 90rpx;
|
||||||
margin-top: auto;
|
border-radius: 58rpx;
|
||||||
padding: 0rpx 0 80rpx 0;
|
background: linear-gradient(
|
||||||
text-align: center;
|
90deg,
|
||||||
|
rgba(255, 222, 8, 0.28) 0%,
|
||||||
|
rgba(252, 100, 102, 0.58) 64%,
|
||||||
|
rgba(244, 88, 104, 0.58) 100%
|
||||||
|
);
|
||||||
|
box-shadow: 4rpx 4rpx 8rpx 0 rgba(242, 21, 21, 0.47);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
opacity: 0.96;
|
||||||
}
|
}
|
||||||
|
|
||||||
.agreement-wrapper {
|
.login-btn-text {
|
||||||
display: flex;
|
font-size: 36rpx;
|
||||||
align-items: center;
|
color: #fff9e7;
|
||||||
justify-content: center;
|
text-shadow: -2rpx 2rpx 8rpx rgba(0, 0, 0, 0.84);
|
||||||
flex-wrap: wrap;
|
font-family: "Abhaya Libre Medium", sans-serif;
|
||||||
padding: 0 40rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.agreement-checkbox {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
margin-right: 10rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 自定义圆形checkbox样式 */
|
|
||||||
.agreement-checkbox {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
margin-right: 10rpx;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.custom-checkbox {
|
|
||||||
width: 32rpx;
|
|
||||||
height: 32rpx;
|
|
||||||
border-radius: 50%;
|
|
||||||
border: 2rpx solid #e6e6e6;
|
|
||||||
background-color: transparent;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
transition: all 0.2s ease;
|
|
||||||
flex-shrink: 0;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.custom-checkbox.checked {
|
|
||||||
border-color: #e6e6e6;
|
|
||||||
background-color: transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
.checkbox-inner {
|
|
||||||
width: 20rpx;
|
|
||||||
height: 20rpx;
|
|
||||||
border-radius: 50%;
|
|
||||||
background-color: #000000;
|
|
||||||
}
|
|
||||||
|
|
||||||
.agreement-label {
|
|
||||||
font-size: 24rpx;
|
|
||||||
color: #e6e6e6;
|
|
||||||
margin-left: 10rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.agreement-link {
|
|
||||||
font-size: 24rpx;
|
|
||||||
color: #e6e6e6;
|
|
||||||
text-decoration: underline;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 协议弹窗样式 */
|
|
||||||
.agreement-dialog-mask {
|
|
||||||
position: fixed;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
bottom: 0;
|
|
||||||
background: rgba(0, 0, 0, 0.6);
|
|
||||||
z-index: 9999;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
padding: 40rpx;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
.agreement-dialog {
|
|
||||||
width: 100%;
|
|
||||||
max-width: 700rpx;
|
|
||||||
max-height: 80vh;
|
|
||||||
background: #e6e6e6;
|
|
||||||
border-radius: 20rpx;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.agreement-dialog-header {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
padding: 30rpx 40rpx;
|
|
||||||
border-bottom: 1rpx solid #e5e5e5;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.agreement-dialog-title {
|
|
||||||
font-size: 36rpx;
|
|
||||||
font-weight: bold;
|
|
||||||
color: #333333;
|
|
||||||
flex: 1;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.agreement-dialog-close {
|
|
||||||
font-size: 48rpx;
|
|
||||||
color: #999999;
|
|
||||||
width: 60rpx;
|
|
||||||
height: 60rpx;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
position: absolute;
|
|
||||||
right: 20rpx;
|
|
||||||
top: 50%;
|
|
||||||
transform: translateY(-50%);
|
|
||||||
}
|
|
||||||
|
|
||||||
.agreement-dialog-content {
|
|
||||||
flex: 1;
|
|
||||||
padding: 40rpx;
|
|
||||||
overflow-y: auto;
|
|
||||||
box-sizing: border-box;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.agreement-text {
|
|
||||||
font-size: 28rpx;
|
|
||||||
color: #333333;
|
|
||||||
line-height: 1.8;
|
|
||||||
white-space: pre-wrap;
|
|
||||||
word-wrap: break-word;
|
|
||||||
word-break: break-all;
|
|
||||||
overflow-wrap: break-word;
|
|
||||||
width: 100%;
|
|
||||||
box-sizing: border-box;
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.agreement-dialog-footer {
|
|
||||||
padding: 30rpx 40rpx;
|
|
||||||
border-top: 1rpx solid #e5e5e5;
|
|
||||||
}
|
|
||||||
|
|
||||||
.agreement-confirm-btn {
|
|
||||||
width: 100%;
|
|
||||||
height: 88rpx;
|
|
||||||
background: #000000;
|
|
||||||
color: #e6e6e6;
|
|
||||||
border-radius: 44rpx;
|
|
||||||
font-size: 32rpx;
|
|
||||||
font-weight: bold;
|
|
||||||
border: none;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.agreement-confirm-btn::after {
|
|
||||||
border: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 提示弹窗样式 */
|
|
||||||
.tip-dialog-mask {
|
|
||||||
position: fixed;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
bottom: 0;
|
|
||||||
background: rgba(0, 0, 0, 0.6);
|
|
||||||
z-index: 9998;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
padding: 40rpx;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tip-dialog {
|
|
||||||
width: 100%;
|
|
||||||
max-width: 600rpx;
|
|
||||||
background: #e6e6e6;
|
|
||||||
border-radius: 20rpx;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tip-dialog-header {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
padding: 30rpx 40rpx;
|
|
||||||
border-bottom: 1rpx solid #e5e5e5;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tip-dialog-title {
|
|
||||||
font-size: 36rpx;
|
|
||||||
font-weight: bold;
|
|
||||||
color: #333333;
|
|
||||||
flex: 1;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tip-dialog-close {
|
|
||||||
font-size: 48rpx;
|
|
||||||
color: #999999;
|
|
||||||
width: 60rpx;
|
|
||||||
height: 60rpx;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
position: absolute;
|
|
||||||
right: 20rpx;
|
|
||||||
top: 50%;
|
|
||||||
transform: translateY(-50%);
|
|
||||||
}
|
|
||||||
|
|
||||||
.tip-dialog-content {
|
|
||||||
padding: 40rpx;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tip-text {
|
|
||||||
font-size: 32rpx;
|
|
||||||
color: #333333;
|
|
||||||
line-height: 1.6;
|
|
||||||
display: block;
|
|
||||||
margin-bottom: 20rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tip-agreement-link {
|
|
||||||
font-size: 32rpx;
|
|
||||||
color: #007AFF;
|
|
||||||
text-decoration: underline;
|
|
||||||
cursor: pointer;
|
|
||||||
display: block;
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|||||||
553
frontend/pages/login/portal.vue
Normal file
@ -0,0 +1,553 @@
|
|||||||
|
<template>
|
||||||
|
<LoginBackground :show-logo="true" :showBack="false">
|
||||||
|
<!-- 内容卡片:登录注册选择卡 -->
|
||||||
|
<view class="portal-content">
|
||||||
|
<view class="portal-card">
|
||||||
|
<!-- Tab 区域 -->
|
||||||
|
<view class="tab-row">
|
||||||
|
<view
|
||||||
|
class="tab-item"
|
||||||
|
:class="{ active: activeTab === 'register' }"
|
||||||
|
@tap="switchTab('register')"
|
||||||
|
>
|
||||||
|
<text class="tab-text">手机号注册</text>
|
||||||
|
</view>
|
||||||
|
<view
|
||||||
|
class="tab-item"
|
||||||
|
:class="{ active: activeTab === 'login' }"
|
||||||
|
@tap="switchTab('login')"
|
||||||
|
>
|
||||||
|
<text class="tab-text">账号密码登录</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 分割线 -->
|
||||||
|
<view class="divider"></view>
|
||||||
|
|
||||||
|
<!-- 其他登录方式 -->
|
||||||
|
<view class="other-login">
|
||||||
|
<text class="other-login-text">其他登录方式</text>
|
||||||
|
<view class="third-party-icons">
|
||||||
|
<view class="third-party-item" @tap="handleWechatLogin">
|
||||||
|
<image
|
||||||
|
class="third-party-icon"
|
||||||
|
src="/static/assetDetail/share/ic_wechat_friend.png"
|
||||||
|
mode="aspectFit"
|
||||||
|
></image>
|
||||||
|
</view>
|
||||||
|
<view class="third-party-item" @tap="handleOtherLogin">
|
||||||
|
<image
|
||||||
|
class="third-party-icon"
|
||||||
|
src="/static/assetDetail/share/ic_qq.png"
|
||||||
|
mode="aspectFit"
|
||||||
|
></image>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 底部协议 -->
|
||||||
|
<view class="agreement-wrapper">
|
||||||
|
<view
|
||||||
|
class="agreement-checkbox"
|
||||||
|
:class="{ checked: agreedToTerms }"
|
||||||
|
@tap="toggleAgreement"
|
||||||
|
>
|
||||||
|
<view v-if="agreedToTerms" class="checkbox-inner"></view>
|
||||||
|
</view>
|
||||||
|
<view class="agreement-text">
|
||||||
|
<text class="agreement-line"
|
||||||
|
>登陆代表您已阅读并接受统一认证服务条款,以及</text
|
||||||
|
>
|
||||||
|
<text class="agreement-line agreement-link" @tap="showAgreementModal"
|
||||||
|
>隐私政策和用户服务协议</text
|
||||||
|
>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 提示弹窗 -->
|
||||||
|
<view v-if="showTipDialog" class="tip-dialog-mask" @tap="closeTipDialog">
|
||||||
|
<view class="tip-dialog" @tap.stop>
|
||||||
|
<view class="tip-dialog-header">
|
||||||
|
<text class="tip-dialog-title">提示</text>
|
||||||
|
<text class="tip-dialog-close" @tap="closeTipDialog">×</text>
|
||||||
|
</view>
|
||||||
|
<view class="tip-dialog-content">
|
||||||
|
<text class="tip-text">阅读并同意以下条款</text>
|
||||||
|
<text class="tip-agreement-link" @tap="openAgreementFromTip"
|
||||||
|
>《Topfans用户服务协议》</text
|
||||||
|
>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 协议全文弹窗 -->
|
||||||
|
<view
|
||||||
|
v-if="showAgreementDialog"
|
||||||
|
class="agreement-dialog-mask"
|
||||||
|
@tap="closeAgreementDialog"
|
||||||
|
>
|
||||||
|
<view class="agreement-dialog" @tap.stop>
|
||||||
|
<view class="agreement-dialog-header">
|
||||||
|
<text class="agreement-dialog-title">Topfans用户服务协议</text>
|
||||||
|
<text class="agreement-dialog-close" @tap="closeAgreementDialog"
|
||||||
|
>×</text
|
||||||
|
>
|
||||||
|
</view>
|
||||||
|
<scroll-view class="agreement-dialog-content" scroll-y>
|
||||||
|
<text class="agreement-dialog-text">{{
|
||||||
|
agreementContent || "协议内容加载中..."
|
||||||
|
}}</text>
|
||||||
|
</scroll-view>
|
||||||
|
<view class="agreement-dialog-footer">
|
||||||
|
<button class="agreement-confirm-btn" @tap="agreeAndClose">
|
||||||
|
我同意
|
||||||
|
</button>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</LoginBackground>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref } from "vue";
|
||||||
|
import LoginBackground from "@/components/LoginBackground.vue";
|
||||||
|
import { AGREEMENT_CONTENT } from "@/utils/agreement";
|
||||||
|
|
||||||
|
// 当前激活的 tab:register=手机号注册, login=账号密码登录
|
||||||
|
const activeTab = ref("register");
|
||||||
|
|
||||||
|
// 是否已勾选协议
|
||||||
|
const agreedToTerms = ref(false);
|
||||||
|
|
||||||
|
// 协议弹窗相关
|
||||||
|
const agreementContent = ref("");
|
||||||
|
const showAgreementDialog = ref(false);
|
||||||
|
const showTipDialog = ref(false);
|
||||||
|
|
||||||
|
// 检查是否同意协议,未同意则弹提示
|
||||||
|
const requireAgreement = () => {
|
||||||
|
if (!agreedToTerms.value) {
|
||||||
|
showTipDialog.value = true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 切换 tab
|
||||||
|
const switchTab = (tab) => {
|
||||||
|
if (!requireAgreement()) return;
|
||||||
|
activeTab.value = tab;
|
||||||
|
if (tab === "login") {
|
||||||
|
uni.reLaunch({ url: "/pages/login/login" });
|
||||||
|
} else if (tab === "register") {
|
||||||
|
uni.reLaunch({ url: "/pages/register/register" });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 微信登录
|
||||||
|
const handleWechatLogin = () => {
|
||||||
|
if (!requireAgreement()) return;
|
||||||
|
uni.showToast({ title: "微信登录开发中", icon: "none" });
|
||||||
|
};
|
||||||
|
|
||||||
|
// 其他登录方式
|
||||||
|
const handleOtherLogin = () => {
|
||||||
|
if (!requireAgreement()) return;
|
||||||
|
uni.showToast({ title: "该登录方式开发中", icon: "none" });
|
||||||
|
};
|
||||||
|
|
||||||
|
// 切换协议勾选状态
|
||||||
|
const toggleAgreement = () => {
|
||||||
|
agreedToTerms.value = !agreedToTerms.value;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 显示协议全文弹窗
|
||||||
|
const showAgreementModal = () => {
|
||||||
|
if (!agreementContent.value) {
|
||||||
|
agreementContent.value = AGREEMENT_CONTENT;
|
||||||
|
}
|
||||||
|
showAgreementDialog.value = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 关闭协议弹窗
|
||||||
|
const closeAgreementDialog = () => {
|
||||||
|
showAgreementDialog.value = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 同意并关闭弹窗
|
||||||
|
const agreeAndClose = () => {
|
||||||
|
agreedToTerms.value = true;
|
||||||
|
showAgreementDialog.value = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 关闭提示弹窗
|
||||||
|
const closeTipDialog = () => {
|
||||||
|
showTipDialog.value = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 从提示弹窗打开协议弹窗
|
||||||
|
const openAgreementFromTip = () => {
|
||||||
|
showTipDialog.value = false;
|
||||||
|
setTimeout(() => {
|
||||||
|
showAgreementModal();
|
||||||
|
}, 300);
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.portal-content {
|
||||||
|
position: relative;
|
||||||
|
z-index: 10;
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 卡片 */
|
||||||
|
.portal-card {
|
||||||
|
width: 606rpx;
|
||||||
|
height: 388rpx;
|
||||||
|
margin-top: 660rpx;
|
||||||
|
padding: 20rpx 50rpx;
|
||||||
|
box-sizing: border-box;
|
||||||
|
border-radius: 28px;
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.15);
|
||||||
|
background: rgba(0, 0, 0, 0);
|
||||||
|
box-shadow: 0 0 20.5px 0 rgba(0, 0, 0, 0.31);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Tab 按钮行 */
|
||||||
|
.tab-row {
|
||||||
|
width: 324rpx;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 18rpx;
|
||||||
|
margin-top: 14rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-item {
|
||||||
|
width: 324rpx;
|
||||||
|
height: 70rpx;
|
||||||
|
border-radius: 58rpx;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
background: linear-gradient(
|
||||||
|
90deg,
|
||||||
|
rgba(255, 222, 8, 0.03) 0%,
|
||||||
|
rgba(252, 100, 102, 0.06) 64%,
|
||||||
|
rgba(244, 88, 104, 0.06) 100%
|
||||||
|
);
|
||||||
|
box-shadow: 4rpx 4rpx 8rpx 0 rgba(242, 21, 21, 0.47);
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-item.active {
|
||||||
|
background: linear-gradient(
|
||||||
|
90deg,
|
||||||
|
rgba(255, 222, 8, 0.41) 0%,
|
||||||
|
rgba(252, 100, 102, 0.83) 64%,
|
||||||
|
rgba(244, 88, 104, 0.83) 100%
|
||||||
|
);
|
||||||
|
opacity: 0.96;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-text {
|
||||||
|
font-size: 30rpx;
|
||||||
|
color: #ffffff;
|
||||||
|
text-shadow: 4rpx 4rpx 15rpx rgba(42, 36, 36, 0.85);
|
||||||
|
font-weight: normal;
|
||||||
|
font-family: "Abhaya Libre Medium", sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 分割线 */
|
||||||
|
.divider {
|
||||||
|
width: 534rpx;
|
||||||
|
height: 2rpx;
|
||||||
|
background: rgba(255, 255, 255, 0.3);
|
||||||
|
border-radius: 100rpx;
|
||||||
|
margin: 22rpx 0 22rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 其他登录方式 */
|
||||||
|
.other-login {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.other-login-text {
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: #fff9e7;
|
||||||
|
text-shadow: -2rpx 2rpx 8rpx rgba(0, 0, 0, 0.84);
|
||||||
|
font-family: "Abhaya Libre Medium", sans-serif;
|
||||||
|
margin-bottom: 10rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.third-party-icons {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 56rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.third-party-item {
|
||||||
|
width: 48rpx;
|
||||||
|
height: 46rpx;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
filter: drop-shadow(0 4rpx 17rpx rgba(198, 27, 27, 0.49));
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.third-party-icon {
|
||||||
|
width: 48rpx;
|
||||||
|
height: 46rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 底部协议 */
|
||||||
|
.agreement-wrapper {
|
||||||
|
position: fixed;
|
||||||
|
bottom: 40rpx;
|
||||||
|
z-index: 10;
|
||||||
|
display: flex;
|
||||||
|
gap: 0.5rem;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.agreement-checkbox {
|
||||||
|
width: 44rpx;
|
||||||
|
height: 44rpx;
|
||||||
|
border-radius: 58rpx;
|
||||||
|
background: rgba(59, 58, 56, 0.19);
|
||||||
|
box-shadow: 4rpx 4rpx 18rpx 0 rgba(242, 21, 21, 0.27);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
cursor: pointer;
|
||||||
|
opacity: 0.96;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox-inner {
|
||||||
|
width: 24rpx;
|
||||||
|
height: 24rpx;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.agreement-text {
|
||||||
|
flex: 1;
|
||||||
|
margin-left: 6rpx;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.agreement-line {
|
||||||
|
color: #fff;
|
||||||
|
text-shadow: 0 1px 6.7px #000;
|
||||||
|
font-family: "Abhaya Libre Medium";
|
||||||
|
font-size: 26rpx;
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 500;
|
||||||
|
line-height: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 协议链接 */
|
||||||
|
.agreement-link {
|
||||||
|
text-decoration: underline;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 协议弹窗样式 */
|
||||||
|
.agreement-dialog-mask {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background: rgba(0, 0, 0, 0.6);
|
||||||
|
z-index: 9999;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 40rpx;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.agreement-dialog {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 700rpx;
|
||||||
|
max-height: 80vh;
|
||||||
|
background: #e6e6e6;
|
||||||
|
border-radius: 20rpx;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.agreement-dialog-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 30rpx 40rpx;
|
||||||
|
border-bottom: 1rpx solid #e5e5e5;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.agreement-dialog-title {
|
||||||
|
font-size: 36rpx;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #333333;
|
||||||
|
flex: 1;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.agreement-dialog-close {
|
||||||
|
font-size: 48rpx;
|
||||||
|
color: #999999;
|
||||||
|
width: 60rpx;
|
||||||
|
height: 60rpx;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
position: absolute;
|
||||||
|
right: 20rpx;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.agreement-dialog-content {
|
||||||
|
flex: 1;
|
||||||
|
padding: 40rpx;
|
||||||
|
overflow-y: auto;
|
||||||
|
box-sizing: border-box;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.agreement-dialog-text {
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #333333;
|
||||||
|
line-height: 1.8;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
word-wrap: break-word;
|
||||||
|
word-break: break-all;
|
||||||
|
overflow-wrap: break-word;
|
||||||
|
width: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.agreement-dialog-footer {
|
||||||
|
padding: 30rpx 40rpx;
|
||||||
|
border-top: 1rpx solid #e5e5e5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.agreement-confirm-btn {
|
||||||
|
width: 100%;
|
||||||
|
height: 88rpx;
|
||||||
|
background: linear-gradient(
|
||||||
|
135deg,
|
||||||
|
rgba(255, 222, 8, 0.41) 0%,
|
||||||
|
rgba(252, 100, 102, 0.83) 64%,
|
||||||
|
rgba(244, 88, 104, 0.83) 100%
|
||||||
|
);
|
||||||
|
color: #e6e6e6;
|
||||||
|
border-radius: 44rpx;
|
||||||
|
font-size: 32rpx;
|
||||||
|
font-weight: bold;
|
||||||
|
border: none;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.agreement-confirm-btn::after {
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 提示弹窗样式 */
|
||||||
|
.tip-dialog-mask {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background: rgba(0, 0, 0, 0.6);
|
||||||
|
z-index: 9998;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 40rpx;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tip-dialog {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 600rpx;
|
||||||
|
background: #e6e6e6;
|
||||||
|
border-radius: 20rpx;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tip-dialog-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 30rpx 40rpx;
|
||||||
|
border-bottom: 1rpx solid #e5e5e5;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tip-dialog-title {
|
||||||
|
font-size: 36rpx;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #333333;
|
||||||
|
flex: 1;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tip-dialog-close {
|
||||||
|
font-size: 48rpx;
|
||||||
|
color: #999999;
|
||||||
|
width: 60rpx;
|
||||||
|
height: 60rpx;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
position: absolute;
|
||||||
|
right: 20rpx;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tip-dialog-content {
|
||||||
|
padding: 40rpx;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tip-text {
|
||||||
|
font-size: 32rpx;
|
||||||
|
color: #333333;
|
||||||
|
line-height: 1.6;
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 20rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tip-agreement-link {
|
||||||
|
font-size: 32rpx;
|
||||||
|
color: #007aff;
|
||||||
|
text-decoration: underline;
|
||||||
|
cursor: pointer;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
613
frontend/pages/login/quickLogin.vue
Normal file
@ -0,0 +1,613 @@
|
|||||||
|
<template>
|
||||||
|
<LoginBackground :show-logo="true" :showBack="false">
|
||||||
|
<view class="quicklogin-content">
|
||||||
|
<view class="quicklogin-card">
|
||||||
|
<!-- 标题 -->
|
||||||
|
<view class="card-title-wrapper">
|
||||||
|
<text class="card-title">欢迎回来</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 头像 -->
|
||||||
|
<view class="avatar-wrapper">
|
||||||
|
<view class="avatar-select-box">
|
||||||
|
<image
|
||||||
|
class="avatar-select-bg"
|
||||||
|
src="/static/login/portal/setnickname-avatar-box.png"
|
||||||
|
mode="aspectFit"
|
||||||
|
></image>
|
||||||
|
<view class="avatar-select-inner">
|
||||||
|
<image
|
||||||
|
v-if="userAvatar"
|
||||||
|
class="avatar-inner-img"
|
||||||
|
:src="userAvatar"
|
||||||
|
mode="aspectFill"
|
||||||
|
></image>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 手机号 -->
|
||||||
|
<view class="phone-row">
|
||||||
|
<text class="phone-text">{{ maskedPhone }}</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 分割线 -->
|
||||||
|
<view class="divider"></view>
|
||||||
|
|
||||||
|
<!-- 一键登录按钮 -->
|
||||||
|
<view class="primary-btn-wrapper">
|
||||||
|
<view class="primary-btn" @tap="handleQuickLogin">
|
||||||
|
<text class="primary-btn-text">一键登录</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 其他账号登录 -->
|
||||||
|
<view class="secondary-btn-wrapper">
|
||||||
|
<view class="secondary-btn" @tap="handleOtherLogin">
|
||||||
|
<text class="secondary-btn-text">其他账号登录</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 底部协议 -->
|
||||||
|
<view class="agreement-wrapper">
|
||||||
|
<view
|
||||||
|
class="agreement-checkbox"
|
||||||
|
:class="{ checked: agreedToTerms }"
|
||||||
|
@tap="toggleAgreement"
|
||||||
|
></view>
|
||||||
|
<view class="agreement-text">
|
||||||
|
<text class="agreement-line"
|
||||||
|
>登陆代表您已阅读并接受统一认证服务条款,以及</text
|
||||||
|
>
|
||||||
|
<text
|
||||||
|
class="agreement-line agreement-link"
|
||||||
|
@tap="showAgreementModal"
|
||||||
|
>隐私政策和用户服务协议</text
|
||||||
|
>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 提示弹窗 -->
|
||||||
|
<view v-if="showTipDialog" class="tip-dialog-mask" @tap="closeTipDialog">
|
||||||
|
<view class="tip-dialog" @tap.stop>
|
||||||
|
<view class="tip-dialog-header">
|
||||||
|
<text class="tip-dialog-title">提示</text>
|
||||||
|
<text class="tip-dialog-close" @tap="closeTipDialog">×</text>
|
||||||
|
</view>
|
||||||
|
<view class="tip-dialog-content">
|
||||||
|
<text class="tip-text">阅读并同意以下条款</text>
|
||||||
|
<text class="tip-agreement-link" @tap="openAgreementFromTip"
|
||||||
|
>《Topfans用户服务协议》</text
|
||||||
|
>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 协议全文弹窗 -->
|
||||||
|
<view
|
||||||
|
v-if="showAgreementDialog"
|
||||||
|
class="agreement-dialog-mask"
|
||||||
|
@tap="closeAgreementDialog"
|
||||||
|
>
|
||||||
|
<view class="agreement-dialog" @tap.stop>
|
||||||
|
<view class="agreement-dialog-header">
|
||||||
|
<text class="agreement-dialog-title">Topfans用户服务协议</text>
|
||||||
|
<text class="agreement-dialog-close" @tap="closeAgreementDialog"
|
||||||
|
>×</text
|
||||||
|
>
|
||||||
|
</view>
|
||||||
|
<scroll-view class="agreement-dialog-content" scroll-y>
|
||||||
|
<text class="agreement-dialog-text">{{
|
||||||
|
agreementContent || "协议内容加载中..."
|
||||||
|
}}</text>
|
||||||
|
</scroll-view>
|
||||||
|
<view class="agreement-dialog-footer">
|
||||||
|
<button class="agreement-confirm-btn" @tap="agreeAndClose">
|
||||||
|
我同意
|
||||||
|
</button>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</LoginBackground>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref } from "vue";
|
||||||
|
import LoginBackground from "@/components/LoginBackground.vue";
|
||||||
|
import { AGREEMENT_CONTENT } from "@/utils/agreement";
|
||||||
|
|
||||||
|
// 用户信息(从本地缓存读取 user 信息)
|
||||||
|
const userInfo = JSON.parse(uni.getStorageSync("user")) || {};
|
||||||
|
// 本地脱敏兜底:服务端若返回原始手机号,前端做脱敏
|
||||||
|
const maskPhone = (phone) => {
|
||||||
|
if (!phone) return "";
|
||||||
|
const s = String(phone);
|
||||||
|
if (s.length < 7) return s;
|
||||||
|
if (s.includes("*")) return s; // 已是脱敏值
|
||||||
|
return `${s.slice(0, 3)}****${s.slice(-4)}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
const maskedPhone = ref(
|
||||||
|
userInfo.mobile_masked || maskPhone(userInfo.mobile) || "174****2223",
|
||||||
|
);
|
||||||
|
const userAvatar = ref(
|
||||||
|
userInfo.avatar_url || "/static/login/portal/quicklogin-avatar.png",
|
||||||
|
);
|
||||||
|
const agreedToTerms = ref(false);
|
||||||
|
|
||||||
|
// 协议弹窗相关
|
||||||
|
const agreementContent = ref("");
|
||||||
|
const showAgreementDialog = ref(false);
|
||||||
|
const showTipDialog = ref(false);
|
||||||
|
|
||||||
|
const toggleAgreement = () => {
|
||||||
|
agreedToTerms.value = !agreedToTerms.value;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 一键登录
|
||||||
|
const handleQuickLogin = () => {
|
||||||
|
if (!agreedToTerms.value) {
|
||||||
|
showTipDialog.value = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
uni.showLoading({ title: "登录中..." });
|
||||||
|
setTimeout(() => {
|
||||||
|
uni.hideLoading();
|
||||||
|
// 标记从一键登录进入,触发 TopfansWelcome 显示
|
||||||
|
uni.setStorageSync("needs_welcome", true);
|
||||||
|
uni.reLaunch({ url: "/pages/square/square" });
|
||||||
|
}, 800);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 其他账号登录
|
||||||
|
const handleOtherLogin = () => {
|
||||||
|
uni.reLaunch({ url: "/pages/login/portal" });
|
||||||
|
};
|
||||||
|
|
||||||
|
// 显示协议全文弹窗
|
||||||
|
const showAgreementModal = () => {
|
||||||
|
if (!agreementContent.value) {
|
||||||
|
agreementContent.value = AGREEMENT_CONTENT;
|
||||||
|
}
|
||||||
|
showAgreementDialog.value = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 关闭协议弹窗
|
||||||
|
const closeAgreementDialog = () => {
|
||||||
|
showAgreementDialog.value = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 同意并关闭弹窗
|
||||||
|
const agreeAndClose = () => {
|
||||||
|
agreedToTerms.value = true;
|
||||||
|
showAgreementDialog.value = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 关闭提示弹窗
|
||||||
|
const closeTipDialog = () => {
|
||||||
|
showTipDialog.value = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 从提示弹窗打开协议弹窗
|
||||||
|
const openAgreementFromTip = () => {
|
||||||
|
showTipDialog.value = false;
|
||||||
|
setTimeout(() => {
|
||||||
|
showAgreementModal();
|
||||||
|
}, 300);
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.quicklogin-content {
|
||||||
|
position: absolute;
|
||||||
|
z-index: 10;
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
top: 160rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 卡片 */
|
||||||
|
.quicklogin-card {
|
||||||
|
width: 710rpx;
|
||||||
|
padding: 30rpx 40rpx 50rpx;
|
||||||
|
box-sizing: border-box;
|
||||||
|
border-radius: 56rpx;
|
||||||
|
border: 2rpx solid rgba(255, 255, 255, 0.15);
|
||||||
|
background: rgba(241, 163, 163, 0.17);
|
||||||
|
backdrop-filter: blur(64rpx);
|
||||||
|
-webkit-backdrop-filter: blur(64rpx);
|
||||||
|
box-shadow: 0 0 41rpx 0 rgba(0, 0, 0, 0.31);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 标题 */
|
||||||
|
.card-title-wrapper {
|
||||||
|
width: 270rpx;
|
||||||
|
height: 84rpx;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
background:
|
||||||
|
linear-gradient(240deg, #cdaffa 9.77%, rgba(205, 175, 250, 0) 62.44%),
|
||||||
|
linear-gradient(108deg, #f7cbbf 13.91%, rgba(247, 203, 191, 0) 51.34%),
|
||||||
|
linear-gradient(297deg, #81a9f8 31.28%, rgba(129, 169, 248, 0) 90.74%),
|
||||||
|
linear-gradient(80deg, #69f4e9 39.86%, rgba(164, 252, 245, 0) 66.88%);
|
||||||
|
border-radius: 84rpx;
|
||||||
|
margin-top: 88rpx;
|
||||||
|
margin-bottom: 30rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-title {
|
||||||
|
font-size: 40rpx;
|
||||||
|
color: #fff9e7;
|
||||||
|
text-shadow: -2rpx 2rpx 8rpx rgba(0, 0, 0, 0.84);
|
||||||
|
font-family: "Abhaya Libre Medium", sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 头像 */
|
||||||
|
.avatar-wrapper {
|
||||||
|
position: relative;
|
||||||
|
width: 270rpx;
|
||||||
|
height: 270rpx;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
margin: 48rpx 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatar-select-box {
|
||||||
|
position: relative;
|
||||||
|
width: 270rpx;
|
||||||
|
height: 270rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatar-select-bg {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
filter: drop-shadow(0 0 23rpx rgba(135, 16, 16, 0.67));
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatar-select-inner {
|
||||||
|
position: absolute;
|
||||||
|
top: 18rpx;
|
||||||
|
left: 18rpx;
|
||||||
|
right: 18rpx;
|
||||||
|
bottom: 18rpx;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: linear-gradient(
|
||||||
|
147deg,
|
||||||
|
rgba(223, 223, 146, 0.87) 17%,
|
||||||
|
rgba(135, 197, 216, 0.87) 39%,
|
||||||
|
rgba(194, 123, 209, 0.87) 99%
|
||||||
|
);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatar-inner-img {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
border-radius: 50%;
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 手机号 */
|
||||||
|
.phone-row {
|
||||||
|
margin-bottom: 56rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.phone-text {
|
||||||
|
font-size: 60rpx;
|
||||||
|
color: #fff9e7;
|
||||||
|
text-shadow: -2rpx 2rpx 8rpx rgba(0, 0, 0, 0.84);
|
||||||
|
font-family: "Acme", sans-serif;
|
||||||
|
letter-spacing: 4rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 分割线 */
|
||||||
|
.divider {
|
||||||
|
width: 584rpx;
|
||||||
|
height: 2rpx;
|
||||||
|
background: rgba(255, 228, 228, 0.46);
|
||||||
|
border-radius: 20rpx;
|
||||||
|
margin: 0 0 62rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 一键登录按钮 */
|
||||||
|
.primary-btn-wrapper {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
margin-bottom: 32rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.primary-btn {
|
||||||
|
width: 394rpx;
|
||||||
|
height: 104rpx;
|
||||||
|
border-radius: 58rpx;
|
||||||
|
background: linear-gradient(
|
||||||
|
90deg,
|
||||||
|
rgba(255, 222, 8, 0.41) 0%,
|
||||||
|
rgba(252, 100, 102, 0.83) 64%,
|
||||||
|
rgba(244, 88, 104, 0.83) 100%
|
||||||
|
);
|
||||||
|
box-shadow: 4rpx 4rpx 8rpx 0 rgba(242, 21, 21, 0.47);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
opacity: 0.96;
|
||||||
|
}
|
||||||
|
|
||||||
|
.primary-btn-text {
|
||||||
|
font-size: 42rpx;
|
||||||
|
color: #fff9e7;
|
||||||
|
text-shadow: -2rpx 2rpx 8rpx rgba(0, 0, 0, 0.84);
|
||||||
|
font-family: "Abhaya Libre Medium", sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 其他账号登录 */
|
||||||
|
.secondary-btn-wrapper {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.secondary-btn {
|
||||||
|
width: 394rpx;
|
||||||
|
height: 104rpx;
|
||||||
|
border-radius: 58rpx;
|
||||||
|
background: linear-gradient(
|
||||||
|
90deg,
|
||||||
|
rgba(255, 222, 8, 0.06) 0%,
|
||||||
|
rgba(252, 100, 102, 0.12) 64%,
|
||||||
|
rgba(244, 88, 104, 0.12) 100%
|
||||||
|
);
|
||||||
|
box-shadow: 4rpx 4rpx 8rpx 0 rgba(242, 21, 21, 0.47);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
opacity: 0.96;
|
||||||
|
}
|
||||||
|
|
||||||
|
.secondary-btn-text {
|
||||||
|
font-size: 42rpx;
|
||||||
|
color: #fff9e7;
|
||||||
|
text-shadow: -2rpx 2rpx 8rpx rgba(0, 0, 0, 0.84);
|
||||||
|
font-family: "Abhaya Libre Medium", sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 底部协议 */
|
||||||
|
.agreement-wrapper {
|
||||||
|
margin-top: 200rpx;
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 0 8rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.agreement-checkbox {
|
||||||
|
width: 44rpx;
|
||||||
|
height: 44rpx;
|
||||||
|
border-radius: 58rpx;
|
||||||
|
background: rgba(59, 58, 56, 0.19);
|
||||||
|
box-shadow: 4rpx 4rpx 18rpx 0 rgba(242, 21, 21, 0.27);
|
||||||
|
flex-shrink: 0;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
opacity: 0.96;
|
||||||
|
}
|
||||||
|
|
||||||
|
.agreement-checkbox.checked {
|
||||||
|
background: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.agreement-text {
|
||||||
|
margin-left: 12rpx;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.agreement-line {
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: #fff;
|
||||||
|
text-shadow: 0 2rpx 13rpx rgba(0, 0, 0, 1);
|
||||||
|
font-family: "Abhaya Libre Medium", sans-serif;
|
||||||
|
line-height: 1.4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.agreement-link {
|
||||||
|
text-decoration: underline;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 提示弹窗样式 */
|
||||||
|
.tip-dialog-mask {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background: rgba(0, 0, 0, 0.6);
|
||||||
|
z-index: 9998;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 40rpx;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tip-dialog {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 600rpx;
|
||||||
|
background: #e6e6e6;
|
||||||
|
border-radius: 20rpx;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tip-dialog-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 30rpx 40rpx;
|
||||||
|
border-bottom: 1rpx solid #e5e5e5;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tip-dialog-title {
|
||||||
|
font-size: 36rpx;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #333333;
|
||||||
|
flex: 1;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tip-dialog-close {
|
||||||
|
font-size: 48rpx;
|
||||||
|
color: #999999;
|
||||||
|
width: 60rpx;
|
||||||
|
height: 60rpx;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
position: absolute;
|
||||||
|
right: 20rpx;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tip-dialog-content {
|
||||||
|
padding: 40rpx;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tip-text {
|
||||||
|
font-size: 32rpx;
|
||||||
|
color: #333333;
|
||||||
|
line-height: 1.6;
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 20rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tip-agreement-link {
|
||||||
|
font-size: 32rpx;
|
||||||
|
color: #007aff;
|
||||||
|
text-decoration: underline;
|
||||||
|
cursor: pointer;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 协议全文弹窗 */
|
||||||
|
.agreement-dialog-mask {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background: rgba(0, 0, 0, 0.6);
|
||||||
|
z-index: 9999;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 40rpx;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.agreement-dialog {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 700rpx;
|
||||||
|
max-height: 80vh;
|
||||||
|
background: #e6e6e6;
|
||||||
|
border-radius: 20rpx;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.agreement-dialog-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 30rpx 40rpx;
|
||||||
|
border-bottom: 1rpx solid #e5e5e5;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.agreement-dialog-title {
|
||||||
|
font-size: 36rpx;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #333333;
|
||||||
|
flex: 1;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.agreement-dialog-close {
|
||||||
|
font-size: 48rpx;
|
||||||
|
color: #999999;
|
||||||
|
width: 60rpx;
|
||||||
|
height: 60rpx;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
position: absolute;
|
||||||
|
right: 20rpx;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.agreement-dialog-content {
|
||||||
|
flex: 1;
|
||||||
|
padding: 40rpx;
|
||||||
|
overflow-y: auto;
|
||||||
|
box-sizing: border-box;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.agreement-dialog-text {
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #333333;
|
||||||
|
line-height: 1.8;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
word-wrap: break-word;
|
||||||
|
word-break: break-all;
|
||||||
|
overflow-wrap: break-word;
|
||||||
|
width: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.agreement-dialog-footer {
|
||||||
|
padding: 30rpx 40rpx;
|
||||||
|
border-top: 1rpx solid #e5e5e5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.agreement-confirm-btn {
|
||||||
|
width: 100%;
|
||||||
|
height: 88rpx;
|
||||||
|
background: #000000;
|
||||||
|
color: #e6e6e6;
|
||||||
|
border-radius: 44rpx;
|
||||||
|
font-size: 32rpx;
|
||||||
|
font-weight: bold;
|
||||||
|
border: none;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.agreement-confirm-btn::after {
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -1,5 +1,8 @@
|
|||||||
<template>
|
<template>
|
||||||
<view class="square-container">
|
<!-- TOPFANS 欢迎页(确认后才显示主内容) -->
|
||||||
|
<TopfansWelcome v-if="showWelcome" @enter="handleEnterTopfans" />
|
||||||
|
|
||||||
|
<view v-else class="square-container">
|
||||||
<!-- 背景图片 -->
|
<!-- 背景图片 -->
|
||||||
<!-- <image
|
<!-- <image
|
||||||
class="bg-wrapper"
|
class="bg-wrapper"
|
||||||
@ -75,10 +78,7 @@
|
|||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 在线榜单区块 - 仅在 星榜 时显示 -->
|
<!-- 在线榜单区块 - 仅在 星榜 时显示 -->
|
||||||
<view
|
<view v-if="activeContentTab === 'xingbang'" class="hot-category-wrapper">
|
||||||
v-if="activeContentTab === 'xingbang'"
|
|
||||||
class="hot-category-wrapper"
|
|
||||||
>
|
|
||||||
<HotCategoryBlock @cardClick="handleCardClick" />
|
<HotCategoryBlock @cardClick="handleCardClick" />
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
@ -105,6 +105,7 @@ import ContentTabs from "./components/ContentTabs.vue";
|
|||||||
import HotCategoryBlock from "./components/HotCategoryBlock.vue";
|
import HotCategoryBlock from "./components/HotCategoryBlock.vue";
|
||||||
import CreationGrid from "./components/CreationGrid.vue";
|
import CreationGrid from "./components/CreationGrid.vue";
|
||||||
import StarGalaxy from "./components/StarGalaxy/index.vue";
|
import StarGalaxy from "./components/StarGalaxy/index.vue";
|
||||||
|
import TopfansWelcome from "@/components/TopfansWelcome.vue";
|
||||||
// import { clearSubStepProgress, shouldShowGuideStartModal } from '@/utils/guideConfig.js'
|
// import { clearSubStepProgress, shouldShowGuideStartModal } from '@/utils/guideConfig.js'
|
||||||
import { useBanner } from "./composables/useBanner.js";
|
import { useBanner } from "./composables/useBanner.js";
|
||||||
import { useBackgroundRefresh } from "@/composables/useBackgroundRefresh.js";
|
import { useBackgroundRefresh } from "@/composables/useBackgroundRefresh.js";
|
||||||
@ -117,6 +118,24 @@ const currentStarId = ref(uni.getStorageSync("star_id") || null);
|
|||||||
|
|
||||||
// ========== UI State ==========
|
// ========== UI State ==========
|
||||||
const activeContentTab = ref("xinghe");
|
const activeContentTab = ref("xinghe");
|
||||||
|
|
||||||
|
// TOPFANS 欢迎页:满足两个条件才显示
|
||||||
|
// 1) needs_welcome=true:quickLogin / 首次注册时由上游页面写入
|
||||||
|
// (持久化,只在 app 销毁/卸载时才丢)
|
||||||
|
// 2) !globalData.welcomeShownThisSession:本次 app 会话内尚未展示过
|
||||||
|
// (App.vue 的 onLaunch 触发时重置为 false,即"后台关闭后重新打开"或"首次启动"两种冷启动场景)
|
||||||
|
// (onShow 不重置,避免按 home 键前后台切换时误触发)
|
||||||
|
const showWelcome = ref(
|
||||||
|
!!uni.getStorageSync("needs_welcome") &&
|
||||||
|
!getApp().globalData.welcomeShownThisSession,
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleEnterTopfans = () => {
|
||||||
|
showWelcome.value = false;
|
||||||
|
// 标记本次会话已展示过,避免后续回到 square 时再次触发。
|
||||||
|
// needs_welcome 不在这里焚,按需保留到 app 销毁。
|
||||||
|
getApp().globalData.welcomeShownThisSession = true;
|
||||||
|
};
|
||||||
const navExpanded = ref(false);
|
const navExpanded = ref(false);
|
||||||
const showRankingModal = ref(false);
|
const showRankingModal = ref(false);
|
||||||
const creationGridRef = ref(null);
|
const creationGridRef = ref(null);
|
||||||
@ -198,20 +217,22 @@ const handleBannerClick = (banner) => {
|
|||||||
|
|
||||||
// 拼接 banner 跳转 url:route + params(JSON 字符串解析后作为 query)
|
// 拼接 banner 跳转 url:route + params(JSON 字符串解析后作为 query)
|
||||||
const buildBannerUrl = (banner) => {
|
const buildBannerUrl = (banner) => {
|
||||||
const base = banner.route || ""
|
const base = banner.route || "";
|
||||||
if (!banner.params) return base
|
if (!banner.params) return base;
|
||||||
try {
|
try {
|
||||||
const obj = JSON.parse(banner.params)
|
const obj = JSON.parse(banner.params);
|
||||||
const entries = Object.entries(obj)
|
const entries = Object.entries(obj)
|
||||||
.filter(([, v]) => v !== undefined && v !== null)
|
.filter(([, v]) => v !== undefined && v !== null)
|
||||||
.map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(String(v))}`)
|
.map(
|
||||||
if (entries.length === 0) return base
|
([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(String(v))}`,
|
||||||
const sep = base.includes("?") ? "&" : "?"
|
);
|
||||||
return `${base}${sep}${entries.join("&")}`
|
if (entries.length === 0) return base;
|
||||||
|
const sep = base.includes("?") ? "&" : "?";
|
||||||
|
return `${base}${sep}${entries.join("&")}`;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// params 解析失败时只打印日志,不影响 route 跳转
|
// params 解析失败时只打印日志,不影响 route 跳转
|
||||||
console.warn("[square] banner.params 解析失败, 忽略", banner.params, e)
|
console.warn("[square] banner.params 解析失败, 忽略", banner.params, e);
|
||||||
return base
|
return base;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
BIN
frontend/static/login/portal/bg.png
Normal file
|
After Width: | Height: | Size: 548 KiB |
BIN
frontend/static/login/portal/character1.png
Normal file
|
After Width: | Height: | Size: 1.1 MiB |
BIN
frontend/static/login/portal/character2.png
Normal file
|
After Width: | Height: | Size: 864 KiB |
BIN
frontend/static/login/portal/monster-large.png
Normal file
|
After Width: | Height: | Size: 4.0 MiB |
BIN
frontend/static/login/portal/quicklogin-avatar.png
Normal file
|
After Width: | Height: | Size: 70 KiB |
BIN
frontend/static/login/portal/register-avatar.png
Normal file
|
After Width: | Height: | Size: 1.1 MiB |
BIN
frontend/static/login/portal/register-tab-bg.png
Normal file
|
After Width: | Height: | Size: 56 KiB |
BIN
frontend/static/login/portal/selectrole-photo1.png
Normal file
|
After Width: | Height: | Size: 93 KiB |
BIN
frontend/static/login/portal/selectrole-photo2.png
Normal file
|
After Width: | Height: | Size: 312 KiB |
BIN
frontend/static/login/portal/setnickname-avatar-box.png
Normal file
|
After Width: | Height: | Size: 2.0 MiB |
BIN
frontend/static/login/portal/setnickname-edit-icon.png
Normal file
|
After Width: | Height: | Size: 200 KiB |
BIN
frontend/static/login/portal/topfans-logo.png
Normal file
|
After Width: | Height: | Size: 257 KiB |
BIN
frontend/static/login/portal/welcome-bg.png
Normal file
|
After Width: | Height: | Size: 2.3 MiB |
@ -79,6 +79,18 @@ const mutations = {
|
|||||||
// 清除每日登录打卡记录
|
// 清除每日登录打卡记录
|
||||||
const today = new Date().toISOString().split('T')[0]
|
const today = new Date().toISOString().split('T')[0]
|
||||||
uni.removeStorageSync(`daily_login_completed_${today}`)
|
uni.removeStorageSync(`daily_login_completed_${today}`)
|
||||||
|
|
||||||
|
// 重置 TopfansWelcome 相关标记,让登出后再登录(quickLogin / 注册)能再次展示欢迎页
|
||||||
|
// 同时清掉 storage 和内存标记,避免 password 登录误触发(见 store/modules/user.js 注释表)
|
||||||
|
uni.removeStorageSync('needs_welcome')
|
||||||
|
try {
|
||||||
|
const app = getApp()
|
||||||
|
if (app && app.globalData) {
|
||||||
|
app.globalData.welcomeShownThisSession = false
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
// getApp() 在非 App 上下文不可用,忽略即可
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||