Files
jurong_circle_frontdesk/src/views/Address.vue
2025-09-15 15:25:35 +08:00

728 lines
18 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>
<div class="address-page">
<!-- 导航栏 -->
<nav class="navbar">
<div class="nav-left">
<el-button
type="text"
@click="$router.go(-1)"
class="back-btn"
>
<el-icon><ArrowLeft /></el-icon>
返回
</el-button>
</div>
<div class="nav-center">
<h1 class="nav-title">地址</h1>
</div>
<div class="nav-right">
<el-button
type="primary"
@click="showAddDialog = true"
class="add-btn"
>
+新增地址
</el-button>
</div>
</nav>
<div v-loading="loading" class="address-content">
<!-- 地址列表为空 -->
<div v-if="addresses.length === 0 && !loading" class="empty-address">
<el-icon class="empty-icon"><Location /></el-icon>
<p>暂无收货地址</p>
<p class="empty-tip">点击右上角添加收货地址</p>
</div>
<!-- 地址列表 -->
<div v-else class="address-list">
<div
v-for="(address,index) in addresses"
:key="address.id"
class="address-item"
:class="{ 'default-address': address.isDefault }"
>
<div class="address-info">
<div class="address-header">
<el-icon><Location /></el-icon>
<div class="address-location">
<div class="region-info">{{ address.province_name }} {{ address.city_name }} {{ address.district_name }}</div>
<div class="detail-info">{{ address.detailAddress }}</div>
</div>
<el-tag v-if="address.isDefault" type="warning" size="small" class="default-tag">
默认
</el-tag>
</div>
<div class="address-detail">
<span class="recipient-name">{{ address.recipientName }}</span>
<span class="recipient-phone">{{ address.recipientPhone }}</span>
</div>
</div>
<div class="address-actions">
<el-button
type="text"
@click="editAddress(address)"
class="edit-btn"
>
编辑
</el-button>
<el-button
type="text"
@click="deleteAddress(address.id)"
class="delete-btn"
>
删除
</el-button>
<el-button
v-if="!address.isDefault"
type="text"
@click="setDefaultAddress(index)"
class="default-btn"
>
设为默认
</el-button>
</div>
</div>
</div>
</div>
<!-- 新增/编辑地址对话框 -->
<el-dialog
v-model="showAddDialog"
:title="editingAddress ? '编辑地址' : '新增地址'"
width="90%"
class="address-dialog"
>
<el-form
ref="addressFormRef"
:model="addressForm"
:rules="addressRules"
label-width="80px"
class="address-form"
>
<el-form-item label="收货人" prop="recipientName">
<el-input
v-model="addressForm.recipientName"
placeholder="请输入收货人姓名"
maxlength="20"
/>
</el-form-item>
<el-form-item label="手机号" prop="recipientPhone">
<el-input
v-model="addressForm.recipientPhone"
placeholder="请输入手机号"
maxlength="11"
/>
</el-form-item>
<el-form-item label="省市区" prop="region">
<el-cascader
v-model="addressForm.region"
:options="regionOptions"
placeholder="请选择省市区"
style="width: 100%"
:props="{ expandTrigger: 'hover', value:'code' }"
:show-all-levels="true"
:collapse-tags="true"
:max-collapse-tags="1"
:teleported="false"
popper-class="mobile-cascader-popper"
@change="handleRegionChange"
/>
</el-form-item>
<el-form-item label="详细地址" prop="detailAddress">
<el-input
v-model="addressForm.detailAddress"
type="textarea"
:rows="3"
placeholder="请输入详细地址"
maxlength="100"
show-word-limit
/>
</el-form-item>
<el-form-item>
<el-checkbox v-model="addressForm.isDefault">
设为默认地址
</el-checkbox>
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button @click="cancelEdit">取消</el-button>
<el-button type="primary" @click="saveAddress" :loading="saving">
{{ editingAddress ? '保存' : '添加' }}
</el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<script setup>
import { ref, reactive, onMounted } from 'vue'
import { useRouter } from 'vue-router'
import { ElMessage, ElMessageBox } from 'element-plus'
import {
ArrowLeft,
Location
} from '@element-plus/icons-vue'
import api from '@/utils/api'
const router = useRouter()
// 响应式数据
const loading = ref(false)
const saving = ref(false)
const addresses = ref([])
const showAddDialog = ref(false)
const editingAddress = ref(null)
const addressFormRef = ref(null)
const addressList = ref([])
// 地址表单数据
const addressForm = reactive({
recipientName: '',
recipientPhone: '',
region: [],
province: '',
city: '',
district: '',
detailAddress: '',
isDefault: false
})
// 省市区选项数据从API获取
const regionOptions = ref([])
// 表单验证规则
const addressRules = {
recipientName: [
{ required: true, message: '请输入收货人姓名', trigger: 'blur' },
{ min: 2, max: 20, message: '姓名长度在 2 到 20 个字符', trigger: 'blur' }
],
recipientPhone: [
{ required: true, message: '请输入手机号', trigger: 'blur' },
{ pattern: /^1[3-9]\d{9}$/, message: '请输入正确的手机号', trigger: 'blur' }
],
region: [
{ required: true, message: '请选择省市区', trigger: 'change' }
],
detailAddress: [
{ required: true, message: '请输入详细地址', trigger: 'blur' },
{ min: 5, max: 100, message: '详细地址长度在 5 到 100 个字符', trigger: 'blur' }
]
}
// 方法
const loadRegionOptions = async () => {
try {
// 获取所有省份
const provincesResponse = await api.get('/regions/provinces')
console.log('获取省份数据:', provincesResponse)
if (!provincesResponse.data.success) {
throw new Error(provincesResponse.data.message || '获取省份数据失败')
}
regionOptions.value= provincesResponse.data.data || []
console.log('获取省份数据:', regionOptions.value);
} catch (error) {
console.error('获取省市区数据失败:', error)
ElMessage.error(error.message || '获取省市区数据失败')
}
}
const loadAddresses = async () => {
try {
loading.value = true
const response = await api.get('/addresses')
console.log('获取地址列表响应:', response)
if (response.data.success) {
// 根据接口文档转换数据格式
addressList.value = response.data.data || []
addresses.value = addressList.value.map(addr => ({
id: addr.id,
recipientName: addr.receiver_name,
recipientPhone: addr.receiver_phone,
province: addr.province_name,
city: addr.city_name,
district: addr.district_name,
detailAddress: addr.detailed_address,
isDefault: addr.is_default,
labelName: addr.label_name,
labelColor: addr.label_color,
city_code:addr.city_code,
province_code:addr.province_code,
district_code:addr.district_code,
...addr
}))
} else {
// 保留原有的测试数据作为回退
addresses.value = [{
id: 1,
recipientName: '张三',
recipientPhone: '11111111111',
province: '浙江省',
city: '宁波市',
district: '鄞州区',
detailAddress: '宁波外经合作大厦',
isDefault: true
},{
id: 2,
recipientName: '李四',
recipientPhone: '22222222222',
province: '浙江省',
city: '宁波市',
district: '鄞州区',
detailAddress: '四明东路',
isDefault: false
}]
throw new Error(response.data.message || '获取地址列表失败')
}
} catch (error) {
ElMessage.error(error.message || '获取地址列表失败')
} finally {
loading.value = false
}
}
const handleRegionChange = (value) => {
console.log(value);
if (value && value.length === 3) {
addressForm.province = value[0]
addressForm.city = value[1]
addressForm.district = value[2]
}
}
const editAddress = (address) => {
editingAddress.value = address
addressForm.recipientName = address.recipientName
addressForm.recipientPhone = address.recipientPhone
addressForm.region = [address.province, address.city, address.district]
addressForm.province = address.province
addressForm.city = address.city
addressForm.district = address.district
addressForm.detailAddress = address.detailAddress
addressForm.isDefault = address.isDefault
showAddDialog.value = true
}
const saveAddress = async () => {
if (!addressFormRef.value) return
try {
await addressFormRef.value.validate()
saving.value = true
// 根据接口文档构建请求数据
const addressData = {
recipient_name: addressForm.recipientName,
phone: addressForm.recipientPhone,
province_code: addressForm.province,
city_code: addressForm.city,
district_code: addressForm.district,
detailed_address: addressForm.detailAddress,
is_default: addressForm.isDefault
}
let response
if (editingAddress.value) {
// 编辑地址
response = await api.put(`/addresses/${editingAddress.value.id}`, addressData)
} else {
// 新增地址
console.log('表单原始数据:', addressForm)
response = await api.post('/addresses', addressData)
}
if (response.data.success) {
ElMessage.success(editingAddress.value ? '地址更新成功' : '地址添加成功')
showAddDialog.value = false
resetForm()
await loadAddresses()
} else {
throw new Error(response.data.message || '保存地址失败')
}
} catch (error) {
if (error.message) {
ElMessage.error(error.message)
}
} finally {
saving.value = false
}
}
const deleteAddress = async (addressId) => {
try {
await ElMessageBox.confirm(
'确定要删除这个地址吗?',
'确认删除',
{
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}
)
const response = await api.delete(`/addresses/${addressId}`)
console.log("删除id",addressId)
if (response.data.success) {
ElMessage.success('地址删除成功')
await loadAddresses()
} else {
throw new Error(response.data.message || '删除地址失败')
}
} catch (error) {
if (error !== 'cancel') {
ElMessage.error(error.message || '删除地址失败')
}
}
}
const setDefaultAddress = async (addressId) => {
console.log(addressId,addresses.value[addressId]);
let addressDataTest = addresses.value[addressId]
addressDataTest.isDefault = true
const addressData = {
recipient_name: addressDataTest.receiver_name,
phone: addressDataTest.receiver_phone,
province_code: addressDataTest.province,
city_code: addressDataTest.city,
district_code: addressDataTest.district,
detailed_address: addressDataTest.detailAddress,
is_default: addressDataTest.isDefault
}
console.log('addressDataTest',addressDataTest);
console.log('addressData',addressData);
try {
const response = await api.put(`/addresses/${addressDataTest.id}`, addressData)
if (response.data.success) {
ElMessage.success('默认地址设置成功')
await loadAddresses()
} else {
throw new Error(response.data.message || '设置默认地址失败')
}
} catch (error) {console.log(error)}
return
// 从addresses列表中找到对应的地址信息
const getDefaultAddress = () => {
for(add in addresses.value)
{
if(addresses.value[add].isDefault)
{
return addresses.value[add]
}
}
return null
}
console.log('getDefaultAddress', getDefaultAddress)
// const addressData = {
// recipient_name: addressList.value[0].receiver_name,
// phone: addressList.value[0].receiver_phone,
// province_code: addressList.value[0].province,
// city_code: addressList.value[0].city,
// district_code: addressList.value[0].district,
// detailed_address: targetAddress.detailAddress,
// is_default: true
// }
console.log('addresses', addresses.value)
console.log('targetaddress', targetAddress)
console.log('默认地址替换数据', addressData)
try {
const response = await api.put(`/addresses/${addressId}`, addressData)
if (response.data.success) {
ElMessage.success('默认地址设置成功')
await loadAddresses()
} else {
throw new Error(response.data.message || '设置默认地址失败')
}
} catch (error) {console.log(error)}
}
const cancelEdit = () => {
showAddDialog.value = false
resetForm()
}
const resetForm = () => {
editingAddress.value = null
addressForm.recipientName = ''
addressForm.recipientPhone = ''
addressForm.region = []
addressForm.province = ''
addressForm.city = ''
addressForm.district = ''
addressForm.detailAddress = ''
addressForm.isDefault = false
if (addressFormRef.value) {
addressFormRef.value.resetFields()
}
}
// 生命周期
onMounted(() => {
loadRegionOptions()
loadAddresses()
})
</script>
<style scoped>
.address-page {
min-height: 100vh;
background: linear-gradient(to bottom, #72c9ffae, #f3f3f3);
}
.navbar {
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 16px;
height: 60px;
position: sticky;
top: 0;
z-index: 100;
}
.nav-left, .nav-right {
flex: 1;
}
.nav-right {
display: flex;
justify-content: flex-end;
}
.nav-center {
flex: 2;
text-align: center;
}
.nav-title {
margin: 0;
font-size: 18px;
font-weight: 500;
color: white;
}
.back-btn {
color: white;
font-size: 16px;
}
.add-btn {
font-size: 14px;
background-color: none;
border-color: none;
}
.address-content {
padding: 16px;
}
.empty-address {
text-align: center;
padding: 80px 20px;
color: #999;
}
.empty-icon {
font-size: 64px;
color: #ddd;
margin-bottom: 16px;
}
.empty-tip {
font-size: 14px;
margin-top: 8px;
}
.address-list {
display: flex;
flex-direction: column;
gap: 12px;
}
.address-item {
background: white;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
transition: all 0.3s ease;
}
.address-item:hover {
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15);
}
.default-address {
border: 2px solid #ffc640;
}
.address-info {
margin-bottom: 12px;
flex: 1;
}
.address-header {
display: flex;
align-items: flex-start;
gap: 12px;
margin-bottom: 8px;
}
.address-location {
display: flex;
flex-direction: column;
align-items: flex-start;
flex: 1;
}
.region-info {
font-size: 16px;
color: #333;
margin-bottom: 4px;
}
.detail-info {
font-size: 14px;
color: #666;
font-weight: normal;
}
.recipient-name {
font-size: 16px;
font-weight: 500;
color: #333;
}
.recipient-phone {
font-size: 16px;
color: #666;
}
.default-tag {
font-size: 12px;
margin-left: auto;
align-self: flex-start;
}
.address-detail {
display: flex;
align-items: flex-start;
gap: 8px;
color: #666;
font-size: 14px;
line-height: 1.5;
margin-left: 0;
justify-content: flex-end;
}
.address-actions {
display: flex;
gap: 16px;
justify-content: flex-end;
margin-top: 12px;
padding-top: 12px;
border-top: 1px solid #f0f0f0;
}
.edit-btn {
color: #409eff;
}
.delete-btn {
color: #f56c6c;
}
.default-btn {
color: #67c23a;
}
.address-dialog {
border-radius: 8px;
}
.address-form {
padding: 0 8px;
}
.dialog-footer {
display: flex;
justify-content: flex-end;
gap: 12px;
}
@media (max-width: 768px) {
.address-item {
padding: 12px;
}
.address-actions {
gap: 12px;
}
.address-dialog {
width: 95% !important;
}
}
/* 移动端级联选择器样式优化 */
:deep(.mobile-cascader-popper) {
max-width: calc(100vw - 32px) !important;
left: 16px !important;
right: 16px !important;
transform: none !important;
}
:deep(.mobile-cascader-popper .el-cascader-panel) {
max-width: 100% !important;
overflow-x: auto;
display: flex !important;
}
:deep(.mobile-cascader-popper .el-cascader-menu) {
min-width: 120px !important;
max-width: 150px !important;
flex-shrink: 0;
}
@media (max-width: 480px) {
:deep(.mobile-cascader-popper) {
max-width: calc(100vw - 20px) !important;
left: 10px !important;
right: 10px !important;
}
:deep(.mobile-cascader-popper .el-cascader-menu) {
min-width: 100px !important;
max-width: 120px !important;
font-size: 14px;
}
:deep(.mobile-cascader-popper .el-cascader-menu__item) {
padding: 8px 10px;
line-height: 1.2;
font-size: 14px;
}
.address-form :deep(.el-cascader) {
font-size: 14px;
}
.address-form :deep(.el-cascader .el-input__inner) {
font-size: 14px;
}
}
</style>