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
@@ -1,5 +1,5 @@
import { useEffect, useMemo, useState } from 'react';
import { Modal, Spin, Typography, message } from 'antd';
import { Button, Modal, Space, Spin, Typography, message } from 'antd';
import type { ClientRuntimeConfig } from '@dukang/shared-types';
import { request } from '../lib/api';
import {
@@ -39,9 +39,13 @@ export default function TencentLocPickerModal({
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;
@@ -70,30 +74,56 @@ export default function TencentLocPickerModal({
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]);
function confirmPick() {
if (!pending) {
message.warning('请先在地图上选择或搜索一个位置');
return;
}
onPick(pending);
onClose();
}
return (
<Modal
title="腾讯地图选点"
open={open}
onCancel={onClose}
footer={null}
width={720}
destroyOnClose
styles={{ body: { padding: 0, height: 560 } }}
footer={
<Space style={{ width: '100%', justifyContent: 'space-between' }}>
<Typography.Text type="secondary" style={{ maxWidth: 420 }} ellipsis>
{pending
? `${pending.latitude.toFixed(6)}, ${pending.longitude.toFixed(6)}${
pending.name ? ` · ${pending.name}` : ''
}`
: '在地图中点选 / 搜索后,点击确认选点'}
</Typography.Text>
<Space>
<Button onClick={onClose}></Button>
<Button type="primary" disabled={!pending} onClick={confirmPick}>
</Button>
</Space>
</Space>
}
>
{loading ? (
<div style={{ height: 560, display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
@@ -102,11 +132,16 @@ export default function TencentLocPickerModal({
) : error ? (
<div style={{ padding: 24 }}>
<Typography.Text type="danger">{error}</Typography.Text>
<Typography.Paragraph type="secondary" style={{ marginTop: 12, marginBottom: 0 }}>
Key
WebServiceAPI apis.map.qq.com
</Typography.Paragraph>
</div>
) : src ? (
<iframe
title="腾讯地图选点"
src={src}
allow="geolocation *"
style={{ width: '100%', height: 560, border: 0, display: 'block' }}
/>
) : null}
+26 -6
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,18 +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;
// 列表滑动过程中偶发空 POI,忽略无名称且无地址的噪声
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,
};
}