367 lines
8.6 KiB
Plaintext
367 lines
8.6 KiB
Plaintext
<template>
|
||
<view class="address-list-page">
|
||
<view class="address-list">
|
||
<view v-if="addresses.length === 0" class="empty-state">
|
||
<text class="empty-icon">📍</text>
|
||
<text class="empty-text">暂无收货地址</text>
|
||
</view>
|
||
|
||
<view v-else v-for="(item, index) in addresses" :key="item.id" class="address-item" @click="selectAddress(item)">
|
||
<view class="item-content">
|
||
<view class="item-header">
|
||
<text class="user-name">{{ item.name }}</text>
|
||
<text class="user-phone">{{ item.phone }}</text>
|
||
<text v-if="item.isDefault" class="default-tag">默认</text>
|
||
<text v-if="item.label" class="label-tag">{{ item.label }}</text>
|
||
</view>
|
||
<text class="address-text">{{ getFullAddress(item) }}</text>
|
||
</view>
|
||
<view class="item-actions">
|
||
<view class="action-item" @click.stop="editAddress(item.id)">
|
||
<text class="action-icon">📝</text>
|
||
</view>
|
||
<view class="action-item" @click.stop="deleteAddress(item.id)">
|
||
<text class="action-icon">🗑️</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="footer-btn">
|
||
<button class="add-btn" @click="addAddress">新建收货地址</button>
|
||
</view>
|
||
</view>
|
||
</template>
|
||
|
||
<script setup lang="uts">
|
||
import { ref, onMounted, getCurrentInstance } from 'vue'
|
||
import { onShow, onLoad } from '@dcloudio/uni-app'
|
||
import { supabaseService, type UserAddress as SupabaseUserAddress } from '@/utils/supabaseService.uts'
|
||
import { HomeServiceSelectedAddressType } from '@/types/home-service.uts'
|
||
|
||
type Address = {
|
||
id: string
|
||
name: string
|
||
phone: string
|
||
province: string
|
||
city: string
|
||
district: string
|
||
detail: string
|
||
isDefault: boolean
|
||
label?: string
|
||
latitude?: number
|
||
longitude?: number
|
||
coordinateType?: string
|
||
}
|
||
|
||
const addresses = ref<Address[]>([])
|
||
const selectionMode = ref<boolean>(false)
|
||
|
||
const loadAddresses = async () => {
|
||
try {
|
||
// 从Supabase加载地址数据
|
||
const supabaseAddresses = await supabaseService.getAddresses()
|
||
|
||
// 转换数据格式以匹配前端界面
|
||
const transformedAddresses: Address[] = []
|
||
for (let i = 0; i < supabaseAddresses.length; i++) {
|
||
const item = supabaseAddresses[i]
|
||
const addr: Address = {
|
||
id: item.id,
|
||
name: item.recipient_name,
|
||
phone: item.phone,
|
||
province: item.province,
|
||
city: item.city,
|
||
district: item.district,
|
||
detail: item.detail_address,
|
||
isDefault: item.is_default,
|
||
label: '',
|
||
latitude: item.latitude ?? 0,
|
||
longitude: item.longitude ?? 0,
|
||
coordinateType: item.coordinate_type ?? 'gcj02'
|
||
} as Address
|
||
transformedAddresses.push(addr)
|
||
}
|
||
|
||
addresses.value = transformedAddresses
|
||
|
||
// 同时更新本地存储作为缓存
|
||
uni.setStorageSync('addresses', JSON.stringify(addresses.value))
|
||
} catch (error) {
|
||
console.error('加载地址数据失败:', error)
|
||
// 如果API调用失败,尝试从本地存储加载
|
||
const storedAddresses = uni.getStorageSync('addresses')
|
||
if (storedAddresses != null) {
|
||
try {
|
||
addresses.value = JSON.parse(storedAddresses as string) as Address[]
|
||
} catch (e) {
|
||
console.error('解析地址数据失败', e)
|
||
addresses.value = []
|
||
}
|
||
} else {
|
||
addresses.value = []
|
||
}
|
||
}
|
||
}
|
||
|
||
onLoad((options) => {
|
||
if (options == null) return
|
||
const selectMode = options['selectMode']
|
||
if (selectMode != null && String(selectMode) == 'true') {
|
||
selectionMode.value = true
|
||
}
|
||
})
|
||
|
||
onShow(() => {
|
||
loadAddresses()
|
||
})
|
||
|
||
// onMounted logic for EventChannel removed as it is not fully supported in UTS Android
|
||
// Using uni.$emit for global event communication instead
|
||
|
||
const getFullAddress = (item: Address): string => {
|
||
return `${item.province}${item.city}${item.district} ${item.detail}`
|
||
}
|
||
|
||
const addAddress = () => {
|
||
uni.navigateTo({
|
||
url: '/pages/mall/consumer/address-edit'
|
||
})
|
||
}
|
||
|
||
// 删除地址
|
||
const doDeleteAddress = async (id: string): Promise<void> => {
|
||
const success = await supabaseService.deleteAddress(id)
|
||
if (success) {
|
||
const index = addresses.value.findIndex(addr => addr.id === id)
|
||
if (index !== -1) {
|
||
addresses.value.splice(index, 1)
|
||
uni.setStorageSync('addresses', JSON.stringify(addresses.value))
|
||
uni.showToast({
|
||
title: '删除成功',
|
||
icon: 'success'
|
||
})
|
||
}
|
||
} else {
|
||
console.error('删除地址失败')
|
||
uni.showToast({
|
||
title: '删除失败',
|
||
icon: 'none'
|
||
})
|
||
}
|
||
}
|
||
|
||
const deleteAddress = (id: string) => {
|
||
uni.showModal({
|
||
title: '提示',
|
||
content: '确定要删除该地址吗?',
|
||
success: (res) => {
|
||
if (res.confirm) {
|
||
doDeleteAddress(id)
|
||
}
|
||
}
|
||
})
|
||
}
|
||
|
||
const editAddress = (id: string) => {
|
||
uni.navigateTo({
|
||
url: `/pages/mall/consumer/address-edit?id=${id}`
|
||
})
|
||
}
|
||
|
||
const selectAddress = (item: Address) => {
|
||
if (selectionMode.value) {
|
||
const fullAddressText = getFullAddress(item)
|
||
const selectedAddress = {
|
||
addressId: item.id,
|
||
userId: '',
|
||
isDefault: item.isDefault,
|
||
contactName: item.name,
|
||
contactPhone: item.phone,
|
||
phone: item.phone,
|
||
addressName: `${item.province}${item.city}${item.district}`,
|
||
locationName: `${item.province}${item.city}${item.district}`,
|
||
addressDetail: item.detail,
|
||
locationAddress: `${item.province}${item.city}${item.district}`,
|
||
houseNumber: item.detail,
|
||
doorNo: item.detail,
|
||
fullAddress: fullAddressText,
|
||
latitude: item.latitude ?? 0,
|
||
longitude: item.longitude ?? 0,
|
||
remark: item.label ?? '',
|
||
coordinateType: item.coordinateType ?? 'gcj02',
|
||
createdAt: Date.now(),
|
||
updatedAt: Date.now()
|
||
} as HomeServiceSelectedAddressType
|
||
|
||
uni.setStorageSync('hss_selected_service_address', selectedAddress)
|
||
uni.$emit('addressSelected', selectedAddress)
|
||
uni.navigateBack()
|
||
} else {
|
||
editAddress(item.id)
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<style>
|
||
.address-list-page {
|
||
background-color: #f8f8f8;
|
||
padding: 12px;
|
||
padding-bottom: 100px;
|
||
}
|
||
|
||
.address-list {
|
||
display: flex;
|
||
flex-direction: column;
|
||
}
|
||
|
||
.address-item {
|
||
background-color: #ffffff;
|
||
border-radius: 12px;
|
||
padding: 16px;
|
||
margin-bottom: 12px;
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: center;
|
||
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.03);
|
||
}
|
||
|
||
.item-content {
|
||
flex: 1;
|
||
display: flex;
|
||
flex-direction: column;
|
||
margin-right: 12px;
|
||
}
|
||
|
||
.item-header {
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: center;
|
||
margin-bottom: 8px;
|
||
flex-wrap: wrap;
|
||
}
|
||
|
||
.user-name {
|
||
font-size: 16px;
|
||
font-weight: bold;
|
||
color: #333;
|
||
margin-right: 12px;
|
||
}
|
||
|
||
.user-phone {
|
||
font-size: 14px;
|
||
color: #666;
|
||
margin-right: 12px;
|
||
}
|
||
|
||
.default-tag {
|
||
background-color: #fff1eb;
|
||
color: #ff5000;
|
||
font-size: 10px;
|
||
padding: 2px 6px;
|
||
border-radius: 4px;
|
||
margin-right: 6px;
|
||
border: 1px solid #ff5000;
|
||
}
|
||
|
||
.label-tag {
|
||
background-color: #eef5ff;
|
||
color: #007aff;
|
||
font-size: 10px;
|
||
padding: 2px 6px;
|
||
border-radius: 4px;
|
||
border: 1px solid #007aff;
|
||
}
|
||
|
||
.address-text {
|
||
font-size: 14px;
|
||
color: #333;
|
||
line-height: 1.5;
|
||
lines: 2;
|
||
text-overflow: ellipsis;
|
||
}
|
||
|
||
.item-actions {
|
||
padding-left: 16px;
|
||
border-left: 1px solid #f0f0f0;
|
||
display: flex;
|
||
flex-direction: row; /* 改为横向排列图标更符合习惯 */
|
||
align-items: center;
|
||
}
|
||
|
||
.action-item {
|
||
padding: 8px;
|
||
}
|
||
|
||
.empty-state {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
padding-top: 100px;
|
||
}
|
||
|
||
.empty-icon {
|
||
font-size: 64px;
|
||
margin-bottom: 16px;
|
||
opacity: 0.5;
|
||
}
|
||
|
||
.empty-text {
|
||
font-size: 14px;
|
||
color: #999;
|
||
}
|
||
|
||
.footer-btn {
|
||
position: fixed;
|
||
bottom: 0;
|
||
left: 0;
|
||
right: 0;
|
||
background-color: white;
|
||
padding: 10px 15px;
|
||
padding-bottom: 30px;
|
||
box-shadow: 0 -2px 10px rgba(0,0,0,0.05);
|
||
display: flex;
|
||
justify-content: center;
|
||
align-items: center;
|
||
z-index: 100;
|
||
}
|
||
|
||
.add-btn {
|
||
background-color: #ff5000;
|
||
color: white;
|
||
border-radius: 25px;
|
||
font-size: 16px;
|
||
height: 44px;
|
||
line-height: 44px;
|
||
border: none;
|
||
width: 100%; /* 默认占满 */
|
||
}
|
||
|
||
/* 响应式布局优化 */
|
||
@media screen and (min-width: 768px) {
|
||
.address-list {
|
||
max-width: 800px;
|
||
margin: 0 auto;
|
||
}
|
||
|
||
.address-list-page {
|
||
background-color: #f5f5f5;
|
||
}
|
||
|
||
.footer-btn {
|
||
max-width: 800px;
|
||
margin: 0 auto;
|
||
left: 50%;
|
||
transform: translateX(-50%);
|
||
box-shadow: 0 -2px 10px rgba(0,0,0,0.05);
|
||
border-radius: 12px 12px 0 0;
|
||
}
|
||
|
||
.add-btn {
|
||
width: 300px; /* 桌面端限制宽度 */
|
||
}
|
||
}
|
||
</style>
|
||
|