Files
medical-mall/pages/mall/consumer/address-edit.uvue

613 lines
16 KiB
Plaintext
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.
<template>
<view class="page-container">
<scroll-view class="address-edit-scroll" scroll-y="true">
<view class="address-edit-content">
<!-- 基础信息组 -->
<view class="form-group">
<view class="form-item">
<text class="label">收货人</text>
<input class="input" v-model="formData.name" placeholder="请填写收货人姓名" placeholder-class="placeholder" />
</view>
<view class="form-item">
<text class="label">手机号码</text>
<input class="input" v-model="formData.phone" type="number" maxlength="11" placeholder="请填写手机号码" placeholder-class="placeholder" />
</view>
<view class="form-item">
<text class="label">所在地区</text>
<input class="input" v-model="regionString" placeholder="省市区县、乡镇等" placeholder-class="placeholder" />
<text class="arrow-icon"></text>
</view>
<view class="form-item detail-item">
<text class="label">详细地址</text>
<textarea class="textarea" v-model="formData.detail" placeholder="街道、楼牌号等" placeholder-class="placeholder" maxlength="100"></textarea>
</view>
</view>
<!-- 标签与默认设置组 -->
<view class="form-group">
<view class="form-item label-section">
<text class="label">地址标签</text>
<view class="tags-container">
<view
v-for="tag in tags"
:key="tag"
class="tag-item"
:class="{ active: formData.label === tag }"
@click="selectTag(tag)"
>
<text class="tag-text" :class="{ 'tag-text-active': formData.label === tag }">{{ tag }}</text>
</view>
</view>
</view>
<view class="form-item switch-item">
<view class="switch-label-group">
<text class="label">设为默认地址</text>
<text class="sub-label">下单时优先使用该地址</text>
</view>
<switch :checked="formData.isDefault" color="#ff5000" @change="onSwitchChange" />
</view>
</view>
<!-- 智能识别组 -->
<view class="form-group smart-group">
<view class="smart-header">
<text class="smart-title">智能填写</text>
<text class="smart-clear" v-if="smartInput" @click="smartInput = ''">清空</text>
</view>
<textarea class="smart-textarea" v-model="smartInput" placeholder="粘贴整段地址,自动识别姓名、电话、地址" @input="parseSmartInput" maxlength="200"></textarea>
<view class="smart-footer">
<text class="smart-tip">示例张三13800138000北京市朝阳区...</text>
</view>
</view>
<!-- 底部操作按钮 -->
<view class="footer-actions">
<button class="save-btn" @click="saveAddress">保存地址</button>
<button v-if="isEdit" class="delete-btn" @click="deleteAddress">删除此地址</button>
</view>
</view>
</scroll-view>
</view>
</template>
<script setup lang="uts">
import { ref, reactive, computed } from 'vue'
import { onLoad } from '@dcloudio/uni-app'
import { supabaseService, AddAddressParams, UpdateAddressParams } from '@/utils/supabaseService.uts'
type Address = {
id: string
name: string
phone: string
province: string
city: string
district: string
detail: string
isDefault: boolean
label?: string
}
const isEdit = ref(false)
const addressId = ref('')
const regionString = ref('')
const tags = ['家', '公司', '学校']
const smartInput = ref('')
type AddressForm = {
name: string
phone: string
detail: string
isDefault: boolean
label: string
}
const formData = reactive({
name: '',
phone: '',
detail: '',
isDefault: false,
label: ''
} as AddressForm)
const loadAddress = async (id: string) => {
try {
// 从Supabase加载地址详情
const address = await supabaseService.getAddressById(id)
if (address != null) {
formData.name = address.recipient_name
formData.phone = address.phone
formData.detail = address.detail_address
formData.isDefault = address.is_default
formData.label = address.label ?? ''
regionString.value = `${address.province} ${address.city} ${address.district}`.trim()
} else {
// 如果Supabase没有找到尝试从本地存储加载
const storedAddresses = uni.getStorageSync('addresses')
if (storedAddresses != null) {
const addresses = JSON.parse(storedAddresses as string) as Address[]
const localAddress = addresses.find(item => item.id === id)
if (localAddress != null) {
formData.name = localAddress.name
formData.phone = localAddress.phone
formData.detail = localAddress.detail
formData.isDefault = localAddress.isDefault
formData.label = localAddress.label ?? ''
regionString.value = `${localAddress.province} ${localAddress.city} ${localAddress.district}`.trim()
}
}
}
} catch (error) {
console.error('加载地址详情失败:', error)
// 失败时从本地存储加载
const storedAddresses = uni.getStorageSync('addresses')
if (storedAddresses != null) {
try {
const addresses = JSON.parse(storedAddresses as string) as Address[]
const address = addresses.find(item => item.id === id)
if (address != null) {
formData.name = address.name
formData.phone = address.phone
formData.detail = address.detail
formData.isDefault = address.isDefault
formData.label = address.label ?? ''
regionString.value = `${address.province} ${address.city} ${address.district}`.trim()
}
} catch (e) {
console.error('解析本地地址数据失败', e)
}
}
}
}
onLoad((options) => {
if (options['id'] != null) {
isEdit.value = true
addressId.value = options['id'] as string
loadAddress(addressId.value)
}
})
const selectTag = (tag: string) => {
if (formData.label === tag) {
formData.label = ''
} else {
formData.label = tag
}
}
const onSwitchChange = (e: UniSwitchChangeEvent) => {
formData.isDefault = e.detail.value
}
const saveAddress = async () => {
if (formData.name == '') {
uni.showToast({ title: '请填写收货人', icon: 'none' })
return
}
if (formData.phone == '') {
uni.showToast({ title: '请填写手机号码', icon: 'none' })
return
}
if (regionString.value == '') {
uni.showToast({ title: '请填写所在地区', icon: 'none' })
return
}
if (formData.detail == '') {
uni.showToast({ title: '请填写详细地址', icon: 'none' })
return
}
// 简单解析地区(这里简化处理,实际应使用选择器)
const regions = regionString.value.split(' ')
const province = regions[0] ?? ''
const city = regions[1] ?? ''
const district = regions.slice(2).join(' ')
// 构建地址对象
const addressData = {
recipient_name: formData.name,
phone: formData.phone,
province: province,
city: city,
district: district,
detail_address: formData.detail,
postal_code: '', // 如果需要可以添加邮政编码字段
is_default: formData.isDefault,
label: formData.label
} as AddAddressParams
let success = false
if (isEdit.value) {
// 更新地址
const updateData = {
recipient_name: formData.name,
phone: formData.phone,
province: province,
city: city,
district: district,
detail_address: formData.detail,
postal_code: '',
is_default: formData.isDefault,
label: formData.label
} as UpdateAddressParams
success = await supabaseService.updateAddress(addressId.value, updateData)
} else {
// 添加新地址
success = await supabaseService.addAddress(addressData)
}
if (success) {
// 同时更新本地存储作为缓存
const storedAddresses = uni.getStorageSync('addresses')
let addresses: Address[] = []
if (storedAddresses != null) {
try {
addresses = JSON.parse(storedAddresses as string) as Address[]
} catch (e) {
addresses = []
}
}
// 如果设为默认,取消其他默认
if (formData.isDefault) {
addresses.forEach(item => {
item.isDefault = false
})
}
if (isEdit.value) {
const index = addresses.findIndex(item => item.id === addressId.value)
if (index !== -1) {
addresses[index] = {
...addresses[index],
name: formData.name,
phone: formData.phone,
province: province,
city: city,
district: district,
detail: formData.detail,
isDefault: formData.isDefault,
label: formData.label
}
}
} else {
const newAddress: Address = {
id: `addr_${Date.now()}`, // 临时ID实际由Supabase生成
name: formData.name,
phone: formData.phone,
province: province,
city: city,
district: district,
detail: formData.detail,
isDefault: formData.isDefault,
label: formData.label
}
addresses.push(newAddress)
}
uni.setStorageSync('addresses', JSON.stringify(addresses))
uni.showToast({
title: '保存成功',
icon: 'success'
})
setTimeout(() => {
uni.navigateBack()
}, 1500)
} else {
console.error('保存地址失败')
uni.showToast({
title: '保存失败',
icon: 'none'
})
}
}
const parseSmartInput = () => {
const input = smartInput.value.trim()
if (input == '') return
// 提取手机号
const phoneRegex = /(1[3-9]\d{9})/
const phoneMatch = input.match(phoneRegex)
if (phoneMatch != null) {
formData.phone = phoneMatch[0] ?? ''
}
// 提取姓名取第一个2-4位中文
const nameRegex = /([\u4e00-\u9fa5]{2,4})/
const nameMatch = input.match(nameRegex)
if (nameMatch != null) {
formData.name = nameMatch[0] ?? ''
}
// 去掉姓名和电话后剩余作为地址
let addrText = input
if (formData.name != '') addrText = addrText.replace(formData.name, '')
if (formData.phone != '') addrText = addrText.replace(formData.phone, '')
addrText = addrText.replace(/[,;\s]+/g, ' ').trim()
// 解析省市区
const pattern1 = /^(.*?省)?(.*?市)?(.*?[区县])?(.*)$/
const m = addrText.match(pattern1)
if (m != null) {
const province = m[1] ?? ''
const city = m[2] ?? ''
const district = m[3] ?? ''
const detail = m[4] ?? ''
regionString.value = `${province.trim()} ${city.trim()} ${district.trim()}`.trim()
formData.detail = detail.trim()
} else {
formData.detail = addrText
}
}
const deleteAddress = () => {
uni.showModal({
title: '提示',
content: '确定要删除该地址吗?',
success: (res: UniShowModalResult) => {
if (res.confirm) {
// 调用Supabase服务删除地址
supabaseService.deleteAddress(addressId.value).then((success) => {
if (success) {
// 同时从本地存储中移除
const storedAddresses = uni.getStorageSync('addresses')
if (storedAddresses != null) {
try {
let addresses = JSON.parse(storedAddresses as string) as Address[]
addresses = addresses.filter(item => item.id !== addressId.value)
uni.setStorageSync('addresses', JSON.stringify(addresses))
} catch (e) {
console.error('解析本地地址数据失败', e)
}
}
uni.showToast({
title: '删除成功',
icon: 'success'
})
setTimeout(() => {
uni.navigateBack()
}, 1500)
} else {
console.error('删除地址失败')
uni.showToast({
title: '删除失败',
icon: 'none'
})
}
})
}
}
})
}
</script>
<style>
.page-container {
flex: 1;
background-color: #f8f8f8;
display: flex;
flex-direction: column;
}
.address-edit-scroll {
flex: 1;
}
.address-edit-content {
padding: 12px;
padding-bottom: 40px;
}
.form-group {
background-color: #ffffff;
border-radius: 12px;
padding: 16px; /* 给整个组增加内边距 */
margin-bottom: 12px;
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.02);
}
.form-item {
display: flex;
flex-direction: row;
align-items: center;
padding: 0 16px;
min-height: 52px;
background-color: #f8f8f8;
border-radius: 26px; /* 增加大圆角,使其从直角变为圆角 */
margin-bottom: 12px;
border: none;
}
.form-item:last-child {
margin-bottom: 0;
}
.detail-item {
align-items: flex-start;
flex-direction: column;
padding: 16px;
border-radius: 16px; /* 详细地址区域也增加圆角 */
}
.detail-item .label {
margin-bottom: 8px;
}
.label {
width: 80px;
font-size: 15px;
color: #666;
font-weight: 400;
}
.input {
flex: 1;
height: 44px; /* 增加高度 */
line-height: 44px;
font-size: 16px;
color: #333;
padding: 0 4px;
background-color: transparent; /* 确保输入框背景透明 */
border: none; /* 强制去除安卓原生边框 */
}
.textarea {
width: 100%;
height: 80px;
font-size: 15px;
line-height: 1.6;
color: #333;
padding: 4px 0;
background-color: transparent;
border: none; /* 强制去除安卓原生边框 */
}
.placeholder {
color: #bbb;
font-size: 15px;
}
.arrow-icon {
font-size: 18px;
color: #ccc;
margin-left: 8px;
}
/* 标签选择 */
.label-section {
align-items: flex-start;
flex-direction: column;
}
.label-section .label {
margin-bottom: 16px;
}
.tags-container {
display: flex;
flex-direction: row;
flex-wrap: wrap;
}
.tag-item {
padding: 8px 20px; /* 增大点击区域 */
background-color: #f7f7f7;
border-radius: 20px;
margin-right: 12px;
margin-bottom: 8px; /* 增加底部间距 */
border: 1px solid transparent;
}
.tag-item.active {
background-color: #fff1eb;
border-color: #ff5000;
}
.tag-text {
font-size: 14px; /* 增大标签文字 */
color: #666;
}
.tag-text-active {
color: #ff5000;
font-weight: bold;
}
/* 开关项 */
.switch-item {
justify-content: space-between;
min-height: 72px; /* 增加开关项高度 */
}
.switch-label-group {
display: flex;
flex-direction: column;
}
.sub-label {
font-size: 13px; /* 增大副标题 */
color: #999;
margin-top: 6px;
}
/* 智能填写 */
.smart-group {
padding: 16px;
}
.smart-header {
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
margin-bottom: 12px;
}
.smart-title {
font-size: 14px;
color: #333;
font-weight: bold;
}
.smart-clear {
font-size: 12px;
color: #007aff;
}
.smart-textarea {
width: 100%;
height: 80px;
background-color: #f9f9f9;
border-radius: 8px;
padding: 12px;
font-size: 13px;
line-height: 1.6;
color: #666;
}
.smart-footer {
margin-top: 8px;
}
.smart-tip {
font-size: 11px;
color: #999;
}
/* 底部按钮 */
.footer-actions {
margin-top: 32px;
display: flex;
flex-direction: column;
}
.save-btn {
background-color: #ff5000;
color: #ffffff;
height: 48px;
line-height: 48px;
font-size: 16px;
font-weight: bold;
border-radius: 24px;
border: none;
margin-bottom: 16px;
box-shadow: 0 8rpx 20rpx rgba(255, 80, 0, 0.2);
}
.delete-btn {
background-color: #ffffff;
color: #ee0a24;
height: 48px;
line-height: 48px;
font-size: 16px;
border-radius: 24px;
border: 1px solid #f0f0f0;
}
</style>