JewelryMall/miniprogram/pages/address/index.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>