| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687 |
- <template>
- <view class="login-sheet-overlay" :class="{ 'login-sheet-overlay--visible': visible }" @click="handleOverlayClick">
- <view class="login-sheet-panel" :class="{ 'login-sheet-panel--show': panelShow }" @click.stop>
- <view class="login-sheet-header">
- <view class="login-sheet-handle" aria-hidden="true"></view>
- <view class="login-sheet-title">欢迎回来</view>
- </view>
- <scroll-view scroll-y class="login-sheet-scroll" :show-scrollbar="false">
- <view class="login-sheet-content">
- <!-- 品牌区:logo / 品牌名 / 身份 / 主打句(记忆点) -->
- <view class="brand-section">
- <image class="logo" src="/static/logo.png" mode="aspectFit" @error="handleImageError"></image>
- <text class="app-name">浠艾福</text>
- <text class="identity">您的专属家庭主动健康管理师</text>
- <text class="slogan">家里每个人的状态,你真的<text class="slogan-accent">看见了?</text></text>
- </view>
- <!-- 理念卡:四步闭环(细边框表面区分,无单侧彩条) -->
- <view class="concept-card">
- <view class="concept-desc">
- 不等问题发生,先一步<text class="concept-highlight">识别风险 · 主动干预 · 持续评估</text>,把全家健康管理融入每一天。
- </view>
- <view class="loop-row">
- <view class="loop-step">
- <view class="loop-glyph loop-glyph-wisdom" aria-hidden="true"></view>
- <text class="loop-step-text">提前识别</text>
- </view>
- <view class="loop-connector" aria-hidden="true"></view>
- <view class="loop-step">
- <view class="loop-glyph loop-glyph-success" aria-hidden="true"></view>
- <text class="loop-step-text">风险管控</text>
- </view>
- <view class="loop-connector" aria-hidden="true"></view>
- <view class="loop-step">
- <view class="loop-glyph loop-glyph-wealth" aria-hidden="true"></view>
- <text class="loop-step-text">主动干预</text>
- </view>
- <view class="loop-connector" aria-hidden="true"></view>
- <view class="loop-step">
- <view class="loop-glyph loop-glyph-danger" aria-hidden="true"></view>
- <text class="loop-step-text">效果评估</text>
- </view>
- </view>
- </view>
- <!-- 五维标签带:统一描边容器 + 细分割线,色块仅作身份记号(非等权重填充 pills) -->
- <view class="dims-band">
- <view class="dim-cell">
- <view class="dim-glyph dim-glyph-body" aria-hidden="true"></view>
- <text class="dim-label">身·健康</text>
- </view>
- <view class="dim-cell">
- <view class="dim-glyph dim-glyph-wisdom" aria-hidden="true"></view>
- <text class="dim-label">智·认知</text>
- </view>
- <view class="dim-cell">
- <view class="dim-glyph dim-glyph-wealth" aria-hidden="true"></view>
- <text class="dim-label">富·创富</text>
- </view>
- <view class="dim-cell">
- <view class="dim-glyph dim-glyph-action" aria-hidden="true"></view>
- <text class="dim-label">行·和谐</text>
- </view>
- <view class="dim-cell">
- <view class="dim-glyph dim-glyph-mind" aria-hidden="true"></view>
- <text class="dim-label">心·大爱</text>
- </view>
- </view>
- <!-- 登录表单 -->
- <view class="login-form">
- <view class="phone-section">
- <button
- class="phone-btn"
- :class="{ 'phone-btn--error': errorState, 'phone-btn--success': successState }"
- open-type="getPhoneNumber"
- @getphonenumber="getPhoneNumber"
- @tap="onPhoneBtnTap"
- :loading="loading"
- :disabled="!agreed || loading || tapLock"
- >
- <view class="phone-btn-icon" aria-hidden="true"></view>
- <text>手机号快捷登录</text>
- </button>
- </view>
- <view class="agreement">
- <checkbox-group @change="onAgreementChange">
- <label>
- <checkbox value="agree" :checked="agreed" />
- <text class="agreement-text">我已阅读并同意</text>
- </label>
- </checkbox-group>
- <text class="link" @click="showAgreement">《用户协议》</text>
- <text class="agreement-text">和</text>
- <text class="link" @click="showPrivacy">《隐私政策》</text>
- </view>
- <view class="skip-section">
- <view class="skip-btn" @click="handleSkipLogin">暂不登录,先逛逛</view>
- </view>
- </view>
- </view>
- </scroll-view>
- </view>
- </view>
- </template>
- <script>
- import { wechatPhoneLogin } from '../utils/api.js'
- export default {
- name: 'login-sheet',
- data() {
- return {
- visible: false,
- panelShow: false,
- loading: false,
- // tapLock:在「点击」瞬间上锁,用于关闭授权弹窗弹出前的连点窗口(见 onPhoneBtnTap)
- tapLock: false,
- agreed: false,
- nickname: '',
- avatar: '',
- redirectUrl: '',
- _tapLockTimer: null,
- // 八状态视觉位(R2 修复):默认均为布尔 false,不触发任何 class;登录逻辑与外部契约不变
- errorState: false,
- successState: false
- }
- },
- beforeDestroy() {
- this.clearTimers()
- },
- methods: {
- clearTimers() {
- if (this._tapLockTimer) {
- clearTimeout(this._tapLockTimer)
- this._tapLockTimer = null
- }
- },
- // 由宿主页面通过 mixin openLoginSheet() 调用,或 pages/login/login.vue 兜底壳调用
- open(options) {
- options = options || {}
- // 处理邀请码
- if (options.inviteCode) {
- uni.setStorageSync('inviteCode', options.inviteCode)
- uni.setStorageSync('inviteType', options.inviteType || 'family')
- }
- // 处理推荐码(refCode)
- if (options.refCode) {
- uni.setStorageSync('refCode', options.refCode)
- }
- // 存储登录后跳转地址
- if (options.redirect) {
- this.redirectUrl = decodeURIComponent(options.redirect)
- }
- this.visible = true
- // 通知宿主页面(页面可借此隐藏可能盖住浮层的原生 canvas 等)
- this.$emit('open')
- this.$nextTick(() => {
- setTimeout(() => {
- this.panelShow = true
- }, 10)
- })
- },
- close() {
- this.panelShow = false
- setTimeout(() => {
- this.visible = false
- // 浮层完全淡出后再通知,避免宿主提前恢复被隐藏的原生 canvas 造成闪烁
- this.$emit('close')
- }, 250)
- },
- handleOverlayClick() {
- this.close()
- },
- handleImageError() {
- console.log('Logo 加载失败,使用默认显示')
- },
- onAgreementChange(e) {
- this.agreed = e.detail.value.length > 0
- },
- showAgreement() {
- uni.navigateTo({
- url: '/pages/policy/agreement'
- })
- },
- showPrivacy() {
- uni.navigateTo({
- url: '/pages/policy/privacy'
- })
- },
- handleSkipLogin() {
- // 不登录直接关闭浮层,停留当前页
- this.close()
- },
- // 点击瞬间上锁,关闭「授权弹窗弹出前」的连点窗口
- onPhoneBtnTap() {
- if (this.loading || this.tapLock) return
- this.tapLock = true
- if (this._tapLockTimer) clearTimeout(this._tapLockTimer)
- this._tapLockTimer = setTimeout(() => {
- this.tapLock = false
- this._tapLockTimer = null
- }, 3000)
- },
- getPhoneNumber(e) {
- // 授权流程已结束,释放 tap 锁与兜底定时器,按钮恢复可点
- if (this._tapLockTimer) {
- clearTimeout(this._tapLockTimer)
- this._tapLockTimer = null
- }
- this.tapLock = false
- if (!this.agreed) {
- uni.showToast({ title: '请先同意用户协议', icon: 'none' })
- return
- }
- if (!e.detail || !e.detail.code) {
- uni.showToast({ title: '授权失败,请重试', icon: 'none' })
- return
- }
- var self = this
- self.loading = true
- // 点击授权时实时获取 login code(微信 code 一次性有效,不能复用旧 code)
- uni.login({
- provider: 'weixin',
- success: function(loginRes) {
- if (!loginRes.code) {
- uni.showToast({ title: '网络异常,请重试', icon: 'none' })
- self.loading = false
- return
- }
- wechatPhoneLogin({ code: loginRes.code, phoneCode: e.detail.code }).then(function(res) {
- self.completeLogin(res.data)
- }).catch(function(err) {
- uni.showToast({ title: err.message || '登录失败', icon: 'none' })
- self.loading = false
- })
- },
- fail: function() {
- uni.showToast({ title: '网络异常,请重试', icon: 'none' })
- self.loading = false
- }
- })
- },
- completeLogin(data) {
- if (!data) return
- // 写入登录态
- if (data.token) uni.setStorageSync('token', data.token)
- if (data.userId) uni.setStorageSync('userId', data.userId)
- if (data.openid) uni.setStorageSync('openid', data.openid)
- if (data.nickname) {
- this.nickname = data.nickname
- uni.setStorageSync('nickname', data.nickname)
- }
- if (data.avatar) {
- this.avatar = data.avatar
- uni.setStorageSync('avatar', data.avatar)
- }
- // 通知宿主页面(页面通过 @success="onSheetLoginSuccess" 刷新数据)
- this.$emit('success', data)
- // 关闭浮层,停留当前页
- this.close()
- }
- }
- }
- </script>
- <style scoped lang="scss">
- /* =====================================================================
- login-sheet 视觉令牌(局部)
- 来源:全部复用 uni.scss 共享令牌;仅本组件专有的色值以 $ls- 前缀声明,
- 组件体内零裸 hex / 裸 px / 裸毫秒。
- 注意:mp-weixin WXML 不支持模板内联 <svg>(仓库既有约定,
- components/wuxing-sandbox.vue 注释在案),故图标一律用
- data:image/svg+xml 作为 CSS background-image,颜色显式写入 SVG 文档。
- ===================================================================== */
- // ---- 图标工具:构建 data-URI SVG(颜色显式编码为 %23hex)----
- // str-replace 使维度色令牌可注入 data-URI(# 编码为 %23),令牌成为唯一事实源
- @function str-replace($string, $search, $replace) {
- $index: str-index($string, $search);
- @if $index {
- @return str-slice($string, 1, $index - 1) + $replace +
- str-replace(str-slice($string, $index + str-length($search)), $search, $replace);
- }
- @return $string;
- }
- @function ls-icon($color, $inner) {
- @return url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='#{str-replace($color, '#', '%23')}' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'%3E#{$inner}%3C/svg%3E");
- }
- // 图标路径(feather 线性几何,24×24;只编码 < > 与 #,空格/单引号合法)
- $ls-i-search: "%3Ccircle cx='11' cy='11' r='7'/%3E%3Cline x1='21' y1='21' x2='16.65' y2='16.65'/%3E";
- $ls-i-shield: "%3Cpath d='M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z'/%3E%3Cpath d='m9 12 2 2 4-4'/%3E";
- $ls-i-zap: "%3Cpath d='M13 2 3 14h9l-1 8 10-12h-9l1-8z'/%3E";
- $ls-i-chart: "%3Cline x1='18' y1='20' x2='18' y2='10'/%3E%3Cline x1='12' y1='20' x2='12' y2='4'/%3E%3Cline x1='6' y1='20' x2='6' y2='14'/%3E";
- $ls-i-user: "%3Cpath d='M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2'/%3E%3Ccircle cx='12' cy='7' r='4'/%3E";
- $ls-i-bulb: "%3Cpath d='M9 18h6'/%3E%3Cpath d='M10 22h4'/%3E%3Cpath d='M15.09 14c.18-.98.65-1.74 1.41-2.5A4.65 4.65 0 0 0 18 8 6 6 0 0 0 6 8c0 1 .23 2.23 1.5 3.5.76.76 1.23 1.52 1.41 2.5'/%3E";
- $ls-i-trend: "%3Cpolyline points='23 6 13.5 15.5 8.5 10.5 1 18'/%3E%3Cpolyline points='17 6 23 6 23 12'/%3E";
- $ls-i-activity: "%3Cpolyline points='22 12 18 12 15 21 9 3 6 12 2 12'/%3E";
- $ls-i-heart: "%3Cpath d='M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z'/%3E";
- $ls-i-chev: "%3Cpolyline points='9 18 15 12 9 6'/%3E";
- $ls-i-phone: "%3Cpath d='M22 16.92v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07 19.5 19.5 0 0 1-6-6A19.79 19.79 0 0 1 2.08 4.18 2 2 0 0 1 4.06 2h3a2 2 0 0 1 2 1.72c.127.96.361 1.903.7 2.81a2 2 0 0 1-.45 2.11L8.09 9.91a16 16 0 0 0 6 6l1.27-1.27a2 2 0 0 1 2.11-.45c.907.339 1.85.573 2.81.7A2 2 0 0 1 22 16.92z'/%3E";
- // ---- 局部令牌:底色 / 卡片 / 文字 / 描边 ----
- $ls-bg-top: #FFFDFB; // 面板暖白·顶(较原 #fffaf3 更干净)
- $ls-bg-bottom: #FBF4EA; // 面板暖白·底(降饱和)
- $ls-card: #FFFFFF; // 卡片表面
- $ls-card-border: #F0E7DB; // 暖发丝描边
- $ls-divider: #F1EAE0; // 发丝分割线
- $ls-overlay: rgba(20, 15, 8, 0.48); // 暖调遮罩,非纯黑
- $ls-ink-strong: $facade-text-strong; // 主文字 #333
- $ls-ink-body: $facade-text-body; // 正文/次级 #666
- $ls-ink-slate: $facade-text-slate-600; // 弱文字 #5A6478(全面板 ≥4.5:1,含底部 #FBF4EA)
- $ls-handle: #8A8A8A; // 抓握条(UI affordance,对面板顶 3.40:1 ≥3:1)
- $ls-connector: $facade-text-slate; // 步骤连接箭头 #6B7280(对白卡 4.83:1 ≥3:1)
- $ls-brand-deep: $brand-primary-deep; // 品牌橙·文字/主CTA #C2410C(对白 5.18:1,AA)
- $ls-ink-on-brand:$ink-on-dark; // 橙底白字 #FFFFFF
- $ls-radius-card: $radius-lg; // 24rpx 卡片档
- $ls-radius-btn: $radius-lg; // 24rpx 大按钮档
- // 维度标识色(深档,保证白底 ≥3:1;与 uni.scss §12 深档一一对应)
- $ls-dim-body: #9C4A1A; // 身·赭石
- $ls-dim-wisdom: $dim-wisdom-deep; // 智·靛蓝(实际编译值 #4F46E5,见 uni.scss L588 二次声明覆盖;对白 6.29:1)
- $ls-dim-wealth: $dim-wealth-deep; // 富·黄土 #92400E
- $ls-dim-action: $dim-action-deep; // 行·森林绿深档 #047857(token 复用)
- $ls-dim-mind: $dim-mind-deep; // 心·玫红 #9D174D
- // 维度浅底(uni.scss §13 tint;四步闭环用)
- $ls-dim-wisdom-tint: $dim-wisdom-tint; // 智浅底 #F0F0FF
- $ls-dim-wealth-tint: $dim-wealth-tint; // 富浅底 #FFFBEB
- /* ===== 浮层遮罩 ===== */
- .login-sheet-overlay {
- position: fixed;
- top: 0;
- left: 0;
- right: 0;
- bottom: 0;
- background: $ls-overlay; // 暖调遮罩,非纯黑
- z-index: 99999;
- opacity: 0;
- visibility: hidden;
- transition: opacity $uni-transition-base;
- }
- .login-sheet-overlay--visible {
- opacity: 1;
- visibility: visible;
- }
- /* ===== 底部面板 ===== */
- .login-sheet-panel {
- position: fixed;
- left: 0;
- right: 0;
- bottom: 0;
- height: 85vh;
- max-height: 85vh;
- background: linear-gradient(180deg, $ls-bg-top 0%, $ls-bg-bottom 100%);
- border-radius: $ls-radius-card $ls-radius-card 0 0;
- overflow: hidden;
- transform: translateY(100%);
- transition: transform $uni-transition-slow;
- display: flex;
- flex-direction: column;
- }
- .login-sheet-panel--show {
- transform: translateY(0);
- }
- .login-sheet-header {
- padding: $uni-space-lg $uni-space-xl $uni-space-xs;
- display: flex;
- flex-direction: column;
- align-items: center;
- }
- // 抓握条:底部浮层的拖拽 affordance(居中,独立于下方左对齐内容)
- .login-sheet-handle {
- width: 64rpx;
- height: 8rpx;
- border-radius: 999rpx;
- background: $ls-handle;
- }
- .login-sheet-title {
- margin-top: $uni-space-md;
- align-self: flex-start; // R5:标题与品牌区同左锚点
- font-size: calc($uni-font-size-sm * var(--font-scale, 1));
- font-weight: $uni-font-weight-medium;
- letter-spacing: 6rpx;
- color: $ls-ink-slate;
- }
- /* ===== 滚动区域 ===== */
- .login-sheet-scroll {
- flex: 1;
- min-height: 0;
- width: 100%;
- box-sizing: border-box;
- }
- .login-sheet-content {
- padding: $uni-space-lg $uni-space-xl $uni-space-4xl;
- box-sizing: border-box;
- }
- /* ===== 品牌区:清晰层级(R5 不对称锚点:左对齐,打破万物居中) ===== */
- .brand-section {
- display: flex;
- flex-direction: column;
- align-items: flex-start;
- padding: $uni-space-md 0 $uni-space-xs;
- }
- .logo {
- width: 120rpx;
- height: 120rpx;
- border-radius: $radius-md;
- }
- .app-name {
- margin-top: $uni-space-lg;
- font-size: calc($uni-font-size-3xl * var(--font-scale, 1)); // 52rpx 主标题(正文 28rpx 的 1.86x + 收窄行宽)
- font-weight: $uni-font-weight-black;
- color: $ls-ink-strong;
- letter-spacing: 2rpx;
- }
- .identity {
- margin-top: $uni-space-xs;
- font-size: calc($uni-font-size-base * var(--font-scale, 1)); // 28rpx 副标语
- font-weight: $uni-font-weight-medium;
- color: $ls-brand-deep;
- letter-spacing: 2rpx;
- }
- .slogan {
- margin-top: $uni-space-md;
- max-width: 460rpx; // 行宽收窄,形成视觉记忆点
- font-size: calc($uni-font-size-lg * var(--font-scale, 1)); // 32rpx 主打句
- line-height: 1.6;
- color: $ls-ink-body;
- text-align: left; // 与品牌区左对齐(R5)
- }
- .slogan-accent {
- color: $ls-brand-deep;
- font-weight: $uni-font-weight-bold;
- }
- /* ===== 理念卡(细边框表面区分,无单侧彩条) ===== */
- .concept-card {
- background: $ls-card;
- border: 1rpx solid $ls-card-border;
- border-radius: $ls-radius-card;
- padding: $uni-space-xl;
- margin-top: $uni-space-2xl;
- }
- .concept-desc {
- font-size: calc($uni-font-size-base * var(--font-scale, 1));
- color: $ls-ink-body;
- line-height: 1.7;
- }
- .concept-highlight {
- color: $ls-brand-deep;
- font-weight: $uni-font-weight-bold;
- }
- .loop-row {
- display: flex;
- flex-direction: row;
- align-items: center;
- justify-content: space-between;
- margin-top: $uni-space-xl;
- flex-wrap: nowrap; // 保持四步一行
- }
- .loop-step {
- display: flex;
- flex-direction: column;
- align-items: center;
- flex-shrink: 0;
- }
- .loop-glyph {
- width: 56rpx;
- height: 56rpx;
- border-radius: 50%;
- background-repeat: no-repeat;
- background-position: center;
- background-size: 28rpx 28rpx;
- }
- .loop-step-text {
- margin-top: $uni-space-xs;
- font-size: calc($uni-font-size-sm * var(--font-scale, 1));
- color: $ls-ink-slate;
- white-space: nowrap;
- }
- // 四步闭环的色相:仅图标圆底着色,文字统一中性(去彩虹)
- .loop-glyph-wisdom {
- background-color: $ls-dim-wisdom-tint;
- background-image: ls-icon($ls-dim-wisdom, $ls-i-search);
- }
- .loop-glyph-success {
- background-color: $status-success-tint;
- background-image: ls-icon($status-success-deep, $ls-i-shield);
- }
- .loop-glyph-wealth {
- background-color: $ls-dim-wealth-tint;
- background-image: ls-icon($ls-dim-wealth, $ls-i-zap);
- }
- .loop-glyph-danger {
- background-color: $status-danger-tint-2;
- background-image: ls-icon($status-danger-strong, $ls-i-chart);
- }
- .loop-connector {
- width: 20rpx;
- height: 20rpx;
- flex-shrink: 0;
- background-repeat: no-repeat;
- background-position: center;
- background-size: 16rpx 16rpx;
- background-image: ls-icon($ls-connector, $ls-i-chev);
- }
- /* ===== 五维标签带:统一容器 + 发丝分割 + 色块记号 ===== */
- .dims-band {
- display: flex;
- flex-direction: row;
- align-items: stretch;
- background: $ls-card;
- border: 1rpx solid $ls-card-border;
- border-radius: $ls-radius-card;
- margin-top: $uni-space-lg;
- padding: $uni-space-md 0;
- }
- .dim-cell {
- flex: 1;
- display: flex;
- flex-direction: column;
- align-items: center;
- justify-content: center;
- }
- .dim-cell + .dim-cell {
- border-left: 1rpx solid $ls-divider;
- }
- .dim-glyph {
- width: 36rpx;
- height: 36rpx;
- background-repeat: no-repeat;
- background-position: center;
- background-size: 30rpx 30rpx;
- }
- .dim-label {
- margin-top: $uni-space-xs;
- font-size: calc($uni-font-size-sm * var(--font-scale, 1));
- color: $ls-ink-body;
- white-space: nowrap;
- }
- .dim-glyph-body { background-image: ls-icon($ls-dim-body, $ls-i-user); }
- .dim-glyph-wisdom { background-image: ls-icon($ls-dim-wisdom, $ls-i-bulb); }
- .dim-glyph-wealth { background-image: ls-icon($ls-dim-wealth, $ls-i-trend); }
- .dim-glyph-action { background-image: ls-icon($ls-dim-action, $ls-i-activity); }
- .dim-glyph-mind { background-image: ls-icon($ls-dim-mind, $ls-i-heart); }
- // 主CTA(白字需 4.5:1:品牌橙 #F97316 仅 2.80:1 不达标,取深橙档;渐变更暗端保证全段达标)
- $ls-cta-from: #D1440B; // 深橙·亮端(白字 4.62:1 达标)
- $ls-cta-to: $brand-primary-deep; // 深橙·暗端 #C2410C(白字 5.18:1 达标)
- // hover / active 降档(白字 ≥6.0:1)
- $ls-cta-hover-from: #B93D0B;
- $ls-cta-hover-to: #A9330A;
- $ls-cta-press-from: #A9330A;
- $ls-cta-press-to: #962D08;
- // 禁用态:共享 $btn-disabled-bg(#D9D9D9) 对面板仅 1.39:1 不可辨、$btn-disabled-ink 3.78:1 不达 4.5
- // 故在组件局部覆盖为「暖灰底+深字」(不改全局令牌):底 #8E857A 对面板顶/底 3.58/3.32:1 达 ≥3、字 #1A1A1A 达 ≥4.5
- $ls-btn-disabled-bg: #8E857A;
- $ls-btn-disabled-ink: $text-charcoal; // #1A1A1A(uni.scss 既有令牌)
- // error/success 视觉态底色(R3 修复:白字需 ≥4.5)
- $ls-btn-error-bg: $status-danger-strong; // #B91C1C(白字 6.47:1 达标)
- $ls-btn-success-bg: $status-success-strong; // #065F46(白字 7.68:1 达标)
- /* ===== 登录表单 ===== */
- .login-form {
- margin-top: $uni-space-2xl;
- }
- .phone-section {
- display: flex;
- justify-content: center;
- }
- .phone-btn {
- width: 100%;
- height: 96rpx;
- border-radius: $ls-radius-btn;
- background: linear-gradient(135deg, $ls-cta-from 0%, $ls-cta-to 100%);
- color: $ls-ink-on-brand;
- font-size: calc($uni-font-size-lg * var(--font-scale, 1));
- font-weight: $uni-font-weight-bold;
- display: flex;
- flex-direction: row;
- align-items: center;
- justify-content: center;
- border: none;
- box-sizing: border-box;
- // 八态:default/hover/active/disabled/loading 走令牌;error/success 为待接线 class
- transition: background-color $duration-fast $ease-standard,
- transform $duration-fast $ease-standard,
- opacity $duration-fast $ease-standard;
- }
- .phone-btn::after {
- border: none;
- }
- .phone-btn:hover {
- background: linear-gradient(135deg, $ls-cta-hover-from 0%, $ls-cta-hover-to 100%); // H5 悬停:再降一档
- }
- .phone-btn:active {
- transform: scale(0.98);
- background: linear-gradient(135deg, $ls-cta-press-from 0%, $ls-cta-press-to 100%);
- }
- // R2 修复:键盘焦点可见环(H5 端)。mp-weixin 无键盘焦点,不影响小程序端。
- // 用品牌深橙描边对面板顶 5.10:1 ≥3:1(不纯黑);2px 描边 + 外扩,非内凹模糊
- .phone-btn:focus-visible {
- outline: 4rpx solid $ls-brand-deep;
- outline-offset: 4rpx;
- }
- .phone-btn[loading] {
- background: linear-gradient(135deg, $ls-cta-from 0%, $ls-cta-to 100%); // 保持与 default 一致,spinner 由组件自带
- }
- .phone-btn[disabled] {
- background: $ls-btn-disabled-bg !important;
- color: $ls-btn-disabled-ink !important;
- }
- // error/success 视觉态(R2/R3 接线:模板 :class 绑定 errorState/successState,默认 false 不触发)
- .phone-btn--error {
- background: $ls-btn-error-bg !important;
- }
- .phone-btn--success {
- background: $ls-btn-success-bg !important;
- }
- .phone-btn[disabled].phone-btn--error,
- .phone-btn[disabled].phone-btn--success {
- background: $ls-btn-disabled-bg !important; // 禁用态优先,error/success 不覆盖
- }
- .phone-btn[disabled] .phone-btn-icon {
- background-image: ls-icon($ls-btn-disabled-ink, $ls-i-phone);
- }
- .phone-btn--error .phone-btn-icon,
- .phone-btn--success .phone-btn-icon {
- background-image: ls-icon($ls-ink-on-brand, $ls-i-phone); // 深底白图标,与白字一致
- }
- .phone-btn-icon {
- width: 36rpx;
- height: 36rpx;
- margin-right: $uni-space-sm;
- background-repeat: no-repeat;
- background-position: center;
- background-size: 32rpx 32rpx;
- background-image: ls-icon($ls-ink-on-brand, $ls-i-phone);
- }
- /* ===== 协议(R5:与品牌区同左锚点,不再居中) ===== */
- .agreement {
- display: flex;
- flex-direction: row;
- align-items: center;
- justify-content: flex-start;
- flex-wrap: wrap;
- margin-top: $uni-space-lg;
- font-size: calc($uni-font-size-sm * var(--font-scale, 1));
- color: $ls-ink-slate;
- }
- .link {
- color: $ls-brand-deep;
- font-weight: $uni-font-weight-medium;
- }
- /* ===== 暂不登录(R5:左对齐) ===== */
- .skip-section {
- display: flex;
- justify-content: flex-start;
- margin-top: $uni-space-lg;
- }
- .skip-btn {
- font-size: calc($uni-font-size-base * var(--font-scale, 1));
- color: $ls-ink-slate;
- text-decoration: underline;
- padding: $uni-space-xs $uni-space-md; // 扩大点击区
- }
- /* ===== 动效降级:尊重 reduced-motion ===== */
- @media (prefers-reduced-motion: reduce) {
- .login-sheet-overlay,
- .login-sheet-panel,
- .phone-btn {
- transition-duration: 0.01ms;
- }
- }
- </style>
|