fix(api): 替换所有接口请求的域名为 guilixu-api.websoft.top

- 将获取 STS Token 的接口地址由 paopao-api 改为 guilixu-api
- 更新图片上传接口地址为新的 guilixu-api 域名
- 修改用户推广页面中邀请码链接和二维码接口的域名
- 更改注册页微信登录接口请求的域名为 guilixu-api
This commit is contained in:
2026-06-16 17:15:59 +08:00
commit f3886664f7
617 changed files with 77059 additions and 0 deletions

View File

@@ -0,0 +1,561 @@
import React, {useState, useEffect, useRef} from 'react'
import {View, Text, Input, Textarea, ScrollView} from '@tarojs/components'
import Taro, {useRouter} from '@tarojs/taro'
import {Address} from '@nutui/nutui-react-taro'
import {getShopUserAddress, addShopUserAddress, updateShopUserAddress} from '@/api/shop/shopUserAddress'
import type {ShopUserAddress} from '@/api/shop/shopUserAddress/model'
import {parseLngLatFromText} from '@/utils/geofence'
import RegionData from '@/api/json/regions-data.json'
definePageConfig({
navigationBarTitleText: '新增收货地址',
})
type SelectedLocation = { lng: string; lat: string; name?: string; address?: string }
/** 检查是否为定位权限拒绝错误 */
const isLocationDenied = (e: any) => {
const msg = String(e?.errMsg || e?.message || e || '')
return msg.includes('auth deny') || msg.includes('authorize') || msg.includes('permission') || msg.includes('denied') || msg.includes('scope.userLocation')
}
const isUserCancel = (e: any) => {
const msg = String(e?.errMsg || e?.message || e || '')
return msg.includes('cancel')
}
/** 检查经纬度是否有效 */
const hasValidLngLat = (addr?: Partial<ShopUserAddress> | null) => {
if (!addr) return false
const p = parseLngLatFromText(`${(addr as any)?.lng ?? ''},${(addr as any)?.lat ?? ''}`)
if (!p) return false
if (p.lng === 0 && p.lat === 0) return false
return true
}
const AddressEditPage: React.FC = () => {
const {id, fromWx} = useRouter().params
const isEditMode = !!id
const addressId = id ? Number(id) : undefined
const fromWxMode = fromWx === '1' || fromWx === 'true'
const [loading, setLoading] = useState(true)
const [formData, setFormData] = useState<Partial<ShopUserAddress>>({
name: '',
phone: '',
province: '',
city: '',
region: '',
address: '',
isDefault: false,
})
const [regionText, setRegionText] = useState('')
const [inputText, setInputText] = useState('')
const [selectedLocation, setSelectedLocation] = useState<SelectedLocation | null>(null)
const [regionLocked, setRegionLocked] = useState(false)
const [regionPickerVisible, setRegionPickerVisible] = useState(false)
const [regionOptions, setRegionOptions] = useState<any[]>([])
const wxDraftRef = useRef<Partial<ShopUserAddress> | null>(null)
const wxDraftPatchedRef = useRef(false)
/** 初始化省市区选择器数据 */
useEffect(() => {
const options = (RegionData as any[]).map((province) => ({
value: province.label,
text: province.label,
children: province.children?.map((city: any) => ({
value: city.label,
text: city.label,
children: city.children?.map((region: any) => ({
value: region.label,
text: region.label,
})) || [],
})) || [],
}))
setRegionOptions(options)
}, [])
/** 解析省市区 */
const parseRegion = (text: string) => {
for (const province of RegionData as any[]) {
if (text.includes(province.label)) {
const result: any = {province: province.label}
if (province.children) {
for (const city of province.children) {
if (text.includes(city.label)) {
result.city = city.label
if (city.children) {
for (const region of city.children) {
if (text.includes(region.label)) {
result.region = region.label
return result
}
}
}
return result
}
}
}
return result
}
}
return null
}
/** 解析地址文本(智能识别) */
const parseAddressText = (text: string) => {
const result: any = {}
// 手机号正则
const phoneRegex = /1[3-9]\d{9}/
const phoneMatch = text.match(phoneRegex)
if (phoneMatch) result.phone = phoneMatch[0]
// 姓名正则2-4个中文字符通常在开头
const nameRegex = /^[\u4e00-\u9fa5]{2,4}/
const nameMatch = text.match(nameRegex)
if (nameMatch) result.name = nameMatch[0]
// 省市区识别
const regionResult = parseRegion(text)
if (regionResult) {
result.province = regionResult.province
result.city = regionResult.city
result.region = regionResult.region
}
// 详细地址提取
let addressText = text
if (result.name) addressText = addressText.replace(result.name, '')
if (result.phone) addressText = addressText.replace(result.phone, '')
if (result.province) addressText = addressText.replace(result.province, '')
if (result.city) addressText = addressText.replace(result.city, '')
if (result.region) addressText = addressText.replace(result.region, '')
result.address = addressText.replace(/[,。\s]+/g, '').trim()
return result
}
/** 地址智能识别 */
const recognizeAddress = () => {
if (!inputText.trim()) {
Taro.showToast({title: '请输入要识别的文本', icon: 'none'})
return
}
try {
const result = parseAddressText(inputText)
const newFormData: any = {
...formData,
name: result.name || formData.name,
phone: result.phone || formData.phone,
address: result.address || formData.address,
}
if (!regionLocked) {
newFormData.province = result.province || formData.province
newFormData.city = result.city || formData.city
newFormData.region = result.region || formData.region
}
setFormData(newFormData)
if (!regionLocked && result.province && result.city && result.region) {
setRegionText(`${result.province} ${result.city} ${result.region}`)
}
Taro.showToast({title: regionLocked ? '识别成功(所在地区以定位为准)' : '识别成功', icon: 'success'})
setInputText('')
} catch {
Taro.showToast({title: '识别失败,请检查文本格式', icon: 'none'})
}
}
/** 选择定位 */
const chooseGeoLocation = async () => {
const applyChosenLocation = (res: any) => {
if (!res) return
if (res.latitude === undefined || res.longitude === undefined) {
Taro.showToast({title: '定位信息获取失败', icon: 'none'})
return
}
const next: SelectedLocation = {
lng: String(res.longitude),
lat: String(res.latitude),
name: res.name,
address: res.address,
}
// 尝试从地图返回的地址文本解析省市区
const regionResult = res?.provinceName || res?.cityName || res?.adName
? {
province: String(res.provinceName || ''),
city: String(res.cityName || ''),
region: String(res.adName || '')
}
: 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 rawAddr = String(res.address || '').trim()
const name = String(res.name || '').trim()
let detail = rawAddr
for (const part of [province, city, region]) {
if (part) detail = detail.replace(part, '')
}
detail = detail.replace(/[,]+/g, ' ').replace(/\s+/g, ' ').trim()
const base = detail || rawAddr
const nextDetailAddress = (() => {
if (!base && !name) return ''
if (!base) return name
if (!name) return base
return base.includes(name) ? base : `${base} ${name}`
})()
setFormData(prev => ({
...prev,
lng: next.lng,
lat: next.lat,
address: nextDetailAddress || prev.address,
province,
city,
region,
}))
setRegionText(`${province} ${city} ${region}`)
}
try {
const initLat = selectedLocation?.lat ? Number(selectedLocation.lat) : undefined
const initLng = selectedLocation?.lng ? Number(selectedLocation.lng) : undefined
const latitude = typeof initLat === 'number' && Number.isFinite(initLat) ? initLat : undefined
const longitude = typeof initLng === 'number' && Number.isFinite(initLng) ? initLng : undefined
const res = await Taro.chooseLocation({latitude, longitude})
applyChosenLocation(res)
} catch (e: any) {
if (isUserCancel(e)) return
if (isLocationDenied(e)) {
try {
const modal = await Taro.showModal({
title: '需要定位权限',
content: '选择定位需要开启定位权限,请在设置中开启后重试。',
confirmText: '去设置',
})
if (modal.confirm) {
await Taro.openSetting()
const res = await Taro.chooseLocation({})
applyChosenLocation(res)
}
} catch (_e) { /* ignore */
}
return
}
Taro.showToast({title: '打开地图失败,请重试', icon: 'none'})
}
}
/** 打开地区选择器 */
const openRegionPicker = () => {
if (regionLocked) {
Taro.showToast({title: '所在地区已由定位确定,修改请重新选择定位', icon: 'none'})
return
}
setRegionPickerVisible(true)
}
/** 地区选择器确认 */
const handleRegionChange = (value: string[]) => {
if (regionLocked) {
Taro.showToast({title: '所在地区已由定位确定,修改请重新选择定位', icon: 'none'})
return
}
const [province, city, region] = value
setFormData(prev => ({...prev, province, city, region}))
setRegionText(value.join(' '))
}
/** 保存地址 */
const handleSave = async () => {
// 表单校验
if (!formData.name?.trim()) {
Taro.showToast({title: '请输入收货人姓名', icon: 'none'})
return
}
if (!formData.phone?.trim()) {
Taro.showToast({title: '请输入手机号', icon: 'none'})
return
}
if (!/^1[3-9]\d{9}$/.test(formData.phone)) {
Taro.showToast({title: '手机号格式不正确', icon: 'none'})
return
}
if (!formData.province || !formData.city || !formData.region) {
Taro.showToast({title: '请选择所在地区', icon: 'none'})
return
}
if (!formData.address?.trim()) {
Taro.showToast({title: '请输入详细地址', icon: 'none'})
return
}
// 经纬度检查(可选,不阻断保存流程)
const loc = selectedLocation || (hasValidLngLat(formData) ? {
lng: String(formData.lng),
lat: String(formData.lat)
} : null)
try {
const submitData: any = {
name: formData.name,
phone: formData.phone,
country: formData.country || '中国',
province: formData.province,
city: formData.city,
region: formData.region,
address: formData.address,
isDefault: formData.isDefault ?? true,
...(loc ? {lng: loc.lng, lat: loc.lat} : {}),
}
if (isEditMode && addressId) {
submitData.id = addressId
}
// 执行新增或更新
if (isEditMode) {
await updateShopUserAddress(submitData)
} else {
await addShopUserAddress(submitData)
}
Taro.showToast({title: `${isEditMode ? '更新' : '保存'}成功`, icon: 'success'})
setTimeout(() => Taro.navigateBack(), 1000)
} catch (error) {
console.error('保存失败:', error)
Taro.showToast({title: `${isEditMode ? '更新' : '保存'}失败`, icon: 'none'})
}
}
const updateField = (field: string, value: any) => {
setFormData(prev => ({...prev, [field]: value}))
}
/** 初始化加载 */
useEffect(() => {
const init = async () => {
// 动态设置页面标题
Taro.setNavigationBarTitle({
title: isEditMode ? '编辑收货地址' : (fromWxMode ? '完善收货地址' : '新增收货地址'),
})
// 微信地址导入
if (!isEditMode && fromWxMode && !wxDraftPatchedRef.current) {
try {
const draft = Taro.getStorageSync('WxAddressDraft')
if (draft) {
wxDraftPatchedRef.current = true
wxDraftRef.current = draft as any
Taro.removeStorageSync('WxAddressDraft')
setFormData(prev => ({...prev, ...(draft as any)}))
const p = String((draft as any)?.province || '').trim()
const c = String((draft as any)?.city || '').trim()
const r = String((draft as any)?.region || '').trim()
const rText = [p, c, r].filter(Boolean).join(' ')
if (rText) setRegionText(rText)
}
} catch (_e) { /* ignore */
}
}
// 编辑模式:加载已有地址数据
if (isEditMode && addressId) {
try {
const addr = await getShopUserAddress(addressId)
setFormData(addr)
setRegionText(`${addr.province || ''} ${addr.city || ''} ${addr.region || ''}`)
if (hasValidLngLat(addr)) {
setSelectedLocation({lng: String(addr.lng), lat: String(addr.lat)})
setRegionLocked(true)
}
} catch (error) {
console.error('加载地址失败:', error)
Taro.showToast({title: '加载地址失败', icon: 'none'})
}
}
setLoading(false)
}
init()
}, [])
if (loading) {
return (
<View className="flex items-center justify-center min-h-screen">
<Text className="text-gray-400 text-sm">...</Text>
</View>
)
}
return (
<View className="min-h-screen bg-gray-50 flex flex-col">
<ScrollView scrollY className="flex-1">
{/* 地址智能识别区 */}
<View className="bg-white px-3 pt-3 pb-2">
<View
className="rounded-lg p-2 relative"
style={{border: '1px dashed #0e932e'}}
>
<Textarea
style={{height: '80px', width: '100%', fontSize: '13px'}}
value={inputText}
onInput={e => setInputText(e.detail.value)}
placeholder="粘贴文本,点击「识别」自动识别收货人、地址、电话"
maxlength={200}
/>
<View
className="absolute right-2 bottom-2"
style={{
backgroundColor: '#0e932e',
borderRadius: '12px',
paddingLeft: '12px',
paddingRight: '12px',
paddingTop: '4px',
paddingBottom: '4px'
}}
onClick={recognizeAddress}
>
<Text className="text-white text-xs"></Text>
</View>
</View>
</View>
<View className="bg-gray-100" style={{height: '8px'}}/>
{/* 选择定位 */}
<View className="bg-white px-4 mb-2">
<View className="flex items-center py-3" onClick={chooseGeoLocation}>
<View className="flex-1">
<View className="flex items-center gap-1 mb-1">
<Text className="text-sm text-gray-700"></Text>
<Text className="text-gray-300" style={{fontSize: '10px'}}></Text>
</View>
<Text className="text-xs text-gray-400">
{selectedLocation?.address || (selectedLocation ? `经度:${selectedLocation.lng}, 纬度:${selectedLocation.lat}` : '用于判断是否超出配送范围')}
</Text>
{selectedLocation?.name && (
<Text className="text-xs text-green-600 mt-1">{selectedLocation.name}</Text>
)}
</View>
<Text className="text-gray-300 text-xs"></Text>
</View>
</View>
{/* 表单区 */}
<View className="bg-white px-4 py-2">
{/* 所在地区 */}
<View className="flex items-center py-3 border-b border-gray-50" onClick={openRegionPicker}>
<Text className="text-sm text-gray-700" style={{minWidth: '60px'}}></Text>
<View className="flex-1 flex items-center justify-between">
<Text className={`text-sm ${regionText ? 'text-gray-800' : 'text-gray-400'}`}>
{regionText || '请选择省市区'}
</Text>
<Text className="text-gray-300 text-xs"></Text>
</View>
</View>
{/* 详细地址 */}
<View className="flex items-center py-3 border-b border-gray-50">
<Text className="text-sm text-gray-700" style={{minWidth: '60px'}}></Text>
<Input
className="flex-1 text-sm"
placeholder="请输入详细收货地址"
maxLength={50}
value={formData.address || ''}
onInput={e => updateField('address', e.detail.value)}
/>
</View>
{/* 收货人 */}
<View className="flex items-center py-3 border-b border-gray-50">
<Text className="text-sm text-gray-700" style={{minWidth: '60px'}}></Text>
<Input
className="flex-1 text-sm"
placeholder="请输入收货人姓名"
maxLength={10}
value={formData.name || ''}
onInput={e => updateField('name', e.detail.value)}
/>
</View>
{/* 手机号 */}
<View className="flex items-center py-3 border-b border-gray-50">
<Text className="text-sm text-gray-700" style={{minWidth: '60px'}}></Text>
<Input
className="flex-1 text-sm"
placeholder="请输入手机号"
type="number"
maxlength={11}
value={formData.phone || ''}
onInput={e => updateField('phone', e.detail.value)}
/>
</View>
</View>
{/* 默认地址开关 */}
<View className="bg-white px-4 mt-2">
<View className="flex items-center justify-between py-3">
<Text className="text-sm text-gray-700"></Text>
<View
className={`rounded-full relative ${formData.isDefault ? 'bg-green-500' : 'bg-gray-300'}`}
style={{width: '40px', height: '22px'}}
onClick={() => updateField('isDefault', !formData.isDefault)}
>
<View
className={`absolute top-1 rounded-full bg-white ${formData.isDefault ? 'right-1' : 'left-1'}`}
style={{width: '16px', height: '16px'}}
/>
</View>
</View>
</View>
{/* 底部占位 */}
<View style={{height: '100px'}}/>
</ScrollView>
{/* 底部保存按钮 - 使用 flex 布局而非 fixed */}
<View className="bg-white border-t border-gray-200 px-4 py-3" style={{paddingBottom: '20px'}}>
<View
className="py-3 rounded-full text-center"
style={{backgroundColor: '#0e932e'}}
onClick={handleSave}
>
<Text className="text-white font-medium text-sm">{isEditMode ? '更新地址' : '保存并使用'}</Text>
</View>
</View>
{/* 省市区选择器 */}
<Address
visible={regionPickerVisible}
options={regionOptions}
title="选择所在地区"
onChange={(value) => handleRegionChange(value as string[])}
onClose={() => setRegionPickerVisible(false)}
/>
</View>
)
}
export default AddressEditPage