Files
tyc-uniapp_V2/src/pages/mine.vue
2026-05-25 17:01:59 +08:00

1105 lines
27 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<script setup lang="ts">
import { onShareAppMessage, onShareTimeline, onShow } from '@dcloudio/uni-app'
import { onUnmounted, ref } from 'vue'
import { clearAuthStorage, getUserDetail, postAuthSendSmsBindMobile, postUserBindMobile } from '@/api'
import { hasToken, saveAuthSession } from '@/utils/session'
definePage({
style: {
navigationBarTitleText: '我的',
navigationStyle: 'default',
navigationBarBackgroundColor: '#ffffff',
navigationBarTextStyle: 'black',
// 微信小程序:允许使用 open-type="share" 与右上角转发
enableShareAppMessage: true,
enableShareTimeline: true,
},
})
/** 商务合作弹窗中的二维码图:将图片放到 `src/static/` 后改为 `/static/xxx.png` 或填网络地址 */
const BUSINESS_COOP_QR_SRC = ''
const SHARE_TITLE = '天远查车 — 买车先查车况,更安心'
const SHARE_PATH = '/pages/index'
const WX_NICK_KEY = 'wx_display_name'
const isLogin = ref(false)
const nickname = ref('')
const userDesc = ref('')
const hasBoundMobile = ref(false)
const wxNickStorage = ref('')
const bindModalOpen = ref(false)
const bindPhone = ref('')
const bindCode = ref('')
const bindSending = ref(false)
const bindSubmitting = ref(false)
const bindCountingDown = ref(false)
const bindCountdown = ref(60)
let bindSmsTimer: ReturnType<typeof setInterval> | null = null
const coopModalOpen = ref(false)
onShareAppMessage(() => ({
title: SHARE_TITLE,
path: SHARE_PATH,
}))
onShareTimeline(() => ({
title: SHARE_TITLE,
query: '',
}))
function maskMobile(plain: string) {
const s = (plain || '').trim()
if (s.length >= 11)
return `${s.slice(0, 3)}****${s.slice(-4)}`
return s || ''
}
function loadWxNickFromStorage() {
try {
const n = uni.getStorageSync(WX_NICK_KEY)
wxNickStorage.value = typeof n === 'string' && n ? n : ''
}
catch {
wxNickStorage.value = ''
}
}
function clearBindSmsTimer() {
if (bindSmsTimer) {
clearInterval(bindSmsTimer)
bindSmsTimer = null
}
}
function startBindCountdown() {
clearBindSmsTimer()
bindCountingDown.value = true
bindCountdown.value = 60
bindSmsTimer = setInterval(() => {
if (bindCountdown.value > 0) {
bindCountdown.value--
}
else {
clearBindSmsTimer()
bindCountingDown.value = false
}
}, 1000)
}
const isBindPhoneValid = () => /^1[3-9]\d{9}$/.test(bindPhone.value)
async function refreshUserCard() {
isLogin.value = hasToken()
if (!isLogin.value) {
nickname.value = ''
userDesc.value = ''
hasBoundMobile.value = false
return
}
loadWxNickFromStorage()
try {
const res = await getUserDetail() as {
code?: number
data?: { userInfo?: { mobile?: string, nickName?: string } }
}
if (res && res.code === 200 && res.data?.userInfo) {
const u = res.data.userInfo
const mobile = (u.mobile || '').trim()
const apiNick = (u.nickName || '').trim()
hasBoundMobile.value = /^1[3-9]\d{9}$/.test(mobile)
if (hasBoundMobile.value) {
nickname.value = maskMobile(mobile)
userDesc.value = '已绑定手机号,可同步历史报告与收藏'
}
else {
nickname.value = wxNickStorage.value || apiNick || '微信用户'
userDesc.value = '绑定手机号后,可同步历史报告与收藏'
}
}
else {
nickname.value = wxNickStorage.value || '已登录'
userDesc.value = '获取资料失败,请稍后下拉刷新'
}
}
catch {
nickname.value = wxNickStorage.value || '已登录'
userDesc.value = '网络异常,请稍后重试'
}
}
onShow(() => {
void refreshUserCard()
// #ifdef MP-WEIXIN
try {
uni.showShareMenu({
withShareTicket: true,
menus: ['shareAppMessage', 'shareTimeline'],
})
}
catch {
/* ignore */
}
// #endif
})
function handleUserTap() {
if (!isLogin.value) {
uni.navigateTo({ url: '/pages/login' })
}
}
async function handleLogout() {
uni.showModal({
title: '提示',
content: '确定要退出登录吗?',
success(res) {
if (res.confirm) {
clearAuthStorage()
isLogin.value = false
nickname.value = ''
userDesc.value = ''
hasBoundMobile.value = false
uni.showToast({ title: '已退出', icon: 'none' })
}
},
})
}
/** 微信小程序:用户主动触发才可调 getUserProfile */
async function syncWxNickname() {
// #ifdef MP-WEIXIN
try {
const res = await new Promise<{ userInfo?: { nickName?: string } }>((resolve, reject) => {
uni.getUserProfile({
desc: '用于在本页展示昵称',
lang: 'zh_CN',
success: resolve,
fail: reject,
})
})
const name = res.userInfo?.nickName?.trim()
if (name) {
uni.setStorageSync(WX_NICK_KEY, name)
wxNickStorage.value = name
if (!hasBoundMobile.value)
nickname.value = name
uni.showToast({ title: '昵称已更新', icon: 'none' })
}
}
catch {
uni.showToast({ title: '需要您确认授权', icon: 'none' })
}
// #endif
}
function openBindModal() {
bindPhone.value = ''
bindCode.value = ''
bindModalOpen.value = true
}
function closeBindModal() {
bindModalOpen.value = false
clearBindSmsTimer()
bindCountingDown.value = false
}
function onBindPhoneInput(e: { detail?: { value?: string } }) {
const raw = e.detail?.value ?? ''
bindPhone.value = String(raw).replace(/\D/g, '').slice(0, 11)
}
function onBindCodeInput(e: { detail?: { value?: string } }) {
const raw = e.detail?.value ?? ''
bindCode.value = String(raw).replace(/\D/g, '').slice(0, 6)
}
async function sendBindSms() {
if (bindSending.value || bindCountingDown.value)
return
if (!isBindPhoneValid()) {
uni.showToast({ title: '请输入正确手机号', icon: 'none' })
return
}
bindSending.value = true
try {
const res = await postAuthSendSmsBindMobile({ mobile: bindPhone.value }) as { code?: number }
if (res && res.code === 200) {
uni.showToast({ title: '验证码已发送', icon: 'none' })
startBindCountdown()
}
}
finally {
bindSending.value = false
}
}
async function submitBindMobile() {
if (!isBindPhoneValid()) {
uni.showToast({ title: '请输入正确手机号', icon: 'none' })
return
}
if (bindCode.value.length < 6) {
uni.showToast({ title: '请输入 6 位验证码', icon: 'none' })
return
}
bindSubmitting.value = true
try {
const res = await postUserBindMobile({
mobile: bindPhone.value,
code: bindCode.value,
}) as { code?: number, data?: { accessToken: string, refreshAfter: number | string, accessExpire: number | string } }
if (res && res.code === 200 && res.data) {
saveAuthSession(res.data)
uni.showToast({ title: '绑定成功', icon: 'success' })
closeBindModal()
await refreshUserCard()
}
}
finally {
bindSubmitting.value = false
}
}
function goHistoryReport() {
uni.navigateTo({ url: '/pages/report' })
}
function goFreeValuation() {
uni.showToast({ title: '敬请期待', icon: 'none' })
}
/** 非微信小程序:无 open-type=share仅提示 */
function goShareFallback() {
uni.showToast({ title: '请在微信小程序内使用分享', icon: 'none' })
}
function openCoopModal() {
coopModalOpen.value = true
}
function closeCoopModal() {
coopModalOpen.value = false
}
function goLegalUserAgreement() {
uni.navigateTo({ url: '/pages/legal/user-agreement' })
}
function goLegalPrivacyPolicy() {
uni.navigateTo({ url: '/pages/legal/privacy-policy' })
}
function goLegalAuthorization() {
uni.navigateTo({ url: '/pages/legal/authorization' })
}
function goIllegalCode() {
uni.navigateTo({ url: '/pages/toolbox/query?key=jtwfcode' })
}
function goOilPrice() {
uni.navigateTo({ url: '/pages/toolbox/query?key=oilprice' })
}
function goHelp() {
uni.navigateTo({ url: '/pages/help-center' })
}
function goAbout() {
uni.navigateTo({ url: '/pages/about-us' })
}
function goSettings() {
uni.showToast({ title: '敬请期待', icon: 'none' })
}
/** 非微信小程序:无 open-type=contact */
function goServiceFallback() {
uni.showToast({ title: '请在微信小程序内使用在线客服', icon: 'none' })
}
onUnmounted(() => {
clearBindSmsTimer()
})
</script>
<template>
<view class="page-root">
<view class="page">
<!-- 区块1: 个人信息栏 -->
<view class="profile-card">
<view class="profile-bg" />
<view class="profile-content" @tap="handleUserTap">
<view class="profile-avatar">
<view class="avatar-inner">
<view class="avatar-icon i-carbon-user" />
</view>
</view>
<view class="profile-info">
<text class="profile-name">{{ isLogin ? nickname : '点击登录' }}</text>
<text class="profile-desc">{{ isLogin ? userDesc : '登录后可同步历史报告与收藏' }}</text>
</view>
<view v-if="!isLogin" class="profile-arrow">
<view class="i-carbon-chevron-right" />
</view>
</view>
<!-- 已登录未绑定手机 -->
<view v-if="isLogin && !hasBoundMobile" class="profile-actions">
<view class="action-btn" @tap.stop="openBindModal">
<view class="action-icon i-carbon-phone" />
<text class="action-text">绑定手机号</text>
</view>
<!-- #ifdef MP-WEIXIN -->
<view class="action-btn" @tap.stop="syncWxNickname">
<view class="action-icon i-carbon-edit" />
<text class="action-text">同步昵称</text>
</view>
<!-- #endif -->
</view>
</view>
<!-- 区块2: 快捷功能4宫格 -->
<view class="section">
<view class="section-title">快捷功能</view>
<view class="quick-grid">
<view class="quick-item" @tap="goHistoryReport">
<view class="quick-icon-wrap" style="background: rgba(23,104,255,0.08)">
<view class="quick-icon i-carbon-document" style="color: #1768ff" />
</view>
<text class="quick-name">历史报告</text>
</view>
<view class="quick-item" @tap="goFreeValuation">
<view class="quick-icon-wrap" style="background: rgba(250,140,22,0.08)">
<view class="quick-icon i-carbon-chart-line" style="color: #fa8c16" />
</view>
<text class="quick-name">免费估值</text>
</view>
<!-- #ifdef MP-WEIXIN -->
<button class="quick-item quick-item-btn" open-type="share" hover-class="quick-item-hover">
<view class="quick-icon-wrap" style="background: rgba(19,194,94,0.08)">
<view class="quick-icon i-carbon-share" style="color: #13c25e" />
</view>
<text class="quick-name">分享好友</text>
</button>
<!-- #endif -->
<!-- #ifndef MP-WEIXIN -->
<view class="quick-item" @tap="goShareFallback">
<view class="quick-icon-wrap" style="background: rgba(19,194,94,0.08)">
<view class="quick-icon i-carbon-share" style="color: #13c25e" />
</view>
<text class="quick-name">分享好友</text>
</view>
<!-- #endif -->
<!-- 暂时关闭商务合作功能 -->
<!-- <view class="quick-item" @tap="openCoopModal">
<view class="quick-icon-wrap" style="background: rgba(114,46,209,0.08)">
<view class="quick-icon i-carbon-enterprise" style="color: #722ed1" />
</view>
<text class="quick-name">商务合作</text>
</view> -->
</view>
</view>
<!-- 区块3: 常用工具 -->
<view class="section">
<view class="section-title">常用工具</view>
<view class="tool-grid">
<view class="tool-item" @tap="goOilPrice">
<view class="tool-item-icon-wrap" style="background: rgba(23,104,255,0.08)">
<view class="tool-item-icon i-carbon-gas-station" style="color: #1768ff" />
</view>
<text class="tool-item-name">实时油价查询</text>
</view>
<view class="tool-item" @tap="goIllegalCode">
<view class="tool-item-icon-wrap" style="background: rgba(250,140,22,0.08)">
<view class="tool-item-icon i-carbon-search" style="color: #fa8c16" />
</view>
<text class="tool-item-name">违章代码查询</text>
</view>
</view>
</view>
<!-- 区块4: 服务与支持 -->
<view class="section">
<view class="section-title">服务与支持</view>
<view class="tool-grid">
<!-- #ifdef MP-WEIXIN -->
<button class="tool-item-btn" open-type="contact" hover-class="tool-item-hover">
<view class="tool-item-icon-wrap" style="background: rgba(19,194,94,0.08)">
<view class="tool-item-icon i-carbon-chat" style="color: #13c25e" />
</view>
<text class="tool-item-name">在线客服</text>
</button>
<!-- #endif -->
<!-- #ifndef MP-WEIXIN -->
<view class="tool-item" @tap="goServiceFallback">
<view class="tool-item-icon-wrap" style="background: rgba(19,194,94,0.08)">
<view class="tool-item-icon i-carbon-chat" style="color: #13c25e" />
</view>
<text class="tool-item-name">在线客服</text>
</view>
<!-- #endif -->
<view class="tool-item" @tap="goHelp">
<view class="tool-item-icon-wrap" style="background: rgba(114,46,209,0.08)">
<view class="tool-item-icon i-carbon-help" style="color: #722ed1" />
</view>
<text class="tool-item-name">帮助中心</text>
</view>
<view class="tool-item" @tap="goAbout">
<view class="tool-item-icon-wrap" style="background: rgba(78,89,105,0.08)">
<view class="tool-item-icon i-carbon-information" style="color: #4e5969" />
</view>
<text class="tool-item-name">关于我们</text>
</view>
<view v-if="isLogin" class="tool-item" @tap="handleLogout">
<view class="tool-item-icon-wrap" style="background: rgba(245,63,63,0.08)">
<view class="tool-item-icon i-carbon-logout" style="color: #f53f3f" />
</view>
<text class="tool-item-name">退出登录</text>
</view>
</view>
</view>
<!-- 区块5: 法律条款 -->
<view class="section">
<view class="section-title">法律条款</view>
<view class="tool-grid">
<view class="tool-item" @tap="goLegalUserAgreement">
<view class="tool-item-icon-wrap" style="background: rgba(78,89,105,0.06)">
<view class="tool-item-icon i-carbon-document-blank" style="color: #86909c" />
</view>
<text class="tool-item-name">用户协议</text>
</view>
<view class="tool-item" @tap="goLegalPrivacyPolicy">
<view class="tool-item-icon-wrap" style="background: rgba(78,89,105,0.06)">
<view class="tool-item-icon i-carbon-security" style="color: #86909c" />
</view>
<text class="tool-item-name">隐私政策</text>
</view>
<view class="tool-item" @tap="goLegalAuthorization">
<view class="tool-item-icon-wrap" style="background: rgba(78,89,105,0.06)">
<view class="tool-item-icon i-carbon-certificate" style="color: #86909c" />
</view>
<text class="tool-item-name">授权书</text>
</view>
</view>
</view>
<view style="height: 24rpx" />
</view>
<view v-if="coopModalOpen" class="coop-mask" @tap.self="closeCoopModal">
<view class="coop-dialog" @tap.stop>
<view class="coop-title">
商务合作
</view>
<view class="coop-hint">
扫码添加商务微信图片路径可在代码中配置
</view>
<image
v-if="BUSINESS_COOP_QR_SRC"
class="coop-qr"
:src="BUSINESS_COOP_QR_SRC"
mode="aspectFit"
show-menu-by-longpress
/>
<view v-else class="coop-qr-placeholder">
<text class="coop-qr-placeholder-text">二维码图片路径待定</text>
<text class="coop-qr-placeholder-text coop-qr-placeholder-sub">请将图片放入 static 目录并在 mine.vue 中为 BUSINESS_COOP_QR_SRC 赋值</text>
</view>
<view class="coop-close" @tap="closeCoopModal">
知道了
</view>
</view>
</view>
<view v-if="bindModalOpen" class="bind-mask" @tap.self="closeBindModal">
<view class="bind-sheet" @tap.stop>
<view class="bind-sheet-title">
绑定手机号
</view>
<view class="bind-field">
<text class="bind-label">手机号</text>
<input
class="bind-input"
type="digit"
:value="bindPhone"
:maxlength="11"
placeholder="请输入手机号"
placeholder-class="bind-ph"
confirm-type="done"
@input="onBindPhoneInput"
>
</view>
<view class="bind-field bind-field-row">
<view class="bind-field-grow">
<text class="bind-label">验证码</text>
<input
class="bind-input"
type="digit"
:value="bindCode"
:maxlength="6"
placeholder="6 位短信码"
placeholder-class="bind-ph"
confirm-type="done"
@input="onBindCodeInput"
>
</view>
<view
class="bind-sms"
:class="{ disabled: bindSending || bindCountingDown || !isBindPhoneValid() }"
@tap="sendBindSms"
>
{{ bindCountingDown ? `${bindCountdown}s` : '获取验证码' }}
</view>
</view>
<view
class="bind-submit"
:class="{ disabled: bindSubmitting || !isBindPhoneValid() || bindCode.length < 6 }"
@tap="submitBindMobile"
>
确认绑定
</view>
<view class="bind-cancel" @tap="closeBindModal">
取消
</view>
</view>
</view>
</view>
</template>
<style scoped lang="scss">
.page-root {
height: 100vh;
display: flex;
flex-direction: column;
background: linear-gradient(180deg, #f0f5ff 0%, #f5f7fa 30%, #f3f5fb 100%);
overflow: hidden;
}
.page {
padding: 0 24rpx 24rpx;
box-sizing: border-box;
}
/* ═══ 区块1: 个人信息栏 ═══ */
.profile-card {
position: relative;
border-radius: 0 0 32rpx 32rpx;
overflow: hidden;
margin-bottom: 16rpx;
}
.profile-bg {
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
background: linear-gradient(135deg, #1768ff 0%, #4d94ff 60%, #7bb8ff 100%);
}
.profile-content {
position: relative;
display: flex;
align-items: center;
padding: 36rpx 32rpx 24rpx;
gap: 20rpx;
}
.profile-avatar {
flex-shrink: 0;
}
.avatar-inner {
width: 80rpx;
height: 80rpx;
border-radius: 50%;
background: rgba(255, 255, 255, 0.2);
display: flex;
align-items: center;
justify-content: center;
border: 3rpx solid rgba(255, 255, 255, 0.4);
}
.avatar-icon {
font-size: 38rpx;
color: #fff;
}
.profile-info {
flex: 1;
min-width: 0;
}
.profile-name {
display: block;
font-size: 32rpx;
font-weight: 600;
color: #fff;
margin-bottom: 6rpx;
}
.profile-desc {
display: block;
font-size: 22rpx;
color: rgba(255, 255, 255, 0.8);
}
.profile-arrow {
flex-shrink: 0;
color: rgba(255, 255, 255, 0.6);
font-size: 28rpx;
}
.profile-actions {
position: relative;
display: flex;
gap: 24rpx;
padding: 0 32rpx 20rpx;
}
.action-btn {
display: flex;
align-items: center;
gap: 8rpx;
padding: 10rpx 24rpx;
border-radius: 24rpx;
background: rgba(255, 255, 255, 0.18);
}
.action-icon {
font-size: 24rpx;
color: #fff;
}
.action-text {
font-size: 22rpx;
color: #fff;
}
/* ═══ 区块通用 ═══ */
.section {
background: #fff;
border-radius: 24rpx;
padding: 20rpx 24rpx 4rpx;
margin-bottom: 16rpx;
box-shadow: 0 4rpx 16rpx rgba(15, 35, 52, 0.03);
}
.section-title {
font-size: 26rpx;
font-weight: 600;
color: #1d2129;
margin-bottom: 14rpx;
padding-left: 4rpx;
}
/* ═══ 区块2: 快捷功能4宫格 ═══ */
.quick-grid {
display: flex;
flex-wrap: wrap;
padding-bottom: 16rpx;
}
.quick-item {
width: 25%;
display: flex;
flex-direction: column;
align-items: center;
padding: 8rpx 0;
}
.quick-item-btn {
margin: 0;
padding: 8rpx 0;
border: none;
background: transparent;
line-height: normal;
font-size: inherit;
color: inherit;
box-sizing: border-box;
}
.quick-item-btn::after {
display: none;
}
.quick-item-hover {
opacity: 0.7;
}
.quick-icon-wrap {
width: 68rpx;
height: 68rpx;
border-radius: 20rpx;
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 8rpx;
}
.quick-icon {
font-size: 32rpx;
}
.quick-name {
font-size: 22rpx;
color: #4e5969;
text-align: center;
}
/* ═══ 区块3/4/5: 工具网格 ═══ */
.tool-grid {
display: flex;
flex-wrap: wrap;
padding: 4rpx 8rpx;
}
.tool-item {
width: 25%;
display: flex;
flex-direction: column;
align-items: center;
padding: 10rpx 0;
}
.tool-item-btn {
margin: 0;
padding: 10rpx 0;
border: none;
background: transparent;
line-height: normal;
font-size: inherit;
color: inherit;
box-sizing: border-box;
}
.tool-item-btn::after {
display: none;
}
.tool-item-hover {
opacity: 0.7;
}
.tool-item-icon-wrap {
width: 76rpx;
height: 76rpx;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 8rpx;
}
.tool-item-icon {
font-size: 36rpx;
}
.tool-item-name {
font-size: 24rpx;
color: #1d2129;
text-align: center;
line-height: 1.3;
max-width: 100%;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
padding: 0 4rpx;
}
/* ═══ 区块3/4/5: 列表行 ═══ */
.tool-list {
padding-bottom: 8rpx;
}
.tool-row {
display: flex;
align-items: center;
padding: 20rpx 4rpx;
border-bottom: 1rpx solid #f5f5f5;
}
.tool-row:last-child {
border-bottom: none;
}
.tool-row:active {
background: #fafbfc;
border-radius: 12rpx;
}
.tool-row-btn {
margin: 0;
padding: 20rpx 4rpx;
border: none;
border-radius: 0;
background: transparent;
text-align: left;
line-height: inherit;
font-size: inherit;
color: inherit;
box-sizing: border-box;
width: 100%;
border-bottom: 1rpx solid #f5f5f5;
}
.tool-row-btn::after {
display: none;
}
.tool-row-hover {
background: rgba(23, 104, 255, 0.04);
}
.tool-row-icon-wrap {
width: 56rpx;
height: 56rpx;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
margin-right: 16rpx;
flex-shrink: 0;
}
.tool-row-icon {
font-size: 28rpx;
}
.tool-row-text {
flex: 1;
min-width: 0;
}
.tool-row-name {
flex: 1;
font-size: 26rpx;
color: #1d2129;
}
.tool-row-sub {
display: block;
font-size: 20rpx;
color: #ffb020;
margin-top: 2rpx;
}
.tool-row-arrow {
font-size: 28rpx;
color: #c9cdd4;
flex-shrink: 0;
margin-left: 8rpx;
}
.no-border {
border-bottom: none !important;
}
/* ═══ 弹窗样式 ═══ */
.coop-mask {
position: fixed;
left: 0;
right: 0;
top: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
z-index: 1001;
display: flex;
align-items: center;
justify-content: center;
padding: 48rpx;
box-sizing: border-box;
}
.coop-dialog {
width: 100%;
max-width: 600rpx;
background: #fff;
border-radius: 24rpx;
padding: 36rpx 32rpx 28rpx;
box-sizing: border-box;
}
.coop-title {
font-size: 32rpx;
font-weight: 600;
color: #1d2129;
text-align: center;
margin-bottom: 12rpx;
}
.coop-hint {
font-size: 24rpx;
color: #86909c;
text-align: center;
margin-bottom: 28rpx;
line-height: 1.5;
}
.coop-qr {
display: block;
width: 360rpx;
height: 360rpx;
margin: 0 auto 28rpx;
border-radius: 12rpx;
background: #f7f8fa;
}
.coop-qr-placeholder {
width: 360rpx;
height: 360rpx;
margin: 0 auto 28rpx;
border-radius: 12rpx;
border: 2rpx dashed #dcdfe6;
background: #fafbfc;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 24rpx;
box-sizing: border-box;
gap: 12rpx;
}
.coop-qr-placeholder-text {
font-size: 22rpx;
color: #86909c;
line-height: 1.6;
text-align: center;
}
.coop-qr-placeholder-sub {
font-size: 20rpx;
color: #c9cdd4;
}
.coop-close {
height: 80rpx;
line-height: 80rpx;
text-align: center;
background: linear-gradient(90deg, #1768ff 0%, #4d94ff 100%);
color: #fff;
font-size: 28rpx;
font-weight: 600;
border-radius: 40rpx;
}
/* ═══ 绑定手机弹窗 ═══ */
.bind-mask {
position: fixed;
left: 0;
right: 0;
top: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.45);
z-index: 1000;
display: flex;
align-items: flex-end;
justify-content: center;
}
.bind-sheet {
width: 100%;
background: #fff;
border-radius: 24rpx 24rpx 0 0;
padding: 28rpx 28rpx calc(28rpx + env(safe-area-inset-bottom));
box-sizing: border-box;
}
.bind-sheet-title {
font-size: 32rpx;
font-weight: 600;
color: #1d2129;
margin-bottom: 24rpx;
text-align: center;
}
.bind-field {
margin-bottom: 20rpx;
padding-bottom: 12rpx;
border-bottom: 1rpx solid #f0f0f0;
}
.bind-field-row {
display: flex;
align-items: flex-end;
gap: 16rpx;
}
.bind-field-grow {
flex: 1;
min-width: 0;
}
.bind-label {
display: block;
font-size: 24rpx;
color: #86909c;
margin-bottom: 8rpx;
}
.bind-input {
width: 100%;
height: 72rpx;
font-size: 30rpx;
color: #1d2129;
box-sizing: border-box;
}
.bind-ph {
color: #c9cdd4;
}
.bind-sms {
flex-shrink: 0;
height: 64rpx;
line-height: 64rpx;
padding: 0 20rpx;
font-size: 24rpx;
color: #1768ff;
background: #f0f5ff;
border-radius: 12rpx;
text-align: center;
}
.bind-sms.disabled {
color: #c9cdd4;
background: #f7f8fa;
pointer-events: none;
}
.bind-submit {
margin-top: 12rpx;
height: 88rpx;
line-height: 88rpx;
text-align: center;
background: linear-gradient(90deg, #1768ff 0%, #4d94ff 100%);
color: #fff;
font-size: 30rpx;
font-weight: 600;
border-radius: 44rpx;
}
.bind-submit.disabled {
opacity: 0.45;
pointer-events: none;
}
.bind-cancel {
margin-top: 20rpx;
text-align: center;
font-size: 28rpx;
color: #86909c;
padding: 12rpx;
}
</style>