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

793 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="address-edit-page">
<!-- 顶部栏 -->
<view class="edit-header">
<text class="back-btn" @click="goBack"></text>
<text class="header-title">{{ addressId ? '编辑地址' : '新增地址' }}</text>
<text v-if="addressId" class="delete-btn" @click="deleteAddress">删除</text>
</view>
<scroll-view class="edit-content" scroll-y>
<!-- 表单 -->
<view class="form-section">
<view class="form-item">
<text class="item-label">收货人</text>
<input class="item-input"
v-model="formData.recipient_name"
placeholder="请输入收货人姓名" />
</view>
<view class="form-item">
<text class="item-label">手机号码</text>
<input class="item-input"
v-model="formData.phone"
placeholder="请输入手机号码"
type="number"
maxlength="11" />
</view>
<view class="form-item" @click="showRegionPicker">
<text class="item-label">所在地区</text>
<text class="item-value">{{ regionText || '请选择省市区' }}</text>
<text class="item-arrow"></text>
</view>
<view class="form-item">
<text class="item-label">详细地址</text>
<textarea class="item-textarea"
v-model="formData.detail"
placeholder="请输入详细地址,如街道、小区、楼栋号、单元室等"
maxlength="100" />
</view>
<view class="form-item">
<text class="item-label">邮政编码</text>
<input class="item-input"
v-model="formData.postal_code"
placeholder="请输入邮政编码"
type="number"
maxlength="6" />
</view>
<view class="form-item">
<view class="default-switch">
<text class="switch-label">设为默认地址</text>
<switch :checked="formData.is_default"
@change="toggleDefault" />
</view>
</view>
</view>
<!-- 地址簿 -->
<view v-if="addressList.length > 0" class="address-book">
<view class="section-title">地址簿</view>
<view v-for="address in addressList"
:key="address.id"
class="book-item"
@click="fillFromAddressBook(address)">
<view class="book-info">
<text class="book-name">{{ address.recipient_name }}</text>
<text class="book-phone">{{ address.phone }}</text>
</view>
<text class="book-address">{{ getFullAddress(address) }}</text>
</view>
</view>
</scroll-view>
<!-- 保存按钮 -->
<view class="save-btn-container">
<button class="save-btn" @click="saveAddress">保存地址</button>
</view>
<!-- 地区选择器 -->
<view v-if="showPicker" class="region-picker">
<view class="picker-mask" @click="hideRegionPicker"></view>
<view class="picker-content">
<view class="picker-header">
<text class="cancel-btn" @click="hideRegionPicker">取消</text>
<text class="picker-title">选择地区</text>
<text class="confirm-btn" @click="confirmRegion">确定</text>
</view>
<picker-view class="picker-view"
:value="pickerValue"
@change="onPickerChange">
<picker-view-column>
<view v-for="(province, index) in provinces"
:key="index"
class="picker-item">
{{ province.name }}
</view>
</picker-view-column>
<picker-view-column>
<view v-for="(city, index) in cities"
:key="index"
class="picker-item">
{{ city.name }}
</view>
</picker-view-column>
<picker-view-column>
<view v-for="(district, index) in districts"
:key="index"
class="picker-item">
{{ district.name }}
</view>
</picker-view-column>
</picker-view>
</view>
</view>
</view>
</template>
<script setup lang="uts">
import { ref, onMounted } from 'vue'
import supa from '@/components/supadb/aksupainstance.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
}
type RegionType = {
code: string
name: string
children?: RegionType[]
}
const addressId = ref<string>('')
const formData = ref<AddressType>({
user_id: '',
recipient_name: '',
phone: '',
province: '',
city: '',
district: '',
detail: '',
postal_code: null,
is_default: false
})
const addressList = ref<Array<AddressType>>([])
const showPicker = ref<boolean>(false)
const provinces = ref<Array<RegionType>>([])
const cities = ref<Array<RegionType>>([])
const districts = ref<Array<RegionType>>([])
const pickerValue = ref<Array<number>>([0, 0, 0])
const selectedRegion = ref<{
province: RegionType | null
city: RegionType | null
district: RegionType | null
}>({
province: null,
city: null,
district: null
})
// 获取地区文本
const regionText = computed(() => {
const { province, city, district } = selectedRegion.value
if (province && city && district) {
return `${province.name} ${city.name} ${district.name}`
}
return ''
})
// 生命周期
onMounted(() => {
loadAddresses()
loadRegions()
const pages = getCurrentPages()
const currentPage = pages[pages.length - 1]
const options = currentPage.options as any
if (options.id) {
addressId.value = options.id
loadAddressDetail(options.id)
}
})
// 加载地址列表
const loadAddresses = async () => {
const userId = getCurrentUserId()
if (!userId) return
try {
const { data, error } = await supa
.from('user_addresses')
.select('*')
.eq('user_id', userId)
.order('is_default', { ascending: false })
.limit(5)
if (error !== null) {
console.error('加载地址列表失败:', error)
return
}
addressList.value = data ?? []
} catch (err) {
console.error('加载地址列表异常:', err)
}
}
// 加载地址详情
const loadAddressDetail = async (id: string) => {
try {
const { data, error } = await supa
.from('user_addresses')
.select('*')
.eq('id', id)
.single()
if (error !== null) {
console.error('加载地址详情失败:', error)
return
}
formData.value = data
// 设置选中的地区
selectedRegion.value = {
province: { code: '', name: formData.value.province },
city: { code: '', name: formData.value.city },
district: { code: '', name: formData.value.district }
}
} catch (err) {
console.error('加载地址详情异常:', err)
}
}
// 加载地区数据
const loadRegions = () => {
// 这里应该从API加载地区数据这里使用模拟数据
provinces.value = [
{ code: '110000', name: '北京市' },
{ code: '310000', name: '上海市' },
{ code: '440000', name: '广东省' },
{ code: '330000', name: '浙江省' },
{ code: '320000', name: '江苏省' }
]
// 默认加载第一个省份的城市
if (provinces.value.length > 0) {
loadCities(provinces.value[0].code)
}
}
// 加载城市
const loadCities = (provinceCode: string) => {
// 模拟城市数据
const cityMap: Record<string, RegionType[]> = {
'110000': [
{ code: '110100', name: '北京市' }
],
'310000': [
{ code: '310100', name: '上海市' }
],
'440000': [
{ code: '440100', name: '广州市' },
{ code: '440300', name: '深圳市' },
{ code: '440600', name: '佛山市' }
],
'330000': [
{ code: '330100', name: '杭州市' },
{ code: '330200', name: '宁波市' },
{ code: '330300', name: '温州市' }
],
'320000': [
{ code: '320100', name: '南京市' },
{ code: '320200', name: '无锡市' },
{ code: '320500', name: '苏州市' }
]
}
cities.value = cityMap[provinceCode] || []
// 加载第一个城市的区县
if (cities.value.length > 0) {
loadDistricts(cities.value[0].code)
}
}
// 加载区县
const loadDistricts = (cityCode: string) => {
// 模拟区县数据
const districtMap: Record<string, RegionType[]> = {
'110100': [
{ code: '110101', name: '东城区' },
{ code: '110102', name: '西城区' },
{ code: '110105', name: '朝阳区' },
{ code: '110106', name: '丰台区' }
],
'440100': [
{ code: '440103', name: '荔湾区' },
{ code: '440104', name: '越秀区' },
{ code: '440105', name: '海珠区' },
{ code: '440106', name: '天河区' }
],
'330100': [
{ code: '330102', name: '上城区' },
{ code: '330103', name: '下城区' },
{ code: '330104', name: '江干区' },
{ code: '330105', name: '拱墅区' }
]
}
districts.value = districtMap[cityCode] || []
}
// 获取当前用户ID
const getCurrentUserId = (): string => {
const userStore = uni.getStorageSync('userInfo')
return userStore?.id || ''
}
// 获取完整地址
const getFullAddress = (address: AddressType): string => {
return `${address.province}${address.city}${address.district}${address.detail}`
}
// 显示地区选择器
const showRegionPicker = () => {
showPicker.value = true
}
// 隐藏地区选择器
const hideRegionPicker = () => {
showPicker.value = false
}
// 选择器变化
const onPickerChange = (event: any) => {
const value = event.detail.value
pickerValue.value = value
// 省份变化
if (value[0] !== pickerValue.value[0]) {
const province = provinces.value[value[0]]
selectedRegion.value.province = province
loadCities(province.code)
pickerValue.value = [value[0], 0, 0]
}
// 城市变化
if (value[1] !== pickerValue.value[1]) {
const city = cities.value[value[1]]
selectedRegion.value.city = city
loadDistricts(city.code)
pickerValue.value = [value[0], value[1], 0]
}
// 区县变化
if (value[2] !== pickerValue.value[2]) {
const district = districts.value[value[2]]
selectedRegion.value.district = district
}
}
// 确认地区选择
const confirmRegion = () => {
const province = provinces.value[pickerValue.value[0]]
const city = cities.value[pickerValue.value[1]]
const district = districts.value[pickerValue.value[2]]
if (province && city && district) {
selectedRegion.value = { province, city, district }
formData.value.province = province.name
formData.value.city = city.name
formData.value.district = district.name
}
hideRegionPicker()
}
// 切换默认地址
const toggleDefault = (event: any) => {
formData.value.is_default = event.detail.value
}
// 从地址簿填充
const fillFromAddressBook = (address: AddressType) => {
formData.value = { ...address }
selectedRegion.value = {
province: { code: '', name: address.province },
city: { code: '', name: address.city },
district: { code: '', name: address.district }
}
}
// 保存地址
const saveAddress = async () => {
// 验证表单
if (!formData.value.recipient_name.trim()) {
uni.showToast({
title: '请输入收货人姓名',
icon: 'none'
})
return
}
if (!formData.value.phone.trim()) {
uni.showToast({
title: '请输入手机号码',
icon: 'none'
})
return
}
if (!/^1[3-9]\d{9}$/.test(formData.value.phone)) {
uni.showToast({
title: '手机号码格式错误',
icon: 'none'
})
return
}
if (!formData.value.province) {
uni.showToast({
title: '请选择所在地区',
icon: 'none'
})
return
}
if (!formData.value.detail.trim()) {
uni.showToast({
title: '请输入详细地址',
icon: 'none'
})
return
}
const userId = getCurrentUserId()
if (!userId) {
uni.showToast({
title: '用户信息错误',
icon: 'none'
})
return
}
formData.value.user_id = userId
try {
if (formData.value.is_default) {
// 取消其他默认地址
await supa
.from('user_addresses')
.update({ is_default: false })
.eq('user_id', userId)
.eq('is_default', true)
}
if (addressId.value) {
// 更新地址
const { error } = await supa
.from('user_addresses')
.update(formData.value)
.eq('id', addressId.value)
if (error !== null) {
throw error
}
uni.showToast({
title: '地址更新成功',
icon: 'success'
})
} else {
// 新增地址
const { error } = await supa
.from('user_addresses')
.insert(formData.value)
if (error !== null) {
throw error
}
uni.showToast({
title: '地址添加成功',
icon: 'success'
})
}
// 返回上一页
setTimeout(() => {
uni.navigateBack()
}, 1500)
} catch (err) {
console.error('保存地址失败:', err)
uni.showToast({
title: '保存失败',
icon: 'none'
})
}
}
// 删除地址
const deleteAddress = () => {
uni.showModal({
title: '删除地址',
content: '确定要删除这个地址吗?',
success: async (res) => {
if (res.confirm) {
try {
const { error } = await supa
.from('user_addresses')
.delete()
.eq('id', addressId.value)
if (error !== null) {
throw error
}
uni.showToast({
title: '地址删除成功',
icon: 'success'
})
setTimeout(() => {
uni.navigateBack()
}, 1500)
} catch (err) {
console.error('删除地址失败:', err)
uni.showToast({
title: '删除失败',
icon: 'none'
})
}
}
}
})
}
// 返回
const goBack = () => {
uni.navigateBack()
}
</script>
<style scoped>
.address-edit-page {
display: flex;
flex-direction: column;
height: 100vh;
background-color: #f5f5f5;
}
.edit-header {
background-color: #ffffff;
padding: 15px;
display: flex;
align-items: center;
justify-content: space-between;
border-bottom: 1px solid #e5e5e5;
}
.back-btn {
font-size: 24px;
color: #333333;
padding: 5px;
}
.header-title {
font-size: 18px;
font-weight: bold;
color: #333333;
}
.delete-btn {
color: #ff4757;
font-size: 14px;
padding: 5px;
}
.edit-content {
flex: 1;
}
.form-section {
background-color: #ffffff;
margin-bottom: 10px;
padding: 0 15px;
}
.form-item {
padding: 15px 0;
border-bottom: 1px solid #f5f5f5;
display: flex;
align-items: center;
}
.form-item:last-child {
border-bottom: none;
}
.item-label {
width: 80px;
font-size: 14px;
color: #333333;
}
.item-input {
flex: 1;
height: 30px;
font-size: 14px;
color: #333333;
}
.item-value {
flex: 1;
font-size: 14px;
color: #666666;
}
.item-arrow {
color: #999999;
font-size: 16px;
margin-left: 10px;
}
.item-textarea {
flex: 1;
min-height: 60px;
font-size: 14px;
color: #333333;
line-height: 1.4;
}
.default-switch {
flex: 1;
display: flex;
justify-content: space-between;
align-items: center;
}
.switch-label {
font-size: 14px;
color: #333333;
}
.address-book {
background-color: #ffffff;
padding: 15px;
}
.section-title {
font-size: 16px;
font-weight: bold;
color: #333333;
margin-bottom: 15px;
}
.book-item {
padding: 15px 0;
border-bottom: 1px solid #f5f5f5;
}
.book-item:last-child {
border-bottom: none;
}
.book-info {
display: flex;
align-items: center;
margin-bottom: 8px;
}
.book-name {
font-size: 14px;
color: #333333;
font-weight: bold;
margin-right: 15px;
}
.book-phone {
font-size: 14px;
color: #666666;
}
.book-address {
font-size: 13px;
color: #666666;
line-height: 1.4;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
overflow: hidden;
}
.save-btn-container {
background-color: #ffffff;
padding: 15px;
border-top: 1px solid #e5e5e5;
}
.save-btn {
background-color: #007aff;
color: #ffffff;
height: 50px;
border-radius: 25px;
font-size: 16px;
font-weight: bold;
border: none;
}
.region-picker {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 999;
}
.picker-mask {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.5);
}
.picker-content {
position: absolute;
bottom: 0;
left: 0;
right: 0;
background-color: #ffffff;
border-top-left-radius: 10px;
border-top-right-radius: 10px;
}
.picker-header {
padding: 15px;
display: flex;
align-items: center;
justify-content: space-between;
border-bottom: 1px solid #e5e5e5;
}
.cancel-btn {
color: #999999;
font-size: 14px;
padding: 5px;
}
.picker-title {
font-size: 16px;
font-weight: bold;
color: #333333;
}
.confirm-btn {
color: #007aff;
font-size: 14px;
font-weight: bold;
padding: 5px;
}
.picker-view {
height: 300px;
}
.picker-item {
height: 50px;
display: flex;
align-items: center;
justify-content: center;
font-size: 14px;
color: #333333;
}
</style>