fix(admin,partner): harden Tencent locpicker coords and confirm flow
CI / verify (pull_request) Has been cancelled

Accept latlng-only postMessage, confirm before apply, and read LBS key per request.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-07-28 00:47:08 +08:00
parent 50349dd8e9
commit 9ed0c24d11
7 changed files with 157 additions and 35 deletions
@@ -40,9 +40,13 @@ export default function TencentLocPickerOverlay({
const [key, setKey] = useState<string | null>(cachedKey ?? null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState('');
const [pending, setPending] = useState<TencentPickedLocation | null>(null);
useEffect(() => {
if (!open) return;
if (!open) {
setPending(null);
return;
}
let cancelled = false;
setError('');
if (key) return;
@@ -67,23 +71,30 @@ export default function TencentLocPickerOverlay({
function onMessage(event: MessageEvent) {
const picked = parseTencentLocPickerMessage(event.data);
if (!picked) return;
onPick(picked);
onClose();
setPending(picked);
}
window.addEventListener('message', onMessage);
return () => window.removeEventListener('message', onMessage);
}, [open, onPick, onClose]);
}, [open]);
const src = useMemo(() => {
if (!key) return '';
const lat = latitude != null ? Number(latitude) : undefined;
const lng = longitude != null ? Number(longitude) : undefined;
return buildTencentLocPickerUrl(key, {
latitude: latitude != null ? Number(latitude) : undefined,
longitude: longitude != null ? Number(longitude) : undefined,
latitude: lat != null && Number.isFinite(lat) ? lat : undefined,
longitude: lng != null && Number.isFinite(lng) ? lng : undefined,
});
}, [key, latitude, longitude]);
if (!open) return null;
function confirmPick() {
if (!pending) return;
onPick(pending);
onClose();
}
return (
<div
style={{
@@ -109,21 +120,50 @@ export default function TencentLocPickerOverlay({
</button>
<span style={{ fontWeight: 600 }}></span>
<span style={{ width: 52 }} />
<button
type="button"
className="partner-btn-primary"
style={{ padding: '6px 12px', opacity: pending ? 1 : 0.45, width: 'auto' }}
disabled={!pending}
onClick={confirmPick}
>
</button>
</div>
{pending ? (
<p
className="label-md text-muted"
style={{
margin: 0,
padding: '8px 16px',
borderBottom: '1px solid rgba(0,0,0,0.04)',
flexShrink: 0,
}}
>
{pending.latitude.toFixed(6)}, {pending.longitude.toFixed(6)}
{pending.name ? ` · ${pending.name}` : ''}
</p>
) : null}
<div style={{ flex: 1, minHeight: 0, position: 'relative' }}>
{loading ? (
<p className="label-md text-muted" style={{ padding: 24, textAlign: 'center' }}>
</p>
) : error ? (
<p className="label-md" style={{ padding: 24, color: 'var(--color-heritage-red, #a61d24)' }}>
{error}
</p>
<div style={{ padding: 24 }}>
<p className="label-md" style={{ color: 'var(--color-heritage-red, #a61d24)' }}>
{error}
</p>
<p className="label-md text-muted" style={{ marginTop: 12 }}>
Key WebServiceAPI
apis.map.qq.com
</p>
</div>
) : src ? (
<iframe
title="腾讯地图选点"
src={src}
allow="geolocation *"
style={{ width: '100%', height: '100%', border: 0, display: 'block' }}
/>
) : null}
+26 -5
View File
@@ -14,7 +14,22 @@ type LocPickerMessage = {
cityname?: string;
};
const REFERER = 'dukang-haoke';
const REFERER = 'dukang';
function coerceMessageData(data: unknown): LocPickerMessage | null {
if (data == null) return null;
if (typeof data === 'string') {
const trimmed = data.trim();
if (!trimmed || (trimmed[0] !== '{' && trimmed[0] !== '[')) return null;
try {
return JSON.parse(trimmed) as LocPickerMessage;
} catch {
return null;
}
}
if (typeof data === 'object') return data as LocPickerMessage;
return null;
}
export function buildTencentLocPickerUrl(
key: string,
@@ -26,6 +41,8 @@ export function buildTencentLocPickerUrl(
key,
referer: REFERER,
policy: '1',
total: '20',
radius: '2000',
});
const lat = options?.latitude;
const lng = options?.longitude;
@@ -43,17 +60,21 @@ export function buildTencentLocPickerUrl(
}
export function parseTencentLocPickerMessage(data: unknown): TencentPickedLocation | null {
const loc = data as LocPickerMessage;
const loc = coerceMessageData(data);
if (!loc || loc.module !== 'locationPicker') return null;
const lat = Number(loc.latlng?.lat);
const lng = Number(loc.latlng?.lng);
if (!Number.isFinite(lat) || !Number.isFinite(lng)) return null;
if (!loc.poiname && !loc.poiaddress) return null;
let name = loc.poiname?.trim() || undefined;
const address = loc.poiaddress?.trim() || undefined;
if (name === '我的位置' && address) name = address;
return {
latitude: lat,
longitude: lng,
address: loc.poiaddress?.trim() || undefined,
name: loc.poiname?.trim() || undefined,
address,
name,
cityname: loc.cityname?.trim() || undefined,
};
}