peixue-dev/peidu/uniapp/user-package/pages/user/address.vue

604 lines
14 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<view class="address-page">
<!-- 地址列表 -->
<view class="address-list" v-if="addressList.length > 0">
<view
class="address-card"
v-for="(item, index) in addressList"
:key="index"
@click="selectAddress(item)"
>
<!-- 默认标签 -->
<view class="default-tag" v-if="item.isDefault">默认</view>
<!-- 地址信息 -->
<view class="address-info">
<view class="address-header">
<text class="contact-name">{{ item.name }}</text>
<text class="contact-phone">{{ item.phone }}</text>
</view>
<view class="address-detail">
<text class="tag" v-if="item.tag">{{ item.tag }}</text>
<text class="detail-text">{{ item.province }}{{ item.city }}{{ item.district }}{{ item.detail }}</text>
</view>
</view>
<!-- 操作按钮 -->
<view class="address-actions">
<view class="action-item" @click.stop="setDefault(item)" v-if="!item.isDefault">
<text class="action-icon">⭐</text>
<text class="action-text">设为默认</text>
</view>
<view class="action-item" @click.stop="editAddress(item)">
<text class="action-icon">✏️</text>
<text class="action-text">编辑</text>
</view>
<view class="action-item" @click.stop="deleteAddress(item)">
<text class="action-icon">🗑️</text>
<text class="action-text">删除</text>
</view>
</view>
</view>
</view>
<!-- 空状态 -->
<view class="empty-state" v-else>
<text class="empty-icon">📍</text>
<text class="empty-text">暂无收货地址</text>
</view>
<!-- 添加地址按钮 -->
<view class="add-btn-wrapper">
<button class="btn-add" @click="addAddress">+ 新增地址</button>
</view>
<!-- 编辑弹窗 -->
<view class="edit-modal" v-if="showEditModal" @click="closeModal">
<view class="modal-content" @click.stop>
<view class="modal-header">
<text class="modal-title">{{ editForm.id ? '编辑地址' : '新增地址' }}</text>
<text class="modal-close" @click="closeModal">✕</text>
</view>
<view class="modal-body">
<!-- 联系人 -->
<view class="form-item">
<text class="form-label">联系人</text>
<input
v-model="editForm.name"
placeholder="请输入联系人姓名"
class="form-input"
/>
</view>
<!-- 手机号 -->
<view class="form-item">
<text class="form-label">手机号</text>
<input
v-model="editForm.phone"
type="number"
maxlength="11"
placeholder="请输入手机号"
class="form-input"
/>
</view>
<!-- 所在地区 -->
<view class="form-item">
<text class="form-label">所在地区</text>
<picker
mode="region"
:value="[editForm.province, editForm.city, editForm.district]"
@change="onRegionChange"
>
<view class="form-picker">
<text v-if="editForm.province">{{ editForm.province }} {{ editForm.city }} {{ editForm.district }}</text>
<text class="placeholder" v-else>请选择省市区</text>
<text class="picker-arrow"></text>
</view>
</picker>
</view>
<!-- 详细地址 -->
<view class="form-item">
<text class="form-label">详细地址</text>
<textarea
v-model="editForm.detail"
placeholder="请输入详细地址,如小区名、楼栋号、门牌号等"
class="form-textarea"
:maxlength="100"
/>
</view>
<!-- 地址标签 -->
<view class="form-item">
<text class="form-label">地址标签</text>
<view class="tag-list">
<view
class="tag-item"
:class="{ active: editForm.tag === tag }"
v-for="tag in tagList"
:key="tag"
@click="editForm.tag = tag"
>
{{ tag }}
</view>
</view>
</view>
<!-- 设为默认 -->
<view class="form-item switch-item">
<text class="form-label">设为默认地址</text>
<switch :checked="editForm.isDefault" @change="onDefaultChange" color="#5fc9ba" />
</view>
</view>
<view class="modal-footer">
<button class="btn-cancel" @click="closeModal">取消</button>
<button class="btn-save" @click="saveAddress">保存</button>
</view>
</view>
</view>
</view>
</template>
<script>
import { addressApi } from '@/api/index.js'
export default {
data() {
return {
selectMode: false, // 是否为选择模式
addressList: [],
showEditModal: false,
loading: false,
editForm: {
id: null,
name: '',
phone: '',
province: '',
city: '',
district: '',
detail: '',
tag: '',
isDefault: 0
},
tagList: ['家', '公司', '学校', '其他']
}
},
onLoad(options) {
this.selectMode = options.select === 'true'
},
onShow() {
this.loadAddressList()
},
methods: {
async loadAddressList() {
try {
this.loading = true
const res = await addressApi.getList()
if (res.code === 200) {
this.addressList = (res.data || []).map(item => ({
...item,
isDefault: item.isDefault === 1
}))
}
} catch (e) {
console.error('加载地址列表失败:', e)
} finally {
this.loading = false
}
},
selectAddress(item) {
if (this.selectMode) {
// 返回选中的地址
const pages = getCurrentPages()
const prevPage = pages[pages.length - 2]
if (prevPage) {
prevPage.$vm.selectedAddress = item
}
uni.navigateBack()
}
},
addAddress() {
this.editForm = {
id: null,
name: '',
phone: '',
province: '',
city: '',
district: '',
detail: '',
tag: '',
isDefault: this.addressList.length === 0 ? 1 : 0
}
this.showEditModal = true
},
editAddress(item) {
this.editForm = {
...item,
isDefault: item.isDefault ? 1 : 0
}
this.showEditModal = true
},
deleteAddress(item) {
uni.showModal({
title: '删除地址',
content: '确定要删除该地址吗?',
success: async (res) => {
if (res.confirm) {
try {
const result = await addressApi.delete(item.id)
if (result.code === 200) {
uni.showToast({ title: '删除成功', icon: 'success' })
this.loadAddressList()
} else {
uni.showToast({ title: result.message || '删除失败', icon: 'none' })
}
} catch (e) {
uni.showToast({ title: '删除失败', icon: 'none' })
}
}
}
})
},
async setDefault(item) {
try {
const res = await addressApi.setDefault(item.id)
if (res.code === 200) {
uni.showToast({ title: '设置成功', icon: 'success' })
this.loadAddressList()
} else {
uni.showToast({ title: res.message || '设置失败', icon: 'none' })
}
} catch (e) {
uni.showToast({ title: '设置失败', icon: 'none' })
}
},
onRegionChange(e) {
const [province, city, district] = e.detail.value
this.editForm.province = province
this.editForm.city = city
this.editForm.district = district
},
onDefaultChange(e) {
this.editForm.isDefault = e.detail.value ? 1 : 0
},
closeModal() {
this.showEditModal = false
},
async saveAddress() {
// 表单验证
if (!this.editForm.name) {
uni.showToast({ title: '请输入联系人姓名', icon: 'none' })
return
}
if (!this.editForm.phone || !/^1[3-9]\d{9}$/.test(this.editForm.phone)) {
uni.showToast({ title: '请输入正确的手机号', icon: 'none' })
return
}
if (!this.editForm.province) {
uni.showToast({ title: '请选择所在地区', icon: 'none' })
return
}
if (!this.editForm.detail) {
uni.showToast({ title: '请输入详细地址', icon: 'none' })
return
}
try {
uni.showLoading({ title: '保存中...' })
let res
if (this.editForm.id) {
// 编辑
res = await addressApi.update(this.editForm)
} else {
// 新增
res = await addressApi.add(this.editForm)
}
uni.hideLoading()
if (res.code === 200) {
this.showEditModal = false
uni.showToast({ title: '保存成功', icon: 'success' })
this.loadAddressList()
} else {
uni.showToast({ title: res.message || '保存失败', icon: 'none' })
}
} catch (e) {
uni.hideLoading()
uni.showToast({ title: '保存失败', icon: 'none' })
}
}
}
}
</script>
<style lang="scss" scoped>
.address-page {
min-height: 100vh;
background: #f5f5f5;
padding-bottom: 120rpx;
}
.address-list {
padding: 20rpx;
}
.address-card {
background: #fff;
border-radius: 16rpx;
padding: 24rpx;
margin-bottom: 20rpx;
position: relative;
.default-tag {
position: absolute;
top: 0;
right: 0;
background: #5fc9ba;
color: #fff;
font-size: 20rpx;
padding: 4rpx 16rpx;
border-radius: 0 16rpx 0 16rpx;
}
.address-info {
margin-bottom: 20rpx;
.address-header {
display: flex;
align-items: center;
margin-bottom: 12rpx;
.contact-name {
font-size: 32rpx;
font-weight: 500;
color: #333;
margin-right: 20rpx;
}
.contact-phone {
font-size: 28rpx;
color: #666;
}
}
.address-detail {
display: flex;
align-items: flex-start;
.tag {
background: #e8f5e9;
color: #5fc9ba;
font-size: 22rpx;
padding: 4rpx 12rpx;
border-radius: 4rpx;
margin-right: 12rpx;
flex-shrink: 0;
}
.detail-text {
font-size: 26rpx;
color: #666;
line-height: 1.5;
}
}
}
.address-actions {
display: flex;
justify-content: flex-end;
gap: 30rpx;
padding-top: 16rpx;
border-top: 1rpx solid #f0f0f0;
.action-item {
display: flex;
align-items: center;
.action-icon {
font-size: 28rpx;
margin-right: 6rpx;
}
.action-text {
font-size: 26rpx;
color: #666;
}
}
}
}
.empty-state {
display: flex;
flex-direction: column;
align-items: center;
padding: 150rpx 0;
.empty-icon {
font-size: 100rpx;
margin-bottom: 20rpx;
}
.empty-text {
font-size: 28rpx;
color: #999;
}
}
.add-btn-wrapper {
position: fixed;
bottom: 0;
left: 0;
right: 0;
padding: 20rpx;
background: #fff;
box-shadow: 0 -4rpx 20rpx rgba(0, 0, 0, 0.05);
.btn-add {
background: #5fc9ba;
color: #fff;
height: 88rpx;
line-height: 88rpx;
font-size: 32rpx;
border-radius: 44rpx;
}
}
.edit-modal {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
display: flex;
align-items: flex-end;
z-index: 1000;
.modal-content {
background: #fff;
width: 100%;
border-radius: 24rpx 24rpx 0 0;
max-height: 90vh;
overflow-y: auto;
}
.modal-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 30rpx;
border-bottom: 1rpx solid #f0f0f0;
.modal-title {
font-size: 32rpx;
font-weight: 500;
color: #333;
}
.modal-close {
font-size: 36rpx;
color: #999;
padding: 10rpx;
}
}
.modal-body {
padding: 20rpx 30rpx;
.form-item {
margin-bottom: 24rpx;
.form-label {
display: block;
font-size: 28rpx;
color: #333;
margin-bottom: 12rpx;
}
.form-input {
width: 100%;
height: 80rpx;
background: #f5f5f5;
border-radius: 12rpx;
padding: 0 20rpx;
font-size: 28rpx;
}
.form-textarea {
width: 100%;
height: 160rpx;
background: #f5f5f5;
border-radius: 12rpx;
padding: 20rpx;
font-size: 28rpx;
}
.form-picker {
display: flex;
justify-content: space-between;
align-items: center;
height: 80rpx;
background: #f5f5f5;
border-radius: 12rpx;
padding: 0 20rpx;
font-size: 28rpx;
color: #333;
.placeholder {
color: #999;
}
.picker-arrow {
color: #999;
font-size: 32rpx;
}
}
.tag-list {
display: flex;
gap: 20rpx;
.tag-item {
padding: 12rpx 32rpx;
background: #f5f5f5;
border-radius: 30rpx;
font-size: 26rpx;
color: #666;
&.active {
background: #e8f5e9;
color: #5fc9ba;
}
}
}
&.switch-item {
display: flex;
justify-content: space-between;
align-items: center;
}
}
}
.modal-footer {
display: flex;
gap: 20rpx;
padding: 20rpx 30rpx 40rpx;
button {
flex: 1;
height: 88rpx;
line-height: 88rpx;
font-size: 32rpx;
border-radius: 44rpx;
margin: 0;
}
.btn-cancel {
background: #f5f5f5;
color: #666;
}
.btn-save {
background: #5fc9ba;
color: #fff;
}
}
}
</style>