474 lines
9.6 KiB
Plaintext
474 lines
9.6 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" scroll-y>
|
|
<!-- 地址为空 -->
|
|
<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="addNewAddress">
|
|
<text class="btn-icon">+</text>
|
|
<text class="btn-text">添加新地址</text>
|
|
</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
|
|
created_at: string
|
|
}
|
|
|
|
const addressList = ref<Array<AddressType>>([])
|
|
const fromSelect = ref<boolean>(false)
|
|
const selectCallback = ref<any>(null)
|
|
|
|
// 生命周期
|
|
onMounted(() => {
|
|
const eventChannel = uni.getEventChannel()
|
|
if (eventChannel) {
|
|
eventChannel.on('fromSelect', (data: any) => {
|
|
fromSelect.value = data.fromSelect || false
|
|
selectCallback.value = data.callback
|
|
})
|
|
}
|
|
|
|
loadAddresses()
|
|
})
|
|
|
|
// 加载地址列表
|
|
const loadAddresses = async () => {
|
|
const userId = getCurrentUserId()
|
|
if (!userId) {
|
|
uni.showToast({
|
|
title: '请先登录',
|
|
icon: 'none'
|
|
})
|
|
uni.navigateTo({
|
|
url: '/pages/user/login'
|
|
})
|
|
return
|
|
}
|
|
|
|
try {
|
|
const { data, error } = await supa
|
|
.from('user_addresses')
|
|
.select('*')
|
|
.eq('user_id', userId)
|
|
.order('is_default', { ascending: false })
|
|
.order('created_at', { ascending: false })
|
|
|
|
if (error !== null) {
|
|
console.error('加载地址失败:', error)
|
|
return
|
|
}
|
|
|
|
addressList.value = data ?? []
|
|
} catch (err) {
|
|
console.error('加载地址异常:', err)
|
|
}
|
|
}
|
|
|
|
// 获取当前用户ID
|
|
const getCurrentUserId = (): string | null => {
|
|
const userStore = uni.getStorageSync('userInfo')
|
|
return userStore?.id || null
|
|
}
|
|
|
|
// 获取完整地址
|
|
const getFullAddress = (address: AddressType): string => {
|
|
return `${address.province}${address.city}${address.district}${address.detail}`
|
|
}
|
|
|
|
// 选择地址
|
|
const selectAddress = (address: AddressType) => {
|
|
if (fromSelect.value && selectCallback.value) {
|
|
// 返回选择的地址
|
|
selectCallback.value(address)
|
|
uni.navigateBack()
|
|
}
|
|
}
|
|
|
|
// 编辑地址
|
|
const editAddress = (address: AddressType) => {
|
|
uni.navigateTo({
|
|
url: `/pages/mall/consumer/address-edit?id=${address.id}`
|
|
})
|
|
}
|
|
|
|
// 删除地址
|
|
const deleteAddress = (address: AddressType) => {
|
|
uni.showModal({
|
|
title: '删除地址',
|
|
content: '确定要删除这个收货地址吗?',
|
|
success: async (res) => {
|
|
if (res.confirm) {
|
|
try {
|
|
// 如果是默认地址,删除前检查是否还有其他地址
|
|
if (address.is_default && addressList.value.length > 1) {
|
|
uni.showModal({
|
|
title: '提示',
|
|
content: '删除默认地址后,系统会自动设置第一个地址为默认地址',
|
|
success: async (confirmRes) => {
|
|
if (confirmRes.confirm) {
|
|
await performDelete(address)
|
|
}
|
|
}
|
|
})
|
|
} else {
|
|
await performDelete(address)
|
|
}
|
|
} catch (err) {
|
|
console.error('删除地址异常:', err)
|
|
}
|
|
}
|
|
}
|
|
})
|
|
}
|
|
|
|
// 执行删除
|
|
const performDelete = async (address: AddressType) => {
|
|
try {
|
|
const { error } = await supa
|
|
.from('user_addresses')
|
|
.delete()
|
|
.eq('id', address.id)
|
|
|
|
if (error !== null) {
|
|
console.error('删除地址失败:', error)
|
|
uni.showToast({
|
|
title: '删除失败',
|
|
icon: 'none'
|
|
})
|
|
return
|
|
}
|
|
|
|
// 从列表中移除
|
|
const index = addressList.value.findIndex(item => item.id === address.id)
|
|
if (index !== -1) {
|
|
addressList.value.splice(index, 1)
|
|
}
|
|
|
|
// 如果是默认地址被删除,设置第一个地址为默认
|
|
if (address.is_default && addressList.value.length > 0) {
|
|
const newDefault = addressList.value[0]
|
|
await setAsDefault(newDefault)
|
|
}
|
|
|
|
uni.showToast({
|
|
title: '删除成功',
|
|
icon: 'success'
|
|
})
|
|
} catch (err) {
|
|
console.error('执行删除异常:', err)
|
|
}
|
|
}
|
|
|
|
// 设为默认地址
|
|
const setDefaultAddress = async (address: AddressType) => {
|
|
try {
|
|
const userId = getCurrentUserId()
|
|
if (!userId) return
|
|
|
|
// 1. 取消当前所有默认地址
|
|
const { error: updateError } = await supa
|
|
.from('user_addresses')
|
|
.update({ is_default: false })
|
|
.eq('user_id', userId)
|
|
.eq('is_default', true)
|
|
|
|
if (updateError !== null) {
|
|
console.error('取消默认地址失败:', updateError)
|
|
return
|
|
}
|
|
|
|
// 2. 设置新的默认地址
|
|
const { error: setError } = await supa
|
|
.from('user_addresses')
|
|
.update({ is_default: true })
|
|
.eq('id', address.id)
|
|
|
|
if (setError !== null) {
|
|
console.error('设置默认地址失败:', setError)
|
|
return
|
|
}
|
|
|
|
// 更新本地数据
|
|
addressList.value.forEach(item => {
|
|
item.is_default = item.id === address.id
|
|
})
|
|
|
|
uni.showToast({
|
|
title: '已设为默认地址',
|
|
icon: 'success'
|
|
})
|
|
} catch (err) {
|
|
console.error('设置默认地址异常:', err)
|
|
}
|
|
}
|
|
|
|
// 设置地址为默认(内部方法)
|
|
const setAsDefault = async (address: AddressType) => {
|
|
try {
|
|
const { error } = await supa
|
|
.from('user_addresses')
|
|
.update({ is_default: true })
|
|
.eq('id', address.id)
|
|
|
|
if (error !== null) {
|
|
console.error('设置默认地址失败:', error)
|
|
return
|
|
}
|
|
|
|
address.is_default = true
|
|
} catch (err) {
|
|
console.error('设置默认地址异常:', err)
|
|
}
|
|
}
|
|
|
|
// 添加新地址
|
|
const addNewAddress = () => {
|
|
uni.navigateTo({
|
|
url: '/pages/mall/consumer/address-edit'
|
|
})
|
|
}
|
|
</script>
|
|
|
|
<style scoped>
|
|
.address-page {
|
|
display: flex;
|
|
flex-direction: column;
|
|
height: 100vh;
|
|
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;
|
|
}
|
|
</style> |