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:
parent
fa43d9c870
commit
b7d23c408a
@ -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>
|
||||||
|
@ -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} />;
|
||||||
};
|
};
|
||||||
|
@ -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',
|
||||||
|
@ -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';
|
||||||
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -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>
|
||||||
);
|
);
|
||||||
|
@ -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);
|
||||||
|
};
|
||||||
|
@ -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') {
|
||||||
|
@ -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(() => {
|
||||||
|
@ -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',
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
@ -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];
|
||||||
|
};
|
Loading…
Reference in New Issue
Block a user