| hugray-partner-miniapp/pages/login/index.vue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| hugray-user-miniapp/api/index.js | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| hugray-user-miniapp/pages.json | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| hugray-user-miniapp/pages/index/index.vue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| hugray-user-miniapp/pages/login/index.vue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| hugray-user-miniapp/pages/login/register-success.vue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| hugray-user-miniapp/pages/login/register.vue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| hugray-user-miniapp/static/logo.png | 补丁 | 查看 | 原始文档 | blame | 历史 |
hugray-partner-miniapp/pages/login/index.vue
@@ -39,7 +39,7 @@ v-model="form.password" class="field-input" password placeholder="请输入验证码" placeholder="请输入密码" placeholder-style="color:#b8bec7;font-size:28rpx" /> </view> @@ -66,6 +66,20 @@ <view class="bottom-bar"> <view class="login-btn" @tap="_handleLogin">登录</view> </view> <!-- 未勾选协议时的确认弹窗 --> <view v-if="showAgreementModal" class="modal-mask"> <view class="modal-box"> <text class="modal-title">用户协议与隐私权限</text> <text class="modal-content">请确认已同意《用户协议》、《隐私与权限协议》</text> <view class="modal-actions"> <view class="modal-btn modal-btn--cancel" @tap="_handleAgreementCancel">取消</view> <view class="modal-btn modal-btn--confirm" @tap="_handleAgreementConfirm"> 我已阅读并同意 </view> </view> </view> </view> </view> </template> @@ -88,6 +102,9 @@ // 登录请求进行中标记,防止重复提交 const logging = ref(false) // 未勾选协议时确认弹窗的显示状态 const showAgreementModal = ref(false) // 状态栏高度(px),用于自定义导航栏顶部留白 const statusBarHeight = uni.getSystemInfoSync().statusBarHeight || 0 @@ -97,17 +114,34 @@ const _validateForm = () => { if (!form.username.trim()) return '请输入账号' if (!form.password.trim()) return '请输入密码' if (!agreed.value) return '请先阅读并同意用户协议' return '' } /** 执行登录:校验 → 调接口 → 写入 token → 跳转首页 */ /** 关闭协议确认弹窗 */ const _handleAgreementCancel = () => { showAgreementModal.value = false } /** 同意协议:自动勾选并继续登录 */ const _handleAgreementConfirm = () => { showAgreementModal.value = false agreed.value = true _handleLogin() } /** 执行登录:校验 → 协议确认 → 调接口 → 写入 token → 跳转首页 */ const _handleLogin = async () => { if (logging.value) return const errorMsg = _validateForm() if (errorMsg) { uni.showToast({ title: errorMsg, icon: 'none' }) return } // 未勾选协议:先弹窗二次确认,用户同意后再继续登录 if (!agreed.value) { showAgreementModal.value = true return } @@ -330,4 +364,70 @@ font-size: 32rpx; font-weight: 600; } /* ======================== 未勾选协议确认弹窗 ======================== */ /* 遮罩层:全屏半透明,居中承载弹窗 */ .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; } .modal-box { width: 560rpx; background-color: #ffffff; border-radius: 16rpx; overflow: hidden; } /* 标题:绿色居中,下方带分隔线 */ .modal-title { display: block; padding: 40rpx 32rpx 30rpx; font-size: 34rpx; font-weight: 600; color: #8bc34a; text-align: center; border-bottom: 1rpx solid #ececec; } /* 正文:灰色居中的提示文案 */ .modal-content { display: block; padding: 44rpx 48rpx; font-size: 28rpx; line-height: 42rpx; color: #9aa1ab; text-align: center; } /* 按钮行:取消 / 我已阅读并同意 各占一半 */ .modal-actions { display: flex; align-items: center; padding-bottom: 24rpx; } .modal-btn { flex: 1; text-align: center; font-size: 30rpx; font-weight: 600; } .modal-btn--cancel { color: #1f2329; } .modal-btn--confirm { color: #8bc34a; } </style> hugray-user-miniapp/api/index.js
@@ -23,6 +23,32 @@ } /** * 手机号一键登录(微信手机号快速验证,凭证换取登录态) * @param {Object} data { code } 手机号快速验证凭证 */ export function phoneLogin(data) { return post('/auth/phone/login', data, { auth: false }) } /** * 发送短信验证码 * @param {String} phone 手机号 * @param {String} [scene] 业务场景,register=注册 / reset=重置密码,默认注册 * 说明:注册场景下后端会先判断手机号是否已注册,已注册返回「手机号已注册,请前往登录」 */ export function sendSmsCode(phone, scene = 'register') { return post('/auth/sms/code', { phone, scene }, { auth: false }) } /** * 用户注册(推荐码必填,无推荐码无法注册) * @param {Object} data { phone, code, referralCode } */ export function register(data) { return post('/auth/register', data, { auth: false }) } /** * 退出登录 */ export function logout() { hugray-user-miniapp/pages.json
@@ -3,13 +3,33 @@ { "path": "pages/index/index", "style": { "navigationBarTitleText": "uni-app" "navigationBarTitleText": "" } }, { "path": "pages/login/index", "style": { "navigationStyle": "custom" } }, { "path": "pages/login/register", "style": { "navigationStyle": "custom" } }, { "path": "pages/login/register-success", "style": { "navigationBarTitleText": "注册成功", "navigationBarBackgroundColor": "#ffffff", "navigationBarTextStyle": "black" } } ], "globalStyle": { "navigationBarTextStyle": "black", "navigationBarTitleText": "uni-app", "navigationBarTitleText": "", "navigationBarBackgroundColor": "#F8F8F8", "backgroundColor": "#F8F8F8" }, hugray-user-miniapp/pages/index/index.vue
@@ -1,52 +1,24 @@ <template> <view class="content"> <image class="logo" src="/static/logo.png"></image> <view class="text-area"> <text class="title">{{title}}</text> </view> </view> </template> <script> export default { data() { return { title: 'Hello' } }, onLoad() { <script setup> import { onShow } from '@dcloudio/uni-app' import { getToken } from '@/utils/request.js' import config from '@/config/index.js' }, methods: { /** * 校验登录态:本地无 token 时重定向至登录页 */ const _checkLoginStatus = () => { if (getToken()) return uni.reLaunch({ url: config.loginPage }) } } } // 每次页面显示均校验登录态,避免退出登录后仍停留在首页 onShow(() => { _checkLoginStatus() }) </script> <style> .content { display: flex; flex-direction: column; align-items: center; justify-content: center; } .logo { height: 200rpx; width: 200rpx; margin-top: 200rpx; margin-left: auto; margin-right: auto; margin-bottom: 50rpx; } .text-area { display: flex; justify-content: center; } .title { font-size: 36rpx; color: #8f8f94; } <style scoped lang="scss"> </style> hugray-user-miniapp/pages/login/index.vue
New file @@ -0,0 +1,376 @@ <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> </view> </view> </view> <!-- 底部主体区:协议勾选 + 手机号一键登录 --> <view class="login-body"> <!-- 协议勾选 --> <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> <!-- 手机号一键登录:未勾选协议时 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> </view> <!-- 未勾选协议时的确认弹窗 --> <view v-if="showAgreementModal" class="modal-mask"> <view class="modal-box"> <text class="modal-title">用户协议与隐私权限</text> <text class="modal-content">请确认已同意《用户协议》、《隐私与权限协议》</text> <view class="modal-actions"> <view class="modal-btn modal-btn--cancel" @tap="_handleAgreementCancel">取消</view> <view class="modal-btn modal-btn--confirm" @tap="_handleAgreementConfirm"> 我已阅读并同意 </view> </view> </view> </view> </view> </template> <script setup> import { ref } from 'vue' import { phoneLogin } from '@/api/index.js' import { setToken } from '@/utils/request.js' // ======================== 数据定义区域 ======================== // 是否已勾选同意用户协议 const agreed = ref(false) // 登录请求进行中标记,防止重复提交 const logging = ref(false) // 未勾选协议时确认弹窗的显示状态 const showAgreementModal = ref(false) // 状态栏高度(px),用于顶部品牌区留白 const statusBarHeight = uni.getSystemInfoSync().statusBarHeight || 0 // ======================== 方法定义区域 ======================== /** 点击登录:未勾选协议时弹窗二次确认 */ const _handleLoginTap = () => { if (!agreed.value) { showAgreementModal.value = true } } /** 关闭协议确认弹窗 */ const _handleAgreementCancel = () => { showAgreementModal.value = false } /** * 同意协议:自动勾选(手机号授权必须由用户点击按钮触发, * 故此处仅提示用户再次点击按钮完成登录) */ const _handleAgreementConfirm = () => { showAgreementModal.value = false agreed.value = true uni.showToast({ title: '请再次点击「手机号一键登录」', icon: 'none' }) } /** * 微信手机号授权回调:拿到手机号凭证后换取登录态 * @param {Object} e 授权结果,e.detail.code 为手机号快速验证凭证 */ const _handleGetPhoneNumber = async (e) => { if (logging.value) return const detail = (e && e.detail) || {} // 用户拒绝授权或未取得手机号凭证 if (!detail.code) { uni.showToast({ title: '已取消手机号授权', icon: 'none' }) return } 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' }) } catch (err) { uni.showToast({ title: err.message || '登录失败', icon: 'none' }) } finally { logging.value = false } } /** 跳转注册页 */ const _goRegister = () => { uni.navigateTo({ url: '/pages/login/register' }) } /** 查看协议(协议页尚未开发,先以提示占位) */ const _openAgreement = (name) => { uni.showToast({ title: name + '页面开发中', icon: 'none' }) } </script> <style scoped lang="scss"> /* 页面最外层:纵向弹性布局,撑满整屏 */ .login-page { display: flex; flex-direction: column; min-height: 100vh; background-color: #ffffff; } /* 顶部品牌区:深蓝底 + 品牌 logo(底色与 logo 背景 #0b3f6f 一致,使 logo 无缝融入) */ .hero { min-height: 38vh; 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; } .brand-logo { height: 56rpx; display: block; } /* 欢迎语与品牌名:margin-top 自动撑开上方空间,使其贴近品牌区底部 */ .welcome-block { margin-top: auto; padding: 0 40rpx; } .welcome-title { display: block; font-size: 44rpx; font-weight: 700; color: #ffffff; line-height: 62rpx; } .brand-row { display: flex; align-items: flex-end; margin-top: 24rpx; } .brand-name { font-size: 44rpx; font-weight: 700; color: #ffffff; line-height: 62rpx; } /* 品牌数字:绿色大号斜体,突出代谢 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 { display: flex; align-items: center; justify-content: center; flex-wrap: wrap; margin-bottom: 44rpx; } .checkbox { position: relative; width: 32rpx; height: 32rpx; border: 2rpx solid #c0c4cc; border-radius: 4rpx; margin-right: 14rpx; flex-shrink: 0; box-sizing: border-box; } .checkbox--checked { background-color: #8bc34a; border-color: #8bc34a; } /* 勾选后的白色对勾 */ .checkbox--checked::after { content: ''; position: absolute; left: 9rpx; top: 3rpx; width: 8rpx; height: 16rpx; border-right: 3rpx solid #ffffff; border-bottom: 3rpx solid #ffffff; transform: rotate(45deg); } .agreement-text { font-size: 24rpx; color: #6b7280; } .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-weight: 600; } /* 覆盖小程序 button 默认的边框伪元素 */ .login-btn::after { border: none; } /* 注册入口:居中排列的提示文案 + 链接 */ .register-entry { display: flex; align-items: center; justify-content: center; margin-top: 40rpx; } .register-tip { font-size: 26rpx; color: #6b7280; } .register-link { margin-left: 8rpx; font-size: 26rpx; color: #1a73e8; } /* ======================== 未勾选协议确认弹窗 ======================== */ /* 遮罩层:全屏半透明,居中承载弹窗 */ .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; } .modal-box { width: 560rpx; background-color: #ffffff; border-radius: 16rpx; overflow: hidden; } /* 标题:绿色居中,下方带分隔线 */ .modal-title { display: block; padding: 40rpx 32rpx 30rpx; font-size: 34rpx; font-weight: 600; color: #8bc34a; text-align: center; border-bottom: 1rpx solid #ececec; } /* 正文:灰色居中的提示文案 */ .modal-content { display: block; padding: 44rpx 48rpx; font-size: 28rpx; line-height: 42rpx; color: #9aa1ab; text-align: center; } /* 按钮行:取消 / 我已阅读并同意 各占一半 */ .modal-actions { display: flex; align-items: center; padding-bottom: 24rpx; } .modal-btn { flex: 1; text-align: center; font-size: 30rpx; font-weight: 600; } .modal-btn--cancel { color: #1f2329; } .modal-btn--confirm { color: #8bc34a; } </style> hugray-user-miniapp/pages/login/register-success.vue
New file @@ -0,0 +1,91 @@ <template> <view class="success-page"> <!-- 结果区:绿色圆形对勾 + 成功文案 --> <view class="result-block"> <view class="success-icon"></view> <text class="success-text">您的账号已成功注册</text> </view> <!-- 底部通栏返回登录按钮 --> <view class="bottom-bar"> <view class="submit-btn" @tap="_handleBackLogin">返回登录</view> </view> </view> </template> <script setup> // ======================== 方法定义区域 ======================== /** 返回登录页:清空页面栈,重新进入登录页 */ const _handleBackLogin = () => { uni.reLaunch({ url: '/pages/login/index' }) } </script> <style scoped lang="scss"> /* 页面最外层:纵向弹性布局,撑满整屏 */ .success-page { display: flex; flex-direction: column; min-height: 100vh; background-color: #ffffff; box-sizing: border-box; } /* 结果区:内容水平居中,与顶部导航拉开距离 */ .result-block { display: flex; flex-direction: column; align-items: center; padding: 90rpx 0 50rpx; } /* 绿色圆形底 + 白色对勾(对勾由两条边框旋转 45° 拼成) */ .success-icon { position: relative; width: 160rpx; height: 160rpx; border-radius: 50%; background-color: #8bc34a; } .success-icon::after { content: ''; position: absolute; left: 54rpx; top: 30rpx; width: 40rpx; height: 72rpx; border-right: 8rpx solid #ffffff; border-bottom: 8rpx solid #ffffff; transform: rotate(45deg); } .success-text { margin-top: 40rpx; font-size: 30rpx; color: #1f2329; } /* 底部按钮区:白底 + 左右留边距,底部叠加安全区 */ .bottom-bar { margin-top: auto; padding: 20rpx 32rpx; padding-bottom: calc(20rpx + constant(safe-area-inset-bottom)); padding-bottom: calc(20rpx + env(safe-area-inset-bottom)); background-color: #ffffff; } /* 返回登录按钮:绿色实心,圆角与登录页「手机号一键登录」保持一致 */ .submit-btn { height: 96rpx; display: flex; align-items: center; justify-content: center; border-radius: 16rpx; background-color: #8bc34a; color: #ffffff; font-size: 32rpx; font-weight: 600; } </style> hugray-user-miniapp/pages/login/register.vue
New file @@ -0,0 +1,360 @@ <template> <view class="register-page"> <!-- 顶部品牌导航栏:深蓝底 + 品牌 logo(与登录页保持一致) --> <view class="nav-bar" :style="{ paddingTop: statusBarHeight + 'px' }"> <view class="nav-inner"> <image class="brand-logo" src="/static/logo.png" mode="heightFix" /> </view> </view> <!-- 表单区 --> <view class="form-area"> <!-- 手机号 --> <view class="field-block"> <text class="field-label">手机号</text> <view class="input-wrap"> <view class="field-icon icon-phone"></view> <input v-model="form.phone" class="field-input" type="number" maxlength="11" placeholder="请输入账号" placeholder-style="color:#b8bec7;font-size:28rpx" /> </view> </view> <!-- 验证码 --> <view class="field-block"> <text class="field-label">验证码</text> <view class="code-row"> <view class="input-wrap input-wrap--flex"> <view class="field-icon icon-shield"></view> <input v-model="form.code" class="field-input" type="number" maxlength="6" placeholder="请输入验证码" placeholder-style="color:#b8bec7;font-size:28rpx" /> </view> <!-- 获取验证码:倒计时期间禁用,显示剩余秒数 --> <view class="code-btn" :class="{ 'code-btn--disabled': counting }" @tap="_handleSendCode" > {{ counting ? countdown + 's' : '获取验证码' }} </view> </view> </view> <!-- 推荐码(必填,无推荐码无法注册) --> <view class="field-block"> <text class="field-label">推荐码</text> <view class="input-wrap"> <view class="field-icon icon-grid"></view> <input v-model="form.referralCode" class="field-input" maxlength="20" placeholder="请输入推荐人码" placeholder-style="color:#b8bec7;font-size:28rpx" /> </view> </view> <!-- 已有账号:右对齐,点击返回登录页 --> <view class="has-account" @tap="_handleBackLogin"> <text class="has-account-text">已有账号</text> <text class="has-account-link">去登录</text> </view> </view> <!-- 底部通栏注册按钮 --> <view class="bottom-bar"> <view class="submit-btn" @tap="_handleRegister">注册</view> </view> </view> </template> <script setup> import { ref, reactive, computed } from 'vue' import { onUnload } from '@dcloudio/uni-app' import { sendSmsCode, register } from '@/api/index.js' // ======================== 数据定义区域 ======================== // 注册表单数据 const form = reactive({ phone: '', code: '', referralCode: '' }) // 验证码倒计时剩余秒数(0 表示未在倒计时) const countdown = ref(0) // 是否处于倒计时状态(用于禁用「获取验证码」按钮) const counting = computed(() => countdown.value > 0) // 注册请求进行中标记,防止重复提交 const registering = ref(false) // 倒计时定时器句柄 let countdownTimer = null // 状态栏高度(px),用于顶部品牌区留白 const statusBarHeight = uni.getSystemInfoSync().statusBarHeight || 0 // ======================== 方法定义区域 ======================== /** 校验手机号格式 */ const _isValidPhone = (phone) => /^1[3-9]\d{9}$/.test(phone) /** 启动 30 秒倒计时,期间禁用「获取验证码」,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 const phone = form.phone.trim() if (!phone) { uni.showToast({ title: '请输入手机号', icon: 'none' }) return } if (!_isValidPhone(phone)) { uni.showToast({ title: '请输入正确手机号', icon: 'none' }) return } try { await sendSmsCode(phone) uni.showToast({ title: '验证码已发送', icon: 'none' }) _startCountdown() } catch (e) { uni.showToast({ title: e.message || '验证码发送失败', icon: 'none' }) } } /** 表单校验,返回校验失败原因(通过则返回空串) */ const _validateForm = () => { if (!form.phone.trim()) return '请输入手机号' if (!_isValidPhone(form.phone.trim())) return '请输入正确手机号' if (!form.code.trim()) return '请输入验证码' // 推荐码为必填,没有推荐码无法注册 if (!form.referralCode.trim()) return '请输入推荐人码' return '' } /** 提交注册:校验 → 调接口 → 跳转注册成功页 */ const _handleRegister = async () => { if (registering.value) return const errorMsg = _validateForm() if (errorMsg) { uni.showToast({ title: errorMsg, icon: 'none' }) return } registering.value = true try { await register({ phone: form.phone.trim(), code: form.code.trim(), referralCode: form.referralCode.trim() }) // 替换当前页,注册成功页返回时直接回到登录页 uni.redirectTo({ url: '/pages/login/register-success' }) } catch (e) { uni.showToast({ title: e.message || '注册失败', icon: 'none' }) } finally { registering.value = false } } /** 返回登录页 */ const _handleBackLogin = () => { uni.navigateBack() } // 页面卸载时清理倒计时,避免定时器泄漏 onUnload(() => { if (countdownTimer) clearInterval(countdownTimer) }) </script> <style scoped lang="scss"> /* 页面最外层:纵向弹性布局,撑满整屏 */ .register-page { display: flex; flex-direction: column; height: 100vh; background-color: #ffffff; } /* 顶部品牌导航栏:深蓝底,与登录页品牌区一致 */ .nav-bar { background-color: #0b3f6f; } .nav-inner { height: 88rpx; display: flex; align-items: center; padding-left: 28rpx; box-sizing: border-box; } .brand-logo { height: 56rpx; display: block; } .form-area { flex: 1; padding: 40rpx 28rpx 0; box-sizing: border-box; } /* 单个输入项 */ .field-block { margin-bottom: 40rpx; } .field-label { display: block; font-size: 28rpx; color: #1f2329; margin-bottom: 20rpx; } /* 带图标的输入框 */ .input-wrap { display: flex; align-items: center; height: 96rpx; padding: 0 24rpx; border: 1rpx solid #dcdfe6; border-radius: 8rpx; box-sizing: border-box; } /* 验证码行:输入框自适应占满剩余宽度 */ .code-row { display: flex; align-items: center; } .input-wrap--flex { flex: 1; min-width: 0; margin-right: 20rpx; } .field-icon { width: 44rpx; height: 44rpx; margin-right: 20rpx; flex-shrink: 0; background-repeat: no-repeat; background-position: center center; background-size: 100% 100%; } /* 手机号图标:手机外框 + 底部听筒线 */ .icon-phone { background-image: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA0NCA0NCI+CiAgPHJlY3QgeD0iMTIuNSIgeT0iNS41IiB3aWR0aD0iMTkiIGhlaWdodD0iMzMiIHJ4PSI0LjUiIGZpbGw9Im5vbmUiIHN0cm9rZT0iIzBiM2Y2ZiIgc3Ryb2tlLXdpZHRoPSIzIi8+CiAgPGxpbmUgeDE9IjE4LjUiIHkxPSIzMi41IiB4Mj0iMjUuNSIgeTI9IjMyLjUiIHN0cm9rZT0iIzBiM2Y2ZiIgc3Ryb2tlLXdpZHRoPSIzIiBzdHJva2UtbGluZWNhcD0icm91bmQiLz4KPC9zdmc+Cg=='); } /* 验证码图标:上凸尖角盾牌 + 四宫格棋盘 */ .icon-shield { background-image: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA0NCA0NCI+CiAgPGRlZnM+CiAgICA8Y2xpcFBhdGggaWQ9InNjIj4KICAgICAgPHBhdGggZD0iTTIyIDkgTDMzIDE3LjUgTDMzIDI1IEMzMyAzMS41IDI3LjUgMzYgMjIgMzggQzE2LjUgMzYgMTEgMzEuNSAxMSAyNSBMMTEgMTcuNSBaIi8+CiAgICA8L2NsaXBQYXRoPgogIDwvZGVmcz4KICA8cGF0aCBkPSJNMjIgNCBMMzggMTUgTDM4IDI1IEMzOCAzNCAzMCA0MCAyMiA0MiBDMTQgNDAgNiAzNCA2IDI1IEw2IDE1IFoiIGZpbGw9IiMwYjNmNmYiLz4KICA8ZyBjbGlwLXBhdGg9InVybCgjc2MpIj4KICAgIDxyZWN0IHg9IjExIiB5PSI5IiB3aWR0aD0iMjIiIGhlaWdodD0iMjkiIGZpbGw9IiNmZmZmZmYiLz4KICAgIDxyZWN0IHg9IjIyIiB5PSI5IiB3aWR0aD0iMTEiIGhlaWdodD0iMTQuNSIgZmlsbD0iIzBiM2Y2ZiIvPgogICAgPHJlY3QgeD0iMTEiIHk9IjIzLjUiIHdpZHRoPSIxMSIgaGVpZ2h0PSIxNC41IiBmaWxsPSIjMGIzZjZmIi8+CiAgPC9nPgo8L3N2Zz4K'); } /* 推荐码图标:2×2 宫格 */ .icon-grid { background-image: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA0NCA0NCI+CiAgPHJlY3QgeD0iNiIgeT0iNiIgd2lkdGg9IjEzLjUiIGhlaWdodD0iMTMuNSIgcng9IjIuNSIgZmlsbD0iIzBiM2Y2ZiIvPgogIDxyZWN0IHg9IjI0LjUiIHk9IjYiIHdpZHRoPSIxMy41IiBoZWlnaHQ9IjEzLjUiIHJ4PSIyLjUiIGZpbGw9IiMwYjNmNmYiLz4KICA8cmVjdCB4PSI2IiB5PSIyNC41IiB3aWR0aD0iMTMuNSIgaGVpZ2h0PSIxMy41IiByeD0iMi41IiBmaWxsPSIjMGIzZjZmIi8+CiAgPHJlY3QgeD0iMjQuNSIgeT0iMjQuNSIgd2lkdGg9IjEzLjUiIGhlaWdodD0iMTMuNSIgcng9IjIuNSIgZmlsbD0iIzBiM2Y2ZiIvPgo8L3N2Zz4K'); } .field-input { flex: 1; height: 100%; font-size: 28rpx; color: #1f2329; min-width: 0; } /* 获取验证码按钮:绿色实心,倒计时期间置灰不可点 */ .code-btn { width: 190rpx; height: 96rpx; display: flex; align-items: center; justify-content: center; border-radius: 8rpx; background-color: #8bc34a; color: #ffffff; font-size: 28rpx; flex-shrink: 0; } .code-btn--disabled { background-color: #c3dd9d; } /* 已有账号:右对齐,点击返回登录 */ .has-account { display: flex; align-items: center; justify-content: flex-end; margin-top: 8rpx; } .has-account-text { font-size: 28rpx; color: #6b7280; } .has-account-link { margin-left: 8rpx; font-size: 28rpx; color: #1a73e8; } /* 底部按钮区:白底 + 左右留边距,底部叠加安全区 */ .bottom-bar { padding: 20rpx 32rpx; padding-bottom: calc(20rpx + constant(safe-area-inset-bottom)); padding-bottom: calc(20rpx + env(safe-area-inset-bottom)); background-color: #ffffff; } /* 注册按钮:绿色实心,圆角与登录页「手机号一键登录」保持一致 */ .submit-btn { height: 96rpx; display: flex; align-items: center; justify-content: center; border-radius: 16rpx; background-color: #8bc34a; color: #ffffff; font-size: 32rpx; font-weight: 600; } </style> hugray-user-miniapp/static/logo.png