From a90d5fe6d6a9d3ddc7e19909bc114dada4040519 Mon Sep 17 00:00:00 2001 From: Dunqing Date: Thu, 2 Nov 2023 15:03:49 +0800 Subject: [PATCH] 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 --- .../AssociationFilter.Item.Designer.tsx | 33 +---------- .../src/schema-settings/SchemaSettings.tsx | 47 +++++++++++++-- .../src/client/block/MapBlockDesigner.tsx | 1 + .../src/client/block/MapBlockProvider.tsx | 9 ++- .../src/client/components/AMap/Block.tsx | 44 ++++++++++++-- .../client/components/GoogleMaps/Block.tsx | 59 +++++++++++++++++-- .../@nocobase/plugin-map/src/locale/zh-CN.ts | 3 + 7 files changed, 151 insertions(+), 45 deletions(-) diff --git a/packages/core/client/src/schema-component/antd/association-filter/AssociationFilter.Item.Designer.tsx b/packages/core/client/src/schema-component/antd/association-filter/AssociationFilter.Item.Designer.tsx index aa13f11be2..3b9ca5325c 100644 --- a/packages/core/client/src/schema-component/antd/association-filter/AssociationFilter.Item.Designer.tsx +++ b/packages/core/client/src/schema-component/antd/association-filter/AssociationFilter.Item.Designer.tsx @@ -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 ( { }); }} /> - { - _.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, - }); - }} - /> + { + 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 ( { }} /> ) : null} + { }; 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 ( - + ); @@ -57,5 +63,6 @@ export const useMapBlockProps = () => { ...ctx, dataSource: ctx?.service?.data?.data, zoom: ctx?.field?.componentProps?.zoom || 13, + lineSort: ctx?.field?.componentProps?.lineSort, }; }; diff --git a/packages/plugins/@nocobase/plugin-map/src/client/components/AMap/Block.tsx b/packages/plugins/@nocobase/plugin-map/src/client/components/AMap/Block.tsx index 8391a64397..9c1e0bdb54 100644 --- a/packages/plugins/@nocobase/plugin-map/src/client/components/AMap/Block.tsx +++ b/packages/plugins/@nocobase/plugin-map/src/client/components/AMap/Block.tsx @@ -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(() => { diff --git a/packages/plugins/@nocobase/plugin-map/src/client/components/GoogleMaps/Block.tsx b/packages/plugins/@nocobase/plugin-map/src/client/components/GoogleMaps/Block.tsx index bd9587155a..0aec2ae36a 100644 --- a/packages/plugins/@nocobase/plugin-map/src/client/components/GoogleMaps/Block.tsx +++ b/packages/plugins/@nocobase/plugin-map/src/client/components/GoogleMaps/Block.tsx @@ -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()); diff --git a/packages/plugins/@nocobase/plugin-map/src/locale/zh-CN.ts b/packages/plugins/@nocobase/plugin-map/src/locale/zh-CN.ts index e70ab59113..8c6d1f9603 100644 --- a/packages/plugins/@nocobase/plugin-map/src/locale/zh-CN.ts +++ b/packages/plugins/@nocobase/plugin-map/src/locale/zh-CN.ts @@ -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;