hugray-user-miniapp/pages/login/index.vue
@@ -1,49 +1,123 @@
<template>
    <view class="login-page">
        <!-- 顶部品牌区:深蓝底承载品牌 logo 与欢迎语 -->
        <view class="hero" :style="{ paddingTop: statusBarHeight + 'px' }">
            <view class="nav-inner">
                <image class="brand-logo" src="/static/logo.png" mode="heightFix" />
            </view>
            <view class="welcome-block">
                <text class="welcome-title">欢迎使用</text>
                <view class="brand-row">
                    <text class="brand-name">代谢</text>
                    <text class="brand-num">300</text>
        <!-- 背景装饰层:3 处微透十字 + 3 处白色微点阵(置于内容之下,不拦截点击) -->
        <view class="deco-cross deco-cross--tr"></view>
        <view class="deco-cross deco-cross--ml"></view>
        <view class="deco-cross deco-cross--bl"></view>
        <view class="deco-dots deco-dots--lt"></view>
        <view class="deco-dots deco-dots--rt"></view>
        <view class="deco-dots deco-dots--rb"></view>
        <!-- 核心内容区:欢迎语 + 官方 Logo + 英译标语 + 医学双背书 + 产业4.0标语卡 -->
        <view class="content" :style="{ paddingTop: statusBarHeight + 40 + 'px' }">
            <text class="welcome-title">欢迎来到</text>
            <image class="brand-logo" src="/static/metabolism-300-logo.png" mode="widthFix" />
            <text class="en-slogan">RESET METABOLISM &amp; DELIVER HEALTH</text>
            <!-- 医学价值双背书行:严谨医学循证 / 传递健康价值 -->
            <view class="endorse-row">
                <view class="endorse-item">
                    <image class="endorse-icon" src="/static/medical-plus-icon.png" mode="aspectFit" />
                    <text class="endorse-text">严谨医学循证</text>
                </view>
                <view class="endorse-item">
                    <image
                        class="endorse-icon endorse-icon--check"
                        src="/static/check-icon.png"
                        mode="aspectFit"
                    />
                    <text class="endorse-text">传递健康价值</text>
                </view>
            </view>
            <!-- 产业4.0核心标语卡:整图呈现,图片自带透明留白(左右对称、上下需负边距补偿) -->
            <image class="slogan-card" src="/static/industry-4-0-slogan-card.png" mode="widthFix" />
        </view>
        <!-- 底部主体区:协议勾选 + 手机号一键登录 -->
        <view class="login-body">
            <!-- 协议勾选 -->
        <!-- 底部操作区:双按钮 + 内嵌验证码面板 + 协议行 -->
        <view class="bottom-area">
            <!-- 双按钮组:微信一键登录 / 手机验证登录 -->
            <view class="btn-group">
                <!-- 微信一键登录:未勾选协议时 open-type 置空,由点击事件拦截并弹窗提示 -->
                <button
                    class="btn btn--wechat"
                    :open-type="agreed ? 'getPhoneNumber' : ''"
                    @tap="_handleWechatTap"
                    @getphonenumber="_handleGetPhoneNumber"
                >
                    <image class="btn-icon" src="/static/wechat-icon.png" mode="aspectFit" />
                    <text class="btn-text">微信一键登录</text>
                </button>
                <!-- 手机验证登录:点击展开内嵌手机号+验证码面板(展开后隐藏本按钮) -->
                <view v-if="!showCodePanel" class="btn btn--phone" @tap="_toggleCodePanel">
                    <image class="btn-icon" src="/static/phone-icon.png" mode="aspectFit" />
                    <text class="btn-text">手机验证登录</text>
                </view>
            </view>
            <!-- 内嵌手机验证登录面板(不新增页面,当前页展开) -->
            <view v-if="showCodePanel" class="code-panel">
                <!-- 收起入口:按钮已隐藏,由此恢复「手机验证登录」按钮 -->
                <view class="code-panel-collapse" @tap="_toggleCodePanel">收起</view>
                <view class="input-wrap">
                    <input
                        v-model="phone"
                        class="field-input"
                        type="number"
                        maxlength="11"
                        placeholder="请输入手机号"
                        placeholder-style="color:#94A3B8"
                    />
                </view>
                <view class="code-row">
                    <view class="input-wrap input-wrap--flex">
                        <input
                            v-model="code"
                            class="field-input"
                            type="number"
                            maxlength="6"
                            placeholder="请输入验证码"
                            placeholder-style="color:#94A3B8"
                        />
                    </view>
                    <!-- 获取验证码:倒计时期间禁用,显示剩余秒数 -->
                    <view
                        class="code-btn"
                        :class="{ 'code-btn--disabled': counting }"
                        @tap="_handleSendCode"
                    >
                        {{ counting ? countdown + 's' : '获取验证码' }}
                    </view>
                </view>
                <view class="submit-btn" @tap="_handleCodeLogin">登录</view>
            </view>
            <!-- 协议勾选行:复选框 + 协议文案(设计稿 16pt 复选框 / 11.5pt 文案) -->
            <view class="agreement-row">
                <view
                    class="checkbox"
                    :class="{ 'checkbox--checked': agreed }"
                    @tap="agreed = !agreed"
                ></view>
                <text class="agreement-text">我已阅读并同意</text>
                <text class="agreement-link" @tap.stop="_openAgreement('用户协议')">《用户协议》</text>
                <text class="agreement-text">、</text>
                <text class="agreement-link" @tap.stop="_openAgreement('隐私与权限协议')">《隐私与权限协议》</text>
                <view class="agreement-words">
                    <text class="agreement-text">我已阅读并同意</text>
                    <text class="agreement-link" @tap.stop="_openAgreement('用户协议')">《用户协议》</text>
                    <text class="agreement-text">与</text>
                    <text class="agreement-link" @tap.stop="_openAgreement('隐私与权限协议')">
                        《隐私与权限》
                    </text>
                </view>
            </view>
            <!-- 手机号一键登录:未勾选协议时 open-type 置空,由点击事件拦截并弹窗提示 -->
            <button
                class="login-btn"
                :open-type="agreed ? 'getPhoneNumber' : ''"
                @tap="_handleLoginTap"
                @getphonenumber="_handleGetPhoneNumber"
            >
                手机号一键登录
            </button>
            <!-- 注册入口:跳转注册页 -->
            <!-- 注册入口:设计稿无该入口,按业务要求保留在协议行下方(弱化样式) -->
            <view class="register-entry">
                <text class="register-tip">还没有账号?</text>
                <text class="register-link" @tap="_goRegister">立即注册</text>
            </view>
            <!-- 底部 Home 条(设计稿 135×5pt,居中贴底) -->
            <view class="home-indicator"></view>
        </view>
        <!-- 未勾选协议时的确认弹窗 -->
