feat: 添加分页组件和地址选择组件,优化样式和类型定义

refactor: 重构路由配置,移除订单页面路由

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

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

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

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

chore: 更新TypeScript类型定义,添加订单和地址相关类型
This commit is contained in:
qingfeng1121
2026-01-19 11:35:50 +08:00
parent c287650fbb
commit 73cf25e586
20 changed files with 4618 additions and 422 deletions

View 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>