907 lines
22 KiB
Vue
907 lines
22 KiB
Vue
<template>
|
||
<view class="page">
|
||
<!-- 1. 头部标题区 -->
|
||
<view class="header">
|
||
<view class="header-title">
|
||
<text class="header-title__bind">{{ faceBindMode ? '人脸' : '绑定' }}</text>
|
||
<text class="header-title__card">{{ faceBindMode ? '绑定' : '赣湘全域旅游年卡/超级玩家卡' }}</text>
|
||
</view>
|
||
<text class="header-desc">{{ faceBindMode ? '请上传持卡人照片完成人脸绑定' : '请务必填写您的真实信息,以便于景区实名制预约' }}</text>
|
||
</view>
|
||
|
||
<!-- 2. 白色圆角表单卡片 -->
|
||
<view class="form-card">
|
||
<!-- 卡号 -->
|
||
<view class="form-row">
|
||
<text class="form-label">卡号</text>
|
||
<input
|
||
class="form-input"
|
||
v-model="form.cardNo"
|
||
placeholder="请输入卡号"
|
||
placeholder-class="placeholder"
|
||
:disabled="faceBindMode"
|
||
/>
|
||
</view>
|
||
<view class="divider" />
|
||
|
||
<!-- 持卡人姓名 -->
|
||
<view class="form-row">
|
||
<text class="form-label">持卡人姓名</text>
|
||
<input
|
||
class="form-input"
|
||
v-model="form.name"
|
||
placeholder="请输入持卡人姓名"
|
||
placeholder-class="placeholder"
|
||
:disabled="faceBindMode"
|
||
/>
|
||
</view>
|
||
<view class="divider" />
|
||
|
||
<!-- 持卡人手机 -->
|
||
<view class="form-row">
|
||
<text class="form-label">持卡人手机</text>
|
||
<input
|
||
class="form-input"
|
||
v-model="form.phone"
|
||
type="number"
|
||
maxlength="11"
|
||
disabled
|
||
placeholder-class="placeholder"
|
||
/>
|
||
</view>
|
||
<view class="divider" />
|
||
|
||
<!-- 证件类型(绑卡模式显示) -->
|
||
<view v-if="!faceBindMode" class="form-row" @tap="showIdTypePicker = true">
|
||
<text class="form-label">证件类型</text>
|
||
<text class="form-value">{{ idTypeLabel }}</text>
|
||
<view class="chevron-right" />
|
||
</view>
|
||
<view v-if="!faceBindMode" class="divider" />
|
||
|
||
<!-- 证件号码 -->
|
||
<view class="form-row">
|
||
<text class="form-label">证件号码</text>
|
||
<input
|
||
class="form-input"
|
||
v-model="form.idCard"
|
||
:placeholder="idPlaceholder"
|
||
placeholder-class="placeholder"
|
||
:maxlength="docType === '1' ? 18 : 30"
|
||
:disabled="faceBindMode"
|
||
/>
|
||
</view>
|
||
<view class="divider" />
|
||
|
||
<!-- 持卡人照片 -->
|
||
<view class="form-row form-row--photo">
|
||
<text class="form-label form-label--top">持卡人照片</text>
|
||
<view class="upload-area" @tap="photoUrl ? previewPhoto() : chooseImage()">
|
||
<image v-if="photoUrl" class="upload-preview" :src="photoUrl" mode="aspectFit" />
|
||
<view v-else class="upload-placeholder">
|
||
<view class="upload-icon" />
|
||
<text class="upload-text">点击上传</text>
|
||
</view>
|
||
<view v-if="photoUrl" class="upload-remove" @tap.stop="removePhoto">
|
||
<view class="upload-remove__x" />
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 3. 协议勾选区域(绑卡模式显示) -->
|
||
<view v-if="!faceBindMode" class="agreement" @tap="agreed = !agreed">
|
||
<view class="checkbox" :class="{ 'checkbox--checked': agreed }">
|
||
<view v-if="agreed" class="checkbox__check" />
|
||
</view>
|
||
<view class="agreement__text">
|
||
<text class="agreement__label">我已阅读并知晓</text>
|
||
<text class="agreement__link" @tap.stop="openNotice">《用卡须知》</text>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 4. 底部操作按钮组 -->
|
||
<view class="btn-group">
|
||
<button
|
||
class="btn-confirm"
|
||
:class="{ 'btn-confirm--disabled': submitting }"
|
||
:disabled="submitting"
|
||
@tap="handleSubmit"
|
||
>
|
||
<text class="btn-confirm__text">{{ submitting ? '提交中...' : (faceBindMode ? '确认绑定人脸' : '确认绑卡') }}</text>
|
||
</button>
|
||
<button class="btn-cancel" @tap="handleCancel">
|
||
<text class="btn-cancel__text">取消</text>
|
||
</button>
|
||
</view>
|
||
|
||
<!-- 5. 照片拍摄要求提示 -->
|
||
<view class="photo-tips">
|
||
<text class="photo-tips__title">照片拍摄要求</text>
|
||
<view class="photo-tips__item">
|
||
<view class="photo-tips__dot" />
|
||
<text class="photo-tips__text">图片仅允许1张人脸,多人脸直接驳回</text>
|
||
</view>
|
||
<view class="photo-tips__item">
|
||
<view class="photo-tips__dot" />
|
||
<text class="photo-tips__text">图片不能无人脸、侧脸过大、遮挡严重(口罩/帽子遮大半脸)</text>
|
||
</view>
|
||
<view class="photo-tips__item">
|
||
<view class="photo-tips__dot" />
|
||
<text class="photo-tips__text">不允许全身、远景、远距离人像照片</text>
|
||
</view>
|
||
<view class="photo-tips__item">
|
||
<view class="photo-tips__dot" />
|
||
<text class="photo-tips__text">不允许脸部极致特写、贴脸拍摄无留白</text>
|
||
</view>
|
||
<view class="photo-tips__item">
|
||
<view class="photo-tips__dot" />
|
||
<text class="photo-tips__text">图片清晰,无严重模糊、逆光、黑影、反光</text>
|
||
</view>
|
||
<view class="photo-tips__item">
|
||
<view class="photo-tips__dot" />
|
||
<text class="photo-tips__text">不允许翻拍屏幕、截图、证件复印件照片</text>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 弹窗:证件类型选择 -->
|
||
<uni-popup ref="idTypePopup" type="bottom" :mask-click="true" mask-background="transparent" @change="(e: any) => showIdTypePicker = e.show">
|
||
<view class="popup-sheet">
|
||
<view class="popup-header">
|
||
<text class="popup-title">选择证件类型</text>
|
||
<view class="popup-close" @tap="showIdTypePicker = false">
|
||
<view class="popup-close__icon" />
|
||
</view>
|
||
</view>
|
||
<view class="id-type-list">
|
||
<view
|
||
v-for="item in idTypeOptions"
|
||
:key="item.value"
|
||
class="id-type-item"
|
||
:class="{ 'id-type-item--active': docType === item.value }"
|
||
@tap="selectIdType(item.value)"
|
||
>
|
||
<text class="id-type-item__text">{{ item.label }}</text>
|
||
<view v-if="docType === item.value" class="id-type-item__check" />
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</uni-popup>
|
||
|
||
<!-- 隐藏 canvas,用于图片压缩 -->
|
||
<canvas
|
||
canvas-id="myCanvas"
|
||
id="myCanvas"
|
||
style="position:absolute;left:0;top:-10000rpx;width:375px;height:375px;z-index:-1;opacity:0"
|
||
/>
|
||
</view>
|
||
</template>
|
||
|
||
<script setup lang="ts">
|
||
import { ref, reactive, computed, watch } from 'vue'
|
||
import { onLoad } from '@dcloudio/uni-app'
|
||
import { uploadImage, bindCard as apiBindCard, bindFaceImg } from '../api/card'
|
||
|
||
const faceBindMode = ref(false)
|
||
|
||
const form = reactive({
|
||
cardNo: '',
|
||
name: '',
|
||
phone: (uni.getStorageSync('phone') as string) || '',
|
||
idCard: ''
|
||
})
|
||
|
||
const idTypeOptions = [
|
||
{ value: '1', label: '身份证' },
|
||
{ value: '2', label: '护照' },
|
||
{ value: '3', label: '港澳居民来往内地通行证' },
|
||
{ value: '4', label: '台湾居民来往大陆通行证' },
|
||
]
|
||
const docType = ref('1')
|
||
|
||
const idTypeLabel = computed(() => idTypeOptions.find(i => i.value === docType.value)?.label || '')
|
||
|
||
const idPlaceholder = computed(() => {
|
||
const map: Record<string, string> = {
|
||
'1': '请输入身份证号码',
|
||
'2': '请输入护照号码',
|
||
'3': '请输入通行证号码',
|
||
'4': '请输入台胞证号码',
|
||
}
|
||
return map[docType.value] || '请输入证件号码'
|
||
})
|
||
|
||
const photoUrl = ref('')
|
||
const agreed = ref(false)
|
||
const submitting = ref(false)
|
||
const uploading = ref(false)
|
||
const showIdTypePicker = ref(false)
|
||
const idTypePopup = ref<any>(null)
|
||
|
||
onLoad(async (options) => {
|
||
if (options?.faceBind === '1') {
|
||
faceBindMode.value = true
|
||
form.cardNo = options.cardNo || ''
|
||
form.name = options.userName || ''
|
||
form.phone = options.mobile || form.phone
|
||
const idType = options.idType || ''
|
||
docType.value = idType || '1'
|
||
form.idCard = options.idNo || ''
|
||
} else if (options?.redeemCode) {
|
||
form.cardNo = options.redeemCode
|
||
}
|
||
})
|
||
|
||
/** 打开用卡须知(跳转新页面) */
|
||
function openNotice() {
|
||
uni.navigateTo({ url: '/packages/user/pages/card-notice' })
|
||
}
|
||
|
||
watch(showIdTypePicker, (v) => {
|
||
if (v) idTypePopup.value?.open()
|
||
else idTypePopup.value?.close()
|
||
})
|
||
|
||
function selectIdType(value: string) {
|
||
docType.value = value
|
||
showIdTypePicker.value = false
|
||
}
|
||
|
||
function chooseImage() {
|
||
if (uploading.value) return
|
||
uni.chooseImage({
|
||
count: 1,
|
||
sizeType: ['original'],
|
||
sourceType: ['camera'],
|
||
success: (res) => handleImage(res.tempFilePaths[0]),
|
||
fail: (err) => {
|
||
if (!err.errMsg.includes('cancel')) console.log('选择图片失败:', err)
|
||
}
|
||
})
|
||
}
|
||
|
||
async function handleImage(filePath: string) {
|
||
uploading.value = true
|
||
uni.showLoading({ title: '处理中...' })
|
||
try {
|
||
const compressedPath = await compressImage(filePath)
|
||
uni.showLoading({ title: '上传中...' })
|
||
const url = await uploadImage(compressedPath, '/open-api/oss/uploadWithFaceCheck')
|
||
photoUrl.value = url
|
||
uni.showToast({ title: '上传成功', icon: 'success' })
|
||
} catch (e) {
|
||
uni.showModal({
|
||
title: '上传失败',
|
||
content: (e as Error).message || '上传失败',
|
||
showCancel: false,
|
||
confirmText: '确定',
|
||
})
|
||
} finally {
|
||
uploading.value = false
|
||
uni.hideLoading()
|
||
}
|
||
}
|
||
|
||
function compressImage(filePath: string): Promise<string> {
|
||
return new Promise((resolve) => {
|
||
const maxSize = 375
|
||
uni.getImageInfo({
|
||
src: filePath,
|
||
success: (info) => {
|
||
let { width, height } = info
|
||
if (width <= maxSize && height <= maxSize) { resolve(filePath); return }
|
||
if (width > height) {
|
||
height = Math.round((height / width) * maxSize)
|
||
width = maxSize
|
||
} else {
|
||
width = Math.round((width / height) * maxSize)
|
||
height = maxSize
|
||
}
|
||
const ctx = uni.createCanvasContext('myCanvas')
|
||
ctx.drawImage(filePath, 0, 0, width, height)
|
||
ctx.draw(false, () => {
|
||
setTimeout(() => {
|
||
uni.canvasToTempFilePath({
|
||
canvasId: 'myCanvas', x: 0, y: 0,
|
||
width, height, destWidth: width, destHeight: height,
|
||
quality: 0.8,
|
||
success: (r) => resolve(r.tempFilePath),
|
||
fail: () => resolve(filePath),
|
||
})
|
||
}, 200)
|
||
})
|
||
},
|
||
fail: () => resolve(filePath)
|
||
})
|
||
})
|
||
}
|
||
|
||
function previewPhoto() {
|
||
uni.previewImage({ urls: [photoUrl.value] })
|
||
}
|
||
|
||
function removePhoto() {
|
||
photoUrl.value = ''
|
||
}
|
||
|
||
function validateForm() {
|
||
if (!form.cardNo.trim()) return '请输入卡号'
|
||
if (!form.name.trim()) return '请输入姓名'
|
||
if (!form.phone.trim()) return '请输入联系电话'
|
||
if (!/^1[3-9]\d{9}$/.test(form.phone)) return '手机号格式不正确'
|
||
if (!form.idCard.trim()) return `请输入${idTypeLabel.value}号码`
|
||
if (docType.value === '1' && !/^\d{17}[\dXx]$/.test(form.idCard)) return '身份证号格式不正确'
|
||
if (!photoUrl.value) return '请上传持卡人照片'
|
||
return ''
|
||
}
|
||
|
||
async function handleSubmit() {
|
||
if (!faceBindMode.value && !agreed.value) {
|
||
uni.showToast({ title: '请先阅读并知晓用卡须知', icon: 'none' })
|
||
return
|
||
}
|
||
|
||
if (faceBindMode.value) {
|
||
if (!photoUrl.value) {
|
||
uni.showToast({ title: '请上传持卡人照片', icon: 'none' })
|
||
return
|
||
}
|
||
} else {
|
||
const err = validateForm()
|
||
if (err) {
|
||
uni.showToast({ title: err, icon: 'none' })
|
||
return
|
||
}
|
||
}
|
||
|
||
submitting.value = true
|
||
uni.showLoading({ title: '提交中...', mask: true })
|
||
|
||
try {
|
||
const apiFn = faceBindMode.value ? bindFaceImg : apiBindCard
|
||
await apiFn({
|
||
cardNum: form.cardNo.trim(),
|
||
userName: form.name.trim(),
|
||
mobile: form.phone.trim(),
|
||
idType: docType.value,
|
||
idNo: form.idCard.trim(),
|
||
img: photoUrl.value,
|
||
smsCode: '',
|
||
thirdId: '',
|
||
bindType: 'SELF',
|
||
bindSource: 'zfb',
|
||
channel: '',
|
||
activeCode: '',
|
||
})
|
||
uni.hideLoading()
|
||
uni.showToast({ title: faceBindMode.value ? '人脸绑定成功' : '绑卡成功', icon: 'success' })
|
||
setTimeout(() => {
|
||
uni.switchTab({ url: '/pages/qrcode/index' })
|
||
}, 1500)
|
||
} catch (err: any) {
|
||
uni.hideLoading()
|
||
uni.showToast({ title: err.message || (faceBindMode.value ? '人脸绑定失败' : '绑卡失败'), icon: 'none' })
|
||
} finally {
|
||
submitting.value = false
|
||
}
|
||
}
|
||
|
||
function handleCancel() {
|
||
uni.navigateBack({ fail: () => uni.switchTab({ url: '/pages/index/index' }) })
|
||
}
|
||
</script>
|
||
|
||
<style lang="scss" scoped>
|
||
/* ===== Design Tokens ===== */
|
||
|
||
/* ===== Page ===== */
|
||
.page {
|
||
min-height: 100vh;
|
||
overflow-y: auto;
|
||
background: $color-card;
|
||
font-family: $font-family-body;
|
||
}
|
||
|
||
/* ===== 1. Header ===== */
|
||
.header {
|
||
padding: 40rpx 32rpx 0;
|
||
text-align: center;
|
||
}
|
||
|
||
.header-title {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
justify-content: center;
|
||
align-items: baseline;
|
||
line-height: 48rpx;
|
||
}
|
||
|
||
.header-title__bind {
|
||
font-family: $font-family-body;
|
||
font-size: $font-size-title;
|
||
font-weight: $font-weight-bold;
|
||
color: $uv-main-color;
|
||
}
|
||
|
||
.header-title__card {
|
||
font-family: $font-family-body;
|
||
font-size: $font-size-title;
|
||
font-weight: $font-weight-bold;
|
||
color: $uv-primary;
|
||
}
|
||
|
||
.header-desc {
|
||
display: block;
|
||
font-family: $font-family-body;
|
||
font-size: $font-size-sm;
|
||
font-weight: 400;
|
||
color: $uv-tips-color;
|
||
line-height: 36rpx;
|
||
margin-top: 12rpx;
|
||
text-align: center;
|
||
}
|
||
|
||
/* ===== 2. Form Card ===== */
|
||
.form-card {
|
||
margin: 24rpx 24rpx 0;
|
||
background: $color-card;
|
||
border-radius: 24rpx;
|
||
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.06);
|
||
overflow: hidden;
|
||
}
|
||
|
||
.form-row {
|
||
display: flex;
|
||
align-items: center;
|
||
height: 88rpx;
|
||
padding: 0 32rpx;
|
||
}
|
||
|
||
.form-row--photo {
|
||
align-items: flex-start;
|
||
height: auto;
|
||
padding: 24rpx 32rpx;
|
||
}
|
||
|
||
.form-label {
|
||
font-family: $font-family-body;
|
||
font-size: $font-size-sm;
|
||
color: $uv-main-color;
|
||
font-weight: 400;
|
||
line-height: 40rpx;
|
||
flex-shrink: 0;
|
||
width: 160rpx;
|
||
}
|
||
|
||
.form-label--top {
|
||
padding-top: 20rpx;
|
||
}
|
||
|
||
.form-input {
|
||
flex: 1;
|
||
height: 88rpx;
|
||
font-family: $font-family-body;
|
||
font-size: $font-size-sm;
|
||
font-weight: 400;
|
||
color: $uv-content-color;
|
||
line-height: 40rpx;
|
||
text-align: right;
|
||
}
|
||
|
||
.placeholder {
|
||
font-family: $font-family-body;
|
||
color: $uv-placeholder-color;
|
||
font-size: $font-size-sm;
|
||
font-weight: 400;
|
||
}
|
||
|
||
.divider {
|
||
height: 1rpx;
|
||
background: $uv-border-color;
|
||
margin: 0 32rpx;
|
||
}
|
||
|
||
/* ===== Form Value + Chevron ===== */
|
||
.form-value {
|
||
flex: 1;
|
||
font-family: $font-family-body;
|
||
font-size: $font-size-sm;
|
||
font-weight: 400;
|
||
color: $uv-content-color;
|
||
text-align: right;
|
||
}
|
||
|
||
.chevron-right {
|
||
width: 12rpx;
|
||
height: 12rpx;
|
||
border-top: 2rpx solid $uv-placeholder-color;
|
||
border-right: 2rpx solid $uv-placeholder-color;
|
||
transform: rotate(45deg);
|
||
flex-shrink: 0;
|
||
margin-left: 12rpx;
|
||
}
|
||
|
||
/* ===== Photo Upload ===== */
|
||
.upload-area {
|
||
width: 180rpx;
|
||
height: 180rpx;
|
||
border-radius: 16rpx;
|
||
background: #f7f7f7;
|
||
flex-shrink: 0;
|
||
margin-left: auto;
|
||
overflow: hidden;
|
||
position: relative;
|
||
transition: transform 0.15s ease;
|
||
|
||
&:active {
|
||
transform: scale(0.97);
|
||
}
|
||
}
|
||
|
||
.upload-placeholder {
|
||
width: 100%;
|
||
height: 100%;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
|
||
.upload-icon {
|
||
width: 60rpx;
|
||
height: 60rpx;
|
||
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='60' height='60' viewBox='0 0 24 24' fill='none' stroke='%23999' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2'/%3E%3Ccircle cx='12' cy='7' r='4'/%3E%3C/svg%3E");
|
||
background-size: contain;
|
||
background-repeat: no-repeat;
|
||
background-position: center;
|
||
margin-bottom: 8rpx;
|
||
}
|
||
|
||
.upload-text {
|
||
font-family: $font-family-body;
|
||
font-size: 22rpx;
|
||
color: $uv-primary;
|
||
font-weight: 400;
|
||
}
|
||
|
||
.upload-preview {
|
||
width: 100%;
|
||
height: 100%;
|
||
}
|
||
|
||
.upload-remove {
|
||
position: absolute;
|
||
top: 8rpx;
|
||
right: 8rpx;
|
||
width: 40rpx;
|
||
height: 40rpx;
|
||
background: rgba(0, 0, 0, 0.5);
|
||
border-radius: 50%;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
|
||
.upload-remove__x {
|
||
width: 16rpx;
|
||
height: 16rpx;
|
||
position: relative;
|
||
|
||
&::before,
|
||
&::after {
|
||
content: '';
|
||
position: absolute;
|
||
top: 50%;
|
||
left: 50%;
|
||
width: 16rpx;
|
||
height: 2rpx;
|
||
background: $color-card;
|
||
border-radius: 1rpx;
|
||
}
|
||
|
||
&::before {
|
||
transform: translate(-50%, -50%) rotate(45deg);
|
||
}
|
||
|
||
&::after {
|
||
transform: translate(-50%, -50%) rotate(-45deg);
|
||
}
|
||
}
|
||
|
||
/* ===== 3. Agreement ===== */
|
||
.agreement {
|
||
display: flex;
|
||
align-items: flex-start;
|
||
margin: 24rpx 24rpx;
|
||
}
|
||
|
||
.checkbox {
|
||
flex-shrink: 0;
|
||
width: 20rpx;
|
||
height: 20rpx;
|
||
border: 2rpx solid #dadbde;
|
||
border-radius: 50%;
|
||
margin-right: 12rpx;
|
||
margin-top: 8rpx;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
transition: all 0.2s ease;
|
||
}
|
||
|
||
.checkbox--checked {
|
||
background: $uv-primary;
|
||
border-color: $uv-primary;
|
||
}
|
||
|
||
.checkbox__check {
|
||
width: 12rpx;
|
||
height: 12rpx;
|
||
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='white' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M20 6 9 17l-5-5'/%3E%3C/svg%3E");
|
||
background-size: contain;
|
||
background-repeat: no-repeat;
|
||
background-position: center;
|
||
}
|
||
|
||
.agreement__text {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
align-items: center;
|
||
line-height: 36rpx;
|
||
}
|
||
|
||
.agreement__label {
|
||
font-family: $font-family-body;
|
||
font-size: $font-size-sm;
|
||
font-weight: 400;
|
||
color: $uv-tips-color;
|
||
}
|
||
|
||
.agreement__link {
|
||
font-family: $font-family-body;
|
||
font-size: $font-size-sm;
|
||
font-weight: 400;
|
||
color: $uv-primary;
|
||
|
||
&:active {
|
||
opacity: 0.7;
|
||
}
|
||
}
|
||
|
||
/* ===== 4. Buttons ===== */
|
||
.btn-group {
|
||
display: flex;
|
||
gap: 24rpx;
|
||
padding: 0 24rpx;
|
||
margin-top: 48rpx;
|
||
}
|
||
|
||
.btn-confirm {
|
||
flex: 1;
|
||
height: 96rpx;
|
||
border-radius: 48rpx;
|
||
background: #3c9cff;
|
||
border: none;
|
||
padding: 0;
|
||
margin: 0;
|
||
line-height: normal;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
box-shadow: 0 6rpx 20rpx rgba(60, 156, 255, 0.28);
|
||
transition: all 0.2s ease;
|
||
|
||
&::after {
|
||
display: none;
|
||
}
|
||
|
||
&:active:not(.btn-confirm--disabled) {
|
||
transform: scale(0.98);
|
||
box-shadow: 0 2rpx 8rpx rgba(57, 138, 222, 0.2);
|
||
}
|
||
}
|
||
|
||
.btn-confirm--disabled {
|
||
background: $uv-bg-color;
|
||
box-shadow: none;
|
||
}
|
||
|
||
.btn-confirm__text {
|
||
font-family: $font-family-body;
|
||
font-size: $font-size-base;
|
||
font-weight: $font-weight-medium;
|
||
color: #ffffff;
|
||
letter-spacing: 2rpx;
|
||
}
|
||
|
||
.btn-confirm--disabled .btn-confirm__text {
|
||
color: $uv-placeholder-color;
|
||
}
|
||
|
||
.btn-cancel {
|
||
flex: 1;
|
||
height: 96rpx;
|
||
border-radius: 48rpx;
|
||
background: $color-card;
|
||
border: 2rpx solid #dadbde;
|
||
padding: 0;
|
||
margin: 0;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
transition: all 0.2s ease;
|
||
|
||
&::after {
|
||
display: none;
|
||
}
|
||
|
||
&:active {
|
||
transform: scale(0.98);
|
||
background: $uv-bg-color;
|
||
}
|
||
}
|
||
|
||
.btn-cancel__text {
|
||
font-family: $font-family-body;
|
||
font-size: $font-size-base;
|
||
font-weight: 400;
|
||
color: $uv-tips-color;
|
||
}
|
||
|
||
/* ===== 5. Photo Tips ===== */
|
||
.photo-tips {
|
||
margin: 32rpx 24rpx;
|
||
padding: 28rpx;
|
||
background: #f7f8fa;
|
||
border-radius: 16rpx;
|
||
}
|
||
|
||
.photo-tips__title {
|
||
display: block;
|
||
font-family: $font-family-body;
|
||
font-size: $font-size-sm;
|
||
font-weight: $font-weight-medium;
|
||
color: $uv-main-color;
|
||
margin-bottom: 16rpx;
|
||
}
|
||
|
||
.photo-tips__item {
|
||
display: flex;
|
||
align-items: flex-start;
|
||
margin-bottom: 12rpx;
|
||
|
||
&:last-child {
|
||
margin-bottom: 0;
|
||
}
|
||
}
|
||
|
||
.photo-tips__dot {
|
||
width: 8rpx;
|
||
height: 8rpx;
|
||
border-radius: 50%;
|
||
background: $uv-primary;
|
||
flex-shrink: 0;
|
||
margin-top: 14rpx;
|
||
margin-right: 12rpx;
|
||
}
|
||
|
||
.photo-tips__text {
|
||
flex: 1;
|
||
font-family: $font-family-body;
|
||
font-size: $font-size-xs;
|
||
font-weight: 400;
|
||
color: $uv-tips-color;
|
||
line-height: 36rpx;
|
||
}
|
||
|
||
/* ===== Popup Sheet ===== */
|
||
.popup-sheet {
|
||
background: $color-card;
|
||
border-radius: 24rpx 24rpx 0 0;
|
||
max-height: 70vh;
|
||
display: flex;
|
||
flex-direction: column;
|
||
box-shadow: none;
|
||
padding-bottom: 0;
|
||
font-family: $font-family-body;
|
||
}
|
||
|
||
.popup-header {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
padding: 32rpx;
|
||
border-bottom: 1rpx solid $uv-border-color;
|
||
}
|
||
|
||
.popup-title {
|
||
font-family: $font-family-body;
|
||
font-size: $font-size-base;
|
||
font-weight: $font-weight-bold;
|
||
color: $uv-main-color;
|
||
line-height: 48rpx;
|
||
}
|
||
|
||
.popup-close {
|
||
width: 48rpx;
|
||
height: 48rpx;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
|
||
&:active {
|
||
opacity: 0.6;
|
||
}
|
||
}
|
||
|
||
.popup-close__icon {
|
||
width: 20rpx;
|
||
height: 20rpx;
|
||
position: relative;
|
||
|
||
&::before,
|
||
&::after {
|
||
content: '';
|
||
position: absolute;
|
||
top: 50%;
|
||
left: 50%;
|
||
width: 20rpx;
|
||
height: 3rpx;
|
||
background: $uv-tips-color;
|
||
border-radius: 2rpx;
|
||
}
|
||
|
||
&::before {
|
||
transform: translate(-50%, -50%) rotate(45deg);
|
||
}
|
||
|
||
&::after {
|
||
transform: translate(-50%, -50%) rotate(-45deg);
|
||
}
|
||
}
|
||
|
||
/* ===== ID Type Picker List ===== */
|
||
.id-type-list {
|
||
padding: 16rpx 0;
|
||
}
|
||
|
||
.id-type-item {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
height: 96rpx;
|
||
padding: 0 32rpx;
|
||
transition: background 0.15s ease;
|
||
|
||
&:active {
|
||
background: $uv-bg-color;
|
||
}
|
||
|
||
&--active {
|
||
background: rgba(60, 156, 255, 0.04);
|
||
}
|
||
}
|
||
|
||
.id-type-item__text {
|
||
font-family: $font-family-body;
|
||
font-size: $font-size-sm;
|
||
font-weight: 400;
|
||
color: $uv-content-color;
|
||
line-height: 40rpx;
|
||
}
|
||
|
||
.id-type-item--active .id-type-item__text {
|
||
color: $uv-primary;
|
||
font-weight: 400;
|
||
}
|
||
|
||
.id-type-item__check {
|
||
width: 24rpx;
|
||
height: 24rpx;
|
||
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='%233c9cff' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M20 6 9 17l-5-5'/%3E%3C/svg%3E");
|
||
background-size: contain;
|
||
background-repeat: no-repeat;
|
||
background-position: center;
|
||
}
|
||
</style>
|