Files
dukang/apps/h5-partner/src/components/TencentLocPickerOverlay.tsx
T
jacy ca621c9db8 fix(partner): restyle map picker header actions
Title centered; wide confirm on left, close on right.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-29 13:08:35 +08:00

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>
);
}