feat: add agreement-gated login state

This commit is contained in:
gqt
2026-06-10 11:46:39 +08:00
parent 11fe7399f3
commit 0d5fe7d1cf
14 changed files with 662 additions and 61 deletions
@@ -0,0 +1,260 @@
<template>
<view class="unauth-login-panel" :class="{ compact }">
<view class="brand-section">
<image class="brand-logo" src="/static/brand/logo.png" mode="aspectFit" />
<text class="brand-name">大兴区冰上运动中心</text>
</view>
<view class="login-card">
<view class="unauth-copy" v-if="title || description">
<text class="unauth-title" v-if="title">{{ title }}</text>
<text class="unauth-description" v-if="description">{{ description }}</text>
</view>
<button
class="login-button"
:class="{ disabled: loading }"
:loading="loading"
:disabled="loading"
@tap="handleLoginTap"
>
{{ primaryText }}
</button>
<view class="agreement-checks">
<view class="acceptance-row acceptance-user" @tap="toggleUserAgreement">
<view class="checkbox" :class="{ checked: hasAcceptedUserAgreement }">
<text v-if="hasAcceptedUserAgreement"></text>
</view>
<text class="acceptance-text">我已阅读并同意</text>
<text class="agreement-inline-link" @tap.stop="openUserAgreement">用户协议</text>
</view>
<view class="acceptance-row acceptance-privacy" @tap="togglePrivacyPolicy">
<view class="checkbox" :class="{ checked: hasAcceptedPrivacyPolicy }">
<text v-if="hasAcceptedPrivacyPolicy"></text>
</view>
<text class="acceptance-text">我已阅读并同意</text>
<text class="agreement-inline-link" @tap.stop="openPrivacyPolicy">隐私政策</text>
</view>
</view>
</view>
<AgreementLinks ref="agreementLinksRef" :show-entry="false" />
</view>
</template>
<script setup>
import { computed, ref } from 'vue'
import AgreementLinks from '@/components/AgreementLinks.vue'
defineProps({
title: {
type: String,
default: ''
},
description: {
type: String,
default: ''
},
primaryText: {
type: String,
default: '微信一键登录'
},
loading: {
type: Boolean,
default: false
},
compact: {
type: Boolean,
default: false
}
})
const emit = defineEmits(['login'])
const hasAcceptedUserAgreement = ref(false)
const hasAcceptedPrivacyPolicy = ref(false)
const agreementLinksRef = ref(null)
const canLogin = computed(() => hasAcceptedUserAgreement.value && hasAcceptedPrivacyPolicy.value)
const toggleUserAgreement = () => {
hasAcceptedUserAgreement.value = !hasAcceptedUserAgreement.value
}
const togglePrivacyPolicy = () => {
hasAcceptedPrivacyPolicy.value = !hasAcceptedPrivacyPolicy.value
}
const openUserAgreement = () => {
agreementLinksRef.value?.openUserAgreement()
}
const openPrivacyPolicy = () => {
agreementLinksRef.value?.openPrivacyPolicy()
}
const handleLoginTap = () => {
if (!canLogin.value) {
uni.showToast({
title: '请先勾选用户协议和隐私政策',
icon: 'none'
})
return
}
emit('login')
}
</script>
<style lang="scss" scoped>
.unauth-login-panel {
min-height: calc(100vh - 176rpx - env(safe-area-inset-bottom));
padding: 0 $spacing-lg 48rpx;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
text-align: center;
gap: 58rpx;
position: relative;
overflow: hidden;
background: transparent;
&.compact {
min-height: 640rpx;
padding: 0 $spacing-lg 40rpx;
gap: 44rpx;
}
}
.brand-section {
width: 100%;
position: relative;
z-index: 1;
display: flex;
flex-direction: column;
align-items: center;
}
.brand-logo {
width: 144rpx;
height: 144rpx;
border-radius: 50%;
}
.brand-name {
margin-top: 34rpx;
font-size: 40rpx;
line-height: 1.26;
font-weight: 900;
color: $text-color-main;
}
.login-card {
width: 100%;
max-width: 560rpx;
position: relative;
z-index: 1;
display: flex;
flex-direction: column;
align-items: stretch;
}
.unauth-copy {
margin-bottom: 24rpx;
display: flex;
flex-direction: column;
align-items: center;
gap: $spacing-sm;
max-width: 560rpx;
}
.unauth-title {
font-size: 30rpx;
line-height: 1.35;
font-weight: 800;
color: $text-color-main;
}
.unauth-description {
font-size: 24rpx;
line-height: 1.6;
color: $text-color-muted;
}
.login-button {
width: 100%;
height: 88rpx;
border-radius: $border-radius-base;
color: #FFFFFF;
background-color: $text-color-main;
font-size: 28rpx;
font-weight: 700;
display: flex;
align-items: center;
justify-content: center;
&::after {
border: none;
}
&.disabled,
&[disabled] {
color: #FFFFFF;
background-color: $text-color-light;
}
}
.agreement-checks {
margin-top: 24rpx;
display: flex;
flex-direction: column;
gap: 18rpx;
align-items: center;
}
.acceptance-row {
display: flex;
align-items: center;
min-height: 38rpx;
text-align: left;
align-self: center;
}
.checkbox {
width: 30rpx;
height: 30rpx;
margin-right: 12rpx;
flex-shrink: 0;
border: 2rpx solid $border-color;
border-radius: 8rpx;
display: flex;
align-items: center;
justify-content: center;
color: #FFFFFF;
font-size: 20rpx;
line-height: 1;
font-weight: 900;
background-color: #FFFFFF;
&.checked {
border-color: $text-color-main;
background-color: $text-color-main;
}
}
.acceptance-text,
.agreement-inline-link {
font-size: 24rpx;
line-height: 1.5;
}
.acceptance-text {
color: $text-color-muted;
}
.agreement-inline-link {
color: $text-color-main;
font-weight: 700;
}
</style>