ca621c9db8
Title centered; wide confirm on left, close on right. Co-authored-by: Cursor <cursoragent@cursor.com>
235 lines
6.5 KiB
TypeScript
235 lines
6.5 KiB
TypeScript
import { useEffect, useMemo, useState } from 'react';
|
|
import type { ClientRuntimeConfig } from '@dukang/shared-types';
|
|
import { request } from '../lib/api';
|
|
import {
|
|
buildTencentLocPickerUrl,
|
|
parseTencentLocPickerMessage,
|
|
type TencentPickedLocation,
|
|
} from '../lib/tencentLocPicker';
|
|
|
|
type Props = {
|
|
open: boolean;
|
|
onClose: () => void;
|
|
onPick: (loc: TencentPickedLocation) => void;
|
|
latitude?: number | null;
|
|
longitude?: number | null;
|
|
/** 保留兼容,iframe 选点不使用 */
|
|
region?: string | null;
|
|
};
|
|
|
|
let cachedKey: string | null | undefined;
|
|
|
|
async function loadTencentLbsKey(): Promise<string> {
|
|
if (cachedKey) return cachedKey;
|
|
const cfg = await request<ClientRuntimeConfig>('PARTNER_H5', '/common/client-config', {
|
|
silent: true,
|
|
});
|
|
const key = (cfg.tencentLbsKey || '').trim();
|
|
if (!key) {
|
|
throw new Error('未配置腾讯位置服务 Key,请联系管理员在系统设置中配置');
|
|
}
|
|
cachedKey = key;
|
|
return key;
|
|
}
|
|
|
|
export default function TencentLocPickerOverlay({
|
|
open,
|
|
onClose,
|
|
onPick,
|
|
latitude,
|
|
longitude,
|
|
}: Props) {
|
|
const [key, setKey] = useState<string | null>(cachedKey ?? null);
|
|
const [loading, setLoading] = useState(false);
|
|
const [error, setError] = useState('');
|
|
/** 选点结果仅存内存,界面不展示经纬度 */
|
|
const [pending, setPending] = useState<TencentPickedLocation | null>(null);
|
|
const [iframeReady, setIframeReady] = useState(false);
|
|
|
|
useEffect(() => {
|
|
if (!open) {
|
|
setPending(null);
|
|
setIframeReady(false);
|
|
setError('');
|
|
return;
|
|
}
|
|
let cancelled = false;
|
|
if (key) return;
|
|
setLoading(true);
|
|
void loadTencentLbsKey()
|
|
.then((k) => {
|
|
if (!cancelled) setKey(k);
|
|
})
|
|
.catch((e) => {
|
|
if (!cancelled) setError(e instanceof Error ? e.message : '加载地图配置失败');
|
|
})
|
|
.finally(() => {
|
|
if (!cancelled) setLoading(false);
|
|
});
|
|
return () => {
|
|
cancelled = true;
|
|
};
|
|
}, [open, key]);
|
|
|
|
useEffect(() => {
|
|
if (!open) return;
|
|
function onMessage(event: MessageEvent) {
|
|
const picked = parseTencentLocPickerMessage(event.data);
|
|
if (!picked) return;
|
|
setPending(picked);
|
|
}
|
|
window.addEventListener('message', onMessage);
|
|
return () => window.removeEventListener('message', onMessage);
|
|
}, [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: 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
|
|
className="partner-locpicker-overlay"
|
|
style={{
|
|
position: 'fixed',
|
|
inset: 0,
|
|
zIndex: 1000,
|
|
background: '#fff',
|
|
display: 'flex',
|
|
flexDirection: 'column',
|
|
height: '100dvh',
|
|
maxHeight: '-webkit-fill-available',
|
|
paddingTop: 'env(safe-area-inset-top)',
|
|
paddingBottom: 'env(safe-area-inset-bottom)',
|
|
boxSizing: 'border-box',
|
|
}}
|
|
>
|
|
<div
|
|
style={{
|
|
display: 'flex',
|
|
flexDirection: 'column',
|
|
padding: '12px 12px 10px',
|
|
borderBottom: '1px solid rgba(0,0,0,0.06)',
|
|
flexShrink: 0,
|
|
gap: 10,
|
|
background: '#fff',
|
|
}}
|
|
>
|
|
<h2
|
|
style={{
|
|
margin: 0,
|
|
textAlign: 'center',
|
|
fontWeight: 700,
|
|
fontSize: 17,
|
|
lineHeight: 1.3,
|
|
}}
|
|
>
|
|
地图选点
|
|
</h2>
|
|
<div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
|
|
<button
|
|
type="button"
|
|
className="partner-btn-primary"
|
|
style={{
|
|
flex: 1,
|
|
padding: '12px 16px',
|
|
fontSize: 15,
|
|
fontWeight: 600,
|
|
opacity: pending ? 1 : 0.45,
|
|
width: 'auto',
|
|
}}
|
|
disabled={!pending}
|
|
onClick={confirmPick}
|
|
>
|
|
确认
|
|
</button>
|
|
<button
|
|
type="button"
|
|
className="partner-btn-outline"
|
|
style={{ padding: '12px 16px', minWidth: 72, flexShrink: 0 }}
|
|
onClick={onClose}
|
|
>
|
|
关闭
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<p
|
|
className="label-md text-muted"
|
|
style={{
|
|
margin: 0,
|
|
padding: '8px 14px',
|
|
borderBottom: '1px solid rgba(0,0,0,0.04)',
|
|
flexShrink: 0,
|
|
fontSize: 12,
|
|
}}
|
|
>
|
|
{pending ? '已选定位置,请点右上角「确认」' : '在地图上点选或搜索地点,选中后点右上角「确认」'}
|
|
</p>
|
|
|
|
<div style={{ flex: 1, minHeight: 0, position: 'relative', background: '#f5f5f5' }}>
|
|
{loading ? (
|
|
<p className="label-md text-muted" style={{ padding: 24, textAlign: 'center' }}>
|
|
正在加载地图…
|
|
</p>
|
|
) : error ? (
|
|
<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,并白名单 apis.map.qq.com。
|
|
</p>
|
|
</div>
|
|
) : src ? (
|
|
<>
|
|
{!iframeReady ? (
|
|
<p
|
|
className="label-md text-muted"
|
|
style={{
|
|
position: 'absolute',
|
|
inset: 0,
|
|
display: 'flex',
|
|
alignItems: 'center',
|
|
justifyContent: 'center',
|
|
margin: 0,
|
|
zIndex: 1,
|
|
pointerEvents: 'none',
|
|
}}
|
|
>
|
|
地图加载中…
|
|
</p>
|
|
) : null}
|
|
<iframe
|
|
title="地图选点"
|
|
src={src}
|
|
allow="geolocation *"
|
|
onLoad={() => setIframeReady(true)}
|
|
style={{
|
|
width: '100%',
|
|
height: '100%',
|
|
border: 0,
|
|
display: 'block',
|
|
background: '#fff',
|
|
}}
|
|
/>
|
|
</>
|
|
) : null}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|