feat(map-plugin): map block support select map field of association tables (#2214)

* feat(map-plugin): map block support select map field of association tables

* fix: update incorrect default value

* fix: should support o2m and fix bugs

* fix: height Close T1185 Close 1183

* fix: o2m, m2m cannot display data

* fix: switch map field will break
This commit is contained in:
Dunqing 2023-08-10 17:36:02 +08:00 committed by GitHub
parent fa43d9c870
commit b7d23c408a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 202 additions and 142 deletions

View File

@ -728,6 +728,7 @@ SchemaSettings.CascaderItem = (props: CascaderProps<any> & { title: any }) => {
onChange={onChange as any} onChange={onChange as any}
options={options} options={options}
style={{ textAlign: 'right', minWidth: 100 }} style={{ textAlign: 'right', minWidth: 100 }}
{...props}
/> />
</div> </div>
</SchemaSettings.Item> </SchemaSettings.Item>

View File

@ -1,11 +1,15 @@
import React, { useState } from 'react'; import { useCollection, useCollectionManager, useProps } from '@nocobase/client';
import React, { useMemo } from 'react';
import { MapBlockComponent } from '../components'; import { MapBlockComponent } from '../components';
import { useCollection, useProps } from '@nocobase/client';
export const MapBlock = (props) => { export const MapBlock = (props) => {
const { fieldNames } = useProps(props); const { fieldNames } = useProps(props);
const { getField } = useCollection(); const { getCollectionJoinField } = useCollectionManager();
const field = getField(fieldNames?.field); const { name } = useCollection();
const fieldComponentProps = field?.uiSchema?.['x-component-props']; const collectionField = useMemo(() => {
return <MapBlockComponent {...fieldComponentProps} {...props} />; return getCollectionJoinField([name, fieldNames?.field].flat().join('.'));
}, [name, fieldNames?.field]);
const fieldComponentProps = collectionField?.uiSchema?.['x-component-props'];
return <MapBlockComponent {...fieldComponentProps} {...props} collectionField={collectionField} />;
}; };

View File

@ -13,9 +13,9 @@ import {
} from '@nocobase/client'; } from '@nocobase/client';
import lodash from 'lodash'; import lodash from 'lodash';
import React from 'react'; import React from 'react';
import { useTranslation } from 'react-i18next';
import { useMapTranslation } from '../locale'; import { useMapTranslation } from '../locale';
import { useMapBlockContext } from './MapBlockProvider'; import { useMapBlockContext } from './MapBlockProvider';
import { findNestedOption } from './utils';
export const MapBlockDesigner = () => { export const MapBlockDesigner = () => {
const { name, title } = useCollection(); const { name, title } = useCollection();
@ -23,8 +23,7 @@ export const MapBlockDesigner = () => {
const fieldSchema = useFieldSchema(); const fieldSchema = useFieldSchema();
const dataSource = useCollectionFilterOptions(name); const dataSource = useCollectionFilterOptions(name);
const { service } = useMapBlockContext(); const { service } = useMapBlockContext();
const { t: mapT } = useMapTranslation(); const { t } = useMapTranslation();
const { t } = useTranslation();
const { dn } = useDesignable(); const { dn } = useDesignable();
const { getCollectionFieldsOptions } = useCollectionManager(); const { getCollectionFieldsOptions } = useCollectionManager();
const collection = useCollection(); const collection = useCollection();
@ -35,17 +34,21 @@ export const MapBlockDesigner = () => {
const template = useSchemaTemplate(); const template = useSchemaTemplate();
const mapFieldOptions = getCollectionFieldsOptions(collection?.name, ['point', 'lineString', 'polygon']); const mapFieldOptions = getCollectionFieldsOptions(collection?.name, ['point', 'lineString', 'polygon'], {
association: ['o2o', 'obo', 'oho', 'o2m', 'm2o', 'm2m'],
});
const markerFieldOptions = getCollectionFieldsOptions(collection?.name, 'string'); const markerFieldOptions = getCollectionFieldsOptions(collection?.name, 'string');
const isPointField = findNestedOption(fieldNames.field, mapFieldOptions)?.type === 'point';
return ( return (
<GeneralSchemaDesigner template={template} title={title || name}> <GeneralSchemaDesigner template={template} title={title || name}>
<SchemaSettings.BlockTitleItem /> <SchemaSettings.BlockTitleItem />
<FixedBlockDesignerItem /> <FixedBlockDesignerItem />
<SchemaSettings.SelectItem <SchemaSettings.CascaderItem
title={mapT('Map field')} title={t('Map field')}
value={fieldNames.field} value={fieldNames.field}
options={mapFieldOptions} options={mapFieldOptions}
allowClear={false}
onChange={(v) => { onChange={(v) => {
const fieldNames = field.decoratorProps.fieldNames || {}; const fieldNames = field.decoratorProps.fieldNames || {};
fieldNames['field'] = v; fieldNames['field'] = v;
@ -61,34 +64,36 @@ export const MapBlockDesigner = () => {
dn.refresh(); dn.refresh();
}} }}
/> />
<SchemaSettings.SelectItem {isPointField ? (
title={mapT('Marker field')} <SchemaSettings.SelectItem
value={fieldNames.marker} title={t('Marker field')}
options={markerFieldOptions} value={fieldNames.marker}
onChange={(v) => { options={markerFieldOptions}
const fieldNames = field.decoratorProps.fieldNames || {}; onChange={(v) => {
fieldNames['marker'] = v; const fieldNames = field.decoratorProps.fieldNames || {};
field.decoratorProps.fieldNames = fieldNames; fieldNames['marker'] = v;
fieldSchema['x-decorator-props']['fieldNames'] = fieldNames; field.decoratorProps.fieldNames = fieldNames;
service.refresh(); fieldSchema['x-decorator-props']['fieldNames'] = fieldNames;
dn.emit('patch', { service.refresh();
schema: { dn.emit('patch', {
['x-uid']: fieldSchema['x-uid'], schema: {
'x-decorator-props': field.decoratorProps, ['x-uid']: fieldSchema['x-uid'],
}, 'x-decorator-props': field.decoratorProps,
}); },
dn.refresh(); });
}} dn.refresh();
/> }}
/>
) : null}
<SchemaSettings.ModalItem <SchemaSettings.ModalItem
title={mapT('The default zoom level of the map')} title={t('The default zoom level of the map')}
schema={ schema={
{ {
type: 'object', type: 'object',
title: mapT('Set default zoom level'), title: t('Set default zoom level'),
properties: { properties: {
zoom: { zoom: {
title: mapT('Zoom'), title: t('Zoom'),
default: defaultZoom, default: defaultZoom,
'x-component': 'InputNumber', 'x-component': 'InputNumber',
'x-decorator': 'FormItem', 'x-decorator': 'FormItem',

View File

@ -11,7 +11,7 @@ import {
} from '@nocobase/client'; } from '@nocobase/client';
import React, { useContext } from 'react'; import React, { useContext } from 'react';
import { useMapTranslation } from '../locale'; import { useMapTranslation } from '../locale';
import { createMapBlockSchema } from './utils'; import { createMapBlockSchema, findNestedOption } from './utils';
export const MapBlockInitializer = (props) => { export const MapBlockInitializer = (props) => {
const { insert } = props; const { insert } = props;
@ -26,7 +26,9 @@ export const MapBlockInitializer = (props) => {
componentType={'Map'} componentType={'Map'}
icon={<TableOutlined />} icon={<TableOutlined />}
onCreateBlockSchema={async ({ item }) => { onCreateBlockSchema={async ({ item }) => {
const mapFieldOptions = getCollectionFieldsOptions(item.name, ['point', 'lineString', 'polygon']); const mapFieldOptions = getCollectionFieldsOptions(item.name, ['point', 'lineString', 'polygon'], {
association: ['o2o', 'obo', 'oho', 'o2m', 'm2o', 'm2m'],
});
const markerFieldOptions = getCollectionFieldsOptions(item.name, 'string'); const markerFieldOptions = getCollectionFieldsOptions(item.name, 'string');
const values = await FormDialog( const values = await FormDialog(
t('Create map block'), t('Create map block'),
@ -41,9 +43,13 @@ export const MapBlockInitializer = (props) => {
title: t('Map field'), title: t('Map field'),
enum: mapFieldOptions, enum: mapFieldOptions,
required: true, required: true,
'x-component': 'Select', 'x-component': 'Cascader',
'x-decorator': 'FormItem', 'x-decorator': 'FormItem',
default: mapFieldOptions[0]?.value, default: mapFieldOptions.length
? [mapFieldOptions[0].value, mapFieldOptions[0].children?.[0].value].filter(
(v) => v !== undefined && v !== null,
)
: [],
}, },
marker: { marker: {
title: t('Marker field'), title: t('Marker field'),
@ -52,17 +58,14 @@ export const MapBlockInitializer = (props) => {
'x-decorator': 'FormItem', 'x-decorator': 'FormItem',
'x-reactions': (field) => { 'x-reactions': (field) => {
const value = field.form.values.field; const value = field.form.values.field;
console.log('🚀 ~ file: MapBlockInitializer.tsx:45 ~ values ~ value:', value); if (!value?.length) {
console.log(
'🚀 ~ file: MapBlockInitializer.tsx:50 ~ values ~ mapFieldOptions:',
mapFieldOptions,
);
if (!value) {
return; return;
} }
const item = mapFieldOptions.find((item) => item.value === value).type; const item = findNestedOption(value, mapFieldOptions);
field.hidden = item !== 'point';
if (item) {
field.hidden = item.type !== 'point';
}
}, },
}, },
}, },

View File

@ -33,8 +33,14 @@ const InternalMapBlockProvider = (props) => {
}; };
export const MapBlockProvider = (props) => { export const MapBlockProvider = (props) => {
const { params, fieldNames } = props;
const appends = params.appends || [];
const { field } = fieldNames || {};
if (Array.isArray(field) && field.length > 1) {
appends.push(field[0]);
}
return ( return (
<BlockProvider {...props} params={{ ...props.params, paginate: false }}> <BlockProvider {...props} params={{ ...params, appends, paginate: false }}>
<InternalMapBlockProvider {...props} /> <InternalMapBlockProvider {...props} />
</BlockProvider> </BlockProvider>
); );

View File

@ -79,3 +79,13 @@ export const createMapBlockSchema = (options) => {
}; };
return schema; return schema;
}; };
export const findNestedOption = (value: string[] | string, options = []) => {
if (typeof value === 'string') {
value = [value];
}
return value?.reduce((cur, v, index) => {
const matched = cur?.find((item) => item.value === v);
return index === value.length - 1 ? matched : matched?.children;
}, options);
};

View File

@ -2,9 +2,10 @@ import { CheckOutlined, EnvironmentOutlined, ExpandOutlined } from '@ant-design/
import { RecursionField, Schema, useFieldSchema } from '@formily/react'; import { RecursionField, Schema, useFieldSchema } from '@formily/react';
import { import {
ActionContextProvider, ActionContextProvider,
css,
RecordProvider, RecordProvider,
css,
useCollection, useCollection,
useCollectionManager,
useCompile, useCompile,
useFilterAPI, useFilterAPI,
useProps, useProps,
@ -15,11 +16,13 @@ import React, { useEffect, useMemo, useRef, useState } from 'react';
import { defaultImage, selectedImage } from '../../constants'; import { defaultImage, selectedImage } from '../../constants';
import { useMapTranslation } from '../../locale'; import { useMapTranslation } from '../../locale';
import { AMapComponent, AMapForwardedRefProps } from './Map'; import { AMapComponent, AMapForwardedRefProps } from './Map';
import { getSource } from '../../utils';
export const AMapBlock = (props) => { export const AMapBlock = (props) => {
const { fieldNames, dataSource = [], fixedBlock, zoom, setSelectedRecordKeys } = useProps(props); const { collectionField, fieldNames, dataSource, fixedBlock, zoom, setSelectedRecordKeys } = useProps(props);
const { getField, getPrimaryKey } = useCollection(); const { name, getPrimaryKey } = useCollection();
const field = getField(fieldNames?.field); const { getCollectionJoinField } = useCollectionManager();
const primaryKey = getPrimaryKey();
const [isMapInitialization, setIsMapInitialization] = useState(false); const [isMapInitialization, setIsMapInitialization] = useState(false);
const mapRef = useRef<AMapForwardedRefProps>(); const mapRef = useRef<AMapForwardedRefProps>();
const geometryUtils: AMap.IGeometryUtil = mapRef.current?.aMap?.GeometryUtil; const geometryUtils: AMap.IGeometryUtil = mapRef.current?.aMap?.GeometryUtil;
@ -28,7 +31,7 @@ export const AMapBlock = (props) => {
const { t } = useMapTranslation(); const { t } = useMapTranslation();
const compile = useCompile(); const compile = useCompile();
const { isConnected, doFilter } = useFilterAPI(); const { isConnected, doFilter } = useFilterAPI();
const [, setPrevSelected] = useState(null); const [, setPrevSelected] = useState<any>(null);
const selectingModeRef = useRef(selectingMode); const selectingModeRef = useRef(selectingMode);
selectingModeRef.current = selectingMode; selectingModeRef.current = selectingMode;
@ -38,7 +41,7 @@ export const AMapBlock = (props) => {
extData.selected = !selected; extData.selected = !selected;
if ('setIcon' in overlay) { if ('setIcon' in overlay) {
overlay.setIcon( overlay.setIcon(
new mapRef.current.aMap.Icon({ new mapRef.current!.aMap.Icon({
imageSize: [19, 32], imageSize: [19, 32],
image: selected ? defaultImage : selectedImage, image: selected ? defaultImage : selectedImage,
} as AMap.IconOpts), } as AMap.IconOpts),
@ -54,9 +57,9 @@ export const AMapBlock = (props) => {
const removeSelection = () => { const removeSelection = () => {
if (!mapRef.current) return; if (!mapRef.current) return;
mapRef.current.mouseTool().close(true); mapRef.current?.mouseTool().close(true);
mapRef.current.editor().setTarget(null); mapRef.current?.editor().setTarget(null);
mapRef.current.editor().close(); mapRef.current?.editor().close();
}; };
// selection // selection
@ -64,11 +67,11 @@ export const AMapBlock = (props) => {
if (selectingMode !== 'selection') { if (selectingMode !== 'selection') {
return; return;
} }
if (!mapRef.current.editor()) { if (!mapRef.current?.editor()) {
mapRef.current.createEditor('polygon'); mapRef.current?.createEditor('polygon');
mapRef.current.createMouseTool('polygon'); mapRef.current?.createMouseTool('polygon');
} else { } else {
mapRef.current.executeMouseTool('polygon'); mapRef.current?.executeMouseTool('polygon');
} }
return () => { return () => {
removeSelection(); removeSelection();
@ -79,7 +82,7 @@ export const AMapBlock = (props) => {
if (selectingMode) { if (selectingMode) {
return () => { return () => {
if (!selectingModeRef.current) { if (!selectingModeRef.current) {
mapRef.current.map.getAllOverlays().forEach((o) => { mapRef.current?.map.getAllOverlays().forEach((o) => {
setOverlayOptions(o, false); setOverlayOptions(o, false);
}); });
} }
@ -88,47 +91,54 @@ export const AMapBlock = (props) => {
}, [selectingMode]); }, [selectingMode]);
const onSelectingComplete = useMemoizedFn(() => { const onSelectingComplete = useMemoizedFn(() => {
const selectingOverlay = mapRef.current.editor().getTarget(); const selectingOverlay = mapRef.current?.editor().getTarget();
const overlays = mapRef.current.map.getAllOverlays(); const overlays = mapRef.current?.map.getAllOverlays();
const selectedOverlays = overlays.filter((o) => { const selectedOverlays = overlays?.filter((o) => {
if (o === selectingOverlay || o.getExtData().id === undefined) return; if (o === selectingOverlay || o.getExtData().id === undefined) return;
if ('getPosition' in o) { if ('getPosition' in o) {
return geometryUtils.isPointInRing(o.getPosition(), selectingOverlay.getPath() as any); return geometryUtils.isPointInRing(o.getPosition(), selectingOverlay?.getPath() as any);
} }
return geometryUtils.doesRingRingIntersect(o.getPath(), selectingOverlay.getPath() as any); return geometryUtils.doesRingRingIntersect(o.getPath(), selectingOverlay?.getPath() as any);
}); });
const ids = selectedOverlays.map((o) => { const ids = selectedOverlays?.map((o) => {
setOverlayOptions(o, true); setOverlayOptions(o, true);
return o.getExtData().id; return o.getExtData().id;
}); });
setSelectedRecordKeys((lastIds) => ids.concat(lastIds)); setSelectedRecordKeys((lastIds) => ids?.concat(lastIds));
selectingOverlay.remove(); selectingOverlay?.remove();
mapRef.current.editor().close(); mapRef.current?.editor().close();
}); });
useEffect(() => { useEffect(() => {
if (!field || !mapRef.current) return; if (!collectionField || !mapRef.current || !dataSource) return;
const fieldPaths =
Array.isArray(fieldNames?.field) && fieldNames?.field.length > 1
? fieldNames?.field.slice(0, -1)
: fieldNames?.field;
const cf = getCollectionJoinField([name, ...fieldPaths].flat().join('.'));
const overlays = dataSource const overlays = dataSource
.map((item) => { .map((item) => {
const data = item[fieldNames?.field]; const data = getSource(item, fieldNames?.field, cf?.interface);
if (!data) return; if (!data?.length) return [];
const overlay = mapRef.current.setOverlay(field.type, data, { return data?.filter(Boolean).map((mapItem) => {
strokeColor: '#4e9bff', const overlay = mapRef.current?.setOverlay(collectionField.type, mapItem, {
fillColor: '#4e9bff', strokeColor: '#4e9bff',
cursor: 'pointer', fillColor: '#4e9bff',
label: { cursor: 'pointer',
direction: 'bottom', label: {
offset: [0, 5], direction: 'bottom',
content: fieldNames?.marker ? compile(item[fieldNames.marker]) : undefined, offset: [0, 5],
}, content: fieldNames?.marker ? compile(item[fieldNames.marker]) : undefined,
extData: { },
id: item[getPrimaryKey()], extData: {
}, id: item[primaryKey],
},
});
return overlay;
}); });
return overlay;
}) })
.filter(Boolean); .flat();
mapRef.current.map?.setFitView(overlays); mapRef.current?.map?.setFitView(overlays);
const events = overlays.map((o: AMap.Marker) => { const events = overlays.map((o: AMap.Marker) => {
const onClick = (e) => { const onClick = (e) => {
@ -144,8 +154,8 @@ export const AMapBlock = (props) => {
} }
return; return;
} }
const data = dataSource?.find((item) => { const data = dataSource.find((item) => {
return extData.id === item[getPrimaryKey()]; return extData.id === item[primaryKey];
}); });
// 筛选区块模式 // 筛选区块模式
@ -160,7 +170,7 @@ export const AMapBlock = (props) => {
return null; return null;
} else { } else {
selectMarker(o); selectMarker(o);
doFilter(data[getPrimaryKey()], (target) => target.field || getPrimaryKey(), '$eq'); doFilter(data[primaryKey], (target) => target.field || primaryKey, '$eq');
} }
return o; return o;
}); });
@ -182,7 +192,7 @@ export const AMapBlock = (props) => {
}); });
events.forEach((e) => e()); events.forEach((e) => e());
}; };
}, [dataSource, isMapInitialization, fieldNames, field.type, isConnected]); }, [dataSource, isMapInitialization, fieldNames, name, primaryKey, collectionField.type, isConnected]);
useEffect(() => { useEffect(() => {
setTimeout(() => { setTimeout(() => {
@ -210,7 +220,7 @@ export const AMapBlock = (props) => {
z-index: 999; z-index: 999;
`} `}
> >
{isMapInitialization && !mapRef.current.errMessage ? ( {isMapInitialization && !mapRef.current?.errMessage ? (
<Space direction="vertical"> <Space direction="vertical">
<Button <Button
style={{ style={{
@ -247,7 +257,7 @@ export const AMapBlock = (props) => {
</div> </div>
<MapBlockDrawer record={record} setVisible={setRecord} /> <MapBlockDrawer record={record} setVisible={setRecord} />
<AMapComponent <AMapComponent
{...field?.uiSchema?.['x-component-props']} {...collectionField?.uiSchema?.['x-component-props']}
ref={mapRefCallback} ref={mapRefCallback}
style={{ height: fixedBlock ? '100%' : null }} style={{ height: fixedBlock ? '100%' : null }}
zoom={zoom} zoom={zoom}
@ -265,7 +275,7 @@ export const AMapBlock = (props) => {
const MapBlockDrawer = (props) => { const MapBlockDrawer = (props) => {
const { setVisible, record } = props; const { setVisible, record } = props;
const fieldSchema = useFieldSchema(); const fieldSchema = useFieldSchema();
const schema: Schema = useMemo( const schema = useMemo(
() => () =>
fieldSchema.reduceProperties((buf, current) => { fieldSchema.reduceProperties((buf, current) => {
if (current.name === 'drawer') { if (current.name === 'drawer') {

View File

@ -2,9 +2,10 @@ import { CheckOutlined, EnvironmentOutlined, ExpandOutlined } from '@ant-design/
import { RecursionField, Schema, useFieldSchema } from '@formily/react'; import { RecursionField, Schema, useFieldSchema } from '@formily/react';
import { import {
ActionContextProvider, ActionContextProvider,
css,
RecordProvider, RecordProvider,
css,
useCollection, useCollection,
useCollectionManager,
useCompile, useCompile,
useFilterAPI, useFilterAPI,
useProps, useProps,
@ -16,6 +17,7 @@ import { defaultImage, selectedImage } from '../../constants';
import { useMapTranslation } from '../../locale'; import { useMapTranslation } from '../../locale';
import { GoogleMapForwardedRefProps, GoogleMapsComponent, OverlayOptions } from './Map'; import { GoogleMapForwardedRefProps, GoogleMapsComponent, OverlayOptions } from './Map';
import { getIcon } from './utils'; import { getIcon } from './utils';
import { getSource } from '../../utils';
const OVERLAY_KEY = 'google-maps-overlay-id'; const OVERLAY_KEY = 'google-maps-overlay-id';
const OVERLAY_SELECtED = 'google-maps-overlay-selected'; const OVERLAY_SELECtED = 'google-maps-overlay-selected';
@ -28,13 +30,10 @@ const labelClass = css`
`; `;
export const GoogleMapsBlock = (props) => { export const GoogleMapsBlock = (props) => {
const { fieldNames, dataSource = [], fixedBlock, zoom, setSelectedRecordKeys } = useProps(props); const { collectionField, fieldNames, dataSource, fixedBlock, zoom, setSelectedRecordKeys } = useProps(props);
const { getField, getPrimaryKey } = useCollection(); const { getPrimaryKey } = useCollection();
const { marker: markerName, field: fieldName } = fieldNames || { const primaryKey = getPrimaryKey();
marker: 'id', const { marker: markerName = 'id' } = fieldNames;
field: 'id',
};
const field = getField(fieldName);
const [isMapInitialization, setIsMapInitialization] = useState(false); const [isMapInitialization, setIsMapInitialization] = useState(false);
const mapRef = useRef<GoogleMapForwardedRefProps>(); const mapRef = useRef<GoogleMapForwardedRefProps>();
const [record, setRecord] = useState(); const [record, setRecord] = useState();
@ -42,12 +41,14 @@ export const GoogleMapsBlock = (props) => {
const { t } = useMapTranslation(); const { t } = useMapTranslation();
const compile = useCompile(); const compile = useCompile();
const { isConnected, doFilter } = useFilterAPI(); const { isConnected, doFilter } = useFilterAPI();
const [, setPrevSelected] = useState(null); const [, setPrevSelected] = useState<any>(null);
const selectingModeRef = useRef(selectingMode); const selectingModeRef = useRef(selectingMode);
const selectionOverlayRef = useRef<google.maps.Polygon>(); const selectionOverlayRef = useRef<google.maps.Polygon | null>(null);
const overlaysRef = useRef<google.maps.MVCObject[]>([]); const overlaysRef = useRef<google.maps.MVCObject[]>([]);
selectingModeRef.current = selectingMode; selectingModeRef.current = selectingMode;
const { getCollectionJoinField } = useCollectionManager();
const setOverlayOptions = (overlay: google.maps.MVCObject, state?: boolean) => { const setOverlayOptions = (overlay: google.maps.MVCObject, state?: boolean) => {
const selected = typeof state !== 'undefined' ? !state : overlay.get(OVERLAY_SELECtED); const selected = typeof state !== 'undefined' ? !state : overlay.get(OVERLAY_SELECtED);
overlay.set(OVERLAY_SELECtED, !selected); overlay.set(OVERLAY_SELECtED, !selected);
@ -71,17 +72,17 @@ export const GoogleMapsBlock = (props) => {
if (selectingMode !== 'selection') { if (selectingMode !== 'selection') {
return; return;
} }
if (!mapRef.current.drawingManager) { if (mapRef.current && !mapRef.current?.drawingManager) {
mapRef.current.drawingManager = mapRef.current.createDraw(true, { mapRef.current.drawingManager = mapRef.current?.createDraw(true, {
editable: true, editable: true,
draggable: true, draggable: true,
}); });
} }
const listenerSet = new Set<() => void>(); const listenerSet = new Set<() => void>();
mapRef.current.drawingManager.setDrawingMode(google.maps.drawing.OverlayType.POLYGON); mapRef.current?.drawingManager.setDrawingMode(google.maps.drawing.OverlayType.POLYGON);
mapRef.current.drawingManager.addListener('overlaycomplete', (event) => { mapRef.current?.drawingManager.addListener('overlaycomplete', (event) => {
const polygon = event.overlay as google.maps.Polygon; const polygon = event.overlay as google.maps.Polygon;
mapRef.current.drawingManager.setDrawingMode(null); mapRef.current?.drawingManager.setDrawingMode(null);
selectionOverlayRef.current = polygon; selectionOverlayRef.current = polygon;
const path = polygon.getPath(); const path = polygon.getPath();
['insert_at', 'remove_at', 'set_at'].forEach((key) => { ['insert_at', 'remove_at', 'set_at'].forEach((key) => {
@ -96,8 +97,8 @@ export const GoogleMapsBlock = (props) => {
selectionOverlayRef.current?.unbindAll(); selectionOverlayRef.current?.unbindAll();
selectionOverlayRef.current?.setMap(null); selectionOverlayRef.current?.setMap(null);
selectionOverlayRef.current = null; selectionOverlayRef.current = null;
mapRef.current.drawingManager.setDrawingMode(null); mapRef.current?.drawingManager.setDrawingMode(null);
mapRef.current.drawingManager.unbindAll(); mapRef.current?.drawingManager.unbindAll();
}; };
}, [selectingMode]); }, [selectingMode]);
@ -120,15 +121,15 @@ export const GoogleMapsBlock = (props) => {
const selectedOverlays = overlays.filter((o) => { const selectedOverlays = overlays.filter((o) => {
if (o === overlay || o.get(OVERLAY_KEY) === undefined) return; if (o === overlay || o.get(OVERLAY_KEY) === undefined) return;
if (o instanceof google.maps.Marker) { if (o instanceof google.maps.Marker) {
return poly.containsLocation(o.getPosition(), overlay); return poly.containsLocation(o.getPosition()!, overlay!);
} else if (o instanceof google.maps.Circle) { } else if (o instanceof google.maps.Circle) {
return poly.containsLocation(o.getCenter(), overlay); return poly.containsLocation(o.getCenter()!, overlay!);
} else { } else {
return (o as google.maps.Polygon) return (o as google.maps.Polygon)
.getPath() .getPath()
.getArray() .getArray()
.some((position) => { .some((position) => {
return poly.containsLocation(position, overlay); return poly.containsLocation(position, overlay!);
}); });
} }
}); });
@ -137,36 +138,45 @@ export const GoogleMapsBlock = (props) => {
return o.get(OVERLAY_KEY); return o.get(OVERLAY_KEY);
}); });
setSelectedRecordKeys((lastIds) => ids.concat(lastIds)); setSelectedRecordKeys((lastIds) => ids.concat(lastIds));
overlay.unbindAll(); overlay?.unbindAll();
overlay.setMap(null); overlay?.setMap(null);
mapRef.current.drawingManager.setDrawingMode(google.maps.drawing.OverlayType.POLYGON); mapRef.current?.drawingManager.setDrawingMode(google.maps.drawing.OverlayType.POLYGON);
}); });
useEffect(() => { useEffect(() => {
if (!field || !mapRef.current?.map) return; if (!collectionField || !dataSource?.length || !mapRef.current?.map) return;
const fieldPaths =
Array.isArray(fieldNames?.field) && fieldNames?.field.length > 1
? fieldNames?.field.slice(0, -1)
: fieldNames?.field;
const cf = getCollectionJoinField([name, ...fieldPaths].flat().join('.'));
const overlays: google.maps.Polygon[] = dataSource const overlays: google.maps.Polygon[] = dataSource
.map((item) => { .map((item) => {
const data = item[fieldNames?.field]; const data = getSource(item, fieldNames?.field, cf?.interface);
if (!data) return; if (!data?.length) return [];
const overlay = mapRef.current.setOverlay(field.type, data, { return data?.filter(Boolean).map((mapItem) => {
strokeColor: '#4e9bff', if (!data) return;
fillColor: '#4e9bff', const overlay = mapRef.current?.setOverlay(collectionField.type, mapItem, {
cursor: 'pointer', strokeColor: '#4e9bff',
label: { fillColor: '#4e9bff',
className: labelClass, cursor: 'pointer',
fontFamily: 'inherit', label: {
fontSize: '13px', className: labelClass,
color: '#333', fontFamily: 'inherit',
text: fieldNames?.marker ? compile(item[markerName]) : undefined, fontSize: '13px',
} as google.maps.MarkerLabel, color: '#333',
text: fieldNames?.marker ? compile(item[markerName]) : undefined,
} as google.maps.MarkerLabel,
});
overlay?.set(OVERLAY_KEY, item[primaryKey]);
return overlay;
}); });
overlay.set(OVERLAY_KEY, item[getPrimaryKey()]);
return overlay;
}) })
.filter(Boolean); .flat();
overlaysRef.current = overlays; overlaysRef.current = overlays;
mapRef.current.setFitView(overlays); mapRef.current?.setFitView(overlays);
const events = overlays.map((o: google.maps.MVCObject) => { const events = overlays.map((o: google.maps.MVCObject) => {
const onClick = (event) => { const onClick = (event) => {
@ -175,7 +185,7 @@ export const GoogleMapsBlock = (props) => {
if (!id) return; if (!id) return;
const data = dataSource?.find((item) => { const data = dataSource?.find((item) => {
return id === item[getPrimaryKey()]; return id === item[primaryKey];
}); });
// 筛选区块模式 // 筛选区块模式
@ -190,7 +200,7 @@ export const GoogleMapsBlock = (props) => {
return null; return null;
} else { } else {
selectMarker(overlay); selectMarker(overlay);
doFilter(data[getPrimaryKey()], (target) => target.field || getPrimaryKey(), '$eq'); doFilter(data[primaryKey], (target) => target.field || primaryKey, '$eq');
} }
return overlay; return overlay;
}); });
@ -213,7 +223,7 @@ export const GoogleMapsBlock = (props) => {
}); });
events.forEach((e) => e()); events.forEach((e) => e());
}; };
}, [dataSource, isMapInitialization, markerName, field.type, isConnected]); }, [dataSource, isMapInitialization, markerName, collectionField.type, isConnected]);
useEffect(() => { useEffect(() => {
setTimeout(() => { setTimeout(() => {

View File

@ -11,9 +11,11 @@ export function lang(key: string) {
} }
export function generateNTemplate(key: string) { export function generateNTemplate(key: string) {
return `{{t('${key}', { ns: '${NAMESPACE}' })}}`; return `{{t('${key}', { ns: '${NAMESPACE}', nsMode: 'fallback' })}}`;
} }
export function useMapTranslation() { export function useMapTranslation() {
return useTranslation(NAMESPACE); return useTranslation(NAMESPACE, {
nsMode: 'fallback',
});
} }

View File

@ -0,0 +1,9 @@
export const getSource = (data: Record<string, any>, fields?: string[], type?: string) => {
const res = fields?.reduce((obj, field, index) => {
if (index === fields.length - 1 && (type === 'o2m' || type === 'm2m')) {
return obj?.map((item) => item[field]).filter((v) => v !== null && v !== undefined);
}
return obj?.[field];
}, data);
return type === 'o2m' || type === 'm2m' ? res : [res];
};