829 lines
18 KiB
Plaintext
829 lines
18 KiB
Plaintext
<!-- 地址管理页面 -->
|
||
<template>
|
||
<view class="address-page">
|
||
<!-- 顶部栏 -->
|
||
<view class="address-header">
|
||
<view class="header-title">
|
||
<text class="title-text">收货地址</text>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 地址列表 -->
|
||
<scroll-view class="address-list" direction="vertical">
|
||
<!-- 地址为空 -->
|
||
<view v-if="addressList.length === 0" class="empty-address">
|
||
<text class="empty-icon">📍</text>
|
||
<text class="empty-text">暂无收货地址</text>
|
||
<text class="empty-subtext">点击下方按钮添加地址</text>
|
||
</view>
|
||
|
||
<!-- 地址项 -->
|
||
<view v-for="address in addressList" :key="address.id" class="address-item">
|
||
<view class="address-info" @click="selectAddress(address)">
|
||
<view class="address-header-row">
|
||
<text class="address-name">{{ address.recipient_name }}</text>
|
||
<text class="address-phone">{{ address.phone }}</text>
|
||
<view v-if="address.is_default" class="default-tag">
|
||
<text class="tag-text">默认</text>
|
||
</view>
|
||
</view>
|
||
<view class="address-detail">
|
||
<text class="detail-text">{{ getFullAddress(address) }}</text>
|
||
</view>
|
||
</view>
|
||
<view class="address-actions">
|
||
<view class="action-item" @click="editAddress(address)">
|
||
<text class="action-icon">✏️</text>
|
||
<text class="action-text">编辑</text>
|
||
</view>
|
||
<view class="action-item" @click="deleteAddress(address)">
|
||
<text class="action-icon">🗑️</text>
|
||
<text class="action-text">删除</text>
|
||
</view>
|
||
<view v-if="!address.is_default" class="action-item" @click="setDefaultAddress(address)">
|
||
<text class="action-icon">⭐</text>
|
||
<text class="action-text">设为默认</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 从选择页面返回时的提示 -->
|
||
<view v-if="fromSelect && addressList.length > 0" class="select-tip">
|
||
<text class="tip-text">请选择收货地址</text>
|
||
<text class="tip-subtext">或点击下方添加新地址</text>
|
||
</view>
|
||
</scroll-view>
|
||
|
||
<!-- 添加地址按钮 -->
|
||
<view class="add-address-btn" @click="showNewAddressForm = true">
|
||
<text class="btn-icon">+</text>
|
||
<text class="btn-text">添加新地址</text>
|
||
</view>
|
||
|
||
<!-- 新建地址表单弹窗 -->
|
||
<view v-if="showNewAddressForm" class="address-form-mask" @click="cancelNewAddress">
|
||
<view class="address-form-popup" @click.stop>
|
||
<view class="form-header">
|
||
<text class="form-title">新建收货地址</text>
|
||
<text class="form-close" @click="cancelNewAddress">×</text>
|
||
</view>
|
||
|
||
<scroll-view class="form-content" direction="vertical">
|
||
<view class="form-item">
|
||
<text class="form-label">收货人</text>
|
||
<input class="form-input" v-model="newAddress.recipient_name"
|
||
placeholder="请输入收货人姓名" />
|
||
</view>
|
||
|
||
<view class="form-item">
|
||
<text class="form-label">手机号</text>
|
||
<input class="form-input" v-model="newAddress.phone"
|
||
placeholder="请输入手机号码" type="number" />
|
||
</view>
|
||
|
||
<!-- 地址智能填写 -->
|
||
<view class="form-item">
|
||
<text class="form-label">智能填写地址</text>
|
||
<textarea class="form-textarea smart-address-input"
|
||
v-model="smartAddressInput"
|
||
placeholder="请输入完整地址,系统将自动识别省市区和详细地址"
|
||
@blur="parseSmartAddress"
|
||
maxlength="200"></textarea>
|
||
<text class="smart-tip">例如:北京市朝阳区三里屯SOHO A座</text>
|
||
</view>
|
||
|
||
<view class="form-item">
|
||
<text class="form-label">所在地区</text>
|
||
<view class="region-inputs">
|
||
<input class="form-input region-input" v-model="newAddress.province"
|
||
placeholder="省" readonly />
|
||
<input class="form-input region-input" v-model="newAddress.city"
|
||
placeholder="市" readonly />
|
||
<input class="form-input region-input" v-model="newAddress.district"
|
||
placeholder="区/县" readonly />
|
||
</view>
|
||
</view>
|
||
|
||
<view class="form-item">
|
||
<text class="form-label">详细地址</text>
|
||
<textarea class="form-textarea" v-model="newAddress.detail"
|
||
placeholder="街道、小区、楼栋、门牌号等"
|
||
maxlength="100"></textarea>
|
||
</view>
|
||
|
||
<view class="form-item checkbox-item">
|
||
<view class="checkbox-wrapper" @click="newAddress.is_default = !newAddress.is_default">
|
||
<view :class="['checkbox', { checked: newAddress.is_default }]">
|
||
<text v-if="newAddress.is_default" class="checkbox-check">✓</text>
|
||
</view>
|
||
<text class="checkbox-label">设为默认地址</text>
|
||
</view>
|
||
</view>
|
||
</scroll-view>
|
||
|
||
<view class="form-buttons">
|
||
<button class="form-cancel-btn" @click="cancelNewAddress">取消</button>
|
||
<button class="form-submit-btn" @click="saveNewAddress">保存</button>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</template>
|
||
|
||
<script setup lang="uts">
|
||
import { ref, onMounted, onUnmounted } from 'vue'
|
||
import { onLoad } from '@dcloudio/uni-app'
|
||
import { supabaseService, type UserAddress, type AddAddressParams } from '@/utils/supabaseService.uts'
|
||
|
||
type AddressType = {
|
||
id: string
|
||
user_id: string
|
||
recipient_name: string
|
||
phone: string
|
||
province: string
|
||
city: string
|
||
district: string
|
||
detail: string
|
||
postal_code: string | null
|
||
is_default: boolean
|
||
created_at: string
|
||
}
|
||
|
||
type NewAddressForm = {
|
||
recipient_name: string
|
||
phone: string
|
||
province: string
|
||
city: string
|
||
district: string
|
||
detail: string
|
||
is_default: boolean
|
||
}
|
||
|
||
const addressList = ref<Array<AddressType>>([])
|
||
const fromSelect = ref<boolean>(false)
|
||
const showNewAddressForm = ref<boolean>(false)
|
||
const newAddress = ref<NewAddressForm>({
|
||
recipient_name: '',
|
||
phone: '',
|
||
province: '',
|
||
city: '',
|
||
district: '',
|
||
detail: '',
|
||
is_default: false
|
||
})
|
||
const smartAddressInput = ref<string>('')
|
||
|
||
function mapAddress(item: UserAddress): AddressType {
|
||
return {
|
||
id: item.id,
|
||
user_id: item.user_id,
|
||
recipient_name: item.recipient_name ?? '',
|
||
phone: item.phone ?? '',
|
||
province: item.province ?? '',
|
||
city: item.city ?? '',
|
||
district: item.district ?? '',
|
||
detail: item.detail_address ?? '',
|
||
postal_code: item.postal_code ?? null,
|
||
is_default: item.is_default,
|
||
created_at: item.created_at ?? ''
|
||
}
|
||
}
|
||
|
||
function getCurrentUserId(): string {
|
||
return supabaseService.getCurrentUserId() ?? ''
|
||
}
|
||
|
||
function getFullAddress(address: AddressType): string {
|
||
return `${address.province}${address.city}${address.district}${address.detail}`
|
||
}
|
||
|
||
function resetNewAddressForm(): void {
|
||
showNewAddressForm.value = false
|
||
newAddress.value = {
|
||
recipient_name: '',
|
||
phone: '',
|
||
province: '',
|
||
city: '',
|
||
district: '',
|
||
detail: '',
|
||
is_default: false
|
||
}
|
||
smartAddressInput.value = ''
|
||
}
|
||
|
||
function cancelNewAddress(): void {
|
||
resetNewAddressForm()
|
||
}
|
||
|
||
function selectAddress(address: AddressType): void {
|
||
if (fromSelect.value) {
|
||
try {
|
||
uni.setStorageSync('selectedAddress', JSON.stringify(address))
|
||
} catch (e) {}
|
||
uni.$emit('addressSelected', address)
|
||
uni.navigateBack()
|
||
}
|
||
}
|
||
|
||
function editAddress(address: AddressType): void {
|
||
uni.navigateTo({
|
||
url: `/pages/mall/consumer/address-edit?id=${address.id}`
|
||
})
|
||
}
|
||
|
||
async function loadAddresses(): Promise<void> {
|
||
const userId = getCurrentUserId()
|
||
if (userId === '') {
|
||
uni.showToast({
|
||
title: '请先登录',
|
||
icon: 'none'
|
||
})
|
||
uni.navigateTo({
|
||
url: '/pages/user/login'
|
||
})
|
||
return
|
||
}
|
||
|
||
try {
|
||
const items = await supabaseService.getAddresses()
|
||
const nextList: AddressType[] = []
|
||
for (let i = 0; i < items.length; i++) {
|
||
nextList.push(mapAddress(items[i]))
|
||
}
|
||
addressList.value = nextList
|
||
} catch (err) {
|
||
console.error('加载地址异常:', err)
|
||
addressList.value = []
|
||
}
|
||
}
|
||
|
||
async function doDeleteAddress(address: AddressType): Promise<void> {
|
||
const success = await supabaseService.deleteAddress(address.id)
|
||
if (!success) {
|
||
uni.showToast({
|
||
title: '删除失败',
|
||
icon: 'none'
|
||
})
|
||
return
|
||
}
|
||
|
||
await loadAddresses()
|
||
if (address.is_default && addressList.value.length > 0) {
|
||
await supabaseService.setDefaultAddress(addressList.value[0].id)
|
||
await loadAddresses()
|
||
}
|
||
|
||
uni.showToast({
|
||
title: '删除成功',
|
||
icon: 'success'
|
||
})
|
||
}
|
||
|
||
function confirmDeleteAddress(address: AddressType): void {
|
||
doDeleteAddress(address)
|
||
}
|
||
|
||
function deleteAddress(address: AddressType): void {
|
||
uni.showModal({
|
||
title: '删除地址',
|
||
content: '确定要删除这个收货地址吗?',
|
||
success: (res) => {
|
||
if (!res.confirm) return
|
||
if (address.is_default && addressList.value.length > 1) {
|
||
uni.showModal({
|
||
title: '提示',
|
||
content: '删除默认地址后,系统会自动设置第一个地址为默认地址',
|
||
success: (confirmRes) => {
|
||
if (confirmRes.confirm) {
|
||
confirmDeleteAddress(address)
|
||
}
|
||
}
|
||
})
|
||
return
|
||
}
|
||
confirmDeleteAddress(address)
|
||
}
|
||
})
|
||
}
|
||
|
||
async function setDefaultAddress(address: AddressType): Promise<void> {
|
||
const success = await supabaseService.setDefaultAddress(address.id)
|
||
if (!success) {
|
||
uni.showToast({
|
||
title: '设置失败',
|
||
icon: 'none'
|
||
})
|
||
return
|
||
}
|
||
await loadAddresses()
|
||
uni.showToast({
|
||
title: '已设为默认地址',
|
||
icon: 'success'
|
||
})
|
||
}
|
||
|
||
function parseSmartAddress(): void {
|
||
const input = smartAddressInput.value.trim()
|
||
if (input === '') return
|
||
|
||
newAddress.value.recipient_name = ''
|
||
newAddress.value.phone = ''
|
||
newAddress.value.province = ''
|
||
newAddress.value.city = ''
|
||
newAddress.value.district = ''
|
||
newAddress.value.detail = ''
|
||
|
||
const phoneRegex = /(1[3-9]\d{9})/g
|
||
const phoneMatches = input.match(phoneRegex)
|
||
if (phoneMatches != null && phoneMatches.length > 0) {
|
||
newAddress.value.phone = phoneMatches[0] ?? ''
|
||
}
|
||
|
||
const nameRegex = /([\u4e00-\u9fa5]{2,4})/g
|
||
const nameMatches = input.match(nameRegex)
|
||
if (nameMatches != null && nameMatches.length > 0) {
|
||
newAddress.value.recipient_name = nameMatches[0] ?? ''
|
||
}
|
||
|
||
let addressText = input
|
||
if (newAddress.value.recipient_name !== '') {
|
||
addressText = addressText.replace(newAddress.value.recipient_name, '')
|
||
}
|
||
if (newAddress.value.phone !== '') {
|
||
addressText = addressText.replace(newAddress.value.phone, '')
|
||
}
|
||
|
||
addressText = addressText.replace(/[,,;;\s]+/g, ' ').trim()
|
||
|
||
const patterns = [
|
||
/^(.*?省)?(.*?市)?(.*?[区县])?(.*)$/,
|
||
/^(.*?省)?(.*?市)?(.*)$/
|
||
]
|
||
|
||
for (let i = 0; i < patterns.length; i++) {
|
||
const match = addressText.match(patterns[i])
|
||
if (match != null) {
|
||
const province = match.length > 1 ? (match[1] ?? '') : ''
|
||
const city = match.length > 2 ? (match[2] ?? '') : ''
|
||
const district = match.length > 3 ? (match[3] ?? '') : ''
|
||
const detail = match.length > 4 ? (match[4] ?? '') : ''
|
||
|
||
if (province !== '') newAddress.value.province = province.replace('省', '').trim()
|
||
if (city !== '') newAddress.value.city = city.replace('市', '').trim()
|
||
if (district !== '') newAddress.value.district = district.trim()
|
||
if (detail !== '') newAddress.value.detail = detail.trim()
|
||
|
||
if (newAddress.value.detail === '' && district !== '' && detail !== '') {
|
||
newAddress.value.detail = detail.trim()
|
||
}
|
||
break
|
||
}
|
||
}
|
||
|
||
if (newAddress.value.province === '' && newAddress.value.city === '' && newAddress.value.district === '') {
|
||
const parts = addressText.split(/[省市县区]/)
|
||
if (parts.length >= 2) {
|
||
newAddress.value.province = parts[0] ?? ''
|
||
newAddress.value.city = parts[1] ?? ''
|
||
const detailCandidate = parts.slice(2).join('').trim()
|
||
newAddress.value.detail = detailCandidate !== '' ? detailCandidate : addressText
|
||
} else {
|
||
newAddress.value.detail = addressText
|
||
}
|
||
}
|
||
|
||
if (newAddress.value.detail === '' && addressText.trim() !== '') {
|
||
newAddress.value.detail = addressText.trim()
|
||
}
|
||
}
|
||
|
||
async function saveNewAddress(): Promise<void> {
|
||
if (newAddress.value.recipient_name === '' || newAddress.value.phone === '' || newAddress.value.detail === '') {
|
||
uni.showToast({
|
||
title: '请填写完整信息',
|
||
icon: 'none'
|
||
})
|
||
return
|
||
}
|
||
|
||
const userId = getCurrentUserId()
|
||
if (userId === '') {
|
||
uni.showToast({
|
||
title: '请先登录',
|
||
icon: 'none'
|
||
})
|
||
return
|
||
}
|
||
|
||
const payload: AddAddressParams = {
|
||
recipient_name: newAddress.value.recipient_name,
|
||
phone: newAddress.value.phone,
|
||
province: newAddress.value.province,
|
||
city: newAddress.value.city,
|
||
district: newAddress.value.district,
|
||
detail_address: newAddress.value.detail,
|
||
is_default: newAddress.value.is_default
|
||
}
|
||
|
||
try {
|
||
const success = await supabaseService.addAddress(payload)
|
||
if (!success) {
|
||
uni.showToast({
|
||
title: '保存失败',
|
||
icon: 'none'
|
||
})
|
||
return
|
||
}
|
||
|
||
await loadAddresses()
|
||
uni.$emit('addressUpdated', addressList.value)
|
||
resetNewAddressForm()
|
||
uni.showToast({
|
||
title: '地址保存成功',
|
||
icon: 'success'
|
||
})
|
||
} catch (err) {
|
||
console.error('保存地址异常:', err)
|
||
uni.showToast({
|
||
title: '保存失败',
|
||
icon: 'none'
|
||
})
|
||
}
|
||
}
|
||
|
||
onLoad((options) => {
|
||
const optObj = (options instanceof UTSJSONObject) ? (options as UTSJSONObject) : (JSON.parse(JSON.stringify(options ?? {})) as UTSJSONObject)
|
||
const flag = optObj.getString('fromSelect') ?? ''
|
||
fromSelect.value = (flag == '1' || flag == 'true')
|
||
})
|
||
|
||
// 生命周期
|
||
function handleAddressUpdated(): void {
|
||
loadAddresses()
|
||
}
|
||
|
||
onMounted(() => {
|
||
loadAddresses()
|
||
|
||
// 监听地址更新事件(从checkout页面或其他页面)
|
||
uni.$on('addressUpdated', handleAddressUpdated)
|
||
})
|
||
|
||
// 组件卸载时移除事件监听
|
||
onUnmounted(() => {
|
||
uni.$off('addressUpdated')
|
||
})
|
||
</script>
|
||
|
||
<style scoped>
|
||
.address-page {
|
||
display: flex;
|
||
flex-direction: column;
|
||
height: 100%;
|
||
background-color: #f5f5f5;
|
||
}
|
||
|
||
.address-header {
|
||
background-color: #ffffff;
|
||
padding: 15px;
|
||
border-bottom: 1px solid #e5e5e5;
|
||
}
|
||
|
||
.header-title {
|
||
text-align: center;
|
||
}
|
||
|
||
.title-text {
|
||
font-size: 18px;
|
||
font-weight: bold;
|
||
color: #333333;
|
||
}
|
||
|
||
.address-list {
|
||
flex: 1;
|
||
padding: 10px;
|
||
}
|
||
|
||
.empty-address {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
padding: 80px 20px;
|
||
background-color: #ffffff;
|
||
border-radius: 8px;
|
||
}
|
||
|
||
.empty-icon {
|
||
font-size: 80px;
|
||
margin-bottom: 20px;
|
||
}
|
||
|
||
.empty-text {
|
||
font-size: 16px;
|
||
color: #666666;
|
||
margin-bottom: 10px;
|
||
}
|
||
|
||
.empty-subtext {
|
||
font-size: 14px;
|
||
color: #999999;
|
||
}
|
||
|
||
.address-item {
|
||
background-color: #ffffff;
|
||
margin-bottom: 10px;
|
||
padding: 15px;
|
||
border-radius: 8px;
|
||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||
}
|
||
|
||
.address-info {
|
||
margin-bottom: 15px;
|
||
padding-bottom: 15px;
|
||
border-bottom: 1px solid #f5f5f5;
|
||
}
|
||
|
||
.address-header-row {
|
||
display: flex;
|
||
align-items: center;
|
||
margin-bottom: 10px;
|
||
}
|
||
|
||
.address-name {
|
||
font-size: 16px;
|
||
font-weight: bold;
|
||
color: #333333;
|
||
margin-right: 15px;
|
||
}
|
||
|
||
.address-phone {
|
||
font-size: 14px;
|
||
color: #666666;
|
||
margin-right: 10px;
|
||
}
|
||
|
||
.default-tag {
|
||
background-color: #ff4757;
|
||
padding: 2px 8px;
|
||
border-radius: 10px;
|
||
}
|
||
|
||
.tag-text {
|
||
color: #ffffff;
|
||
font-size: 12px;
|
||
}
|
||
|
||
.address-detail {
|
||
font-size: 14px;
|
||
color: #333333;
|
||
line-height: 1.5;
|
||
}
|
||
|
||
.detail-text {
|
||
display: -webkit-box;
|
||
-webkit-box-orient: vertical;
|
||
-webkit-line-clamp: 2;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.address-actions {
|
||
display: flex;
|
||
justify-content: flex-end;
|
||
gap: 20px;
|
||
}
|
||
|
||
.action-item {
|
||
display: flex;
|
||
align-items: center;
|
||
}
|
||
|
||
.action-icon {
|
||
font-size: 16px;
|
||
margin-right: 5px;
|
||
}
|
||
|
||
.action-text {
|
||
font-size: 14px;
|
||
color: #666666;
|
||
}
|
||
|
||
.select-tip {
|
||
background-color: #ffffff;
|
||
padding: 20px;
|
||
border-radius: 8px;
|
||
margin-bottom: 10px;
|
||
text-align: center;
|
||
}
|
||
|
||
.tip-text {
|
||
font-size: 16px;
|
||
font-weight: bold;
|
||
color: #333333;
|
||
margin-bottom: 5px;
|
||
display: block;
|
||
}
|
||
|
||
.tip-subtext {
|
||
font-size: 14px;
|
||
color: #999999;
|
||
display: block;
|
||
}
|
||
|
||
.add-address-btn {
|
||
background-color: #007aff;
|
||
margin: 10px;
|
||
padding: 15px;
|
||
border-radius: 8px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
|
||
.btn-icon {
|
||
color: #ffffff;
|
||
font-size: 24px;
|
||
margin-right: 10px;
|
||
}
|
||
|
||
.btn-text {
|
||
color: #ffffff;
|
||
font-size: 16px;
|
||
font-weight: bold;
|
||
}
|
||
|
||
/* 新建地址表单弹窗样式 */
|
||
.address-form-mask {
|
||
position: fixed;
|
||
top: 0;
|
||
left: 0;
|
||
right: 0;
|
||
bottom: 0;
|
||
background-color: rgba(0, 0, 0, 0.5);
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
z-index: 1001;
|
||
}
|
||
|
||
.address-form-popup {
|
||
background-color: #ffffff;
|
||
width: 90%;
|
||
max-width: 500px;
|
||
max-height: 80%;
|
||
border-radius: 12px;
|
||
display: flex;
|
||
flex-direction: column;
|
||
}
|
||
|
||
.form-header {
|
||
padding: 20px 15px;
|
||
border-bottom: 1px solid #e5e5e5;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
}
|
||
|
||
.form-title {
|
||
font-size: 18px;
|
||
font-weight: bold;
|
||
color: #333333;
|
||
}
|
||
|
||
.form-close {
|
||
font-size: 24px;
|
||
color: #999999;
|
||
padding: 5px;
|
||
}
|
||
|
||
.form-content {
|
||
flex: 1;
|
||
padding: 15px;
|
||
max-height: 50%;
|
||
}
|
||
|
||
.form-item {
|
||
margin-bottom: 20px;
|
||
}
|
||
|
||
.form-item:last-child {
|
||
margin-bottom: 0;
|
||
}
|
||
|
||
.form-label {
|
||
font-size: 14px;
|
||
color: #333333;
|
||
margin-bottom: 8px;
|
||
display: block;
|
||
}
|
||
|
||
.form-input {
|
||
width: 100%;
|
||
padding: 12px;
|
||
border: 1px solid #e5e5e5;
|
||
border-radius: 8px;
|
||
font-size: 14px;
|
||
color: #333333;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
.form-input[readonly] {
|
||
background-color: #f9f9f9;
|
||
color: #666666;
|
||
}
|
||
|
||
.region-inputs {
|
||
display: flex;
|
||
gap: 10px;
|
||
}
|
||
|
||
.region-input {
|
||
flex: 1;
|
||
}
|
||
|
||
.form-textarea {
|
||
width: 100%;
|
||
min-height: 80px;
|
||
padding: 12px;
|
||
border: 1px solid #e5e5e5;
|
||
border-radius: 8px;
|
||
font-size: 14px;
|
||
color: #333333;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
.smart-address-input {
|
||
min-height: 60px;
|
||
}
|
||
|
||
.smart-tip {
|
||
font-size: 12px;
|
||
color: #999999;
|
||
margin-top: 5px;
|
||
display: block;
|
||
}
|
||
|
||
.checkbox-item {
|
||
margin-top: 20px;
|
||
}
|
||
|
||
.checkbox-wrapper {
|
||
display: flex;
|
||
align-items: center;
|
||
}
|
||
|
||
.checkbox {
|
||
width: 20px;
|
||
height: 20px;
|
||
border: 1px solid #e5e5e5;
|
||
border-radius: 4px;
|
||
margin-right: 10px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
|
||
.checkbox.checked {
|
||
background-color: #007aff;
|
||
border-color: #007aff;
|
||
}
|
||
|
||
.checkbox-check {
|
||
color: #ffffff;
|
||
font-size: 14px;
|
||
}
|
||
|
||
.checkbox-label {
|
||
font-size: 14px;
|
||
color: #333333;
|
||
}
|
||
|
||
.form-buttons {
|
||
display: flex;
|
||
padding: 15px;
|
||
border-top: 1px solid #e5e5e5;
|
||
gap: 10px;
|
||
}
|
||
|
||
.form-cancel-btn {
|
||
flex: 1;
|
||
background-color: #f5f5f5;
|
||
color: #333333;
|
||
padding: 12px;
|
||
border-radius: 8px;
|
||
font-size: 16px;
|
||
border: none;
|
||
}
|
||
|
||
.form-submit-btn {
|
||
flex: 1;
|
||
background-color: #007aff;
|
||
color: #ffffff;
|
||
padding: 12px;
|
||
border-radius: 8px;
|
||
font-size: 16px;
|
||
border: none;
|
||
}
|
||
</style>
|
||
|