feat(map-plugin): supports connecting each point into a line (#2216)

* feat(map-plugin): supports connecting each point into a line

* feat: update

* feat: support startPoint and endPoint

* fix: data refresh is incorrect when sort changed

* feat: support google map
This commit is contained in:
Dunqing 2023-11-02 15:03:49 +08:00 committed by GitHub
parent 5c937500b7
commit a90d5fe6d6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 151 additions and 45 deletions

View File

@ -3,7 +3,7 @@ import _ from 'lodash';
import React from 'react';
import { useTranslation } from 'react-i18next';
import { useFormBlockContext } from '../../../block-provider';
import { useCollection, useCollectionManager, useSortFields } from '../../../collection-manager';
import { useCollection, useCollectionManager } from '../../../collection-manager';
import { GeneralSchemaDesigner, SchemaSettings } from '../../../schema-settings';
import { useCompile, useDesignable } from '../../hooks';
@ -47,20 +47,6 @@ export const AssociationFilterItemDesigner = (props) => {
dn.refresh();
};
const sortFields = useSortFields(collectionField?.target);
const defaultSort = fieldSchema?.['x-component-props']?.params?.sort || [];
const sort = defaultSort?.map((item: string) => {
return item.startsWith('-')
? {
field: item.substring(1),
direction: 'desc',
}
: {
field: item,
direction: 'asc',
};
});
return (
<GeneralSchemaDesigner {...props} disableInitializer={true}>
<SchemaSettings.ModalItem
@ -127,22 +113,7 @@ export const AssociationFilterItemDesigner = (props) => {
});
}}
/>
<SchemaSettings.DefaultSortingRules
sort={sort}
sortFields={sortFields}
onSubmit={({ sort }) => {
_.set(field.componentProps, 'params', {
...field.componentProps?.params,
sort: sort.map((item) => {
return item.direction === 'desc' ? `-${item.field}` : item.field;
}),
});
fieldSchema['x-component-props']['params'] = field.componentProps.params;
dn.emit('patch', {
schema: fieldSchema,
});
}}
/>
<SchemaSettings.DefaultSortingRules name={collectionField?.target} />
<SchemaSettings.SelectItem
key="title-field"
title={t('Title field')}

View File

