471 lines
10 KiB
Vue

<template>
<view class="page-container">
<!-- 顶部装饰背景 - 漂浮光球 -->
<view class="bg-decoration"></view>
<view class="header-area">
<view class="page-title">地址管理</view>
<view class="page-subtitle">Address Management</view>
</view>
<view class="action-bar">
<button class="add-btn" @click="toAdd">
<text class="plus-icon">+</text>
<text>新增收货地址</text>
</button>
</view>
<scroll-view scroll-y class="content-scroll">
<view v-if="error" class="error-tip">{{ error }}</view>
<!-- 空状态 -->
<view v-if="list.length === 0 && !loading" class="empty-state">
<text class="empty-icon">📍</text>
<text class="empty-text">暂无收货地址</text>
</view>
<!-- 地址列表 -->
<view class="addr-list">
<view
v-for="(item, index) in list"
:key="item.id"
class="addr-card"
:style="{ animationDelay: `${index * 0.05}s` }"
>
<view class="addr-content" @click="toEdit(item)">
<view class="addr-header">
<view class="user-info">
<text class="name">{{ item.name || item.realname }}</text>
<text class="phone">{{ item.phone || item.mobile }}</text>
</view>
<view v-if="item.is_default" class="default-tag">默认</view>
</view>
<view class="addr-detail">
<text class="region">{{ item.province }} {{ item.city }} {{ item.district }}</text>
<text class="detail-text">{{ item.address || item.detail }}</text>
</view>
</view>
<!-- 分割线 -->
<view class="card-divider"></view>
<!-- 操作栏 -->
<view class="addr-actions">
<view class="action-left" @tap.stop="onSetDefault(item)">
<view class="radio-circle" :class="{ checked: item.is_default }"></view>
<text class="action-text">{{ item.is_default ? '默认地址' : '设为默认' }}</text>
</view>
<view class="action-right">
<view class="action-btn" @tap.stop="toEdit(item)">
<text class="btn-icon"></text>
<text>编辑</text>
</view>
<view class="action-btn delete" @tap.stop="onDelete(item)">
<text class="btn-icon">🗑</text>
<text>删除</text>
</view>
</view>
</view>
</view>
</view>
<view class="bottom-spacer"></view>
</scroll-view>
</view>
</template>
<script setup>
import { ref } from 'vue'
import { onLoad, onShow } from '@dcloudio/uni-app'
import { listAddresses, deleteAddress, setDefaultAddress } from '../../api/appUser'
const list = ref([])
const loading = ref(false)
const error = ref('')
async function fetchList() {
const user_id = uni.getStorageSync('user_id')
const token = uni.getStorageSync('token')
const phoneBound = !!uni.getStorageSync('phone_bound')
// 简单的登录检查,实际逻辑可能需要更严谨
if (!user_id || !token) {
// 这里不再强制弹窗,由页面逻辑决定是否跳转
return
}
loading.value = true
error.value = ''
try {
const data = await listAddresses(user_id)
list.value = Array.isArray(data) ? data : (data && (data.list || data.items)) || []
} catch (e) {
// 静默失败或显示轻提示
console.error(e)
// error.value = '获取地址列表失败'
} finally {
loading.value = false
}
}
function toAdd() {
uni.removeStorageSync('edit_address')
uni.navigateTo({ url: '/pages-user/address/edit' })
}
function toEdit(item) {
uni.setStorageSync('edit_address', item)
uni.navigateTo({ url: `/pages-user/address/edit?id=${item.id}` })
}
function onDelete(item) {
const user_id = uni.getStorageSync('user_id')
uni.showModal({
title: '确认删除',
content: '确定删除该地址吗?',
success: async (res) => {
if (res.confirm) {
try {
await deleteAddress(user_id, item.id)
uni.showToast({ title: '已删除', icon: 'none' })
fetchList()
} catch (e) {
uni.showToast({ title: '删除失败', icon: 'none' })
}
}
}
})
}
async function onSetDefault(item) {
console.log('onSetDefault called', item.id, 'is_default:', item.is_default)
if (item.is_default) {
console.log('Already default, skipping')
return
}
try {
const user_id = uni.getStorageSync('user_id')
console.log('Calling setDefaultAddress API', user_id, item.id)
await setDefaultAddress(user_id, item.id)
uni.showToast({ title: '设置成功', icon: 'none' })
fetchList()
} catch (e) {
console.error('setDefaultAddress error', e)
uni.showToast({ title: '设置失败', icon: 'none' })
}
}
onLoad(() => {
fetchList()
})
onShow(() => {
fetchList()
})
</script>
<style lang="scss" scoped>
.page-container {
min-height: 100vh;
background: $bg-page;
position: relative;
overflow: hidden;
display: flex;
flex-direction: column;
}
/* 背景装饰 - 与优惠券页面统一 */
.bg-decoration {
position: absolute;
top: 0; left: 0; width: 100%; height: 100%;
pointer-events: none;
z-index: 0;
overflow: hidden;
&::before {
content: '';
position: absolute;
top: -100rpx; right: -100rpx;
width: 600rpx; height: 600rpx;
background: radial-gradient(circle, rgba($brand-primary, 0.15) 0%, transparent 70%);
filter: blur(60rpx);
border-radius: 50%;
opacity: 0.8;
animation: float 10s ease-in-out infinite;
}
&::after {
content: '';
position: absolute;
top: 200rpx; left: -200rpx;
width: 500rpx; height: 500rpx;
background: radial-gradient(circle, rgba($brand-secondary, 0.1) 0%, transparent 70%);
filter: blur(50rpx);
border-radius: 50%;
opacity: 0.6;
animation: float 15s ease-in-out infinite reverse;
}
}
@keyframes float {
0%, 100% { transform: translate(0, 0); }
50% { transform: translate(30rpx, 50rpx); }
}
.header-area {
padding: $spacing-xl $spacing-lg;
padding-top: calc(env(safe-area-inset-top) + 20rpx);
position: relative;
z-index: 1;
}
.page-title {
font-size: 48rpx;
font-weight: 900;
color: $text-main;
margin-bottom: 8rpx;
letter-spacing: 1rpx;
text-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.05);
}
.page-subtitle {
font-size: 24rpx;
color: $text-tertiary;
text-transform: uppercase;
letter-spacing: 2rpx;
font-weight: 600;
}
.action-bar {
@extend .glass-card;
margin: 0 $spacing-lg $spacing-md;
padding: 20rpx;
z-index: 10;
}
.add-btn {
background: $gradient-brand;
color: #fff;
border: none;
border-radius: 100rpx;
height: 88rpx;
display: flex;
align-items: center;
justify-content: center;
font-size: 30rpx;
font-weight: 600;
box-shadow: $shadow-warm;
transition: all 0.3s;
&:active {
transform: scale(0.98);
opacity: 0.9;
}
}
.plus-icon {
font-size: 40rpx;
margin-right: 12rpx;
margin-top: -4rpx;
font-weight: 300;
}
.content-scroll {
flex: 1;
height: 0;
padding: 0 $spacing-lg;
box-sizing: border-box;
z-index: 1;
}
/* 空状态 */
.empty-state {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 100rpx 0;
}
.empty-icon {
font-size: 80rpx;
margin-bottom: 20rpx;
opacity: 0.8;
}
.empty-text {
color: $text-tertiary;
font-size: 28rpx;
}
/* 地址列表 */
.addr-list {
padding-bottom: 40rpx;
}
.addr-card {
background: #fff;
border-radius: 16rpx;
margin-bottom: 24rpx;
box-shadow: $shadow-sm;
overflow: hidden;
animation: fadeInUp 0.5s ease-out backwards;
/* Removed border from glass style */
}
@keyframes fadeInUp {
from { opacity: 0; transform: translateY(20rpx); }
to { opacity: 1; transform: translateY(0); }
}
.addr-content {
padding: 30rpx;
}
.addr-header {
display: flex;
justify-content: space-between;
align-items: flex-start;
margin-bottom: 16rpx;
}
.user-info {
display: flex;
align-items: center;
gap: 16rpx;
}
.name {
font-size: 32rpx;
font-weight: 700;
color: $text-main;
}
.phone {
font-size: 28rpx;
color: $text-sub;
font-family: monospace; /* 数字等宽 */
}
.default-tag {
font-size: 20rpx;
color: #fff;
background: linear-gradient(135deg, $brand-primary, $brand-secondary);
padding: 4rpx 12rpx;
border-radius: 8rpx 0 8rpx 0;
font-weight: 600;
box-shadow: 0 2rpx 6rpx rgba($brand-primary, 0.2);
}
.addr-detail {
display: flex;
flex-direction: column;
gap: 8rpx;
}
.region {
font-size: 26rpx;
color: $text-sub;
}
.detail-text {
font-size: 28rpx;
color: $text-main;
line-height: 1.5;
font-weight: 500;
}
/* 分割线 */
.card-divider {
height: 1rpx;
background: #f0f0f0;
margin: 0 30rpx;
}
/* 操作栏 */
.addr-actions {
display: flex;
justify-content: space-between;
align-items: center;
padding: 20rpx 30rpx;
background: rgba(249, 249, 249, 0.5);
}
.action-left {
display: flex;
align-items: center;
gap: 12rpx;
}
.radio-circle {
width: 32rpx;
height: 32rpx;
border-radius: 50%;
border: 2rpx solid #ddd;
position: relative;
transition: all 0.2s;
&.checked {
border-color: $brand-primary;
background: $brand-primary;
&::after {
content: '';
position: absolute;
top: 50%; left: 50%;
transform: translate(-50%, -50%);
width: 12rpx; height: 6rpx;
border-left: 3rpx solid #fff;
border-bottom: 3rpx solid #fff;
transform: translate(-50%, -60%) rotate(-45deg);
}
}
}
.action-text {
font-size: 24rpx;
color: $text-sub;
}
.action-right {
display: flex;
gap: 30rpx;
}
.action-btn {
display: flex;
align-items: center;
gap: 6rpx;
font-size: 26rpx;
color: $text-sub;
padding: 10rpx;
.btn-icon {
font-size: 28rpx;
}
&:active {
opacity: 0.6;
}
&.delete {
color: $color-error; // 使用全局变量或具体色值
}
}
.error-tip {
color: #ff4d4f;
background: rgba(255, 77, 79, 0.1);
padding: 20rpx;
border-radius: 12rpx;
text-align: center;
font-size: 26rpx;
margin-bottom: 20rpx;
}
.bottom-spacer {
height: 40rpx;
}
</style>