Files
pc_frontend/src/Views/User/UpdataAddress.vue
qingfeng1121 73cf25e586 feat: 添加分页组件和地址选择组件,优化样式和类型定义
refactor: 重构路由配置,移除订单页面路由

style: 统一使用CSS变量替换硬编码颜色值

docs: 添加前端数据需求分析文档

fix: 修复登录页面记住我功能,更新用户信息页面链接

perf: 优化搜索组件动画效果和响应式设计

chore: 更新TypeScript类型定义,添加订单和地址相关类型
2026-01-19 11:35:50 +08:00

447 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>
<a-modal :open="visible" title="更新地址" :footer="null" width="500px">
<div class="modal-content">
<!-- 地址识别输入框 -->
<div class="form-group">
<label for="address-input">地址识别</label>
<a-input v-model:value="addressInput" placeholder="请输入完整地址,系统会自动识别省份、城市、区县"
@change="handleAddressRecognition" />
<div v-if="recognitionError" class="error-message">{{ recognitionError }}</div>
</div>
<!-- 地址信息表单 -->
<div class="form-group">
<label for="province">省份 <span class="required">*</span></label>
<a-select v-model:value="form.province" :options="provinceOptions" placeholder="请选择省份"
@change="handleProvinceChange" />
<div v-if="errors.province" class="error-message">{{ errors.province }}</div>
</div>
<div class="form-group">
<label for="city">城市 <span class="required">*</span></label>
<a-select v-model:value="form.city" :options="cityOptions" placeholder="请选择城市"
@change="handleCityChange" />
<div v-if="errors.city" class="error-message">{{ errors.city }}</div>
</div>
<div class="form-group">
<label for="district">区县 <span class="required">*</span></label>
<a-select v-model:value="form.district" :options="districtOptions" placeholder="请选择区县" />
<div v-if="errors.district" class="error-message">{{ errors.district }}</div>
</div>
<div class="form-group">
<label for="detailAddress">详细地址 <span class="required">*</span></label>
<a-input v-model:value="form.detail" placeholder="请输入详细地址(街道、楼栋、门牌号等)" :rows="3" type="textarea" />
<div v-if="errors.detail" class="error-message">{{ errors.detail }}</div>
</div>
</div>
<div class="modal-footer">
<a-button @click="handleCancel">取消</a-button>
<a-button type="primary" @click="handleOk">确认更新</a-button>
</div>
</a-modal>
</template>
<script setup lang="ts">
import { ref, watch, computed } from 'vue'
import type { Address } from '@/Util/Type'
const props = defineProps({
// 设置弹窗显示状态控制
visible: {
type: Boolean,
default: false,
},
// 传递的地址对象
address: {
type: Object as () => Address,
default: () => ({
name: '',
phone: '',
province: '',
city: '',
district: '',
detail: '',
isDefault: false
}) as Address,
}
})
// 定义事件
const emit = defineEmits(['update:visible', 'update:updateaddress'])
// 弹窗显示状态控制
const visible = ref(props.visible)
// 表单数据
const form = ref<Address>({
id:props.address.id ,
name: props.address.name,
phone: props.address.phone,
province: props.address.province,
city: props.address.city,
district: props.address.district,
detail: props.address.detail,
isDefault: props.address.isDefault
})
// 地址识别输入框
const addressInput = ref('')
const recognitionError = ref('')
// 表单验证错误
const errors = ref({
name: '',
phone: '',
province: '',
city: '',
district: '',
detail: '',
isDefault: ''
})
// 定义类型
interface Data {
[key: string]: { label: string; value: string }[] | undefined;
}
// 城市选项(根据省份动态生成)
const cityOptions = ref<any[]>([])
// 区县选项(根据城市动态生成)
const districtOptions = ref<any[]>([])
// 省份选项
const provinceOptions = ref([
{ label: '北京市', value: '北京市' },
{ label: '上海市', value: '上海市' },
{ label: '广东省', value: '广东省' },
{ label: '浙江省', value: '浙江省' },
{ label: '江苏省', value: '江苏省' },
{ label: '四川省', value: '四川省' },
{ label: '湖北省', value: '湖北省' },
{ label: '湖南省', value: '湖南省' },
{ label: '山东省', value: '山东省' },
{ label: '河南省', value: '河南省' }
])
// 城市数据
const cityData: Data = {
'北京市': [{ label: '北京市', value: '北京市' }],
'上海市': [{ label: '上海市', value: '上海市' }],
'广东省': [
{ label: '广州市', value: '广州市' },
{ label: '深圳市', value: '深圳市' },
{ label: '东莞市', value: '东莞市' },
{ label: '佛山市', value: '佛山市' }
],
'浙江省': [
{ label: '杭州市', value: '杭州市' },
{ label: '宁波市', value: '宁波市' },
{ label: '温州市', value: '温州市' },
{ label: '嘉兴市', value: '嘉兴市' }
],
'江苏省': [
{ label: '南京市', value: '南京市' },
{ label: '苏州市', value: '苏州市' },
{ label: '无锡市', value: '无锡市' },
{ label: '常州市', value: '常州市' }
]
}
// 区县数据
const districtData: Data = {
'北京市-北京市': [
{ label: '东城区', value: '东城区' },
{ label: '西城区', value: '西城区' },
{ label: '朝阳区', value: '朝阳区' },
{ label: '海淀区', value: '海淀区' },
{ label: '丰台区', value: '丰台区' },
{ label: '石景山区', value: '石景山区' },
{ label: '门头沟区', value: '门头沟区' },
{ label: '房山区', value: '房山区' },
{ label: '通州区', value: '通州区' },
{ label: '顺义区', value: '顺义区' }
],
'上海市-上海市': [
{ label: '黄浦区', value: '黄浦区' },
{ label: '徐汇区', value: '徐汇区' },
{ label: '长宁区', value: '长宁区' },
{ label: '静安区', value: '静安区' },
{ label: '普陀区', value: '普陀区' },
{ label: '虹口区', value: '虹口区' },
{ label: '杨浦区', value: '杨浦区' },
{ label: '浦东新区', value: '浦东新区' }
],
'广东省-广州市': [
{ label: '越秀区', value: '越秀区' },
{ label: '海珠区', value: '海珠区' },
{ label: '荔湾区', value: '荔湾区' },
{ label: '天河区', value: '天河区' },
{ label: '白云区', value: '白云区' },
{ label: '黄埔区', value: '黄埔区' },
{ label: '番禺区', value: '番禺区' },
{ label: '花都区', value: '花都区' }
],
'浙江省-杭州市': [
{ label: '上城区', value: '上城区' },
{ label: '下城区', value: '下城区' },
{ label: '江干区', value: '江干区' },
{ label: '拱墅区', value: '拱墅区' },
{ label: '西湖区', value: '西湖区' },
{ label: '滨江区', value: '滨江区' },
{ label: '萧山区', value: '萧山区' },
{ label: '余杭区', value: '余杭区' }
]
}
// 监听 address 变化 用于更新弹窗的地址信息
watch(() => props.address, (newVal) => {
if (newVal) {
form.value = { ...newVal }
// 更新城市和区县选项
handleProvinceChange(newVal.province)
if (newVal.city) {
handleCityChange(newVal.city)
}
}
}, { deep: true })
// 监听 visible 变化 用于更新弹窗的显示状态
watch(() => props.visible, (newVal) => {
visible.value = newVal
})
// 监听弹窗关闭事件
watch(() => props.visible, (newVal) => {
if (!newVal) {
// 重置表单
form.value = {
id:0,
name: '',
phone: '',
province: '',
city: '',
district: '',
detail: '',
isDefault: false
}
addressInput.value = ''
recognitionError.value = ''
errors.value = {
name: '',
phone: '',
province: '',
city: '',
district: '',
detail: '',
isDefault: ''
}
cityOptions.value = []
districtOptions.value = []
}
})
// 地址识别处理
const handleAddressRecognition = () => {
if (!addressInput.value) {
recognitionError.value = ''
return
}
// 简单的地址识别逻辑实际项目中可以使用更复杂的算法或API
const address = addressInput.value
let province = ''
let city = ''
let district = ''
let detail = ''
// 识别省份
const provinces = provinceOptions.value.map(p => p.value)
for (const p of provinces) {
if (address.includes(p)) {
province = p
break
}
}
// 识别城市
// if (province && (cityData as Record<string, { label: string; value: string }[]>)[province]) {
// const cities = cityData[province].map((c: { value: any }) => c.value)
// for (const c of cities) {
// if (address.includes(c)) {
// city = c
// break
// }
// }
// }
// 识别区县
// if (province && city && districtData[`${province}-${city}`]) {
// const districts = districtData[`${province}-${city}`].map((d: { value: any }) => d.value)
// for (const d of districts) {
// if (address.includes(d)) {
// district = d
// break
// }
// }
// }
// 提取详细地址
if (province && city && district) {
detail = address.replace(province, '').replace(city, '').replace(district, '').trim()
} else if (province && city) {
detail = address.replace(province, '').replace(city, '').trim()
} else if (province) {
detail = address.replace(province, '').trim()
} else {
detail = address
}
// 更新表单
if (province) {
form.value.province = province
handleProvinceChange(province)
if (city) {
form.value.city = city
handleCityChange(city)
if (district) {
form.value.district = district
}
}
form.value.detail = detail
recognitionError.value = ''
} else {
recognitionError.value = '未能识别出省份,请手动选择'
}
}
// 省份变化处理
const handleProvinceChange = (province: string) => {
form.value.province = province
form.value.city = ''
form.value.district = ''
// 更新城市选项
if (province && cityData[province]) {
cityOptions.value = cityData[province]
} else {
cityOptions.value = []
}
districtOptions.value = []
// 清除验证错误
errors.value.province = ''
errors.value.city = ''
errors.value.district = ''
}
// 城市变化处理
const handleCityChange = (city: string) => {
form.value.city = city
form.value.district = ''
// 更新区县选项
if (form.value.province && city && districtData[`${form.value.province}-${city}`]) {
districtOptions.value = districtData[`${form.value.province}-${city}`] ?? []
} else {
districtOptions.value = []
}
// 清除验证错误
errors.value.city = ''
errors.value.district = ''
}
// 表单验证
const validateForm = (): boolean => {
let isValid = true
// 重置错误信息
errors.value = {
name: '',
phone: '',
province: '',
city: '',
district: '',
detail: '',
isDefault: ''
}
// 验证省份
if (!form.value.province) {
errors.value.province = '请选择省份'
isValid = false
}
// 验证城市
if (!form.value.city) {
errors.value.city = '请选择城市'
isValid = false
}
// 验证区县
if (!form.value.district) {
errors.value.district = '请选择区县'
isValid = false
}
// 验证详细地址
if (!form.value.detail) {
errors.value.detail = '请输入详细地址'
isValid = false
} else if (form.value.detail.length < 5) {
errors.value.detail = '详细地址至少需要5个字符'
isValid = false
}
return isValid
}
// 点击确认按钮 触发事件 用于更新父组件的 address
const handleOk = () => {
// 表单验证
if (!validateForm()) {
return
}
emit('update:updateaddress', form.value)
emit('update:visible', false)
}
// 点击取消按钮
const handleCancel = () => {
emit('update:visible', false)
}
</script>
<style scoped>
.modal-content {
padding: var(--spacing-md);
}
.form-group {
margin-bottom: var(--spacing-md);
}
label {
display: block;
margin-bottom: var(--spacing-sm);
font-weight: 500;
}
.required {
color: var(--error-color, #ff4d4f);
}
.error-message {
color: var(--error-color, #ff4d4f);
font-size: 12px;
margin-top: 4px;
}
.modal-footer {
display: flex;
justify-content: flex-end;
gap: var(--spacing-sm);
padding: var(--spacing-md);
border-top: 1px solid var(--border-light, #e8e8e8);
margin-top: var(--spacing-lg);
}
/* 为textarea添加最小高度 */
:deep(.ant-input-textarea) {
min-height: 80px;
}
</style>