@@ -63,8 +137,9 @@
</template>
<script setup>
import { ref } from 'vue'
import { phoneLogin } from '@/api/index.js'
import { ref, computed } from 'vue'
import { onUnload } from '@dcloudio/uni-app'
import { phoneLogin, phoneCodeLogin, sendSmsCode } from '@/api/index.js'
import { setToken } from '@/utils/request.js'
// ======================== 数据定义区域 ========================
@@ -72,22 +147,54 @@
// 是否已勾选同意用户协议
const agreed = ref(false)
// 登录请求进行中标记,防止重复提交
// 微信一键登录进行中标记,防止重复提交
const logging = ref(false)
// 验证码登录进行中标记,防止重复提交
const codeLogging = ref(false)
// 未勾选协议时确认弹窗的显示状态
const showAgreementModal = ref(false)
// 状态栏高度(px),用于顶部品牌区留白
// 是否展开内嵌手机验证登录面板
const showCodePanel = ref(false)
// 验证码登录表单
const phone = ref('')
const code = ref('')
// 验证码倒计时剩余秒数(0 表示未在倒计时)
const countdown = ref(0)
// 是否处于倒计时状态(用于禁用「获取验证码」)
const counting = computed(() => countdown.value > 0)
// 倒计时定时器句柄
let countdownTimer = null
// 状态栏高度(px),用于顶部内容区留白
const statusBarHeight = uni.getSystemInfoSync().statusBarHeight || 0
// ======================== 方法定义区域 ========================
/** 点击登录:未勾选协议时弹窗二次确认 */
const _handleLoginTap = () => {
/** 校验手机号格式 */
const _isValidPhone = (phone) => /^1[3-9]\d{9}$/.test(phone)
/**
 * 协议校验:未勾选时弹出确认弹窗并返回 false
 * @returns {Boolean} 是否已同意协议
 */
const _ensureAgreed = () => {
    if (!agreed.value) {
        showAgreementModal.value = true
        return false
    }
    return true
}
/** 点击微信一键登录:未勾选协议时弹窗二次确认(授权由 button open-type 触发) */
const _handleWechatTap = () => {
    _ensureAgreed()
}
/** 关闭协议确认弹窗 */
@@ -96,13 +203,13 @@
}
/**
 * 同意协议:自动勾选(手机号授权必须由用户点击按钮触发,
 * 同意协议:自动勾选(微信授权必须由用户手势触发,
 * 故此处仅提示用户再次点击按钮完成登录)
 */