@ -19,7 +19,7 @@ import {
Space,
Switch,
} from 'antd';
import _, { cloneDeep } from 'lodash';
import _, { cloneDeep, get, set } from 'lodash';
import React, {
ReactNode,
createContext,
@ -1059,17 +1059,56 @@ SchemaSettings.BlockTitleItem = function BlockTitleItem() {
};
SchemaSettings.DefaultSortingRules = function DefaultSortingRules(props) {
const { sort, sortFields, onSubmit } = props;
const { path = 'x-component-props.params.sort' } = props;
const { t } = useTranslation();
const { dn } = useDesignable();
const fieldSchema = useFieldSchema();
const field = useField();
const title = props.title || t('Set default sorting rules');
const { name } = useCollection();
const defaultSort = get(fieldSchema, path) || [];
const sort = defaultSort?.map((item: string) => {
return item.startsWith('-')
? {
field: item.substring(1),
direction: 'desc',
}
: {
field: item,
direction: 'asc',
};
});
const sortFields = useSortFields(props.name || name);
const onSubmit = async ({ sort }) => {
if (props?.onSubmit) {
return props.onSubmit({ sort });
}
const value = sort.map((item) => {
return item.direction === 'desc' ? `-${item.field}` : item.field;
});
set(
field,
path.replace('x-component-props', 'componentProps').replace('x-decorator-props', 'decoratorProps'),
value,
);
set(fieldSchema, path, value);
await dn.emit('patch', {
schema: fieldSchema,
});
return props.onSubmitAfter?.();
};
return (
<SchemaSettings.ModalItem
title={t('Set default sorting rules')}
title={title}
components={{ ArrayItems }}
schema={
{
type: 'object',
title: t('Set default sorting rules'),
title,
properties: {
sort: {
type: 'array',

View File

@ -84,6 +84,7 @@ export const MapBlockDesigner = () => {
}}
/>
) : null}
<SchemaSettings.DefaultSortingRules path="x-component-props.lineSort" title={t('Concatenation order field')} />
<SchemaSettings.ModalItem
title={t('The default zoom level of the map')}
schema={

View File

@ -33,6 +33,7 @@ const InternalMapBlockProvider = (props) => {
};
export const MapBlockProvider = (props) => {
const uField = useField();
const { params, fieldNames } = props;
const appends = params.appends || [];
const { field } = fieldNames || {};
@ -40,7 +41,12 @@ export const MapBlockProvider = (props) => {
appends.push(field[0]);
}
return (
<BlockProvider name="map" {...props} params={{ ...params, appends, paginate: false }}>
<BlockProvider
name="map"
{...props}
runWhenParamsChanged
params={{ ...params, appends, paginate: false, sort: uField.componentProps.lineSort }}
>
<InternalMapBlockProvider {...props} />
</BlockProvider>
);
@ -57,5 +63,6 @@ export const useMapBlockProps = () => {
...ctx,
dataSource: ctx?.service?.data?.data,
zoom: ctx?.field?.componentProps?.zoom || 13,
lineSort: ctx?.field?.componentProps?.lineSort,
};
};

View File

@ -1,5 +1,5 @@
import { CheckOutlined, EnvironmentOutlined, ExpandOutlined } from '@ant-design/icons';
import { RecursionField, Schema, useFieldSchema } from '@formily/react';
import { RecursionField, Schema, useField, useFieldSchema } from '@formily/react';
import {
ActionContextProvider,
RecordProvider,
@ -19,7 +19,8 @@ import { AMapComponent, AMapForwardedRefProps } from './Map';
import { getSource } from '../../utils';
export const AMapBlock = (props) => {
const { collectionField, fieldNames, dataSource, fixedBlock, zoom, setSelectedRecordKeys } = useProps(props);
const { collectionField, fieldNames, dataSource, fixedBlock, zoom, setSelectedRecordKeys, lineSort } =
useProps(props);
const { name, getPrimaryKey } = useCollection();
const { getCollectionJoinField } = useCollectionManager();
const primaryKey = getPrimaryKey();
@ -118,9 +119,9 @@ export const AMapBlock = (props) => {
const cf = getCollectionJoinField([name, ...fieldPaths].flat().join('.'));
const overlays = dataSource
.map((item) => {
const data = getSource(item, fieldNames?.field, cf?.interface);
const data = getSource(item, fieldNames?.field, cf?.interface)?.filter(Boolean);
if (!data?.length) return [];
return data?.filter(Boolean).map((mapItem) => {
return data.map((mapItem) => {
const overlay = mapRef.current?.setOverlay(collectionField.type, mapItem, {
strokeColor: '#4e9bff',
fillColor: '#4e9bff',
@ -188,13 +189,46 @@ export const AMapBlock = (props) => {
return () => o.off('click', onClick);
});
if (collectionField.type === 'point' && lineSort?.length && overlays?.length > 1) {
const positions = overlays.map((o: AMap.Marker) => o.getPosition());
(overlays[0] as AMap.Marker).setzIndex(13);
(overlays[overlays.length - 1] as AMap.Marker).setzIndex(13);
const createText = (start = true) => {
if (!mapRef.current?.map) return;
return new AMap.Text({
label: {
direction: 'top',
offset: [0, 0],
content: start ? t('Start point') : t('End point'),
},
position: positions[start ? 0 : positions.length - 1],
map: mapRef.current?.map,
});
};
overlays.push(
...[
mapRef.current?.setOverlay('lineString', positions, {
strokeColor: '#4e9bff',
fillColor: '#4e9bff',
strokeWeight: 2,
cursor: 'pointer',
}),
createText(),
createText(false),
].filter(Boolean),
);
}
return () => {
overlays.forEach((ov) => {
ov.remove();
});
events.forEach((e) => e());
};
}, [dataSource, isMapInitialization, fieldNames, name, primaryKey, collectionField.type, isConnected]);
}, [dataSource, isMapInitialization, fieldNames, name, primaryKey, collectionField.type, isConnected, lineSort]);
useEffect(() => {
setTimeout(() => {

View File

@ -29,8 +29,16 @@ const labelClass = css`
border: 1px solid #0000f5;
`;
const pointClass = css`
margin-top: -64px;
padding: 2px 4px;
background: #fff;
border: 1px solid #0000f5;
`;
export const GoogleMapsBlock = (props) => {
const { collectionField, fieldNames, dataSource, fixedBlock, zoom, setSelectedRecordKeys } = useProps(props);
const { collectionField, fieldNames, dataSource, fixedBlock, zoom, setSelectedRecordKeys, lineSort } =
useProps(props);
const { getPrimaryKey } = useCollection();
const primaryKey = getPrimaryKey();
const { marker: markerName = 'id' } = fieldNames;
@ -151,7 +159,7 @@ export const GoogleMapsBlock = (props) => {
: fieldNames?.field;
const cf = getCollectionJoinField([name, ...fieldPaths].flat().join('.'));
const overlays: google.maps.Polygon[] = dataSource
const overlays: google.maps.MVCObject[] = dataSource
.map((item) => {
const data = getSource(item, fieldNames?.field, cf?.interface);
if (!data?.length) return [];
@ -177,7 +185,6 @@ export const GoogleMapsBlock = (props) => {
.filter(Boolean);
overlaysRef.current = overlays;
mapRef.current?.setFitView(overlays);
const events = overlays.map((o: google.maps.MVCObject) => {
const onClick = (event) => {
@ -217,9 +224,53 @@ export const GoogleMapsBlock = (props) => {
return () => o.unbindAll();
});
if (collectionField.type === 'point' && lineSort?.length && overlays?.length > 1) {
const positions = overlays.map((o: google.maps.Marker) => o.getPosition());
(overlays[0] as google.maps.Marker).setZIndex(138);
(overlays[overlays.length - 1] as google.maps.Marker).setZIndex(138);
const createText = (start = true) => {
if (!mapRef.current?.map) return;
return new google.maps.Marker({
label: {
// direction: 'top',
// offset: [0, 0],
className: pointClass,
fontFamily: 'inherit',
fontSize: '13px',
color: '#333',
text: start ? t('Start point') : t('End point'),
},
icon: getIcon(defaultImage),
position: start ? positions[0] : positions[positions.length - 1],
map: mapRef.current.map,
});
};
overlays.push(
...[
mapRef.current.setOverlay(
'lineString',
positions.map((p) => [p.lng(), p.lat()]),
{
strokeColor: '#4e9bff',
fillColor: '#4e9bff',
strokeWeight: 2,
cursor: 'pointer',
},
),
createText(),
createText(false),
].filter(Boolean),
);
}
mapRef.current?.setFitView(overlays);
return () => {
overlays.forEach((ov) => {
ov.setMap(null);
(ov as google.maps.Polygon).setMap(null);
ov.unbindAll();
});
events.forEach((e) => e());

View File

@ -47,6 +47,9 @@ const locale = {
'Load google maps failed, Please check the Api key and refresh the page':
'加载谷歌地图失败,请检查 Api key 并刷新页面',
'Create map block': '创建地图区块',
'Start point': '起点',
'End point': '终点',
'Concatenation order field': '连接顺序字段',
};
export default locale;