<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>
|