feat(user): 添加地址编辑时的地区锁定功能

- 新增 regionLocked 状态管理地区锁定状态
- 编辑模式下有经纬度时自动锁定地区,防止被识别覆盖
- 地图选点后锁定地区并验证省市区完整性
- 锁定状态下点击地区选择器显示提示信息
- 表单提交前验证必填的省市区字段
- 使用 View 组件替换 div 优化 Taro 兼容性
- 识别成功时根据锁定状态显示不同提示文案
This commit is contained in:
2026-03-06 11:39:47 +08:00
parent 23af704c68
commit b929b8d35e

View File

@@ -47,6 +47,7 @@ const AddUserAddress = () => {
const [FormData, setFormData] = useState<ShopUserAddress>({}) const [FormData, setFormData] = useState<ShopUserAddress>({})
const [inputText, setInputText] = useState<string>('') const [inputText, setInputText] = useState<string>('')
const [selectedLocation, setSelectedLocation] = useState<SelectedLocation | null>(null) const [selectedLocation, setSelectedLocation] = useState<SelectedLocation | null>(null)
const [regionLocked, setRegionLocked] = useState(false)
const formRef = useRef<any>(null) const formRef = useRef<any>(null)
const wxDraftRef = useRef<Partial<ShopUserAddress> | null>(null) const wxDraftRef = useRef<Partial<ShopUserAddress> | null>(null)
const wxDraftPatchedRef = useRef(false) const wxDraftPatchedRef = useRef(false)
@@ -120,7 +121,12 @@ const AddUserAddress = () => {
// 设置所在地区 // 设置所在地区
setText(`${address.province} ${address.city} ${address.region}`) setText(`${address.province} ${address.city} ${address.region}`)
// 回显已保存的经纬度(编辑模式) // 回显已保存的经纬度(编辑模式)
if (hasValidLngLat(address)) setSelectedLocation({ lng: String(address.lng), lat: String(address.lat) }) if (hasValidLngLat(address)) {
setSelectedLocation({ lng: String(address.lng), lat: String(address.lat) })
setRegionLocked(true)
} else {
setRegionLocked(false)
}
} catch (error) { } catch (error) {
console.error('加载地址失败:', error) console.error('加载地址失败:', error)
Taro.showToast({ Taro.showToast({
@@ -172,30 +178,39 @@ const AddUserAddress = () => {
const result = parseAddressText(inputText); const result = parseAddressText(inputText);
// 更新表单数据 // 更新表单数据
const newFormData = { const newFormData: any = {
...FormData, ...FormData,
name: result.name || FormData.name, name: result.name || FormData.name,
phone: result.phone || FormData.phone, phone: result.phone || FormData.phone,
address: result.address || FormData.address, address: result.address || FormData.address
province: result.province || FormData.province,
city: result.city || FormData.city,
region: result.region || FormData.region
}; };
if (!regionLocked) {
newFormData.province = result.province || FormData.province
newFormData.city = result.city || FormData.city
newFormData.region = result.region || FormData.region
}
setFormData(newFormData); setFormData(newFormData);
// 更新地区显示文本 // 更新地区显示文本
if (result.province && result.city && result.region) { if (!regionLocked && result.province && result.city && result.region) {
setText(`${result.province} ${result.city} ${result.region}`); setText(`${result.province} ${result.city} ${result.region}`);
} }
// 更新表单字段值 // 更新表单字段值
if (formRef.current) { if (formRef.current) {
formRef.current.setFieldsValue(newFormData); const patch: any = {
name: newFormData.name,
phone: newFormData.phone,
address: newFormData.address
}
if (!regionLocked && newFormData.region) patch.region = newFormData.region
formRef.current.setFieldsValue(patch);
} }
Taro.showToast({ Taro.showToast({
title: '识别成功', title: regionLocked ? '识别成功(所在地区以定位为准)' : '识别成功',
icon: 'success' icon: 'success'
}); });
@@ -311,7 +326,6 @@ const AddUserAddress = () => {
name: res.name, name: res.name,
address: res.address address: res.address
} }
setSelectedLocation(next)
// 尝试从地图返回的 address 文本解析省市区best-effort // 尝试从地图返回的 address 文本解析省市区best-effort
const regionResult = res?.provinceName || res?.cityName || res?.adName const regionResult = res?.provinceName || res?.cityName || res?.adName
@@ -322,15 +336,22 @@ const AddUserAddress = () => {
} }
: parseRegion(String(res.address || '')) : parseRegion(String(res.address || ''))
const province = String(regionResult?.province || '').trim()
const city = String(regionResult?.city || '').trim()
const region = String(regionResult?.region || '').trim()
if (!province || !city || !region) {
Taro.showToast({ title: '定位未识别到所在地区,请重新选择定位', icon: 'none' })
return
}
setSelectedLocation(next)
setRegionLocked(true)
// 将地图选点的地址同步到“收货地址”(不额外拼接省市区字段,省市区由独立字段保存) // 将地图选点的地址同步到“收货地址”(不额外拼接省市区字段,省市区由独立字段保存)
const nextDetailAddress = (() => { const nextDetailAddress = (() => {
const rawAddr = String(res.address || '').trim() const rawAddr = String(res.address || '').trim()
const name = String(res.name || '').trim() const name = String(res.name || '').trim()
const province = String(regionResult?.province || '').trim()
const city = String(regionResult?.city || '').trim()
const region = String(regionResult?.region || '').trim()
// 选择定位返回的 address 往往包含省市区,这里尽量剥离掉,避免和表单的省市区字段重复 // 选择定位返回的 address 往往包含省市区,这里尽量剥离掉,避免和表单的省市区字段重复
let detail = rawAddr let detail = rawAddr
for (const part of [province, city, region]) { for (const part of [province, city, region]) {
@@ -350,20 +371,18 @@ const AddUserAddress = () => {
lng: next.lng, lng: next.lng,
lat: next.lat, lat: next.lat,
address: nextDetailAddress || prev.address, address: nextDetailAddress || prev.address,
province: regionResult?.province || prev.province, province,
city: regionResult?.city || prev.city, city,
region: regionResult?.region || prev.region region
})) }))
if (regionResult?.province && regionResult?.city && regionResult?.region) { setText(`${province} ${city} ${region}`)
setText(`${regionResult.province} ${regionResult.city} ${regionResult.region}`)
}
// 更新表单展示值Form initialValues 不会跟随 FormData 变化) // 更新表单展示值Form initialValues 不会跟随 FormData 变化)
if (formRef.current) { if (formRef.current) {
const patch: any = {} const patch: any = {}
if (nextDetailAddress) patch.address = nextDetailAddress if (nextDetailAddress) patch.address = nextDetailAddress
if (regionResult?.region) patch.region = regionResult.region patch.region = region
formRef.current.setFieldsValue(patch) formRef.current.setFieldsValue(patch)
} }
} }
@@ -407,6 +426,14 @@ const AddUserAddress = () => {
} }
} }
const openRegionPicker = () => {
if (regionLocked) {
Taro.showToast({ title: '所在地区已由定位确定,修改请重新选择定位', icon: 'none' })
return
}
setVisible(true)
}
// 提交表单 // 提交表单
const submitSucceed = async (values: any) => { const submitSucceed = async (values: any) => {
const loc = const loc =
@@ -416,6 +443,10 @@ const AddUserAddress = () => {
Taro.showToast({ title: '请选择定位', icon: 'none' }) Taro.showToast({ title: '请选择定位', icon: 'none' })
return return
} }
if (!FormData.province || !FormData.city || !FormData.region) {
Taro.showToast({ title: '请先选择定位以自动填写所在地区', icon: 'none' })
return
}
try { try {
// 准备提交的数据 // 准备提交的数据
@@ -487,6 +518,12 @@ const AddUserAddress = () => {
}) })
}, [fromWx, isEditMode]); }, [fromWx, isEditMode]);
useEffect(() => {
if (!regionLocked) return
if (!visible) return
setVisible(false)
}, [regionLocked, visible])
// NutUI Form 的 initialValues 在首次渲染后不再响应更新;微信导入时做一次 setFieldsValue 兜底回填。 // NutUI Form 的 initialValues 在首次渲染后不再响应更新;微信导入时做一次 setFieldsValue 兜底回填。
useEffect(() => { useEffect(() => {
if (loading) return if (loading) return
@@ -523,7 +560,7 @@ const AddUserAddress = () => {
onFinishFailed={(errors) => submitFailed(errors)} onFinishFailed={(errors) => submitFailed(errors)}
> >
<CellGroup className={'px-3'}> <CellGroup className={'px-3'}>
<div <View
style={{ style={{
border: '1px dashed #22c55e', border: '1px dashed #22c55e',
display: 'flex', display: 'flex',
@@ -549,7 +586,7 @@ const AddUserAddress = () => {
> >
</Button> </Button>
</div> </View>
</CellGroup> </CellGroup>
<View className={'bg-gray-100 h-3'}></View> <View className={'bg-gray-100 h-3'}></View>
<CellGroup style={{padding: '4px 0'}}> <CellGroup style={{padding: '4px 0'}}>
@@ -581,10 +618,10 @@ const AddUserAddress = () => {
rules={[{message: '请输入您的所在地区'}]} rules={[{message: '请输入您的所在地区'}]}
required required
> >
<div className={'flex justify-between items-center'} onClick={() => setVisible(true)}> <View className={'flex justify-between items-center'} onClick={openRegionPicker}>
<Input placeholder="选择所在地区" value={text} disabled/> <Input placeholder="选择所在地区" value={text} disabled/>
<ArrowRight className={'text-gray-400'}/> <ArrowRight className={'text-gray-400'}/>
</div> </View>
</Form.Item> </Form.Item>
<Form.Item name="address" label="收货地址" initialValue={FormData.address} required> <Form.Item name="address" label="收货地址" initialValue={FormData.address} required>
<TextArea maxLength={50} placeholder="请输入详细收货地址"/> <TextArea maxLength={50} placeholder="请输入详细收货地址"/>
@@ -598,15 +635,15 @@ const AddUserAddress = () => {
(selectedLocation ? `经纬度:${selectedLocation.lng}, ${selectedLocation.lat}` : '用于计算是否超出配送范围') (selectedLocation ? `经纬度:${selectedLocation.lng}, ${selectedLocation.lat}` : '用于计算是否超出配送范围')
} }
extra={( extra={(
<div className={'flex items-center gap-2'}> <View className={'flex items-center gap-2'}>
<div <View
className={'text-gray-900 text-sm'} className={'text-gray-900 text-sm'}
style={{maxWidth: '200px', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap'}} style={{maxWidth: '200px', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap'}}
> >
{selectedLocation?.name || (selectedLocation ? '已选择' : '请选择')} {selectedLocation?.name || (selectedLocation ? '已选择' : '请选择')}
</div> </View>
<ArrowRight className={'text-gray-400'}/> <ArrowRight className={'text-gray-400'}/>
</div> </View>
)} )}
onClick={chooseGeoLocation} onClick={chooseGeoLocation}
/> />
@@ -618,6 +655,10 @@ const AddUserAddress = () => {
options={optionsDemo1} options={optionsDemo1}
title="选择地址" title="选择地址"
onChange={(value, _) => { onChange={(value, _) => {
if (regionLocked) {
Taro.showToast({ title: '所在地区已由定位确定,修改请重新选择定位', icon: 'none' })
return
}
setFormData({ setFormData({
...FormData, ...FormData,
province: `${value[0]}`, province: `${value[0]}`,