feat: 添加分页组件和地址选择组件,优化样式和类型定义
refactor: 重构路由配置,移除订单页面路由 style: 统一使用CSS变量替换硬编码颜色值 docs: 添加前端数据需求分析文档 fix: 修复登录页面记住我功能,更新用户信息页面链接 perf: 优化搜索组件动画效果和响应式设计 chore: 更新TypeScript类型定义,添加订单和地址相关类型
This commit is contained in:
446
src/Views/User/UpdataAddress.vue
Normal file
446
src/Views/User/UpdataAddress.vue
Normal file
@@ -0,0 +1,446 @@
|
||||
<!-- 更新地址弹窗 上下结构 上方有一个输入框可以识别地址更新到下方 下方有可以更新地址信息(省份、城市、区县、详细地址) -->
|
||||
<!-- 父组件会传递一个地址对象 子组件需要根据这个地址对象更新弹窗的地址信息 -->
|
||||
<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>
|
||||
Reference in New Issue
Block a user