const _handleAgreementConfirm = () => {
    showAgreementModal.value = false
    agreed.value = true
    uni.showToast({ title: '请再次点击「手机号一键登录」', icon: 'none' })
    uni.showToast({ title: '请再次点击登录按钮', icon: 'none' })
}
/**
@@ -122,16 +229,91 @@
    logging.value = true
    try {
        const { data } = await phoneLogin({ code: detail.code })
        const token = (data && (data.token || data.accessToken)) || ''
        if (!token) throw new Error('登录失败:未获取到登录凭证')
        setToken(token)
        uni.reLaunch({ url: '/pages/index/index' })
        _saveTokenAndEnter(data)
    } catch (err) {
        uni.showToast({ title: err.message || '登录失败', icon: 'none' })
    } finally {
        logging.value = false
    }
}
/** 展开/收起内嵌手机验证登录面板 */
const _toggleCodePanel = () => {
    showCodePanel.value = !showCodePanel.value
}
/** 启动 30 秒倒计时,期间禁用「获取验证码」 */
const _startCountdown = () => {
    countdown.value = 30
    countdownTimer = setInterval(() => {
        countdown.value -= 1
        if (countdown.value <= 0) {
            clearInterval(countdownTimer)
            countdownTimer = null
        }
    }, 1000)
}
/** 获取登录验证码:校验协议与手机号后下发登录场景验证码 */
const _handleSendCode = async () => {
    if (counting.value) return
    if (!_ensureAgreed()) return
    const inputPhone = phone.value.trim()
    if (!inputPhone) {
        uni.showToast({ title: '请输入手机号', icon: 'none' })
        return
    }
    if (!_isValidPhone(inputPhone)) {
        uni.showToast({ title: '请输入正确手机号', icon: 'none' })
        return
    }
    try {
        // 登录场景:后端校验手机号已注册,未注册返回「该手机号未注册,请先注册」
        await sendSmsCode(inputPhone, 'login')
        uni.showToast({ title: '验证码已发送', icon: 'none' })
        _startCountdown()
    } catch (err) {
        uni.showToast({ title: err.message || '验证码发送失败', icon: 'none' })
    }
}
/** 提交验证码登录:手机号 + 短信验证码换取登录态 */
const _handleCodeLogin = async () => {
    if (codeLogging.value) return
    if (!_ensureAgreed()) return
    const inputPhone = phone.value.trim()
    const inputCode = code.value.trim()
    if (!inputPhone || !_isValidPhone(inputPhone)) {
        uni.showToast({ title: '请输入正确手机号', icon: 'none' })
        return
    }
    if (!inputCode) {
        uni.showToast({ title: '请输入验证码', icon: 'none' })
        return
    }
    codeLogging.value = true
    try {
        const { data } = await phoneCodeLogin({ phone: inputPhone, code: inputCode })
        _saveTokenAndEnter(data)
    } catch (err) {
        uni.showToast({ title: err.message || '登录失败', icon: 'none' })
    } finally {
        codeLogging.value = false
    }
}
/** 保存令牌并进入首页(两种登录方式共用的收尾逻辑) */
const _saveTokenAndEnter = (data) => {
    // 后端 TokenService 返回字段为 access_token
    const token = (data && (data.access_token || data.token)) || ''
    if (!token) throw new Error('登录失败:未获取到登录凭证')
    setToken(token)
    uni.reLaunch({ url: '/pages/index/index' })
}
/** 跳转注册页 */
@@ -143,117 +325,414 @@
const _openAgreement = (name) => {
    uni.showToast({ title: name + '页面开发中', icon: 'none' })
}
// 页面卸载时清理倒计时,避免定时器泄漏
onUnload(() => {
    if (countdownTimer) clearInterval(countdownTimer)
})
</script>
<style scoped lang="scss">
/* 页面最外层:纵向弹性布局,撑满整屏 */
/* ==================== 页面底板 ==================== */
/* 页面最外层:浅蓝品牌渐变底(165°,0.8 透明),纵向弹性布局撑满整屏 */
.login-page {
    position: relative;
    display: flex;
    flex-direction: column;
    min-height: 100vh;
    box-sizing: border-box;
    overflow: hidden;
    background-color: #ffffff;
    background-image: linear-gradient(
        165deg,
        rgba(211, 228, 248, 0.8) 0%,
        rgba(188, 214, 244, 0.8) 50%,
        rgba(171, 204, 236, 0.8) 100%
    );
}
/* 顶部品牌区:深蓝底 + 品牌 logo(底色与 logo 背景 #0b3f6f 一致,使 logo 无缝融入) */
.hero {
    min-height: 38vh;
/* ==================== 背景装饰层 ==================== */
/* 微透十字:容器尺寸按设计稿,竖条(::before)/横条(::after)等宽居中交叉 */
.deco-cross {
    position: absolute;
    z-index: 0;
    pointer-events: none;
    &::before,
    &::after {
        content: '';
        position: absolute;
        background-color: var(--cross-color);
        border-radius: calc(var(--bar) / 2);
    }
    /* 竖条:水平居中 */
    &::before {
        left: var(--offset);
        top: 0;
        width: var(--bar);
        height: 100%;
    }
    /* 横条:垂直居中 */
    &::after {
        left: 0;
        top: var(--offset);
        width: 100%;
        height: var(--bar);
    }
}
/* 右上十字:设计稿 x290/y10,80×80pt,条宽 22 / R11,#FFFFFF35 */
.deco-cross--tr {
    top: 20rpx;
    right: 10rpx;
    width: 160rpx;
    height: 160rpx;
    --bar: 44rpx;
    --offset: 58rpx;
    --cross-color: rgba(255, 255, 255, 0.208);
}
/* 中左十字:设计稿 x-24/y470,68×68pt,条宽 18 / R9,#FFFFFF30 */
.deco-cross--ml {
    top: 57.9%;
    left: -48rpx;
    width: 136rpx;
    height: 136rpx;
    --bar: 36rpx;
    --offset: 50rpx;
    --cross-color: rgba(255, 255, 255, 0.188);
}
/* 左下十字:设计稿 x-10/y710,100×100pt,条宽 28 / R14,#FFFFFF35 */
.deco-cross--bl {
    bottom: 4rpx;
    left: -20rpx;
    width: 200rpx;
    height: 200rpx;
    --bar: 56rpx;
    --offset: 72rpx;
    --cross-color: rgba(255, 255, 255, 0.208);
}
/*
 * 白色微点阵:微点 3×3pt(6rpx),行内/行间 gap 10pt(20rpx) → 平铺步距 26rpx
 * 说明:设计稿三处点阵均为不规则装饰形状,此处以平铺网格近似
 */
.deco-dots {
    position: absolute;
    z-index: 0;
    pointer-events: none;
    background-image: radial-gradient(circle, rgba(255, 255, 255, 0.333) 3rpx, transparent 3rpx);
    background-size: 26rpx 26rpx;
}
/* 左上点阵:设计稿 x10/y200(5 列 × 4 行) */
.deco-dots--lt {
    top: 24.6%;
    left: 20rpx;
    width: 110rpx;
    height: 84rpx;
}
/* 右上点阵:设计稿 x310/y130(3 列 × 6 行) */
.deco-dots--rt {
    top: 16%;
    right: 72rpx;
    width: 58rpx;
    height: 136rpx;
}
/* 右下点阵:设计稿 x240/y752(3 列 × 3 行) */
.deco-dots--rb {
    bottom: 62rpx;
    right: 212rpx;
    width: 58rpx;
    height: 58rpx;
}
/* ==================== 核心内容区 ==================== */
/* 内容区:x28/y100,宽 319pt,元素间距 20pt(40rpx),整体水平居中 */
.content {
    position: relative;
    z-index: 1;
    display: flex;
    flex-direction: column;
    background-color: #0b3f6f;
    /* 底部留出间距,避免欢迎语贴近色块下边缘 */
    padding-bottom: 90rpx;
    box-sizing: border-box;
}
/* 品牌行:固定高度,与小程序右上角胶囊高度对齐 */
.nav-inner {
    height: 88rpx;
    display: flex;
    align-items: center;
    padding-left: 28rpx;
    box-sizing: border-box;
    gap: 40rpx;
    padding-left: 56rpx;
    padding-right: 56rpx;
}
.brand-logo {
    height: 56rpx;
    display: block;
}
/* 欢迎语与品牌名:margin-top 自动撑开上方空间,使其贴近品牌区底部 */
.welcome-block {
    margin-top: auto;
    padding: 0 40rpx;
}
/* 欢迎语:26pt 500 深板岩色,居中 */
.welcome-title {
    display: block;
    font-size: 44rpx;
    font-weight: 700;
    color: #ffffff;
    line-height: 62rpx;
    flex-shrink: 0;
    font-size: 52rpx;
    font-weight: 500;
    line-height: 74rpx;
    text-align: center;
    color: #0f172a;
}
.brand-row {
    display: flex;
    align-items: flex-end;
    margin-top: 24rpx;
/* 官方 Logo:设计稿 220×76pt(图 660×228,比例一致) */
.brand-logo {
    flex-shrink: 0;
    width: 440rpx;
    height: 152rpx;
}
.brand-name {
    font-size: 44rpx;
    font-weight: 700;
    color: #ffffff;
    line-height: 62rpx;
/* 英译标语:11pt 600 深海蓝,字间距 1.5pt,居中 */
.en-slogan {
    flex-shrink: 0;
    font-size: 22rpx;
    font-weight: 600;
    letter-spacing: 3rpx;
    text-align: center;
    color: #003e7e;
}
/* 品牌数字:绿色大号斜体,突出代谢 300 品牌标识 */
.brand-num {
    margin-left: 16rpx;
    font-size: 96rpx;
    font-weight: 800;
    font-style: italic;
    color: #8bc34a;
    line-height: 96rpx;
}
/* 主体区:承载协议勾选与登录按钮 */
.login-body {
    flex: 1;
    padding: 300rpx 32rpx 0;
    box-sizing: border-box;
}
/* 协议勾选行:整体居中排列 */
.agreement-row {
/* 医学双背书行:占满整行,两项 gap 24pt(48rpx),居中 */
.endorse-row {
    display: flex;
    align-items: center;
    justify-content: center;
    flex-wrap: wrap;
    margin-bottom: 44rpx;
    gap: 48rpx;
    width: 100%;
}
/* 单项:图标 + 文案,gap 6pt(12rpx) */
.endorse-item {
    display: flex;
    align-items: center;
    gap: 12rpx;
}
/* 加号图标 15×15pt → 30rpx */
.endorse-icon {
    width: 30rpx;
    height: 30rpx;
    flex-shrink: 0;
}
/* 勾选图标 18.2×15pt → 36.4×30rpx */
.endorse-icon--check {
    width: 36.4rpx;
    height: 30rpx;
}
/* 背书文案:12pt 600 正文强色 */
.endorse-text {
    font-size: 24rpx;
    font-weight: 600;
    color: #1e293b;
}
/*
 * 产业4.0核心标语卡:整图呈现
 * 图 1141×457,卡片描边 bbox [90,72]-[1049,347](视觉 959×275)
 * 目标卡片视觉宽 = 319pt = 638rpx → 图片宽 759rpx、高 304rpx
 * 透明留白:上 48rpx / 下 72rpx 用负边距抵消,左右对称无需补偿
 */
.slogan-card {
    flex-shrink: 0;
    width: 759rpx;
    height: 304rpx;
    margin-top: -48rpx;
    margin-bottom: -72rpx;
}
/* ==================== 底部操作区 ==================== */
/* 底部操作区:margin-top 自动撑开,整体贴近底部,左右与内容区同边距 */
.bottom-area {
    position: relative;
    z-index: 1;
    margin-top: auto;
    padding: 0 56rpx;
    padding-bottom: calc(32rpx + constant(safe-area-inset-bottom));
    padding-bottom: calc(32rpx + env(safe-area-inset-bottom));
}
/* 双按钮组:纵向排列,间距 14pt(28rpx) */
.btn-group {
    display: flex;
    flex-direction: column;
    gap: 28rpx;
}
/* 按钮基础:H48 → 96rpx,PILL R24 → 48rpx,图标与文字 gap 8pt(16rpx) */
.btn {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 16rpx;
    width: 100%;
    height: 96rpx;
    /* 重置小程序 button 默认的 margin: 0 auto(否则 flex 列内宽度会收缩为内容宽) */
    margin: 0;
    padding: 0;
    border: none;
    border-radius: 48rpx;
    box-sizing: border-box;
    line-height: 1;
}
/* 按钮内图标 18×18pt → 36rpx */
.btn-icon {
    width: 36rpx;
    height: 36rpx;
    flex-shrink: 0;
}
/* 按钮文字:15pt 600 白色 */
.btn-text {
    font-size: 30rpx;
    font-weight: 600;
    color: #ffffff;
}
/* 微信一键登录:微信绿渐变(90°)+ 绿色光晕投影 #07C16040 */
.btn--wechat {
    background-image: linear-gradient(90deg, #07c160 0%, #05a352 100%);
    box-shadow: 0 12rpx 32rpx rgba(7, 193, 96, 0.25);
}
/* 覆盖小程序 button 默认边框伪元素 */
.btn--wechat::after {
    border: none;
}
/* 手机验证登录:医疗蓝渐变(90°)+ 蓝色光晕投影 #1E5EBC30 */
.btn--phone {
    background-image: linear-gradient(90deg, #1e5ebc 0%, #144a94 100%);
    box-shadow: 0 12rpx 32rpx rgba(30, 94, 188, 0.188);
}
/* 内嵌验证码面板:表单间距 20rpx */
.code-panel {
    margin-top: 32rpx;
}
/* 收起入口:右对齐弱化文字,点击恢复「手机验证登录」按钮 */
.code-panel-collapse {
    margin-bottom: 20rpx;
    text-align: right;
    font-size: 26rpx;
    color: #1e5ebc;
}
/* 输入框:R8 → 16rpx,H44 → 88rpx,浅底 + 描边 */
.input-wrap {
    display: flex;
    align-items: center;
    height: 88rpx;
    padding: 0 28rpx;
    border: 2rpx solid #cbd5e1;
    border-radius: 16rpx;
    background-color: #f8fafc;
    box-sizing: border-box;
}
.code-row {
    display: flex;
    align-items: center;
    margin-top: 20rpx;
}
.input-wrap--flex {
    flex: 1;
    min-width: 0;
    margin-right: 16rpx;
}
.field-input {
    flex: 1;
    height: 100%;
    min-width: 0;
    font-size: 28rpx;
    color: #1e293b;
}
/* 获取验证码:白底 + 医疗蓝描边胶囊,倒计时期间置灰 */
.code-btn {
    width: 208rpx;
    height: 88rpx;
    display: flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
    border: 2rpx solid #1e5ebc;
    border-radius: 16rpx;
    background-color: #ffffff;
    color: #1e5ebc;
    font-size: 26rpx;
    font-weight: 600;
}
.code-btn--disabled {
    border-color: #cbd5e1;
    color: #94a3b8;
}
/* 登录提交按钮:品牌主行动渐变,PILL R24 */
.submit-btn {
    height: 96rpx;
    margin-top: 28rpx;
    display: flex;
    align-items: center;
    justify-content: center;
    border-radius: 48rpx;
    background-image: linear-gradient(90deg, #1e5ebc 0%, #003e7e 100%);
    color: #ffffff;
    font-size: 30rpx;
    font-weight: 600;
    box-shadow: 0 12rpx 28rpx rgba(0, 62, 126, 0.2);
}
/* 协议勾选行:与按钮组间距 19pt(38rpx),复选框与文案 gap 8pt(16rpx) */
.agreement-row {
    display: flex;
    align-items: center;
    margin-top: 38rpx;
}
/* 协议文案组合:内部字间距 1pt(2rpx) */
.agreement-words {
    display: flex;
    align-items: center;
    flex: 1;
    min-width: 0;
    gap: 2rpx;
    margin-left: 16rpx;
}
/* 勾选框:16×16pt → 32rpx,R4 → 8rpx */
.checkbox {
    position: relative;
    width: 32rpx;
    height: 32rpx;
    border: 2rpx solid #c0c4cc;
    border-radius: 4rpx;
    margin-right: 14rpx;
    flex-shrink: 0;
    border: 2rpx solid #cbd5e1;
    border-radius: 8rpx;
    background-color: #ffffff;
    box-sizing: border-box;
}
.checkbox--checked {
    background-color: #8bc34a;
    border-color: #8bc34a;
    background-color: #003e7e;
    border-color: #003e7e;
}
/* 勾选后的白色对勾 */
.checkbox--checked::after {
    content: '';
    position: absolute;
    left: 9rpx;
    top: 3rpx;
    left: 10rpx;
    top: 2rpx;
    width: 8rpx;
    height: 16rpx;
    border-right: 3rpx solid #ffffff;
@@ -261,98 +740,88 @@
    transform: rotate(45deg);
}
/* 协议文案:原稿 11.5pt,按反馈上调至 26rpx 提升可读性 */
.agreement-text {
    font-size: 24rpx;
    color: #6b7280;
    font-size: 26rpx;
    color: #64748b;
}
.agreement-link {
    font-size: 24rpx;
    color: #1a73e8;
}
/* 手机号一键登录按钮:绿色圆角块,两侧留边距 */
.login-btn {
    margin: 0 32rpx;
    height: 96rpx;
    line-height: 96rpx;
    border: none;
    border-radius: 16rpx;
    background-color: #8bc34a;
    color: #ffffff;
    font-size: 32rpx;
    font-size: 26rpx;
    font-weight: 600;
    color: #1e5ebc;
}
/* 覆盖小程序 button 默认的边框伪元素 */
.login-btn::after {
    border: none;
}
/* 注册入口:居中排列的提示文案 + 链接 */
/* 注册入口:居中弱化文案 + 链接 */
.register-entry {
    display: flex;
    align-items: center;
    justify-content: center;
    margin-top: 40rpx;
    margin-top: 28rpx;
}
.register-tip {
    font-size: 26rpx;
    color: #6b7280;
    color: #64748b;
}
.register-link {
    margin-left: 8rpx;
    font-size: 26rpx;
    color: #1a73e8;
    font-weight: 600;
    color: #1e5ebc;
}
/* 底部 Home 条:设计稿 135×5pt → 270×10rpx,R2.5 → 5rpx,#0F172A25 */
.home-indicator {
    width: 270rpx;
    height: 10rpx;
    margin: 24rpx auto 0;
    border-radius: 5rpx;
    background-color: rgba(15, 23, 42, 0.145);
}
/* ======================== 未勾选协议确认弹窗 ======================== */
/* 遮罩层:全屏半透明,居中承载弹窗 */
.modal-mask {
    position: fixed;
    left: 0;
    top: 0;
    right: 0;
    bottom: 0;
    background-color: rgba(0, 0, 0, 0.45);
    display: flex;
    align-items: center;
    justify-content: center;
    z-index: 999;
    background-color: rgba(15, 23, 42, 0.45);
}
.modal-box {
    width: 560rpx;
    background-color: #ffffff;
    border-radius: 16rpx;
    overflow: hidden;
    border-radius: 24rpx;
    background-color: #ffffff;
}
/* 标题:绿色居中,下方带分隔线 */
.modal-title {
    display: block;
    padding: 40rpx 32rpx 30rpx;
    font-size: 34rpx;
    font-weight: 600;
    color: #8bc34a;
    text-align: center;
    border-bottom: 1rpx solid #ececec;
    color: #003e7e;
    border-bottom: 2rpx solid #e2e8f0;
}
/* 正文:灰色居中的提示文案 */
.modal-content {
    display: block;
    padding: 44rpx 48rpx;
    font-size: 28rpx;
    line-height: 42rpx;
    color: #9aa1ab;
    text-align: center;
    color: #64748b;
}
/* 按钮行:取消 / 我已阅读并同意 各占一半 */
.modal-actions {
    display: flex;
    align-items: center;
@@ -367,10 +836,10 @@
}
.modal-btn--cancel {
    color: #1f2329;
    color: #334155;
}
.modal-btn--confirm {
    color: #8bc34a;
    color: #1e5ebc;
}
</style>