341 lines
8.4 KiB
Vue
341 lines
8.4 KiB
Vue
<template>
|
|
<view class="address-page">
|
|
<!-- 自定义导航栏 -->
|
|
<view class="custom-navbar" :style="{ paddingTop: statusBarHeight + 'px' }">
|
|
<view class="custom-navbar__content" :style="{ height: navBarHeight + 'px' }">
|
|
<image class="custom-navbar__back" src="/static/ic_back.png" mode="aspectFit" @click="goBack" />
|
|
<text class="custom-navbar__title">收货地址</text>
|
|
<view class="custom-navbar__placeholder" />
|
|
</view>
|
|
</view>
|
|
<view :style="{ height: (statusBarHeight + navBarHeight) + 'px' }" />
|
|
<!-- 地址列表 -->
|
|
<view class="address-list" v-if="!showForm">
|
|
<view class="address-card" v-for="addr in userStore.addresses" :key="addr.id">
|
|
<view class="address-card__info" @click="editAddress(addr)">
|
|
<view class="address-card__top">
|
|
<text class="address-card__name">{{ addr.name }}</text>
|
|
<text class="address-card__phone">{{ addr.phone }}</text>
|
|
<text class="address-card__default" v-if="addr.isDefault">默认</text>
|
|
</view>
|
|
<text class="address-card__detail">{{ addr.province }}{{ addr.city }}{{ addr.district }}{{ addr.detail }}</text>
|
|
</view>
|
|
<view class="address-card__actions">
|
|
<text class="address-card__edit" @click="editAddress(addr)">编辑</text>
|
|
<text class="address-card__delete" @click="handleDelete(addr.id)">删除</text>
|
|
</view>
|
|
</view>
|
|
|
|
<view v-if="userStore.addresses.length === 0" class="empty-tip">
|
|
<text>暂无收货地址</text>
|
|
</view>
|
|
|
|
<view class="add-btn" @click="addNewAddress">
|
|
<text>+ 新增收货地址</text>
|
|
</view>
|
|
</view>
|
|
|
|
<!-- 新增/编辑表单 -->
|
|
<view class="address-form" v-if="showForm">
|
|
<view class="form-item">
|
|
<text class="form-label">收货人</text>
|
|
<input class="form-input" v-model="form.name" placeholder="请输入收货人姓名" />
|
|
</view>
|
|
<view class="form-item">
|
|
<text class="form-label">手机号</text>
|
|
<input class="form-input" v-model="form.phone" type="number" placeholder="请输入手机号" />
|
|
</view>
|
|
<view class="form-item">
|
|
<text class="form-label">省份</text>
|
|
<input class="form-input" v-model="form.province" placeholder="请输入省份" />
|
|
</view>
|
|
<view class="form-item">
|
|
<text class="form-label">城市</text>
|
|
<input class="form-input" v-model="form.city" placeholder="请输入城市" />
|
|
</view>
|
|
<view class="form-item">
|
|
<text class="form-label">区县</text>
|
|
<input class="form-input" v-model="form.district" placeholder="请输入区县" />
|
|
</view>
|
|
<view class="form-item">
|
|
<text class="form-label">详细地址</text>
|
|
<input class="form-input" v-model="form.detail" placeholder="请输入详细地址" />
|
|
</view>
|
|
<view class="form-item form-item--switch">
|
|
<text class="form-label">设为默认</text>
|
|
<switch :checked="form.isDefault" @change="form.isDefault = $event.detail.value" color="#e4393c" />
|
|
</view>
|
|
|
|
<view class="form-actions">
|
|
<view class="form-btn form-btn--cancel" @click="showForm = false">取消</view>
|
|
<view class="form-btn form-btn--save" @click="handleSave">保存</view>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { ref, reactive, onMounted } from 'vue'
|
|
import type { Address } from '../../types'
|
|
import { useUserStore } from '../../store/user'
|
|
import { getAddressList, addAddress, updateAddress, deleteAddress } from '../../api/user'
|
|
|
|
const statusBarHeight = ref(20)
|
|
const navBarHeight = ref(44)
|
|
try {
|
|
const sysInfo = uni.getSystemInfoSync()
|
|
statusBarHeight.value = sysInfo.statusBarHeight || 20
|
|
// #ifdef MP-WEIXIN
|
|
const menuBtn = uni.getMenuButtonBoundingClientRect()
|
|
navBarHeight.value = (menuBtn.top - (sysInfo.statusBarHeight || 20)) * 2 + menuBtn.height
|
|
// #endif
|
|
} catch { /* fallback */ }
|
|
|
|
function goBack() {
|
|
uni.navigateBack({ delta: 1 })
|
|
}
|
|
|
|
const userStore = useUserStore()
|
|
const showForm = ref(false)
|
|
const editingId = ref<number | null>(null)
|
|
|
|
const form = reactive({
|
|
name: '',
|
|
phone: '',
|
|
province: '',
|
|
city: '',
|
|
district: '',
|
|
detail: '',
|
|
isDefault: false,
|
|
})
|
|
|
|
function resetForm() {
|
|
form.name = ''
|
|
form.phone = ''
|
|
form.province = ''
|
|
form.city = ''
|
|
form.district = ''
|
|
form.detail = ''
|
|
form.isDefault = false
|
|
editingId.value = null
|
|
}
|
|
|
|
function addNewAddress() {
|
|
resetForm()
|
|
showForm.value = true
|
|
}
|
|
|
|
function editAddress(addr: Address) {
|
|
editingId.value = addr.id
|
|
form.name = addr.name
|
|
form.phone = addr.phone
|
|
form.province = addr.province
|
|
form.city = addr.city
|
|
form.district = addr.district
|
|
form.detail = addr.detail
|
|
form.isDefault = addr.isDefault
|
|
showForm.value = true
|
|
}
|
|
|
|
async function handleSave() {
|
|
if (!form.name || !form.phone || !form.detail) {
|
|
uni.showToast({ title: '请填写完整信息', icon: 'none' })
|
|
return
|
|
}
|
|
try {
|
|
const data = {
|
|
name: form.name,
|
|
phone: form.phone,
|
|
province: form.province,
|
|
city: form.city,
|
|
district: form.district,
|
|
detail: form.detail,
|
|
isDefault: form.isDefault,
|
|
}
|
|
if (editingId.value) {
|
|
await updateAddress(editingId.value, data)
|
|
} else {
|
|
await addAddress(data)
|
|
}
|
|
showForm.value = false
|
|
await loadAddresses()
|
|
uni.showToast({ title: '保存成功', icon: 'success' })
|
|
} catch {
|
|
uni.showToast({ title: '保存失败', icon: 'none' })
|
|
}
|
|
}
|
|
|
|
async function handleDelete(id: number) {
|
|
uni.showModal({
|
|
title: '提示',
|
|
content: '确定要删除该地址吗?',
|
|
success: async (res) => {
|
|
if (res.confirm) {
|
|
try {
|
|
await deleteAddress(id)
|
|
await loadAddresses()
|
|
uni.showToast({ title: '已删除', icon: 'success' })
|
|
} catch {
|
|
uni.showToast({ title: '删除失败', icon: 'none' })
|
|
}
|
|
}
|
|
},
|
|
})
|
|
}
|
|
|
|
async function loadAddresses() {
|
|
try {
|
|
const list = await getAddressList()
|
|
userStore.setAddresses(list)
|
|
} catch {
|
|
// 错误已在 request 中统一处理
|
|
}
|
|
}
|
|
|
|
onMounted(() => {
|
|
loadAddresses()
|
|
})
|
|
</script>
|
|
|
|
<style scoped>
|
|
.custom-navbar {
|
|
background: linear-gradient(to right, #FFCFDE, #FFA6C4);
|
|
position: fixed;
|
|
top: 0;
|
|
left: 0;
|
|
right: 0;
|
|
z-index: 100;
|
|
}
|
|
.custom-navbar__content {
|
|
display: flex;
|
|
align-items: center;
|
|
padding: 0 24rpx;
|
|
}
|
|
.custom-navbar__back {
|
|
width: 44rpx;
|
|
height: 44rpx;
|
|
}
|
|
.custom-navbar__title {
|
|
flex: 1;
|
|
text-align: center;
|
|
font-size: 34rpx;
|
|
font-weight: bold;
|
|
color: #333;
|
|
}
|
|
.custom-navbar__placeholder {
|
|
width: 44rpx;
|
|
}
|
|
.address-page {
|
|
min-height: 100vh;
|
|
background: #f5f5f5;
|
|
}
|
|
.address-card {
|
|
background: #fff;
|
|
margin-bottom: 16rpx;
|
|
padding: 24rpx 32rpx;
|
|
}
|
|
.address-card__top {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 16rpx;
|
|
margin-bottom: 8rpx;
|
|
}
|
|
.address-card__name {
|
|
font-size: 30rpx;
|
|
color: #333;
|
|
font-weight: bold;
|
|
}
|
|
.address-card__phone {
|
|
font-size: 26rpx;
|
|
color: #666;
|
|
}
|
|
.address-card__default {
|
|
font-size: 22rpx;
|
|
color: #e4393c;
|
|
border: 1rpx solid #e4393c;
|
|
border-radius: 4rpx;
|
|
padding: 2rpx 8rpx;
|
|
}
|
|
.address-card__detail {
|
|
font-size: 26rpx;
|
|
color: #666;
|
|
line-height: 40rpx;
|
|
}
|
|
.address-card__actions {
|
|
display: flex;
|
|
justify-content: flex-end;
|
|
gap: 32rpx;
|
|
margin-top: 16rpx;
|
|
padding-top: 16rpx;
|
|
border-top: 1rpx solid #f0f0f0;
|
|
}
|
|
.address-card__edit {
|
|
font-size: 26rpx;
|
|
color: #1890ff;
|
|
}
|
|
.address-card__delete {
|
|
font-size: 26rpx;
|
|
color: #e4393c;
|
|
}
|
|
.empty-tip {
|
|
text-align: center;
|
|
padding: 80rpx 0;
|
|
color: #999;
|
|
font-size: 28rpx;
|
|
}
|
|
.add-btn {
|
|
margin: 32rpx;
|
|
background: #e4393c;
|
|
color: #fff;
|
|
text-align: center;
|
|
padding: 24rpx 0;
|
|
border-radius: 44rpx;
|
|
font-size: 30rpx;
|
|
}
|
|
.address-form {
|
|
background: #fff;
|
|
padding: 24rpx 32rpx;
|
|
}
|
|
.form-item {
|
|
display: flex;
|
|
align-items: center;
|
|
padding: 20rpx 0;
|
|
border-bottom: 1rpx solid #f0f0f0;
|
|
}
|
|
.form-item--switch {
|
|
justify-content: space-between;
|
|
}
|
|
.form-label {
|
|
font-size: 28rpx;
|
|
color: #333;
|
|
width: 160rpx;
|
|
flex-shrink: 0;
|
|
}
|
|
.form-input {
|
|
flex: 1;
|
|
font-size: 28rpx;
|
|
color: #333;
|
|
}
|
|
.form-actions {
|
|
display: flex;
|
|
gap: 24rpx;
|
|
margin-top: 40rpx;
|
|
padding-bottom: 20rpx;
|
|
}
|
|
.form-btn {
|
|
flex: 1;
|
|
text-align: center;
|
|
padding: 20rpx 0;
|
|
border-radius: 44rpx;
|
|
font-size: 28rpx;
|
|
}
|
|
.form-btn--cancel {
|
|
border: 1rpx solid #ddd;
|
|
color: #666;
|
|
}
|
|
.form-btn--save {
|
|
background: #e4393c;
|
|
color: #fff;
|
|
}
|
|
</style>
|