From e3e3c05ae0b5314cfd2ff80dd3718b03fabc6521 Mon Sep 17 00:00:00 2001 From: jiangzhixiong <710328466@qq.com> Date: Tue, 21 May 2024 10:42:01 +0800 Subject: [PATCH 1/5] =?UTF-8?q?feat(map=E5=9C=B0=E5=9B=BE):=20=E5=88=9D?= =?UTF-8?q?=E5=A7=8B=E5=8C=96=E5=9C=B0=E5=9B=BE=E9=85=8D=E7=BD=AE,?= =?UTF-8?q?=E5=BC=95=E5=85=A5=E7=9B=B8=E5=85=B3js=E6=96=87=E4=BB=B6?= =?UTF-8?q?=E4=BD=9C=E4=B8=BA=E5=8F=82=E8=80=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/map/src/MapBox.tsx | 31 ++-- packages/map/src/constants.ts | 6 +- packages/map/src/demo/basic.tsx | 12 +- packages/map/src/index.md | 7 + packages/map/src/interface.ts | 25 ++- packages/map/src/utils/Draw/constants.js | 100 +++++++++++ .../map/src/utils/Draw/doubleClickZoom.js | 24 +++ .../map/src/utils/Draw/drawCircleMode.draw.js | 69 ++++++++ .../map/src/utils/Draw/drawDirectMode.draw.js | 131 ++++++++++++++ .../src/utils/Draw/drawLineSelectMode.draw.js | 165 ++++++++++++++++++ .../map/src/utils/Draw/drawRectMode.draw.js | 125 +++++++++++++ .../utils/Draw/drawSimpleSelectMode.draw.js | 86 +++++++++ .../map/src/utils/Draw/drawStaticMode.draw.js | 14 ++ packages/map/src/utils/Draw/drawStyle.ts | 144 +++++++++++++++ packages/map/src/utils/Draw/index.ts | 74 ++++++++ packages/map/src/utils/Draw/utils.js | 162 +++++++++++++++++ 16 files changed, 1141 insertions(+), 34 deletions(-) create mode 100644 packages/map/src/utils/Draw/constants.js create mode 100644 packages/map/src/utils/Draw/doubleClickZoom.js create mode 100644 packages/map/src/utils/Draw/drawCircleMode.draw.js create mode 100644 packages/map/src/utils/Draw/drawDirectMode.draw.js create mode 100644 packages/map/src/utils/Draw/drawLineSelectMode.draw.js create mode 100644 packages/map/src/utils/Draw/drawRectMode.draw.js create mode 100644 packages/map/src/utils/Draw/drawSimpleSelectMode.draw.js create mode 100644 packages/map/src/utils/Draw/drawStaticMode.draw.js create mode 100644 packages/map/src/utils/Draw/drawStyle.ts create mode 100644 packages/map/src/utils/Draw/index.ts create mode 100644 packages/map/src/utils/Draw/utils.js diff --git a/packages/map/src/MapBox.tsx b/packages/map/src/MapBox.tsx index 4cd886a..bd8ab17 100644 --- a/packages/map/src/MapBox.tsx +++ b/packages/map/src/MapBox.tsx @@ -1,40 +1,43 @@ import 'mapbox-gl/dist/mapbox-gl.css'; import Map from 'react-map-gl'; import './index.less'; -import React from 'react'; +import React, { forwardRef, useImperativeHandle, useRef } from 'react'; import { MapProps } from './interface'; import { merge } from './utils'; import { MAP_CENTER, defaultMapConfig } from './constants'; -const MapBox: React.FC = (props) => { +export interface MapRefProps { + +} + +const MapBox = forwardRef((props, ref) => { const { style = {}, children, - mapRef, onLoad, mapCenter = MAP_CENTER, - mapConfig = {}, + height = 600, + width = '100%', ...others } = props || {}; + const mapRef = useRef(null) + + useImperativeHandle(ref, () => ({})) + return ( //@ts-ignore { - if (mapRef) { - mapRef.current = e!; - } - }} + ref={mapRef} + initialViewState={{ ...mapCenter, zoom: 10 }} onLoad={(e) => { onLoad && onLoad(e); }} - style={{ width: '100%', height: 600, ...style }} - {...merge(defaultMapConfig, mapConfig)} - initialViewState={{ ...mapCenter, zoom: 10 }} - {...others} + style={{ width: width, height: height, ...style }} + {...merge(defaultMapConfig, others)} > {children} ); -}; +}); export default MapBox; diff --git a/packages/map/src/constants.ts b/packages/map/src/constants.ts index e06b0dc..8f7ef03 100644 --- a/packages/map/src/constants.ts +++ b/packages/map/src/constants.ts @@ -1,9 +1,11 @@ export const mapboxAccessToken = 'pk.eyJ1IjoiZGluZ2xpMTIzIiwiYSI6ImNra204ODhjczBobTgyeHJ6MmJpZHMxNWgifQ.NbKrXh_hb2gvjr5CEMDnyQ'; -export const MAP_CENTER = { + + export const MAP_CENTER = { longitude: 120.2667694313269, latitude: 30.180942826533766, }; //地图中心 + const MapUrl = 'http://10.0.0.120:30003/map'; export const defaultMapConfig = { mapboxAccessToken, @@ -39,4 +41,4 @@ export const defaultMapConfig = { }, ], }, -}; \ No newline at end of file +}; diff --git a/packages/map/src/demo/basic.tsx b/packages/map/src/demo/basic.tsx index 0f6c999..4be95af 100644 --- a/packages/map/src/demo/basic.tsx +++ b/packages/map/src/demo/basic.tsx @@ -13,12 +13,14 @@ const demo = () => { zoom: map?.getMaxZoom(), }); }; + return ( -
- -
+ ); }; diff --git a/packages/map/src/index.md b/packages/map/src/index.md index 5cd4835..f637030 100644 --- a/packages/map/src/index.md +++ b/packages/map/src/index.md @@ -8,6 +8,7 @@ title: 快速上手 基本用法 + ## API | 参数 | 说明 | 类型 | 默认值 | 版本 | @@ -18,3 +19,9 @@ title: 快速上手 | children | 内部元素 | JSX.Element或JSX.Element[]或Array | {} | - | | mapConfig | 地图配置 | MapConfigProps | defaultMapConfig | - | | onLoad | 地图加载事件 | function | ()=>{} | - | + +## 参考文档 + +[官方文档](https://docs.mapbox.com/mapbox-gl-js/example) +[react-map-gl](https://visgl.github.io/react-map-gl/examples) +[mapbox-gl-draw](https://github.com/mapbox/mapbox-gl-draw) diff --git a/packages/map/src/interface.ts b/packages/map/src/interface.ts index 4001f43..2e440f2 100644 --- a/packages/map/src/interface.ts +++ b/packages/map/src/interface.ts @@ -1,19 +1,18 @@ import { CSSProperties } from "react"; -import { MapRef, MapStyle } from "react-map-gl"; +import { MapboxMap, MapRef, MapStyle } from "react-map-gl"; -export interface MapProps { - onLoad?: (e: mapboxgl.MapboxEvent) => void; - mapRef?: React.MutableRefObject; - style?: CSSProperties; - children?: JSX.Element | JSX.Element[] | Array; - mapConfig?: MapConfigProps - mapCenter: {longitude: number, latitude: number} -} - -export interface MapConfigProps { +export interface MapProps extends Partial { mapboxAccessToken?: string; //token minZoom?: number; //最小层级 maxZoom?: number; //最大层级 - dragRotate?: boolean; //是否支持拖拽旋转 mapStyle?: MapStyle; //地图样式 -} \ No newline at end of file + height?: number | string; + width?: string | number; + onLoad?: (e: mapboxgl.MapboxEvent) => void; + mapRef?: MapRef; + style?: CSSProperties; + children?: JSX.Element | JSX.Element[] | Array; + mapCenter?: { + longitude: number, latitude: number + } +} diff --git a/packages/map/src/utils/Draw/constants.js b/packages/map/src/utils/Draw/constants.js new file mode 100644 index 0000000..3c0bda4 --- /dev/null +++ b/packages/map/src/utils/Draw/constants.js @@ -0,0 +1,100 @@ +export const classes = { + CONTROL_BASE: 'mapboxgl-ctrl', + CONTROL_PREFIX: 'mapboxgl-ctrl-', + CONTROL_BUTTON: 'mapbox-gl-draw_ctrl-draw-btn', + CONTROL_BUTTON_LINE: 'mapbox-gl-draw_line', + CONTROL_BUTTON_POLYGON: 'mapbox-gl-draw_polygon', + CONTROL_BUTTON_POINT: 'mapbox-gl-draw_point', + CONTROL_BUTTON_TRASH: 'mapbox-gl-draw_trash', + CONTROL_BUTTON_COMBINE_FEATURES: 'mapbox-gl-draw_combine', + CONTROL_BUTTON_UNCOMBINE_FEATURES: 'mapbox-gl-draw_uncombine', + CONTROL_GROUP: 'mapboxgl-ctrl-group', + ATTRIBUTION: 'mapboxgl-ctrl-attrib', + ACTIVE_BUTTON: 'active', + BOX_SELECT: 'mapbox-gl-draw_boxselect', +}; + +export const sources = { + HOT: 'mapbox-gl-draw-hot', + COLD: 'mapbox-gl-draw-cold', +}; + +export const cursors = { + ADD: 'add', + MOVE: 'move', + DRAG: 'drag', + POINTER: 'pointer', + NONE: 'none', +}; + +export const types = { + POLYGON: 'polygon', + LINE: 'line_string', + POINT: 'point', +}; + +export const geojsonTypes = { + FEATURE: 'Feature', + POLYGON: 'Polygon', + LINE_STRING: 'LineString', + POINT: 'Point', + FEATURE_COLLECTION: 'FeatureCollection', + MULTI_PREFIX: 'Multi', + MULTI_POINT: 'MultiPoint', + MULTI_LINE_STRING: 'MultiLineString', + MULTI_POLYGON: 'MultiPolygon', +}; + +export const modes = { + DRAW_LINE_STRING: 'draw_line_string', + DRAW_POLYGON: 'draw_polygon', + DRAW_POINT: 'draw_point', + SIMPLE_SELECT: 'simple_select', + DIRECT_SELECT: 'direct_select', + STATIC: 'static', +}; + +export const events = { + CREATE: 'draw.create', + DELETE: 'draw.delete', + UPDATE: 'draw.update', + SELECTION_CHANGE: 'draw.selectionchange', + MODE_CHANGE: 'draw.modechange', + ACTIONABLE: 'draw.actionable', + RENDER: 'draw.render', + COMBINE_FEATURES: 'draw.combine', + UNCOMBINE_FEATURES: 'draw.uncombine', +}; + +export const updateActions = { + MOVE: 'move', + CHANGE_COORDINATES: 'change_coordinates', +}; + +export const meta = { + FEATURE: 'feature', + MIDPOINT: 'midpoint', + VERTEX: 'vertex', +}; + +export const activeStates = { + ACTIVE: 'true', + INACTIVE: 'false', +}; + +export const interactions = [ + 'scrollZoom', + 'boxZoom', + 'dragRotate', + 'dragPan', + 'keyboard', + 'doubleClickZoom', + 'touchZoomRotate', +]; + +export const LAT_MIN = -90; +export const LAT_RENDERED_MIN = -85; +export const LAT_MAX = 90; +export const LAT_RENDERED_MAX = 85; +export const LNG_MIN = -270; +export const LNG_MAX = 270; diff --git a/packages/map/src/utils/Draw/doubleClickZoom.js b/packages/map/src/utils/Draw/doubleClickZoom.js new file mode 100644 index 0000000..4e747e1 --- /dev/null +++ b/packages/map/src/utils/Draw/doubleClickZoom.js @@ -0,0 +1,24 @@ +const doubleClickZoom = { + enable(ctx) { + setTimeout(() => { + if ( + !ctx.map || + !ctx.map.doubleClickZoom || + !ctx._ctx || + !ctx._ctx.store || + !ctx._ctx.store.getInitialConfigValue + ) + return; + if (!ctx._ctx.store.getInitialConfigValue('doubleClickZoom')) return; + ctx.map.doubleClickZoom.enable(); + }, 0); + }, + disable(ctx) { + setTimeout(() => { + if (!ctx.map || !ctx.map.doubleClickZoom) return; + ctx.map.doubleClickZoom.disable(); + }, 0); + }, +}; + +export default doubleClickZoom; diff --git a/packages/map/src/utils/Draw/drawCircleMode.draw.js b/packages/map/src/utils/Draw/drawCircleMode.draw.js new file mode 100644 index 0000000..fca766e --- /dev/null +++ b/packages/map/src/utils/Draw/drawCircleMode.draw.js @@ -0,0 +1,69 @@ +import MapboxDraw from '@mapbox/mapbox-gl-draw'; +import doubleClickZoom from './doubleClickZoom'; +import * as turf from '@turf/turf'; +const { circle, distance, helpers: turfHelpers } = turf; +const drawCircleMode = { ...MapboxDraw.modes.draw_polygon }; +drawCircleMode.onSetup = function () { + const polygon = this.newFeature({ + type: 'Feature', + properties: { + isCircle: true, + center: [], + }, + geometry: { + type: 'Polygon', + coordinates: [], + }, + }); + + this.addFeature(polygon); + + this.clearSelectedFeatures(); + doubleClickZoom.disable(this); + // dragPan.disable(this); + this.updateUIClasses({ mouse: 'add' }); + this.activateUIButton('Polygon'); + this.setActionableState({ + trash: true, + }); + + return { + polygon, + currentVertexPosition: 0, + }; +}; +drawCircleMode.onClick = drawCircleMode.onTap = function (state, e) { + const currentCenter = state.polygon.properties.center; + if (currentCenter.length === 0) { + // dragPan.disable(this) + state.polygon.properties.center = [e.lngLat.lng, e.lngLat.lat]; + } else { + // dragPan.enable(this); + return this.changeMode('simple_select', { featureIds: [state.polygon.id] }); + } +}; +drawCircleMode.onDrag = drawCircleMode.onMouseMove = function (state, e) { + const center = state.polygon.properties.center; + if (center.length > 0) { + const distanceInKm = distance( + turfHelpers.point(center), + turfHelpers.point([e.lngLat.lng, e.lngLat.lat]), + { + units: 'kilometers', + } + ); + const circleFeature = circle(center, distanceInKm); + state.polygon.incomingCoords(circleFeature.geometry.coordinates); + state.polygon.properties.radiusInKm = distanceInKm; + state.polygon.properties.lastClickCoord = [e.lngLat.lng, e.lngLat.lat]; + } +}; +//它决定当前 Drew 数据存储中的哪些特性将在地图上呈现。 +//所有传递给“显示”的特性都将被渲染,因此可以为每个内部特性传递多个显示特性。 +//有关如何制作显示特性的建议,请参阅‘ styling-pull’in‘ API.md’ +drawCircleMode.toDisplayFeatures = function (state, geojson, display) { + const isActivePolygon = geojson.properties.id === state.polygon.id; + geojson.properties.active = isActivePolygon ? 'true' : 'false'; + display(geojson); +}; +export default drawCircleMode; diff --git a/packages/map/src/utils/Draw/drawDirectMode.draw.js b/packages/map/src/utils/Draw/drawDirectMode.draw.js new file mode 100644 index 0000000..2179039 --- /dev/null +++ b/packages/map/src/utils/Draw/drawDirectMode.draw.js @@ -0,0 +1,131 @@ +import MapboxDraw from '@mapbox/mapbox-gl-draw'; +import createSupplementaryPoints from '@mapbox/mapbox-gl-draw'; +import moveFeatures from '@mapbox/mapbox-gl-draw'; +import constrainFeatureMovement from '@mapbox/mapbox-gl-draw'; +import createVertex from '@mapbox/mapbox-gl-draw'; +import * as turf from '@turf/turf'; +const { constants } = MapboxDraw; +const { circle, distance, helpers: turfHelpers } = turf; + +function createSupplementaryPointsForCircle(geojson) { + const { properties, geometry } = geojson; + + if (!properties.user_isCircle) return null; + + const supplementaryPoints = []; + const vertices = geometry.coordinates[0].slice(0, -1); + for (let index = 0; index < vertices.length; index += Math.round(vertices.length / 4)) { + supplementaryPoints.push(createVertex(properties.id, vertices[index], `0.${index}`, false)); + } + return supplementaryPoints; +} +const drawDirectMode = { ...MapboxDraw.modes.direct_select }; + +drawDirectMode.dragFeature = function (state, e, delta) { + moveFeatures(this.getSelected(), delta); + this.getSelected() + .filter((feature) => feature.properties.isCircle) + .map((circle) => circle.properties.center) + .forEach((center) => { + center[0] += delta.lng; + center[1] += delta.lat; + }); + state.dragMoveLocation = e.lngLat; +}; + +drawDirectMode.dragVertex = function (state, e, delta) { + //圆处理 + if (state.feature.properties.isCircle) { + const center = state.feature.properties.center; + const movedVertex = [e.lngLat.lng, e.lngLat.lat]; + const radius = distance(turfHelpers.point(center), turfHelpers.point(movedVertex), { + units: 'kilometers', + }); + const circleFeature = circle(center, radius); + state.feature.incomingCoords(circleFeature.geometry.coordinates); + state.feature.properties.radiusInKm = radius; + return; + } + //矩形处理 + if (state.feature.properties.isRect) { + state.selectedCoordPaths.forEach((coordPath) => { + const selectCoord = state.feature.getCoordinate(coordPath); + //更新边缘2点 + const [featureIndex, coordIndex] = coordPath.split('.'); + const coordinates = state.feature.getCoordinates()[featureIndex]; + //对立点判断 + const coordPosMap = { + 1: '3', + 2: '0', + 3: '1', + 0: '2', + }; + const mapCoord = state.feature.getCoordinate(`${featureIndex}.${coordPosMap[coordIndex]}`); + //如果对立点和坐标x||y 一致 则返回 + if (mapCoord[0] === e.lngLat.lng || mapCoord[1] === e.lngLat.lat) { + return; + } + for (let i = 0; i < 4; i++) { + const coord = coordinates[i]; + if (coordIndex == i) { + continue; + } + + if (coord[0] === selectCoord[0]) { + state.feature.updateCoordinate(`${featureIndex}.${i}`, e.lngLat.lng, coord[1]); + + continue; + } + if (coord[1] === selectCoord[1]) { + state.feature.updateCoordinate(`${featureIndex}.${i}`, coord[0], e.lngLat.lat); + continue; + } + } + //更新拖拽的点 + state.feature.updateCoordinate(coordPath, e.lngLat.lng, e.lngLat.lat); + }); + return; + } + //其他走回默认 + const selectedCoords = state.selectedCoordPaths.map((coordPath) => + state.feature.getCoordinate(coordPath) + ); + const selectedCoordPoints = selectedCoords.map((coords) => ({ + type: constants.geojsonTypes.FEATURE, + properties: {}, + geometry: { + type: constants.geojsonTypes.POINT, + coordinates: coords, + }, + })); + + const constrainedDelta = constrainFeatureMovement(selectedCoordPoints, delta); + for (let i = 0; i < selectedCoords.length; i++) { + const coord = selectedCoords[i]; + state.feature.updateCoordinate( + state.selectedCoordPaths[i], + coord[0] + constrainedDelta.lng, + coord[1] + constrainedDelta.lat + ); + } +}; + +drawDirectMode.toDisplayFeatures = function (state, geojson, push) { + if (state.featureId === geojson.properties.id) { + geojson.properties.active = constants.activeStates.ACTIVE; + push(geojson); + const supplementaryPoints = geojson.properties.user_isCircle + ? createSupplementaryPointsForCircle(geojson) + : createSupplementaryPoints(geojson, { + map: this.map, + midpoints: true, + selectedPaths: state.selectedCoordPaths, + }); + supplementaryPoints.forEach(push); + } else { + geojson.properties.active = constants.activeStates.INACTIVE; + push(geojson); + } + this.fireActionable(state); +}; +export default drawDirectMode; diff --git a/packages/map/src/utils/Draw/drawLineSelectMode.draw.js b/packages/map/src/utils/Draw/drawLineSelectMode.draw.js new file mode 100644 index 0000000..4086da1 --- /dev/null +++ b/packages/map/src/utils/Draw/drawLineSelectMode.draw.js @@ -0,0 +1,165 @@ +import MapboxDraw from '@mapbox/mapbox-gl-draw'; +import doubleClickZoom from './doubleClickZoom'; +const { constants, lib } = MapboxDraw; +const drawLineSelectMode = { + //当模式启动时,这个函数将被调用。 + //draw.changeMode(drawLineSelectMode,params}); 切换模式时,params = opts + //返回的值应该是一个对象,并将传递给所有其他生命周期函数 + onSetup: function (opts) { + const featureId = opts.featureId; + let line; + let currentVertexPosition = 0; + let direction = 'forward'; + if (featureId) { + line = this.getFeature(featureId); + if (!line) { + throw new Error('Could not find a feature with the provided featureId'); + } + let from = opts.from; + if (from && from.type === 'Feature' && from.geometry && from.geometry.type === 'Point') { + from = from.geometry; + } + if (from && from.type === 'Point' && from.coordinates && from.coordinates.length === 2) { + from = from.coordinates; + } + if (!from || !Array.isArray(from)) { + throw new Error( + 'Please use the `from` property to indicate which point to continue the line from' + ); + } + const lastCoord = line.coordinates.length - 1; + if ( + line.coordinates[lastCoord][0] === from[0] && + line.coordinates[lastCoord][1] === from[1] + ) { + currentVertexPosition = lastCoord + 1; + line.addCoordinate(currentVertexPosition, ...line.coordinates[lastCoord]); + } else if (line.coordinates[0][0] === from[0] && line.coordinates[0][1] === from[1]) { + direction = 'backwards'; + currentVertexPosition = 0; + line.addCoordinate(currentVertexPosition, ...line.coordinates[0]); + } else { + throw new Error( + '`from` should match the point at either the start or the end of the provided LineString' + ); + } + } else { + line = this.newFeature({ + type: constants.geojsonTypes.FEATURE, + properties: {}, + geometry: { + type: constants.geojsonTypes.LINE_STRING, + coordinates: [], + }, + }); + currentVertexPosition = 0; + this.addFeature(line); + } + this.clearSelectedFeatures(); + doubleClickZoom.disable(this); + this.updateUIClasses({ mouse: 'add' }); //"+" + this.setActionableState({ + //添加地图事件'draw.actionable' + trash: true, + combineFeatures: false, + uncombineFeatures: false, + }); + return { + line, + currentVertexPosition, + direction, + }; + }, + clickAnywhere: function (state, e) { + if ( + (state.currentVertexPosition > 0 && + lib.isEventAtCoordinates(e, state.line.coordinates[state.currentVertexPosition - 1])) || + (state.direction === 'backwards' && + lib.isEventAtCoordinates(e, state.line.coordinates[state.currentVertexPosition + 1])) + ) { + return this.changeMode(constants.modes.SIMPLE_SELECT, { + featureIds: [state.line.id], + }); + } + + this.updateUIClasses({ mouse: constants.cursors.ADD }); + state.line.updateCoordinate(state.currentVertexPosition, e.lngLat.lng, e.lngLat.lat); + if (state.direction === 'forward') { + state.currentVertexPosition++; + state.line.updateCoordinate(state.currentVertexPosition, e.lngLat.lng, e.lngLat.lat); + } else { + state.line.addCoordinate(0, e.lngLat.lng, e.lngLat.lat); + } + }, + clickOnVertex: function (state) { + //点击在已经有点上 + state.line.properties.name = 'line_select'; + return this.changeMode(constants.modes.SIMPLE_SELECT, { featureIds: [state.line.id] }); + }, + onMouseMove: function (state, e) { + state.line.updateCoordinate(state.currentVertexPosition, e.lngLat.lng, e.lngLat.lat); + if (lib.CommonSelectors.isVertex(e)) { + //再次将鼠标放在之前已经有的点上,改变鼠标样式 + this.updateUIClasses({ mouse: constants.cursors.POINTER }); + } + }, + onClick: function (state, e) { + //再次将鼠标放在之前已经有的点上 + if (lib.CommonSelectors.isVertex(e)) return this.clickOnVertex(state, e); + this.clickAnywhere(state, e); + }, + onKeyUp: function (state, e) { + if (lib.CommonSelectors.isEnterKey(e)) { + this.changeMode(constants.modes.SIMPLE_SELECT, { featureIds: [state.line.id] }); + } else if (lib.CommonSelectors.isEscapeKey(e)) { + this.deleteFeature([state.line.id], { silent: true }); + this.changeMode(constants.modes.SIMPLE_SELECT); + } + }, + onStop: function (state) { + doubleClickZoom.enable(this); + if (this.getFeature(state.line.id) === undefined) return; + state.line.removeCoordinate(`${state.currentVertexPosition}`); //双击停止时,最后两个点位是一样的 + if (state.line.isValid()) { + //'draw.create' + this.map.fire(constants.events.CREATE, { + features: [state.line.toGeoJSON()], + }); + } else { + this.deleteFeature([state.line.id], { silent: true }); + this.changeMode(constants.modes.SIMPLE_SELECT, {}, { silent: true }); + } + }, + onTrash: function (state) { + this.deleteFeature([state.line.id], { silent: true }); + this.changeMode(constants.modes.SIMPLE_SELECT); + }, + //它决定当前 Drew 数据存储中的哪些特性将在地图上呈现。 + //所有传递给“显示”的特性都将被渲染,因此可以为每个内部特性传递多个显示特性。 + //有关如何制作显示特性的建议,请参阅‘ styling-pull’in‘ API.md’ + toDisplayFeatures: function (state, geojson, display) { + const isActiveLine = geojson.properties.id === state.line.id; + geojson.properties.active = isActiveLine + ? constants.activeStates.ACTIVE + : constants.activeStates.INACTIVE; + if (!isActiveLine) { + display(geojson); + return; + } + if (geojson.geometry.coordinates.length < 2) return; + geojson.properties.meta = 'line_distance'; + geojson.properties.name = 'line_distance'; + display( + lib.createVertex( + state.line.id, + geojson.geometry.coordinates[ + state.direction === 'forward' ? geojson.geometry.coordinates.length - 2 : 1 + ], + `${state.direction === 'forward' ? geojson.geometry.coordinates.length - 2 : 1}`, + false + ) + ); + display(geojson); + }, +}; +export default drawLineSelectMode; diff --git a/packages/map/src/utils/Draw/drawRectMode.draw.js b/packages/map/src/utils/Draw/drawRectMode.draw.js new file mode 100644 index 0000000..7f7d976 --- /dev/null +++ b/packages/map/src/utils/Draw/drawRectMode.draw.js @@ -0,0 +1,125 @@ +const doubleClickZoom = { + enable: (ctx) => { + setTimeout(() => { + // First check we've got a map and some context. + if ( + !ctx.map || + !ctx.map.doubleClickZoom || + !ctx._ctx || + !ctx._ctx.store || + !ctx._ctx.store.getInitialConfigValue + ) + return; + // Now check initial state wasn't false (we leave it disabled if so) + if (!ctx._ctx.store.getInitialConfigValue('doubleClickZoom')) return; + ctx.map.doubleClickZoom.enable(); + }, 0); + }, + disable(ctx) { + setTimeout(() => { + if (!ctx.map || !ctx.map.doubleClickZoom) return; + // Always disable here, as it's necessary in some cases. + ctx.map.doubleClickZoom.disable(); + }, 0); + }, +}; + +const DrawRectangle = { + // When the mode starts this function will be called. + onSetup: function () { + const rectangle = this.newFeature({ + type: 'Feature', + properties: { + isRect: true, + }, + geometry: { + type: 'Polygon', + coordinates: [[]], + }, + }); + this.addFeature(rectangle); + this.clearSelectedFeatures(); + doubleClickZoom.disable(this); + this.updateUIClasses({ mouse: 'add' }); + this.setActionableState({ + trash: true, + }); + return { + rectangle, + }; + }, + // support mobile taps + onTap: function (state, e) { + // emulate 'move mouse' to update feature coords + if (state.startPoint) this.onMouseMove(state, e); + // emulate onClick + this.onClick(state, e); + }, + // Whenever a user clicks on the map, Draw will call `onClick` + onClick: function (state, e) { + // if state.startPoint exist, means its second click + //change to simple_select mode + if ( + state.startPoint && + state.startPoint[0] !== e.lngLat.lng && + state.startPoint[1] !== e.lngLat.lat + ) { + this.updateUIClasses({ mouse: 'pointer' }); + state.endPoint = [e.lngLat.lng, e.lngLat.lat]; + this.changeMode('simple_select', { featuresId: state.rectangle.id }); + } + // on first click, save clicked point coords as starting for rectangle + const startPoint = [e.lngLat.lng, e.lngLat.lat]; + state.startPoint = startPoint; + }, + onMouseMove: function (state, e) { + // if startPoint, update the feature coordinates, using the bounding box concept + // we are simply using the startingPoint coordinates and the current Mouse Position + // coordinates to calculate the bounding box on the fly, which will be our rectangle + if (state.startPoint) { + state.rectangle.updateCoordinate('0.0', state.startPoint[0], state.startPoint[1]); //minX, minY - the starting point + state.rectangle.updateCoordinate('0.1', e.lngLat.lng, state.startPoint[1]); // maxX, minY + state.rectangle.updateCoordinate('0.2', e.lngLat.lng, e.lngLat.lat); // maxX, maxY + state.rectangle.updateCoordinate('0.3', state.startPoint[0], e.lngLat.lat); // minX,maxY + state.rectangle.updateCoordinate('0.4', state.startPoint[0], state.startPoint[1]); //minX,minY - ending point (equals to starting point) + } + }, + // Whenever a user clicks on a key while focused on the map, it will be sent here + onKeyUp: function (state, e) { + if (e.keyCode === 27) return this.changeMode('simple_select'); + }, + onStop: function (state) { + doubleClickZoom.enable(this); + this.updateUIClasses({ mouse: 'none' }); + this.activateUIButton(); + + // check to see if we've deleted this feature + if (this.getFeature(state.rectangle.id) === undefined) return; + + //remove last added coordinate + state.rectangle.removeCoordinate('0.4'); + if (state.rectangle.isValid()) { + this.map.fire('draw.create', { + features: [state.rectangle.toGeoJSON()], + }); + } else { + this.deleteFeature([state.rectangle.id], { silent: true }); + this.changeMode('simple_select', {}, { silent: true }); + } + }, + toDisplayFeatures: function (state, geojson, display) { + const isActivePolygon = geojson.properties.id === state.rectangle.id; + geojson.properties.active = isActivePolygon ? 'true' : 'false'; + if (!isActivePolygon) return display(geojson); + + // Only render the rectangular polygon if it has the starting point + if (!state.startPoint) return; + return display(geojson); + }, + onTrash: function (state) { + this.deleteFeature([state.rectangle.id], { silent: true }); + this.changeMode('simple_select'); + }, +}; + +export default DrawRectangle; diff --git a/packages/map/src/utils/Draw/drawSimpleSelectMode.draw.js b/packages/map/src/utils/Draw/drawSimpleSelectMode.draw.js new file mode 100644 index 0000000..a31a5ad --- /dev/null +++ b/packages/map/src/utils/Draw/drawSimpleSelectMode.draw.js @@ -0,0 +1,86 @@ +import MapboxDraw from '@mapbox/mapbox-gl-draw'; +import createSupplementaryPoints from '@mapbox/mapbox-gl-draw'; +import moveFeatures from '@mapbox/mapbox-gl-draw'; +import createVertex from '@mapbox/mapbox-gl-draw'; +import { lineToPoly } from './utils'; +const { constants } = MapboxDraw; + +function createSupplementaryPointsForCircle(geojson) { + const { properties, geometry } = geojson; + + if (!properties.user_isCircle) return null; + + const supplementaryPoints = []; + const vertices = geometry.coordinates[0].slice(0, -1); + for (let index = 0; index < vertices.length; index += Math.round(vertices.length / 4)) { + supplementaryPoints.push(createVertex(properties.id, vertices[index], `0.${index}`, false)); + } + return supplementaryPoints; +} +const drawSimpleSelectMode = { ...MapboxDraw.modes.simple_select }; + +drawSimpleSelectMode.dragMove = function (state, e) { + // Dragging when drag move is enabled + state.dragMoving = true; + e.originalEvent.stopPropagation(); + + const delta = { + lng: e.lngLat.lng - state.dragMoveLocation.lng, + lat: e.lngLat.lat - state.dragMoveLocation.lat, + }; + + moveFeatures(this.getSelected(), delta); + + this.getSelected() + .filter((feature) => feature.properties.isCircle) + .map((circle) => circle.properties.center) + .forEach((center) => { + center[0] += delta.lng; + center[1] += delta.lat; + }); + + state.dragMoveLocation = e.lngLat; +}; + +drawSimpleSelectMode.toDisplayFeatures = function (state, geojson, display) { + geojson.properties.active = this.isSelected(geojson.properties.id) + ? constants.activeStates.ACTIVE + : constants.activeStates.INACTIVE; + + if (geojson.properties.user_name === 'line_select') { + const union = lineToPoly(geojson); + display(union); + } + display(geojson); + this.fireActionable(); + //如果是线 每次都创建点 + if ( + geojson?.properties.active !== constants.activeStates.ACTIVE && + geojson.geometry.type === constants.geojsonTypes.LINE_STRING && + geojson.properties.user_name !== 'line_select' + ) { + const points = createSupplementaryPoints(geojson); + points.forEach(display); + } + + if ( + geojson.properties.active !== constants.activeStates.ACTIVE || + geojson.geometry.type === constants.geojsonTypes.POINT + ) { + return; + } + + let supplementaryPoints; + if (geojson.properties.user_isCircle) { + supplementaryPoints = createSupplementaryPointsForCircle(geojson); + } else { + supplementaryPoints = createSupplementaryPoints(geojson); + } + supplementaryPoints.forEach(display); + + // if(geojson.properties.) +}; + +//阻止框选图形拖拽 +drawSimpleSelectMode.onTap = drawSimpleSelectMode.onClick = function () {}; +export default drawSimpleSelectMode; diff --git a/packages/map/src/utils/Draw/drawStaticMode.draw.js b/packages/map/src/utils/Draw/drawStaticMode.draw.js new file mode 100644 index 0000000..9834c2c --- /dev/null +++ b/packages/map/src/utils/Draw/drawStaticMode.draw.js @@ -0,0 +1,14 @@ +import doubleClickZoom from './doubleClickZoom'; + +var StaticMode = {}; + +StaticMode.onSetup = function () { + this.setActionableState(); // default actionable state is false for all actions + doubleClickZoom.disable(this); //静态model 不运行双击 + return {}; +}; + +StaticMode.toDisplayFeatures = function (state, geojson, display) { + display(geojson); +}; +export default StaticMode; diff --git a/packages/map/src/utils/Draw/drawStyle.ts b/packages/map/src/utils/Draw/drawStyle.ts new file mode 100644 index 0000000..41544ff --- /dev/null +++ b/packages/map/src/utils/Draw/drawStyle.ts @@ -0,0 +1,144 @@ +//自定义画框样式 +const mapboxDrawStyle = [ + // ACTIVE (being drawn) + // line stroke + + { + id: 'gl-draw-line', + type: 'line', + filter: [ + 'all', + ['==', '$type', 'LineString'], + ['!=', 'mode', 'static'], + ['==', 'active', 'true'], + ], + layout: { + 'line-cap': 'round', + 'line-join': 'round', + }, + paint: { + 'line-color': 'rgba(246,67,72,1)', + 'line-dasharray': [0.2, 2], + 'line-width': 2, + }, + }, + { + id: 'gl-draw-line-not-active', + type: 'line', + filter: ['all', ['==', '$type', 'LineString'], ['!=', 'active', 'true']], + layout: { + 'line-cap': 'round', + 'line-join': 'round', + }, + paint: { + 'line-color': 'rgba(246,67,72,1)', + 'line-width': 2, + }, + }, + + // polygon fill + { + id: 'gl-draw-polygon-fill', + type: 'fill', + filter: ['all', ['==', '$type', 'Polygon'], ['!=', 'mode', 'static']], + paint: { + 'fill-color': 'rgba(246,67,72,0.2)', + 'fill-outline-color': 'rgba(246,67,72,0.2)', + }, + }, + // polygon outline stroke + // This doesn't style the first edge of the polygon, which uses the line stroke styling instead + { + id: 'gl-draw-polygon-stroke-active', + type: 'line', + filter: [ + 'all', + ['==', '$type', 'Polygon'], + ['!=', 'mode', 'static'], + ['==', 'active', 'false'], + ], + layout: { + 'line-cap': 'round', + 'line-join': 'round', + }, + paint: { + 'line-color': 'rgba(246,67,72,1)', + 'line-width': 2, + }, + }, + { + id: 'gl-draw-polygon-stroke-active-select', + type: 'line', + filter: ['all', ['==', '$type', 'Polygon'], ['!=', 'mode', 'static'], ['==', 'active', 'true']], + layout: { + 'line-cap': 'round', + 'line-join': 'round', + }, + paint: { + 'line-color': 'rgba(246,67,72,1)', + 'line-dasharray': [0.2, 2], + 'line-width': 2, + }, + }, + // vertex point halos + { + id: 'gl-draw-polygon-and-line-vertex-halo-active', + type: 'circle', + filter: ['all', ['==', 'meta', 'vertex'], ['==', '$type', 'Point'], ['!=', 'mode', 'static']], + paint: { + 'circle-radius': 5, + 'circle-color': '#FFF', + }, + }, + // vertex points + { + id: 'gl-draw-polygon-and-line-vertex-active', + type: 'circle', + filter: ['all', ['==', 'meta', 'vertex'], ['==', '$type', 'Point'], ['!=', 'mode', 'static']], + paint: { + 'circle-radius': 3, + 'circle-color': 'rgba(246,67,72,1)', + }, + }, + + // INACTIVE (static, already drawn) + // line stroke + { + id: 'gl-draw-line-static', + type: 'line', + filter: ['all', ['==', '$type', 'LineString'], ['==', 'mode', 'static']], + layout: { + 'line-cap': 'round', + 'line-join': 'round', + }, + paint: { + 'line-color': '#E13F3F', + 'line-width': 3, + }, + }, + // polygon fill + { + id: 'gl-draw-polygon-fill-static', + type: 'fill', + filter: ['all', ['==', '$type', 'Polygon'], ['==', 'mode', 'static']], + paint: { + 'fill-color': 'rgba(225, 63, 63, 0.2)', + 'fill-outline-color': 'rgba(225, 63, 63, 0.2)', + }, + }, + // polygon outline + { + id: 'gl-draw-polygon-stroke-static', + type: 'line', + filter: ['all', ['==', '$type', 'Polygon'], ['==', 'mode', 'static']], + layout: { + 'line-cap': 'round', + 'line-join': 'round', + }, + paint: { + 'line-color': '#E13F3F', + 'line-width': 3, + }, + }, +]; +export default mapboxDrawStyle; diff --git a/packages/map/src/utils/Draw/index.ts b/packages/map/src/utils/Draw/index.ts new file mode 100644 index 0000000..41a5a32 --- /dev/null +++ b/packages/map/src/utils/Draw/index.ts @@ -0,0 +1,74 @@ +import '@mapbox/mapbox-gl-draw/dist/mapbox-gl-draw.css'; +import MapboxDraw from '@mapbox/mapbox-gl-draw'; +import type { DrawModes } from '@mapbox/mapbox-gl-draw'; +import type { MapboxMap } from 'react-map-gl'; +import drawLineSelectMode from './drawLineSelectMode.draw.js'; +import drawCircleMode from './drawCircleMode.draw.js'; +import drawRectMode from './drawRectMode.draw.js'; +import drawDirectMode from './drawDirectMode.draw.js'; +import drawSimpleSelectMode from './drawSimpleSelectMode.draw.js'; +import drawStaticMode from './drawStaticMode.draw.js'; +import mapboxDrawStyle from './drawStyle'; + +interface DrawControl { + modes?: DrawModes; +} +function noop(): void { + /* do nothing */ +} +//mapbox-gl-draw api +//https://github.com/mapbox/mapbox-gl-draw/blob/main/docs/MODES.md#life-cycle-functions + +function drawControl( + map: MapboxMap, + params: DrawControl = {}, + defaultMode = 'static', + onDrawCreate = () => {} +) { + const { modes: paramModes = {} } = params; + + if (!map) { + return; + } + /** + * MapboxDraw 里面有的 + * draw_line_string 画线 + * draw_polygon 多边形 + * draw_point + * simple_select + * direct_select + * + * 需要自定义的 + * draw_line_select; 路径框选1 + * direct_select; + * simple_select; + * draw_circle;1 + * draw_rect;1 + * + * 用MapboxDraw的 + * draw_polygon 多边形 + * draw_line_string 画线 + */ + + const draw = new MapboxDraw({ + defaultMode: defaultMode, + displayControlsDefault: false, // 取消默认的按钮 + styles: mapboxDrawStyle, + modes: { + ...MapboxDraw.modes, + draw_line_select: drawLineSelectMode, + draw_line_string: drawLineSelectMode, + draw_circle: drawCircleMode, + draw_rect: drawRectMode, + direct_select: drawDirectMode, + simple_select: drawSimpleSelectMode, + static: drawStaticMode, + ...paramModes, + }, + }); + map.addControl(draw); + map.on('draw.create', onDrawCreate || noop); + return draw; +} + +export default drawControl; diff --git a/packages/map/src/utils/Draw/utils.js b/packages/map/src/utils/Draw/utils.js new file mode 100644 index 0000000..fabe2a8 --- /dev/null +++ b/packages/map/src/utils/Draw/utils.js @@ -0,0 +1,162 @@ +import * as turf from '@turf/turf'; +import union from '@turf/union'; + +// 获取2个点的距离 +export const getDistance = (point1, point2) => { + const { x: x1, y: y1 } = point1; + const { x: x2, y: y2 } = point2; + return Math.abs(Math.sqrt(Math.pow(x1 - x2, 2) + Math.pow(y1 - y2, 2))); +}; + +//获取两点(格式例如:[120,30])间物理像素距离 +export function getPixelDistance(map, pointA, pointB) { + const pointAPixel = map.transform.locationPoint(pointA); // A点的像素坐标位置 + const pointBPixel = map.transform.locationPoint(pointB); // B点的像素坐标位置 + // A、B两点的像素坐标距离 + return getDistance(pointAPixel, pointBPixel); +} + +//判断是否是0值 给一个偏移量 +export const isZero = (floatValue) => { + return floatValue > -0.00001 && floatValue < 0.00001; +}; + +//判断2个向量是否平行 +export const isParallel = (vectorA, vectorB) => { + const { x: x1, y: y1 } = vectorA; + const { x: x2, y: y2 } = vectorB; + return isZero(x1 * y2 - y1 * x2); +}; + +//判断一个点是否在线段上 判断向量叉乘是否为0 且x坐标在线段2端点间 +export const isPointOnLine = (vectorA, path) => { + const [vectorB, vectorC] = path; + + //与端点重合 + if ( + (vectorA.x === vectorB.x && vectorA.y === vectorB.y) || + (vectorA.x === vectorC.x && vectorA.y === vectorC.y) + ) { + return true; + } + + //在同一竖直方向,线段竖直,点在该线段所在的直线上 + if (isZero(vectorB.x - vectorC.x) && isZero(vectorB.x - vectorA.x)) { + //已判定点在直线上,若点在两端点中间,即点在线段上 + if ( + (vectorA.y < vectorC.y && vectorA.y > vectorB.y) || + (vectorA.y < vectorB.y && vectorA.y > vectorC.y) + ) { + return true; + } + return false; + } + + //在同一水平方向 + if (isZero(vectorB.y - vectorC.y) && isZero(vectorB.y - vectorA.y)) { + if ( + (vectorA.x < vectorC.x && vectorA.x > vectorB.x) || + (vectorA.x < vectorB.x && vectorA.x > vectorC.x) + ) { + return true; + } + return false; + } + + // 线段倾斜,此时线段所在直线存在斜率 + // 点在直线上,AB与AC斜率相等,且有共同点A,此时AC与AB重合,即点A在直线BC上 + if ( + isZero( + (vectorB.y - vectorA.y) / (vectorB.x - vectorA.x) - + (vectorA.y - vectorC.y) / (vectorA.x - vectorC.x) + ) + ) { + if ( + (vectorB.y - vectorA.y) * (vectorA.y - vectorC.y) > 0 && + (vectorB.x - vectorA.x) * (vectorA.x - vectorC.x) > 0 + ) { + return true; + } + return false; + } +}; + +//获取线段重合部分,如果重合返回合并后的线段 如果不重合 返回传入的线段 +//see https://blog.csdn.net/qq_39108767/article/details/81673921 +//see https://www.cnblogs.com/tuyang1129/p/9390376.html +export const getLineCoincide = (path1, path2) => { + let paths = []; + const [vectorA, vectorB] = path1; + const [vectorC, vectorD] = path2; + + const isOnLineA = isPointOnLine(vectorA, [vectorC, vectorD]); + const isOnLineB = isPointOnLine(vectorB, [vectorC, vectorD]); + const isOnLineC = isPointOnLine(vectorC, [vectorA, vectorB]); + const isOnLineD = isPointOnLine(vectorD, [vectorA, vectorB]); + + const isCollinear = isParallel( + { x: vectorA.x - vectorB.x, y: vectorA.y - vectorB.y }, + { x: vectorC.x - vectorD.x, y: vectorC.y - vectorD.y } + ); + //下面6中情况代表合并 + if (isOnLineA && isOnLineC && isCollinear) { + paths = [[vectorB, vectorD]]; + } + if (isOnLineA && isOnLineD && isCollinear) { + paths = [[vectorB, vectorC]]; + } + if (isOnLineB && isOnLineC && isCollinear) { + paths = [[vectorA, vectorD]]; + } + if (isOnLineB && isOnLineD && isCollinear) { + paths = [[vectorA, vectorC]]; + } + if (isOnLineA && isOnLineB && isCollinear) { + paths = [[vectorC, vectorD]]; + } + if (isOnLineC && isOnLineD && isCollinear) { + paths = [[vectorA, vectorB]]; + } + //未匹配到上述情况,则没有重合 原样返回 + if (paths.length === 0) { + paths = [path1, path2]; + } + return paths; +}; + +// 计算与纬线的角度,正方向向上 +const calcAng = (point, p) => (Math.atan2(point[1] - p[1], point[0] - p[0]) * 180) / Math.PI + 180; + +// 多段折线转polygon,直线左右默认范围50米 +export const lineToPoly = (geojson, r = 50) => { + const linesPolygon = []; + const circlePolygon = []; + for (let i = 0; i < geojson.geometry.coordinates.length - 1; i++) { + const [pointA, pointB] = [geojson.geometry.coordinates[i], geojson.geometry.coordinates[i + 1]]; + const line = turf.lineString([pointA, pointB]); + const ang = calcAng(pointA, pointB); + // 与经线的夹角为偏移方向,右正左负, 右偏移-左偏移 = 180 + const translatedPolyA = turf.transformTranslate(line, r / 1000, -ang); + const translatedPolyB = turf.transformTranslate(line, r / 1000, -ang + 180); + const _line = turf.lineString([ + // 逆时针闭合 + ...translatedPolyA.geometry.coordinates.reverse(), + ...translatedPolyB.geometry.coordinates, + ]); + const linePolygon = turf.lineToPolygon(_line); + linesPolygon.push(linePolygon); + circlePolygon.push(turf.circle(pointA, r / 1000)); + if (i == geojson.geometry.coordinates.length - 2) { + circlePolygon.push(turf.circle(pointB, r / 1000)); + } + } + let _union; + try { + //todo: 新版本union和老版本行为不一致 先用老版本 后续观察原因 + // _union = turf.union(...circlePolygon, ...linesPolygon); + _union = union(...circlePolygon, ...linesPolygon); + } catch (e) { + console.error(e); + } + return _union; +}; From 2e753a7259d83c92195535f5ab8b927781ec674b Mon Sep 17 00:00:00 2001 From: jiangzhixiong <710328466@qq.com> Date: Tue, 21 May 2024 19:07:15 +0800 Subject: [PATCH 2/5] =?UTF-8?q?feat(map):=20=E5=B7=A5=E5=85=B7=E7=AE=B1?= =?UTF-8?q?=E5=AE=8C=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/index.md | 99 +++-- packages/map/package.json | 18 +- packages/map/src/MapBox.tsx | 121 ++++- .../customOverlay/CustomOverlay.tsx | 59 +++ .../src/components/customOverlay/index.tsx | 6 + .../drawControl}/Draw/constants.js | 0 .../drawControl}/Draw/doubleClickZoom.js | 0 .../drawControl}/Draw/drawCircleMode.draw.js | 0 .../drawControl}/Draw/drawDirectMode.draw.js | 0 .../Draw/drawLineSelectMode.draw.js | 0 .../drawControl}/Draw/drawRectMode.draw.js | 0 .../Draw/drawSimpleSelectMode.draw.js | 0 .../drawControl}/Draw/drawStaticMode.draw.js | 0 .../drawControl}/Draw/index.ts | 0 .../components/drawControl/DrawControl.tsx | 89 ++++ .../map/src/components/drawControl/index.tsx | 8 + packages/map/src/components/tools/Tools.tsx | 69 +++ packages/map/src/components/tools/index.less | 64 +++ packages/map/src/components/tools/index.tsx | 8 + packages/map/src/hooks/useDraw.ts | 0 packages/map/src/index.less | 10 +- packages/map/src/index.md | 2 + packages/map/src/utils.ts | 35 -- packages/map/src/{ => utils}/constants.ts | 0 .../map/src/utils/{Draw => }/drawStyle.ts | 0 .../map/src/utils/{Draw/utils.js => index.ts} | 95 +++- packages/meta/src/float-button/BackTop.tsx | 107 +++++ .../meta/src/float-button/FloatButton.tsx | 126 ++++++ .../src/float-button/FloatButtonContent.tsx | 34 ++ .../src/float-button/FloatButtonGroup.tsx | 141 ++++++ packages/meta/src/float-button/PurePanel.tsx | 46 ++ packages/meta/src/float-button/context.ts | 9 + .../meta/src/float-button/demo/back-top.md | 7 + .../meta/src/float-button/demo/back-top.tsx | 17 + .../meta/src/float-button/demo/badge-debug.md | 7 + .../src/float-button/demo/badge-debug.tsx | 24 + packages/meta/src/float-button/demo/badge.md | 7 + packages/meta/src/float-button/demo/badge.tsx | 24 + packages/meta/src/float-button/demo/basic.md | 7 + packages/meta/src/float-button/demo/basic.tsx | 6 + .../meta/src/float-button/demo/controlled.md | 7 + .../meta/src/float-button/demo/controlled.tsx | 28 ++ .../meta/src/float-button/demo/description.md | 11 + .../src/float-button/demo/description.tsx | 23 + .../meta/src/float-button/demo/group-menu.md | 7 + .../meta/src/float-button/demo/group-menu.tsx | 28 ++ packages/meta/src/float-button/demo/group.md | 7 + packages/meta/src/float-button/demo/group.tsx | 21 + .../src/float-button/demo/render-panel.md | 7 + .../src/float-button/demo/render-panel.tsx | 39 ++ packages/meta/src/float-button/demo/shape.md | 7 + packages/meta/src/float-button/demo/shape.tsx | 22 + .../meta/src/float-button/demo/tooltip.md | 7 + .../meta/src/float-button/demo/tooltip.tsx | 6 + packages/meta/src/float-button/demo/type.md | 7 + packages/meta/src/float-button/demo/type.tsx | 12 + packages/meta/src/float-button/index.ts | 10 + packages/meta/src/float-button/index.zh-CN.md | 71 +++ packages/meta/src/float-button/interface.ts | 66 +++ packages/meta/src/float-button/style/index.ts | 413 ++++++++++++++++++ packages/meta/src/float-button/util.ts | 10 + packages/meta/src/index.tsx | 8 +- 62 files changed, 1968 insertions(+), 94 deletions(-) create mode 100644 packages/map/src/components/customOverlay/CustomOverlay.tsx create mode 100644 packages/map/src/components/customOverlay/index.tsx rename packages/map/src/{utils => components/drawControl}/Draw/constants.js (100%) rename packages/map/src/{utils => components/drawControl}/Draw/doubleClickZoom.js (100%) rename packages/map/src/{utils => components/drawControl}/Draw/drawCircleMode.draw.js (100%) rename packages/map/src/{utils => components/drawControl}/Draw/drawDirectMode.draw.js (100%) rename packages/map/src/{utils => components/drawControl}/Draw/drawLineSelectMode.draw.js (100%) rename packages/map/src/{utils => components/drawControl}/Draw/drawRectMode.draw.js (100%) rename packages/map/src/{utils => components/drawControl}/Draw/drawSimpleSelectMode.draw.js (100%) rename packages/map/src/{utils => components/drawControl}/Draw/drawStaticMode.draw.js (100%) rename packages/map/src/{utils => components/drawControl}/Draw/index.ts (100%) create mode 100644 packages/map/src/components/drawControl/DrawControl.tsx create mode 100644 packages/map/src/components/drawControl/index.tsx create mode 100644 packages/map/src/components/tools/Tools.tsx create mode 100644 packages/map/src/components/tools/index.less create mode 100644 packages/map/src/components/tools/index.tsx create mode 100644 packages/map/src/hooks/useDraw.ts delete mode 100644 packages/map/src/utils.ts rename packages/map/src/{ => utils}/constants.ts (100%) rename packages/map/src/utils/{Draw => }/drawStyle.ts (100%) rename packages/map/src/utils/{Draw/utils.js => index.ts} (65%) create mode 100644 packages/meta/src/float-button/BackTop.tsx create mode 100644 packages/meta/src/float-button/FloatButton.tsx create mode 100644 packages/meta/src/float-button/FloatButtonContent.tsx create mode 100644 packages/meta/src/float-button/FloatButtonGroup.tsx create mode 100644 packages/meta/src/float-button/PurePanel.tsx create mode 100644 packages/meta/src/float-button/context.ts create mode 100644 packages/meta/src/float-button/demo/back-top.md create mode 100644 packages/meta/src/float-button/demo/back-top.tsx create mode 100644 packages/meta/src/float-button/demo/badge-debug.md create mode 100644 packages/meta/src/float-button/demo/badge-debug.tsx create mode 100644 packages/meta/src/float-button/demo/badge.md create mode 100644 packages/meta/src/float-button/demo/badge.tsx create mode 100644 packages/meta/src/float-button/demo/basic.md create mode 100644 packages/meta/src/float-button/demo/basic.tsx create mode 100644 packages/meta/src/float-button/demo/controlled.md create mode 100644 packages/meta/src/float-button/demo/controlled.tsx create mode 100644 packages/meta/src/float-button/demo/description.md create mode 100644 packages/meta/src/float-button/demo/description.tsx create mode 100644 packages/meta/src/float-button/demo/group-menu.md create mode 100644 packages/meta/src/float-button/demo/group-menu.tsx create mode 100644 packages/meta/src/float-button/demo/group.md create mode 100644 packages/meta/src/float-button/demo/group.tsx create mode 100644 packages/meta/src/float-button/demo/render-panel.md create mode 100644 packages/meta/src/float-button/demo/render-panel.tsx create mode 100644 packages/meta/src/float-button/demo/shape.md create mode 100644 packages/meta/src/float-button/demo/shape.tsx create mode 100644 packages/meta/src/float-button/demo/tooltip.md create mode 100644 packages/meta/src/float-button/demo/tooltip.tsx create mode 100644 packages/meta/src/float-button/demo/type.md create mode 100644 packages/meta/src/float-button/demo/type.tsx create mode 100644 packages/meta/src/float-button/index.ts create mode 100644 packages/meta/src/float-button/index.zh-CN.md create mode 100644 packages/meta/src/float-button/interface.ts create mode 100644 packages/meta/src/float-button/style/index.ts create mode 100644 packages/meta/src/float-button/util.ts diff --git a/docs/index.md b/docs/index.md index 3255e9f..aa15139 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,40 +1,81 @@ --- hero: title: lambo - description: 致力于提升前端开发效率与规范 + description: 致力于提升前端开发效率与规范(开发前请先阅读开发流程) actions: - text: 快速上手 link: /bizs -features: - - title: biz - emoji: 🍑 - description: 业务库 - - title: hooks - emoji: 💎 - description: hooks - - title: func - emoji: 🌈 - description: 常用函数库 - - title: meta - emoji: ☀️ - description: 原子组件库 - - title: constants - emoji: 🈶️ - description: 静态定义库 - - title: request - emoji: 🥣 - description: 网络请求库 - - title: types - emoji: 🈸 - description: typescript 声明库 - - title: material - emoji: 🥱 - description: 物料库 - - title: cli - emoji: 🐔 - description: 脚手架 +# features: +# - title: biz +# emoji: 🍑 +# description: 业务库 +# - title: hooks +# emoji: 💎 +# description: hooks +# - title: func +# emoji: 🌈 +# description: 常用函数库 +# - title: meta +# emoji: ☀️ +# description: 原子组件库 +# - title: constants +# emoji: 🈶️ +# description: 静态定义库 +# - title: request +# emoji: 🥣 +# description: 网络请求库 +# - title: types +# emoji: 🈸 +# description: typescript 声明库 +# - title: material +# emoji: 🥱 +# description: 物料库 +# - title: cli +# emoji: 🐔 +# description: 脚手架 --- +## 开发流程 + +### 1. 确定需求 + +从 gitlab 上的 [issue](http://10.0.0.88/web-project/zhst-lambo/boards) 模块找到对应的需求。将 Assignee 负责人指派为自己(如果多人协同开发可以将需求拆分为多个需求,分别指派),然后将 Labels 标签改为 doing 状态。(截止日期选填) + +> issuse 命名规则:@zhst/{包名} - {模块名},然后在详情页描述对应需求。 + +### 2. 创建 git 分支 + +按照 git flow 规范从 [master](http://10.0.0.88/web-project/zhst-lambo) 上创建分支, 分支的命名规则参考: + +1. feat/XXX: 需求新增 +2. hotfix/XXX: bug 修复 + +### 3. 开始开发 + +进入项目文件夹,在 packages 下找到对应的 npm 包, 然后在 src 目录下按已有的格式进行开发,如果是功能变更就找到对应的页面进行修改 + +### 4. 提交代码,并提交 mr 到 develop 分支 + +完成开发后,给代码提交 commit,格式参考 ${行为}(${影响范围}): ${变更内容}, 例如: + +> feat(package.json): 修改版本号 +> fix(app.ts): 修改环境变量 + +对应的变更会在最终的 npm 包版本号体现:a.b.c - a 对应重构(一般用不上) - b 对应 feat(功能新增) - c 对应 hotfix(一般是 bug 修复) + +push 完代码之后,在 gitlab 上提交一个 mr 到 develop 分支,指定给对应的人员审核(@江志雄),合并成功之后,将 [issue](http://10.0.0.88/web-project/zhst-lambo/boards) 对应的 Labels 状态改为 waittingPublish。 + +### 5. 发布成功 + +发布成功之后,会有两个行为: + +1. 在钉钉群通知发布成功。 +2. 生成线上预览[说明文档](http://10.0.0.204:30080) + +一旦触发了钉钉通知,则需要去到 [issue](http://10.0.0.88/web-project/zhst-lambo/boards) 板块将对应的需求 **close** 掉。 + +这就是 npm 包整个开发链路。 + ## 目录结构 diff --git a/packages/map/package.json b/packages/map/package.json index 907a3cf..aa0f776 100644 --- a/packages/map/package.json +++ b/packages/map/package.json @@ -37,7 +37,21 @@ "registry": "http://10.0.0.77:4874" }, "dependencies": { - "react-map-gl": "^7.1.7", - "mapbox-gl": "^2.15.0" + "@mapbox/mapbox-gl-draw": "^1.4.3", + "@mapbox/mapbox-gl-draw-static-mode": "^1.0.1", + "@turf/turf": "^6.5.0", + "@turf/union": "^6.5.0", + "@zhst/hooks": "workspace:^0.13.1", + "@zhst/icon": "workspace:^0.5.0", + "@zhst/meta": "workspace:^", + "classnames": "^2.5.1", + "mapbox-gl": "^2.15.0", + "mapbox-gl-draw-circle": "^1.1.2", + "mapbox-gl-draw-geodesic": "^2.3.1", + "mapbox-gl-draw-rectangle-mode": "^1.0.4", + "react-map-gl": "^7.1.7" + }, + "devDependencies": { + "@types/mapbox__mapbox-gl-draw": "^1.4.6" } } diff --git a/packages/map/src/MapBox.tsx b/packages/map/src/MapBox.tsx index bd8ab17..1e02e7d 100644 --- a/packages/map/src/MapBox.tsx +++ b/packages/map/src/MapBox.tsx @@ -1,10 +1,22 @@ import 'mapbox-gl/dist/mapbox-gl.css'; +import '@mapbox/mapbox-gl-draw/dist/mapbox-gl-draw.css'; +import React, { forwardRef, useEffect, useImperativeHandle, useRef, useState } from 'react'; import Map from 'react-map-gl'; -import './index.less'; -import React, { forwardRef, useImperativeHandle, useRef } from 'react'; +import { + // MapboxMap, + MapRef, + // MapStyle +} from "react-map-gl"; +import classnames from 'classnames' +import Tools from './components/tools' +import DrawControl, { DrawControlProps, DrawControlRefProps } from './components/drawControl'; import { MapProps } from './interface'; import { merge } from './utils'; -import { MAP_CENTER, defaultMapConfig } from './constants'; +import { MAP_CENTER, defaultMapConfig } from './utils/constants'; +import './index.less'; +import mapboxDrawStyle from './utils/drawStyle'; + +const componentName = 'zhst-map' export interface MapRefProps { @@ -20,23 +32,102 @@ const MapBox = forwardRef((props, ref) => { width = '100%', ...others } = props || {}; - const mapRef = useRef(null) + const mapRef = useRef(null) + const drawControlRef = useRef(null) + + // 默认绘制配置 + const [drawConfig, setConfig] = useState({ + displayControlsDefault: false, + position: 'top-left', + styles: mapboxDrawStyle, + // Select which mapbox-gl-draw control buttons to add to the map. + controls: { + polygon: true, + trash: true + }, + // The user does not have to click the polygon control button first. + defaultMode: 'draw_polygon', + }) + + const handleDrawCreate = e => { + console.log('handleDrawCreate', e) + } + + const handleDrawUpdate = e => { + console.log('handleDrawUpdate', e) + } + + const handleDrawDelete = e => { + console.log('handleDrawDelete', e) + } + + useEffect(() => { + console.log('drawControlRef', drawControlRef.current?.drawer?.deleteAll()) + }, []) useImperativeHandle(ref, () => ({})) return ( //@ts-ignore - { - onLoad && onLoad(e); - }} - style={{ width: width, height: height, ...style }} - {...merge(defaultMapConfig, others)} - > - {children} - +
+ drawControlRef.current?.drawer?.changeMode?.('draw_circle') + }, + { + label: '矩形框选', + key: 'rect', + icon: 'icon-fang', + onClick: () => drawControlRef.current?.drawer?.changeMode?.('draw_rect') + }, + { + label: '多边形框选', + key: 'more', + icon: 'icon-duobianxing', + onClick: () => drawControlRef.current?.drawer?.changeMode?.('draw_polygon') + }, + { + label: '路径框选', + key: 'path', + icon: 'icon-lujingkuangxuannor', + onClick: () => drawControlRef.current?.drawer?.changeMode?.('draw_line_string') + }, + { + label: '清除', + key: 'clear', + icon: 'icon-cuo', + onClick: () => drawControlRef.current?.drawer?.deleteAll() + } + ]} + /> + { + onLoad && onLoad(e); + }} + style={{ width: width, height: height, ...style }} + {...merge(defaultMapConfig, others)} + > + {/* + + */} + + {children} + +
); }); diff --git a/packages/map/src/components/customOverlay/CustomOverlay.tsx b/packages/map/src/components/customOverlay/CustomOverlay.tsx new file mode 100644 index 0000000..6b86c5f --- /dev/null +++ b/packages/map/src/components/customOverlay/CustomOverlay.tsx @@ -0,0 +1,59 @@ +import * as React from 'react'; +import { useState, cloneElement } from 'react'; +import { useControl } from 'react-map-gl'; +import { createPortal } from 'react-dom'; +import type { MapboxMap, IControl } from 'react-map-gl'; + + +// Based on template in https://docs.mapbox.com/mapbox-gl-js/api/markers/#icontrol +class OverlayControl implements IControl { + _map: MapboxMap | undefined; + _container: HTMLDivElement | undefined; + _redraw: () => void; + + constructor(redraw: () => void) { + this._redraw = redraw; + } + + onAdd(map: MapboxMap) { + this._map = map; + map.on('move', this._redraw); + /* global document */ + this._container = document.createElement('div'); + this._redraw(); + return this._container; + } + + onRemove() { + this._container?.remove(); + this._map?.off('move', this._redraw); + this._map = undefined; + } + + getMap() { + return this._map; + } + + getElement() { + return this._container; + } +} + +/** + * 自定义图层 + */ +const CustomOverlay = (props: {children: React.ReactElement}) => { + const [, setVersion] = useState(0); + + const customControl = useControl(() => { + const forceUpdate = () => setVersion(v => v + 1); + return new OverlayControl(forceUpdate); + }); + + const map = customControl.getMap(); + + // @ts-ignore + return map && createPortal(cloneElement(props.children, { map }), customControl.getElement()); +} + +export default React.memo(CustomOverlay); diff --git a/packages/map/src/components/customOverlay/index.tsx b/packages/map/src/components/customOverlay/index.tsx new file mode 100644 index 0000000..774efb3 --- /dev/null +++ b/packages/map/src/components/customOverlay/index.tsx @@ -0,0 +1,6 @@ +/** + * Created by jiangzhixiong on 2024/05/21 + */ +import CustomOverlay from './CustomOverlay' + +export default CustomOverlay diff --git a/packages/map/src/utils/Draw/constants.js b/packages/map/src/components/drawControl/Draw/constants.js similarity index 100% rename from packages/map/src/utils/Draw/constants.js rename to packages/map/src/components/drawControl/Draw/constants.js diff --git a/packages/map/src/utils/Draw/doubleClickZoom.js b/packages/map/src/components/drawControl/Draw/doubleClickZoom.js similarity index 100% rename from packages/map/src/utils/Draw/doubleClickZoom.js rename to packages/map/src/components/drawControl/Draw/doubleClickZoom.js diff --git a/packages/map/src/utils/Draw/drawCircleMode.draw.js b/packages/map/src/components/drawControl/Draw/drawCircleMode.draw.js similarity index 100% rename from packages/map/src/utils/Draw/drawCircleMode.draw.js rename to packages/map/src/components/drawControl/Draw/drawCircleMode.draw.js diff --git a/packages/map/src/utils/Draw/drawDirectMode.draw.js b/packages/map/src/components/drawControl/Draw/drawDirectMode.draw.js similarity index 100% rename from packages/map/src/utils/Draw/drawDirectMode.draw.js rename to packages/map/src/components/drawControl/Draw/drawDirectMode.draw.js diff --git a/packages/map/src/utils/Draw/drawLineSelectMode.draw.js b/packages/map/src/components/drawControl/Draw/drawLineSelectMode.draw.js similarity index 100% rename from packages/map/src/utils/Draw/drawLineSelectMode.draw.js rename to packages/map/src/components/drawControl/Draw/drawLineSelectMode.draw.js diff --git a/packages/map/src/utils/Draw/drawRectMode.draw.js b/packages/map/src/components/drawControl/Draw/drawRectMode.draw.js similarity index 100% rename from packages/map/src/utils/Draw/drawRectMode.draw.js rename to packages/map/src/components/drawControl/Draw/drawRectMode.draw.js diff --git a/packages/map/src/utils/Draw/drawSimpleSelectMode.draw.js b/packages/map/src/components/drawControl/Draw/drawSimpleSelectMode.draw.js similarity index 100% rename from packages/map/src/utils/Draw/drawSimpleSelectMode.draw.js rename to packages/map/src/components/drawControl/Draw/drawSimpleSelectMode.draw.js diff --git a/packages/map/src/utils/Draw/drawStaticMode.draw.js b/packages/map/src/components/drawControl/Draw/drawStaticMode.draw.js similarity index 100% rename from packages/map/src/utils/Draw/drawStaticMode.draw.js rename to packages/map/src/components/drawControl/Draw/drawStaticMode.draw.js diff --git a/packages/map/src/utils/Draw/index.ts b/packages/map/src/components/drawControl/Draw/index.ts similarity index 100% rename from packages/map/src/utils/Draw/index.ts rename to packages/map/src/components/drawControl/Draw/index.ts diff --git a/packages/map/src/components/drawControl/DrawControl.tsx b/packages/map/src/components/drawControl/DrawControl.tsx new file mode 100644 index 0000000..62788a0 --- /dev/null +++ b/packages/map/src/components/drawControl/DrawControl.tsx @@ -0,0 +1,89 @@ +/** + * Created by jiangzhixiong on 2024/05/21 + */ + +import { forwardRef, useImperativeHandle, useRef, } from 'react' +import MapboxDraw, { MapboxDrawOptions } from '@mapbox/mapbox-gl-draw'; +import { + CircleMode, + DragCircleMode, + DirectMode, + SimpleSelectMode +} from 'mapbox-gl-draw-circle' +import drawRectMode from 'mapbox-gl-draw-rectangle-mode' +import drawStaticMode from '@mapbox/mapbox-gl-draw-static-mode' +// import * as MapboxDrawGeodesic from 'mapbox-gl-draw-geodesic'; +import { useControl } from 'react-map-gl'; +import type { ControlPosition } from 'react-map-gl'; +import { MapContextValue } from 'react-map-gl/dist/esm/components/map'; + +type DrawControlProps = ConstructorParameters[0] & { + position?: ControlPosition; + + onCreate?: (evt: {features: object[]}) => void; + onUpdate?: (evt: {features: object[]; action: string}) => void; + onDelete?: (evt: {features: object[]}) => void; +}; + +export interface DrawControlRefProps { + drawer?: MapboxDraw & { + modes: MapboxDraw.Modes | 'static' | 'draw_rect' | 'drag_circle' | 'draw_circle' | 'direct_select' | 'simple_select' + } +} + +const DrawControl = forwardRef((props, ref) => { + const drawRef = useRef(null) + + useControl( + () => { + let draw = new MapboxDraw( + { + modes: { + ...MapboxDraw.modes, + // draw_line_select: drawLineSelectMode, + draw_rect: drawRectMode, + drag_circle: DragCircleMode, + draw_circle : CircleMode, + direct_select: DirectMode, + simple_select: SimpleSelectMode, + static: drawStaticMode, + }, + ...props + } + ) + // 修改模式 + // drawIns?.changeMode('simple_select'); + drawRef.current = draw + return draw + }, + (context: MapContextValue) => { + const { map } = context + map.on('draw.create', props.onCreate); + map.on('draw.update', props.onUpdate); + map.on('draw.delete', props.onDelete); + }, + (context: MapContextValue) => { + const { map } = context + map.off('draw.create', props.onCreate); + map.off('draw.update', props.onUpdate); + map.off('draw.delete', props.onDelete); + }, + { + position: props.position + } + ); + + useImperativeHandle(ref, () => ({ + drawer: drawRef.current + })) + + return null; +}) + +DrawControl.defaultProps = { + onCreate: () => {}, + onUpdate: () => {}, + onDelete: () => {} +}; + +export default DrawControl diff --git a/packages/map/src/components/drawControl/index.tsx b/packages/map/src/components/drawControl/index.tsx new file mode 100644 index 0000000..5a96584 --- /dev/null +++ b/packages/map/src/components/drawControl/index.tsx @@ -0,0 +1,8 @@ +/** + * Created by jiangzhixiong on 2024/05/21 + */ +import DrawControl from './DrawControl' + +export type { DrawControlProps, DrawControlRefProps } from './DrawControl' + +export default DrawControl diff --git a/packages/map/src/components/tools/Tools.tsx b/packages/map/src/components/tools/Tools.tsx new file mode 100644 index 0000000..299a7c5 --- /dev/null +++ b/packages/map/src/components/tools/Tools.tsx @@ -0,0 +1,69 @@ +/** + * Created by jiangzhixiong on 2024/05/21 + */ + +import React, { forwardRef, ReactNode, useContext, useImperativeHandle } from 'react' +import { Button, ConfigProvider } from '@zhst/meta' +import { useToggle } from '@zhst/hooks' +import classNames from 'classnames' +import { IconFont } from '@zhst/icon' +import './index.less' + +const { ConfigContext } = ConfigProvider + +export interface ToolsProps { + prefixCls?: string + defaultValue?: boolean; + buttonList?: { + label: string + key: string + icon?: ReactNode | string + onClick?: () => void + }[] +} + +export interface ToolsRefProps { +} + +const Tools = forwardRef((props, ref) => { + const { + prefixCls: customizePrefixCls, + buttonList = [], + defaultValue + } = props + const { getPrefixCls } = useContext(ConfigContext) + const componentName = getPrefixCls('map-tools', customizePrefixCls) + const [state, { toggle }] = useToggle(defaultValue) + + useImperativeHandle(ref, () => ({ + + })) + + return ( +
+
    + {buttonList.map((item) => ( + <> +
  • + {typeof item.icon === 'string' ? ( + + ) : ( + item.icon + )} + {item.label} +
  • + + ))} +
+
+ ) +}) + +export default Tools diff --git a/packages/map/src/components/tools/index.less b/packages/map/src/components/tools/index.less new file mode 100644 index 0000000..269732b --- /dev/null +++ b/packages/map/src/components/tools/index.less @@ -0,0 +1,64 @@ +.zhst-map-tools { + position: absolute; + display: flex; + align-items: center; + top: 16px; + right: 16px; + z-index: 1; + + &-navs { + display: none; + position: relative; + left: 24px; + margin: 0; + margin-right: 12px; + padding-left: 0; + list-style: none; + font-size: 0; + background-color: #fff; + border: 1px solid #09f; + border-radius: 3px; + opacity: 0; + transition: .3s ease all; + + &_active { + display: block; + left: 0; + opacity: 1; + } + + &-item { + position: relative; + padding: 6px 12px; + display: inline-flex; + align-items: center; + font-size: 12px; + cursor: pointer; + + &:hover { + color: #09f; + } + + &::after { + position: absolute; + content: ''; + width: 1px; + height: 36%; + right: 0; + top: 50%; + background-color: #f0f0f0; + transform: translateY(-50%); + } + + &:last-child { + &::after { + display: none; + } + } + + &-icon { + font-size: 24px; + } + } + } +} diff --git a/packages/map/src/components/tools/index.tsx b/packages/map/src/components/tools/index.tsx new file mode 100644 index 0000000..a01fbac --- /dev/null +++ b/packages/map/src/components/tools/index.tsx @@ -0,0 +1,8 @@ +/** + * Created by jiangzhixiong on 2024/05/21 + */ +import Tools from './Tools' + +export type { ToolsProps, ToolsRefProps } from './Tools' + +export default Tools diff --git a/packages/map/src/hooks/useDraw.ts b/packages/map/src/hooks/useDraw.ts new file mode 100644 index 0000000..e69de29 diff --git a/packages/map/src/index.less b/packages/map/src/index.less index ac327e9..3369b02 100644 --- a/packages/map/src/index.less +++ b/packages/map/src/index.less @@ -1,3 +1,9 @@ -.mapboxgl-ctrl-attrib-button { - display: none; +.zhst-map { + position: relative; + width: auto; + height: auto; + + .mapboxgl-ctrl-logo { + display: none; + } } diff --git a/packages/map/src/index.md b/packages/map/src/index.md index f637030..9240a6b 100644 --- a/packages/map/src/index.md +++ b/packages/map/src/index.md @@ -25,3 +25,5 @@ title: 快速上手 [官方文档](https://docs.mapbox.com/mapbox-gl-js/example) [react-map-gl](https://visgl.github.io/react-map-gl/examples) [mapbox-gl-draw](https://github.com/mapbox/mapbox-gl-draw) +[draw 绘制模式插件](https://github.com/mapbox/mapbox-gl-draw/blob/main/docs/MODES.md#available-custom-modes) +[mapboxJS](https://www.naivemap.com/mapbox-gl-js-cookbook/starter/handlers/control.html) diff --git a/packages/map/src/utils.ts b/packages/map/src/utils.ts deleted file mode 100644 index f095cdc..0000000 --- a/packages/map/src/utils.ts +++ /dev/null @@ -1,35 +0,0 @@ -const getRawType = (val: any) => { - return Object.prototype.toString.call(val).slice(8, -1) -} - -const isPlainObjectOrArray = (val: any) => { - return isPlainObject(val) || Array.isArray(val) -} - -const isPlainObject = (val: any) => { - return getRawType(val) === 'Object' -} - -export const merge = (object: any, ...sources: any) => { - for(const source of sources) { - for(const key in source) { - if(source[key] === undefined && key in object) { - continue - } - if(isPlainObjectOrArray(source[key])) { - if(getRawType(object[key] === getRawType(source[key]))) { - if(isPlainObject(object[key])) { - merge(object[key], source[key]) - } else { - object[key] = object[key].concat(source[key]) - } - } else { - object[key] = source[key] - } - } else { - object[key] = source[key] - } - } - } - return object; -} \ No newline at end of file diff --git a/packages/map/src/constants.ts b/packages/map/src/utils/constants.ts similarity index 100% rename from packages/map/src/constants.ts rename to packages/map/src/utils/constants.ts diff --git a/packages/map/src/utils/Draw/drawStyle.ts b/packages/map/src/utils/drawStyle.ts similarity index 100% rename from packages/map/src/utils/Draw/drawStyle.ts rename to packages/map/src/utils/drawStyle.ts diff --git a/packages/map/src/utils/Draw/utils.js b/packages/map/src/utils/index.ts similarity index 65% rename from packages/map/src/utils/Draw/utils.js rename to packages/map/src/utils/index.ts index fabe2a8..c91ffad 100644 --- a/packages/map/src/utils/Draw/utils.js +++ b/packages/map/src/utils/index.ts @@ -1,15 +1,58 @@ import * as turf from '@turf/turf'; import union from '@turf/union'; -// 获取2个点的距离 -export const getDistance = (point1, point2) => { +// 判断参数类型 +const getRawType = (val: any) => { + return Object.prototype.toString.call(val).slice(8, -1) +} + +/** + * 判断是否为对象或者数组 + * @param val + * @returns + */ +const isPlainObjectOrArray = (val: any) => { + return isPlainObject(val) || Array.isArray(val) +} + +// 判断参数是否为对象 +const isPlainObject = (val: any) => { + return getRawType(val) === 'Object' +} + +export const merge = (object: any, ...sources: any) => { + for(const source of sources) { + for(const key in source) { + if(source[key] === undefined && key in object) { + continue + } + if(isPlainObjectOrArray(source[key])) { + if(getRawType(object[key] === getRawType(source[key]))) { + if(isPlainObject(object[key])) { + merge(object[key], source[key]) + } else { + object[key] = object[key].concat(source[key]) + } + } else { + object[key] = source[key] + } + } else { + object[key] = source[key] + } + } + } + return object; +} + +// 获取 2 个点的距离 +export const getDistance = (point1: { x: any; y: any }, point2: { x: any; y: any }) => { const { x: x1, y: y1 } = point1; const { x: x2, y: y2 } = point2; return Math.abs(Math.sqrt(Math.pow(x1 - x2, 2) + Math.pow(y1 - y2, 2))); }; //获取两点(格式例如:[120,30])间物理像素距离 -export function getPixelDistance(map, pointA, pointB) { +export function getPixelDistance(map: { transform: { locationPoint: (arg0: any) => any } }, pointA: any, pointB: any) { const pointAPixel = map.transform.locationPoint(pointA); // A点的像素坐标位置 const pointBPixel = map.transform.locationPoint(pointB); // B点的像素坐标位置 // A、B两点的像素坐标距离 @@ -17,19 +60,19 @@ export function getPixelDistance(map, pointA, pointB) { } //判断是否是0值 给一个偏移量 -export const isZero = (floatValue) => { +export const isZero = (floatValue: number) => { return floatValue > -0.00001 && floatValue < 0.00001; }; //判断2个向量是否平行 -export const isParallel = (vectorA, vectorB) => { +export const isParallel = (vectorA: { x: any; y: any }, vectorB: { x: any; y: any }) => { const { x: x1, y: y1 } = vectorA; const { x: x2, y: y2 } = vectorB; return isZero(x1 * y2 - y1 * x2); }; //判断一个点是否在线段上 判断向量叉乘是否为0 且x坐标在线段2端点间 -export const isPointOnLine = (vectorA, path) => { +export const isPointOnLine = (vectorA: { x: number; y: number }, path: any[]) => { const [vectorB, vectorC] = path; //与端点重合 @@ -84,8 +127,8 @@ export const isPointOnLine = (vectorA, path) => { //获取线段重合部分,如果重合返回合并后的线段 如果不重合 返回传入的线段 //see https://blog.csdn.net/qq_39108767/article/details/81673921 //see https://www.cnblogs.com/tuyang1129/p/9390376.html -export const getLineCoincide = (path1, path2) => { - let paths = []; +export const getLineCoincide = (path1: [any, any], path2: [any, any]) => { + let paths: string | any[] = []; const [vectorA, vectorB] = path1; const [vectorC, vectorD] = path2; @@ -125,10 +168,10 @@ export const getLineCoincide = (path1, path2) => { }; // 计算与纬线的角度,正方向向上 -const calcAng = (point, p) => (Math.atan2(point[1] - p[1], point[0] - p[0]) * 180) / Math.PI + 180; +const calcAng = (point: number[], p: number[]) => (Math.atan2(point[1] - p[1], point[0] - p[0]) * 180) / Math.PI + 180; // 多段折线转polygon,直线左右默认范围50米 -export const lineToPoly = (geojson, r = 50) => { +export const lineToPoly = (geojson: { geometry: { coordinates: string | any[] } }, r = 50) => { const linesPolygon = []; const circlePolygon = []; for (let i = 0; i < geojson.geometry.coordinates.length - 1; i++) { @@ -160,3 +203,35 @@ export const lineToPoly = (geojson, r = 50) => { } return _union; }; + +/** + * @description 创建 node,绑定 node 到对应父级, + * @param {string} className 类名 + * @param {node} container 父级 node + * @param {string} code data-code 属性值 + * @param {func} fn click 事件 + * @returns {node} + */ +export function createNode( + node: keyof HTMLElementTagNameMap, + className: string, + textContent: string, + container: HTMLElement, + code?: string, + fn?: EventListener +): HTMLElement { + let a = document.createElement(node) + a.className = className + a.textContent = textContent + + if (code) a.setAttribute('data-code', code) + + if (fn) a.addEventListener('click', fn) + container.appendChild(a) + return a +} + +export function removeNode(node: { remove: () => void; removeEventListener: (arg0: string, arg1: any) => void; }, fn: any) { + node.remove() + if (fn) node.removeEventListener('click', fn) +} diff --git a/packages/meta/src/float-button/BackTop.tsx b/packages/meta/src/float-button/BackTop.tsx new file mode 100644 index 0000000..14cdba4 --- /dev/null +++ b/packages/meta/src/float-button/BackTop.tsx @@ -0,0 +1,107 @@ +import React, { useContext, useEffect, useState } from 'react'; +import VerticalAlignTopOutlined from '@ant-design/icons/VerticalAlignTopOutlined'; +import classNames from 'classnames'; +import CSSMotion from 'rc-motion'; +import { composeRef } from 'rc-util/lib/ref'; + +import getScroll from '../_util/getScroll'; +import scrollTo from '../_util/scrollTo'; +import throttleByAnimationFrame from '../_util/throttleByAnimationFrame'; +import type { ConfigConsumerProps } from '../config-provider'; +import { ConfigContext } from '../config-provider'; +import FloatButtonGroupContext from './context'; +import FloatButton, { floatButtonPrefixCls } from './FloatButton'; +import type { + BackTopProps, + FloatButtonElement, + FloatButtonProps, + FloatButtonRef, + FloatButtonShape, +} from './interface'; + +const BackTop = React.forwardRef((props, ref) => { + const { + prefixCls: customizePrefixCls, + className, + type = 'default', + shape = 'circle', + visibilityHeight = 400, + icon = , + target, + onClick, + duration = 450, + ...restProps + } = props; + + const [visible, setVisible] = useState(visibilityHeight === 0); + + const internalRef = React.useRef(null); + + React.useImperativeHandle(ref, () => ({ + nativeElement: internalRef.current, + })); + + const getDefaultTarget = (): HTMLElement | Document | Window => + internalRef.current && internalRef.current.ownerDocument + ? internalRef.current.ownerDocument + : window; + + const handleScroll = throttleByAnimationFrame( + (e: React.UIEvent | { target: any }) => { + const scrollTop = getScroll(e.target, true); + setVisible(scrollTop >= visibilityHeight); + }, + ); + + useEffect(() => { + const getTarget = target || getDefaultTarget; + const container = getTarget(); + handleScroll({ target: container }); + container?.addEventListener('scroll', handleScroll); + return () => { + handleScroll.cancel(); + container?.removeEventListener('scroll', handleScroll); + }; + }, [target]); + + const scrollToTop: React.MouseEventHandler = (e) => { + scrollTo(0, { getContainer: target || getDefaultTarget, duration }); + onClick?.(e); + }; + + const { getPrefixCls } = useContext(ConfigContext); + + const prefixCls = getPrefixCls(floatButtonPrefixCls, customizePrefixCls); + const rootPrefixCls = getPrefixCls(); + + const groupShape = useContext(FloatButtonGroupContext); + + const mergedShape = groupShape || shape; + + const contentProps: FloatButtonProps = { + prefixCls, + icon, + type, + shape: mergedShape, + ...restProps, + }; + + return ( + + {({ className: motionClassName }, setRef) => ( + + )} + + ); +}); + +if (process.env.NODE_ENV !== 'production') { + BackTop.displayName = 'BackTop'; +} + +export default BackTop; diff --git a/packages/meta/src/float-button/FloatButton.tsx b/packages/meta/src/float-button/FloatButton.tsx new file mode 100644 index 0000000..d7fd3a7 --- /dev/null +++ b/packages/meta/src/float-button/FloatButton.tsx @@ -0,0 +1,126 @@ +import React, { useContext, useMemo } from 'react'; +import classNames from 'classnames'; +import omit from 'rc-util/lib/omit'; + +import { devUseWarning } from '../_util/warning'; +import Badge from '../badge'; +import type { ConfigConsumerProps } from '../config-provider'; +import { ConfigContext } from '../config-provider'; +import useCSSVarCls from '../config-provider/hooks/useCSSVarCls'; +import Tooltip from '../tooltip'; +import type BackTop from './BackTop'; +import FloatButtonGroupContext from './context'; +import Content from './FloatButtonContent'; +import type FloatButtonGroup from './FloatButtonGroup'; +import type { + FloatButtonBadgeProps, + FloatButtonContentProps, + FloatButtonElement, + FloatButtonProps, + FloatButtonShape, +} from './interface'; +import type PurePanel from './PurePanel'; +import useStyle from './style'; + +export const floatButtonPrefixCls = 'float-btn'; + +const InternalFloatButton = React.forwardRef((props, ref) => { + const { + prefixCls: customizePrefixCls, + className, + rootClassName, + type = 'default', + shape = 'circle', + icon, + description, + tooltip, + badge = {}, + ...restProps + } = props; + const { getPrefixCls, direction } = useContext(ConfigContext); + const groupShape = useContext(FloatButtonGroupContext); + const prefixCls = getPrefixCls(floatButtonPrefixCls, customizePrefixCls); + const rootCls = useCSSVarCls(prefixCls); + const [wrapCSSVar, hashId, cssVarCls] = useStyle(prefixCls, rootCls); + + const mergedShape = groupShape || shape; + + const classString = classNames( + hashId, + cssVarCls, + rootCls, + prefixCls, + className, + rootClassName, + `${prefixCls}-${type}`, + `${prefixCls}-${mergedShape}`, + { + [`${prefixCls}-rtl`]: direction === 'rtl', + }, + ); + + // 虽然在 ts 中已经 omit 过了,但是为了防止多余的属性被透传进来,这里再 omit 一遍,以防万一 + const badgeProps = useMemo( + () => omit(badge, ['title', 'children', 'status', 'text'] as any[]), + [badge], + ); + + const contentProps = useMemo( + () => ({ prefixCls, description, icon, type }), + [prefixCls, description, icon, type], + ); + + let buttonNode = ( +
+ +
+ ); + + if ('badge' in props) { + buttonNode = {buttonNode}; + } + + if ('tooltip' in props) { + buttonNode = ( + + {buttonNode} + + ); + } + + if (process.env.NODE_ENV !== 'production') { + const warning = devUseWarning('FloatButton'); + + warning( + !(shape === 'circle' && description), + 'usage', + 'supported only when `shape` is `square`. Due to narrow space for text, short sentence is recommended.', + ); + } + + return wrapCSSVar( + props.href ? ( + + {buttonNode} + + ) : ( + + ), + ); +}); + +type CompoundedComponent = typeof InternalFloatButton & { + Group: typeof FloatButtonGroup; + BackTop: typeof BackTop; + _InternalPanelDoNotUseOrYouWillBeFired: typeof PurePanel; +}; + +const FloatButton = InternalFloatButton as CompoundedComponent; + +if (process.env.NODE_ENV !== 'production') { + FloatButton.displayName = 'FloatButton'; +} + +export default FloatButton; diff --git a/packages/meta/src/float-button/FloatButtonContent.tsx b/packages/meta/src/float-button/FloatButtonContent.tsx new file mode 100644 index 0000000..3e388c8 --- /dev/null +++ b/packages/meta/src/float-button/FloatButtonContent.tsx @@ -0,0 +1,34 @@ +import React, { memo } from 'react'; +import FileTextOutlined from '@ant-design/icons/FileTextOutlined'; +import classNames from 'classnames'; + +import type { FloatButtonContentProps } from './interface'; + +const FloatButtonContent: React.FC = (props) => { + const { icon, description, prefixCls, className } = props; + const defaultElement = ( +
+ +
+ ); + return ( +
+ {icon || description ? ( + <> + {icon &&
{icon}
} + {description &&
{description}
} + + ) : ( + defaultElement + )} +
+ ); +}; + +export default memo(FloatButtonContent); diff --git a/packages/meta/src/float-button/FloatButtonGroup.tsx b/packages/meta/src/float-button/FloatButtonGroup.tsx new file mode 100644 index 0000000..6abfe4d --- /dev/null +++ b/packages/meta/src/float-button/FloatButtonGroup.tsx @@ -0,0 +1,141 @@ +import React, { memo, useCallback, useContext, useEffect } from 'react'; +import CloseOutlined from '@ant-design/icons/CloseOutlined'; +import FileTextOutlined from '@ant-design/icons/FileTextOutlined'; +import classNames from 'classnames'; +import CSSMotion from 'rc-motion'; +import useMergedState from 'rc-util/lib/hooks/useMergedState'; + +import { devUseWarning } from '../_util/warning'; +import type { ConfigConsumerProps } from '../config-provider'; +import { ConfigContext } from '../config-provider'; +import useCSSVarCls from '../config-provider/hooks/useCSSVarCls'; +import { FloatButtonGroupProvider } from './context'; +import FloatButton, { floatButtonPrefixCls } from './FloatButton'; +import type { FloatButtonGroupProps, FloatButtonRef } from './interface'; +import useStyle from './style'; + +const FloatButtonGroup: React.FC = (props) => { + const { + prefixCls: customizePrefixCls, + className, + style, + shape = 'circle', + type = 'default', + icon = , + closeIcon, + description, + trigger, + children, + onOpenChange, + open: customOpen, + ...floatButtonProps + } = props; + + const { direction, getPrefixCls, floatButtonGroup } = + useContext(ConfigContext); + + const mergedCloseIcon = closeIcon ?? floatButtonGroup?.closeIcon ?? ; + + const prefixCls = getPrefixCls(floatButtonPrefixCls, customizePrefixCls); + const rootCls = useCSSVarCls(prefixCls); + const [wrapCSSVar, hashId, cssVarCls] = useStyle(prefixCls, rootCls); + const groupPrefixCls = `${prefixCls}-group`; + + const groupCls = classNames(groupPrefixCls, hashId, cssVarCls, rootCls, className, { + [`${groupPrefixCls}-rtl`]: direction === 'rtl', + [`${groupPrefixCls}-${shape}`]: shape, + [`${groupPrefixCls}-${shape}-shadow`]: !trigger, + }); + + const wrapperCls = classNames(hashId, `${groupPrefixCls}-wrap`); + + const [open, setOpen] = useMergedState(false, { value: customOpen }); + + const floatButtonGroupRef = React.useRef(null); + + const floatButtonRef = React.useRef(null); + + const hoverAction = React.useMemo>(() => { + const hoverTypeAction = { + onMouseEnter() { + setOpen(true); + onOpenChange?.(true); + }, + onMouseLeave() { + setOpen(false); + onOpenChange?.(false); + }, + }; + return trigger === 'hover' ? hoverTypeAction : {}; + }, [trigger]); + + const handleOpenChange = () => { + setOpen((prevState) => { + onOpenChange?.(!prevState); + return !prevState; + }); + }; + + const onClick = useCallback( + (e: MouseEvent) => { + if (floatButtonGroupRef.current?.contains(e.target as Node)) { + if (floatButtonRef.current?.contains(e.target as Node)) { + handleOpenChange(); + } + return; + } + setOpen(false); + onOpenChange?.(false); + }, + [trigger], + ); + + useEffect(() => { + if (trigger === 'click') { + document.addEventListener('click', onClick); + return () => { + document.removeEventListener('click', onClick); + }; + } + }, [trigger]); + + // =================== Warning ===================== + if (process.env.NODE_ENV !== 'production') { + const warning = devUseWarning('FloatButton.Group'); + + warning( + !('open' in props) || !!trigger, + 'usage', + '`open` need to be used together with `trigger`', + ); + } + + return wrapCSSVar( + +
+ {trigger && ['click', 'hover'].includes(trigger) ? ( + <> + + {({ className: motionClassName }) => ( +
{children}
+ )} +
+ + + ) : ( + children + )} +
+
, + ); +}; + +export default memo(FloatButtonGroup); diff --git a/packages/meta/src/float-button/PurePanel.tsx b/packages/meta/src/float-button/PurePanel.tsx new file mode 100644 index 0000000..f7780be --- /dev/null +++ b/packages/meta/src/float-button/PurePanel.tsx @@ -0,0 +1,46 @@ +/* eslint-disable react/no-array-index-key */ +import * as React from 'react'; +import classNames from 'classnames'; + +import { ConfigContext } from '../config-provider'; +import BackTop from './BackTop'; +import FloatButton, { floatButtonPrefixCls } from './FloatButton'; +import FloatButtonGroup from './FloatButtonGroup'; +import type { FloatButtonGroupProps, FloatButtonProps } from './interface'; + +export interface PureFloatButtonProps extends Omit { + backTop?: boolean; +} + +export interface PurePanelProps + extends PureFloatButtonProps, + Omit { + /** Convert to FloatGroup when configured */ + items?: PureFloatButtonProps[]; +} + +const PureFloatButton: React.FC = ({ backTop, ...props }) => + backTop ? : ; + +/** @private Internal Component. Do not use in your production. */ +const PurePanel: React.FC = ({ className, items, ...props }) => { + const { prefixCls: customizePrefixCls } = props; + + const { getPrefixCls } = React.useContext(ConfigContext); + const prefixCls = getPrefixCls(floatButtonPrefixCls, customizePrefixCls); + const pureCls = `${prefixCls}-pure`; + + if (items) { + return ( + + {items.map((item, index) => ( + + ))} + + ); + } + + return ; +}; + +export default PurePanel; diff --git a/packages/meta/src/float-button/context.ts b/packages/meta/src/float-button/context.ts new file mode 100644 index 0000000..798cf9f --- /dev/null +++ b/packages/meta/src/float-button/context.ts @@ -0,0 +1,9 @@ +import React from 'react'; + +import type { FloatButtonShape } from './interface'; + +const FloatButtonGroupContext = React.createContext(undefined); + +export const { Provider: FloatButtonGroupProvider } = FloatButtonGroupContext; + +export default FloatButtonGroupContext; diff --git a/packages/meta/src/float-button/demo/back-top.md b/packages/meta/src/float-button/demo/back-top.md new file mode 100644 index 0000000..15eb759 --- /dev/null +++ b/packages/meta/src/float-button/demo/back-top.md @@ -0,0 +1,7 @@ +## zh-CN + +返回页面顶部的操作按钮。 + +## en-US + +`BackTop` makes it easy to go back to the top of the page. diff --git a/packages/meta/src/float-button/demo/back-top.tsx b/packages/meta/src/float-button/demo/back-top.tsx new file mode 100644 index 0000000..29b5958 --- /dev/null +++ b/packages/meta/src/float-button/demo/back-top.tsx @@ -0,0 +1,17 @@ +import React from 'react'; +import { FloatButton } from 'antd'; + +const App: React.FC = () => ( +
+
Scroll to bottom
+
Scroll to bottom
+
Scroll to bottom
+
Scroll to bottom
+
Scroll to bottom
+
Scroll to bottom
+
Scroll to bottom
+ +
+); + +export default App; diff --git a/packages/meta/src/float-button/demo/badge-debug.md b/packages/meta/src/float-button/demo/badge-debug.md new file mode 100644 index 0000000..19b791b --- /dev/null +++ b/packages/meta/src/float-button/demo/badge-debug.md @@ -0,0 +1,7 @@ +## zh-CN + +调试使用。 + +## en-US + +debug use. diff --git a/packages/meta/src/float-button/demo/badge-debug.tsx b/packages/meta/src/float-button/demo/badge-debug.tsx new file mode 100644 index 0000000..81e1143 --- /dev/null +++ b/packages/meta/src/float-button/demo/badge-debug.tsx @@ -0,0 +1,24 @@ +import React, { useState } from 'react'; +import { ConfigProvider, FloatButton, Slider } from 'antd'; +import type { ConfigProviderProps, GetProp } from 'antd'; + +type AliasToken = GetProp['token']; + +const App: React.FC = () => { + const [radius, setRadius] = useState(0); + + const token: Partial = { + borderRadius: radius, + }; + + return ( + <> + + + + + + ); +}; + +export default App; diff --git a/packages/meta/src/float-button/demo/badge.md b/packages/meta/src/float-button/demo/badge.md new file mode 100644 index 0000000..12fc6e9 --- /dev/null +++ b/packages/meta/src/float-button/demo/badge.md @@ -0,0 +1,7 @@ +## zh-CN + +右上角附带圆形徽标数字的悬浮按钮。 + +## en-US + +FloatButton with Badge. diff --git a/packages/meta/src/float-button/demo/badge.tsx b/packages/meta/src/float-button/demo/badge.tsx new file mode 100644 index 0000000..879f95a --- /dev/null +++ b/packages/meta/src/float-button/demo/badge.tsx @@ -0,0 +1,24 @@ +import React from 'react'; +import { QuestionCircleOutlined } from '@ant-design/icons'; +import { FloatButton } from 'antd'; + +const App: React.FC = () => ( + <> + + + custom badge color} + badge={{ count: 5, color: 'blue' }} + /> + + + + } /> + + + + +); + +export default App; diff --git a/packages/meta/src/float-button/demo/basic.md b/packages/meta/src/float-button/demo/basic.md new file mode 100644 index 0000000..c2120a5 --- /dev/null +++ b/packages/meta/src/float-button/demo/basic.md @@ -0,0 +1,7 @@ +## zh-CN + +最简单的用法。 + +## en-US + +The most basic usage. diff --git a/packages/meta/src/float-button/demo/basic.tsx b/packages/meta/src/float-button/demo/basic.tsx new file mode 100644 index 0000000..0b97fd6 --- /dev/null +++ b/packages/meta/src/float-button/demo/basic.tsx @@ -0,0 +1,6 @@ +import React from 'react'; +import { FloatButton } from 'antd'; + +const App: React.FC = () => console.log('onClick')} />; + +export default App; diff --git a/packages/meta/src/float-button/demo/controlled.md b/packages/meta/src/float-button/demo/controlled.md new file mode 100644 index 0000000..ac17a7d --- /dev/null +++ b/packages/meta/src/float-button/demo/controlled.md @@ -0,0 +1,7 @@ +## zh-CN + +通过 `open` 设置组件为受控模式,需要配合 trigger 一起使用。 + +## en-US + +Set the component to controlled mode through `open`, which need to be used together with trigger. diff --git a/packages/meta/src/float-button/demo/controlled.tsx b/packages/meta/src/float-button/demo/controlled.tsx new file mode 100644 index 0000000..95489b5 --- /dev/null +++ b/packages/meta/src/float-button/demo/controlled.tsx @@ -0,0 +1,28 @@ +import React, { useState } from 'react'; +import { CommentOutlined, CustomerServiceOutlined } from '@ant-design/icons'; +import { FloatButton, Switch } from 'antd'; + +const App: React.FC = () => { + const [open, setOpen] = useState(true); + + const onChange = (checked: boolean) => { + setOpen(checked); + }; + + return ( + <> + } + > + + } /> + + + + ); +}; + +export default App; diff --git a/packages/meta/src/float-button/demo/description.md b/packages/meta/src/float-button/demo/description.md new file mode 100644 index 0000000..a20129e --- /dev/null +++ b/packages/meta/src/float-button/demo/description.md @@ -0,0 +1,11 @@ +## zh-CN + +可以通过 `description` 设置文字内容。 + +> 仅当 `shape` 属性为 `square` 时支持。由于空间较小,推荐使用比较精简的双数文字。 + +## en-US + +Setting `description` prop to show FloatButton with description. + +> supported only when `shape` is `square`. Due to narrow space for text, short sentence is recommended. diff --git a/packages/meta/src/float-button/demo/description.tsx b/packages/meta/src/float-button/demo/description.tsx new file mode 100644 index 0000000..a0f8474 --- /dev/null +++ b/packages/meta/src/float-button/demo/description.tsx @@ -0,0 +1,23 @@ +import React from 'react'; +import { FileTextOutlined } from '@ant-design/icons'; +import { FloatButton } from 'antd'; + +const App: React.FC = () => ( + <> + } + description="HELP INFO" + shape="square" + style={{ right: 24 }} + /> + + } + description="HELP" + shape="square" + style={{ right: 164 }} + /> + +); + +export default App; diff --git a/packages/meta/src/float-button/demo/group-menu.md b/packages/meta/src/float-button/demo/group-menu.md new file mode 100644 index 0000000..1f53c07 --- /dev/null +++ b/packages/meta/src/float-button/demo/group-menu.md @@ -0,0 +1,7 @@ +## zh-CN + +设置 `trigger` 属性即可开启菜单模式。提供 `hover` 和 `click` 两种触发方式。 + +## en-US + +Open menu mode with `trigger`, which could be `hover` or `click`. diff --git a/packages/meta/src/float-button/demo/group-menu.tsx b/packages/meta/src/float-button/demo/group-menu.tsx new file mode 100644 index 0000000..302600f --- /dev/null +++ b/packages/meta/src/float-button/demo/group-menu.tsx @@ -0,0 +1,28 @@ +import React from 'react'; +import { CommentOutlined, CustomerServiceOutlined } from '@ant-design/icons'; +import { FloatButton } from 'antd'; + +const App: React.FC = () => ( + <> + } + > + + } /> + + } + > + + } /> + + +); + +export default App; diff --git a/packages/meta/src/float-button/demo/group.md b/packages/meta/src/float-button/demo/group.md new file mode 100644 index 0000000..a5094ac --- /dev/null +++ b/packages/meta/src/float-button/demo/group.md @@ -0,0 +1,7 @@ +## zh-CN + +按钮组合使用时,推荐使用 ``,并通过设置 `shape` 属性改变悬浮按钮组的形状。悬浮按钮组的 `shape` 会覆盖内部 FloatButton 的 `shape` 属性。 + +## en-US + +When multiple buttons are used together, `` is recommended. By setting `shape` of FloatButton.Group, you can change the shape of group. `shape` of FloatButton.Group will override `shape` of FloatButton inside. diff --git a/packages/meta/src/float-button/demo/group.tsx b/packages/meta/src/float-button/demo/group.tsx new file mode 100644 index 0000000..60c8cd5 --- /dev/null +++ b/packages/meta/src/float-button/demo/group.tsx @@ -0,0 +1,21 @@ +import React from 'react'; +import { QuestionCircleOutlined, SyncOutlined } from '@ant-design/icons'; +import { FloatButton } from 'antd'; + +const App: React.FC = () => ( + <> + + } /> + + + + + } /> + + } /> + + + +); + +export default App; diff --git a/packages/meta/src/float-button/demo/render-panel.md b/packages/meta/src/float-button/demo/render-panel.md new file mode 100644 index 0000000..70bcbec --- /dev/null +++ b/packages/meta/src/float-button/demo/render-panel.md @@ -0,0 +1,7 @@ +## zh-CN + +调试用组件,请勿直接使用。 + +## en-US + +Debug usage. Do not use in your production. diff --git a/packages/meta/src/float-button/demo/render-panel.tsx b/packages/meta/src/float-button/demo/render-panel.tsx new file mode 100644 index 0000000..9c3c35e --- /dev/null +++ b/packages/meta/src/float-button/demo/render-panel.tsx @@ -0,0 +1,39 @@ +import React from 'react'; +import { CustomerServiceOutlined, QuestionCircleOutlined, SyncOutlined } from '@ant-design/icons'; +import { FloatButton } from 'antd'; + +/** Test usage. Do not use in your production. */ +const { _InternalPanelDoNotUseOrYouWillBeFired: InternalFloatButton } = FloatButton; + +const App: React.FC = () => ( +
+ + } /> + } + description="HELP" + shape="square" + type="primary" + /> + }, + { icon: }, + { icon: }, + ]} + /> + } + trigger="click" + items={[ + { icon: }, + { icon: }, + { icon: }, + ]} + /> +
+); + +export default App; diff --git a/packages/meta/src/float-button/demo/shape.md b/packages/meta/src/float-button/demo/shape.md new file mode 100644 index 0000000..bd624dd --- /dev/null +++ b/packages/meta/src/float-button/demo/shape.md @@ -0,0 +1,7 @@ +## zh-CN + +通过 `shape` 设置不同的形状。 + +## en-US + +Change the shape of the FloatButton with `shape`. diff --git a/packages/meta/src/float-button/demo/shape.tsx b/packages/meta/src/float-button/demo/shape.tsx new file mode 100644 index 0000000..6fc6f72 --- /dev/null +++ b/packages/meta/src/float-button/demo/shape.tsx @@ -0,0 +1,22 @@ +import React from 'react'; +import { CustomerServiceOutlined } from '@ant-design/icons'; +import { FloatButton } from 'antd'; + +const App: React.FC = () => ( + <> + } + /> + } + /> + +); + +export default App; diff --git a/packages/meta/src/float-button/demo/tooltip.md b/packages/meta/src/float-button/demo/tooltip.md new file mode 100644 index 0000000..e0f254c --- /dev/null +++ b/packages/meta/src/float-button/demo/tooltip.md @@ -0,0 +1,7 @@ +## zh-CN + +设置 tooltip 属性,即可开启气泡卡片。 + +## en-US + +Setting `tooltip` prop to show FloatButton with tooltip. diff --git a/packages/meta/src/float-button/demo/tooltip.tsx b/packages/meta/src/float-button/demo/tooltip.tsx new file mode 100644 index 0000000..73574da --- /dev/null +++ b/packages/meta/src/float-button/demo/tooltip.tsx @@ -0,0 +1,6 @@ +import React from 'react'; +import { FloatButton } from 'antd'; + +const App: React.FC = () => Documents} />; + +export default App; diff --git a/packages/meta/src/float-button/demo/type.md b/packages/meta/src/float-button/demo/type.md new file mode 100644 index 0000000..30c65cb --- /dev/null +++ b/packages/meta/src/float-button/demo/type.md @@ -0,0 +1,7 @@ +## zh-CN + +通过 `type` 改变悬浮按钮的类型。 + +## en-US + +Change the type of the FloatButton with `type`. diff --git a/packages/meta/src/float-button/demo/type.tsx b/packages/meta/src/float-button/demo/type.tsx new file mode 100644 index 0000000..58f7292 --- /dev/null +++ b/packages/meta/src/float-button/demo/type.tsx @@ -0,0 +1,12 @@ +import React from 'react'; +import { QuestionCircleOutlined } from '@ant-design/icons'; +import { FloatButton } from 'antd'; + +const App: React.FC = () => ( + <> + } type="primary" style={{ right: 24 }} /> + } type="default" style={{ right: 94 }} /> + +); + +export default App; diff --git a/packages/meta/src/float-button/index.ts b/packages/meta/src/float-button/index.ts new file mode 100644 index 0000000..663dda1 --- /dev/null +++ b/packages/meta/src/float-button/index.ts @@ -0,0 +1,10 @@ +import BackTop from './BackTop'; +import FloatButton from './FloatButton'; +import FloatButtonGroup from './FloatButtonGroup'; +import PurePanel from './PurePanel'; + +FloatButton.BackTop = BackTop; +FloatButton.Group = FloatButtonGroup; +FloatButton._InternalPanelDoNotUseOrYouWillBeFired = PurePanel; + +export default FloatButton; diff --git a/packages/meta/src/float-button/index.zh-CN.md b/packages/meta/src/float-button/index.zh-CN.md new file mode 100644 index 0000000..fb4f05f --- /dev/null +++ b/packages/meta/src/float-button/index.zh-CN.md @@ -0,0 +1,71 @@ +--- +category: Components +group: 通用 +title: FloatButton +subtitle: 悬浮按钮 +toc: content +description: 悬浮于页面上方的按钮。 +demo: + cols: 2 +tag: 5.0.0 +--- + +## 何时使用 + +- 用于网站上的全局功能; +- 无论浏览到何处都可以看见的按钮。 + +## 代码演示 + + +基本 +类型 +形状 +描述 +含有气泡卡片的悬浮按钮 +浮动按钮组 +菜单模式 +受控模式 +回到顶部 +徽标数 +调试小圆点使用 +\_InternalPanelDoNotUseOrYouWillBeFired + +## API + +通用属性参考:[通用属性](/docs/react/common-props) + +> 自 `antd@5.0.0` 版本开始提供该组件。 + +### 共同的 API + +| 参数 | 说明 | 类型 | 默认值 | 版本 | +| --- | --- | --- | --- | --- | +| icon | 自定义图标 | ReactNode | - | | +| description | 文字及其它内容 | ReactNode | - | | +| tooltip | 气泡卡片的内容 | ReactNode \| () => ReactNode | - | | +| type | 设置按钮类型 | `default` \| `primary` | `default` | | +| shape | 设置按钮形状 | `circle` \| `square` | `circle` | | +| onClick | 点击按钮时的回调 | (event) => void | - | | +| href | 点击跳转的地址,指定此属性 button 的行为和 a 链接一致 | string | - | | +| target | 相当于 a 标签的 target 属性,href 存在时生效 | string | - | | +| badge | 带徽标数字的悬浮按钮(不支持 `status` 以及相关属性) | [BadgeProps](/components/badge-cn#api) | - | 5.4.0 | + +### FloatButton.Group + +| 参数 | 说明 | 类型 | 默认值 | 版本 | +| --- | --- | --- | --- | --- | +| shape | 设置包含的 FloatButton 按钮形状 | `circle` \| `square` | `circle` | | +| trigger | 触发方式(有触发方式为菜单模式) | `click` \| `hover` | - | | +| open | 受控展开,需配合 trigger 一起使用 | boolean | - | | +| closeIcon | 自定义关闭按钮 | React.ReactNode | `` | | +| onOpenChange | 展开收起时的回调,需配合 trigger 一起使用 | (open: boolean) => void | - | | + +### FloatButton.BackTop + +| 参数 | 说明 | 类型 | 默认值 | 版本 | +| ---------------- | ---------------------------------- | ----------------- | ------------ | ---- | +| duration | 回到顶部所需时间(ms) | number | 450 | | +| target | 设置需要监听其滚动事件的元素 | () => HTMLElement | () => window | | +| visibilityHeight | 滚动高度达到此参数值才出现 BackTop | number | 400 | | +| onClick | 点击按钮的回调函数 | () => void | - | | diff --git a/packages/meta/src/float-button/interface.ts b/packages/meta/src/float-button/interface.ts new file mode 100644 index 0000000..72f0fba --- /dev/null +++ b/packages/meta/src/float-button/interface.ts @@ -0,0 +1,66 @@ +import type React from 'react'; + +import type { BadgeProps } from '../badge'; +import type { TooltipProps } from '../tooltip'; + +export type FloatButtonElement = HTMLAnchorElement & HTMLButtonElement; + +export interface FloatButtonRef { + nativeElement: FloatButtonElement | null; +} + +export type FloatButtonType = 'default' | 'primary'; + +export type FloatButtonShape = 'circle' | 'square'; + +export type FloatButtonGroupTrigger = 'click' | 'hover'; + +export type FloatButtonBadgeProps = Omit; + +export interface FloatButtonProps extends React.DOMAttributes { + prefixCls?: string; + className?: string; + rootClassName?: string; + style?: React.CSSProperties; + icon?: React.ReactNode; + description?: React.ReactNode; + type?: FloatButtonType; + shape?: FloatButtonShape; + tooltip?: TooltipProps['title']; + href?: string; + target?: React.HTMLAttributeAnchorTarget; + badge?: FloatButtonBadgeProps; + ['aria-label']?: React.HtmlHTMLAttributes['aria-label']; +} + +export interface FloatButtonContentProps extends React.DOMAttributes { + className?: string; + icon?: FloatButtonProps['icon']; + description?: FloatButtonProps['description']; + prefixCls: FloatButtonProps['prefixCls']; +} + +export interface FloatButtonGroupProps extends FloatButtonProps { + // 包含的 Float Button + children: React.ReactNode; + // 触发方式 (有触发方式为菜单模式) + trigger?: FloatButtonGroupTrigger; + // 受控展开 + open?: boolean; + // 关闭按钮自定义图标 + closeIcon?: React.ReactNode; + // 展开收起的回调 + onOpenChange?: (open: boolean) => void; +} + +export interface BackTopProps extends Omit { + visibilityHeight?: number; + onClick?: React.MouseEventHandler; + target?: () => HTMLElement | Window | Document; + prefixCls?: string; + children?: React.ReactNode; + className?: string; + rootClassName?: string; + style?: React.CSSProperties; + duration?: number; +} diff --git a/packages/meta/src/float-button/style/index.ts b/packages/meta/src/float-button/style/index.ts new file mode 100644 index 0000000..79090d2 --- /dev/null +++ b/packages/meta/src/float-button/style/index.ts @@ -0,0 +1,413 @@ +import type { CSSObject } from '@ant-design/cssinjs'; +import { Keyframes, unit } from '@ant-design/cssinjs'; + +import { resetComponent } from '../../style'; +import { initFadeMotion } from '../../style/motion/fade'; +import { initMotion } from '../../style/motion/motion'; +import type { FullToken, GenerateStyle, GetDefaultToken } from '../../theme/internal'; +import { genStyleHooks, mergeToken } from '../../theme/internal'; +import getOffset from '../util'; + +/** Component only token. Which will handle additional calculation of alias token */ +export interface ComponentToken { + /** + * Offset of the badge dot in a circular button + * @internal + */ + dotOffsetInCircle: number; + /** + * Offset of the badge dot in a square button + * @internal + */ + dotOffsetInSquare: number; +} + +type FloatButtonToken = FullToken<'FloatButton'> & { + floatButtonColor: string; + floatButtonBackgroundColor: string; + floatButtonHoverBackgroundColor: string; + floatButtonFontSize: number; + floatButtonSize: number; + floatButtonIconSize: number | string; + floatButtonBodySize: number | string; + floatButtonBodyPadding: number; + badgeOffset: number | string; + + // Position + floatButtonInsetBlockEnd: number; + floatButtonInsetInlineEnd: number; +}; + +const initFloatButtonGroupMotion = (token: FloatButtonToken) => { + const { componentCls, floatButtonSize, motionDurationSlow, motionEaseInOutCirc } = token; + const groupPrefixCls = `${componentCls}-group`; + const moveDownIn = new Keyframes('antFloatButtonMoveDownIn', { + '0%': { + transform: `translate3d(0, ${unit(floatButtonSize)}, 0)`, + transformOrigin: '0 0', + opacity: 0, + }, + '100%': { + transform: 'translate3d(0, 0, 0)', + transformOrigin: '0 0', + opacity: 1, + }, + }); + + const moveDownOut = new Keyframes('antFloatButtonMoveDownOut', { + '0%': { + transform: 'translate3d(0, 0, 0)', + transformOrigin: '0 0', + opacity: 1, + }, + '100%': { + transform: `translate3d(0, ${unit(floatButtonSize)}, 0)`, + transformOrigin: '0 0', + opacity: 0, + }, + }); + + return [ + { + [`${groupPrefixCls}-wrap`]: { + ...initMotion(`${groupPrefixCls}-wrap`, moveDownIn, moveDownOut, motionDurationSlow, true), + }, + }, + { + [`${groupPrefixCls}-wrap`]: { + [` + &${groupPrefixCls}-wrap-enter, + &${groupPrefixCls}-wrap-appear + `]: { + opacity: 0, + animationTimingFunction: motionEaseInOutCirc, + }, + [`&${groupPrefixCls}-wrap-leave`]: { + animationTimingFunction: motionEaseInOutCirc, + }, + }, + }, + ]; +}; + +// ============================== Group ============================== +const floatButtonGroupStyle: GenerateStyle = (token) => { + const { + antCls, + componentCls, + floatButtonSize, + margin, + borderRadiusLG, + borderRadiusSM, + badgeOffset, + floatButtonBodyPadding, + calc, + } = token; + const groupPrefixCls = `${componentCls}-group`; + return { + [groupPrefixCls]: { + ...resetComponent(token), + zIndex: 99, + display: 'block', + border: 'none', + position: 'fixed', + width: floatButtonSize, + height: 'auto', + boxShadow: 'none', + minHeight: floatButtonSize, + insetInlineEnd: token.floatButtonInsetInlineEnd, + insetBlockEnd: token.floatButtonInsetBlockEnd, + borderRadius: borderRadiusLG, + + [`${groupPrefixCls}-wrap`]: { + zIndex: -1, + display: 'block', + position: 'relative', + marginBottom: margin, + }, + [`&${groupPrefixCls}-rtl`]: { + direction: 'rtl', + }, + [componentCls]: { + position: 'static', + }, + }, + [`${groupPrefixCls}-circle`]: { + [`${componentCls}-circle:not(:last-child)`]: { + marginBottom: token.margin, + [`${componentCls}-body`]: { + width: floatButtonSize, + height: floatButtonSize, + borderRadius: '50%', + }, + }, + }, + [`${groupPrefixCls}-square`]: { + [`${componentCls}-square`]: { + borderRadius: 0, + padding: 0, + '&:first-child': { + borderStartStartRadius: borderRadiusLG, + borderStartEndRadius: borderRadiusLG, + }, + '&:last-child': { + borderEndStartRadius: borderRadiusLG, + borderEndEndRadius: borderRadiusLG, + }, + '&:not(:last-child)': { + borderBottom: `${unit(token.lineWidth)} ${token.lineType} ${token.colorSplit}`, + }, + [`${antCls}-badge`]: { + [`${antCls}-badge-count`]: { + top: calc(calc(floatButtonBodyPadding).add(badgeOffset)).mul(-1).equal(), + insetInlineEnd: calc(calc(floatButtonBodyPadding).add(badgeOffset)).mul(-1).equal(), + }, + }, + }, + [`${groupPrefixCls}-wrap`]: { + display: 'block', + borderRadius: borderRadiusLG, + boxShadow: token.boxShadowSecondary, + [`${componentCls}-square`]: { + boxShadow: 'none', + marginTop: 0, + borderRadius: 0, + padding: floatButtonBodyPadding, + '&:first-child': { + borderStartStartRadius: borderRadiusLG, + borderStartEndRadius: borderRadiusLG, + }, + '&:last-child': { + borderEndStartRadius: borderRadiusLG, + borderEndEndRadius: borderRadiusLG, + }, + '&:not(:last-child)': { + borderBottom: `${unit(token.lineWidth)} ${token.lineType} ${token.colorSplit}`, + }, + [`${componentCls}-body`]: { + width: token.floatButtonBodySize, + height: token.floatButtonBodySize, + }, + }, + }, + }, + [`${groupPrefixCls}-circle-shadow`]: { + boxShadow: 'none', + }, + [`${groupPrefixCls}-square-shadow`]: { + boxShadow: token.boxShadowSecondary, + [`${componentCls}-square`]: { + boxShadow: 'none', + padding: floatButtonBodyPadding, + [`${componentCls}-body`]: { + width: token.floatButtonBodySize, + height: token.floatButtonBodySize, + borderRadius: borderRadiusSM, + }, + }, + }, + }; +}; + +// ============================== Shared ============================== +const sharedFloatButtonStyle: GenerateStyle = (token) => { + const { + antCls, + componentCls, + floatButtonBodyPadding, + floatButtonIconSize, + floatButtonSize, + borderRadiusLG, + badgeOffset, + dotOffsetInSquare, + dotOffsetInCircle, + calc, + } = token; + return { + [componentCls]: { + ...resetComponent(token), + border: 'none', + position: 'fixed', + cursor: 'pointer', + zIndex: 99, + // Do not remove the 'display: block' here. + // Deleting it will cause marginBottom to become ineffective. + // Ref: https://github.com/ant-design/ant-design/issues/44700 + display: 'block', + width: floatButtonSize, + height: floatButtonSize, + insetInlineEnd: token.floatButtonInsetInlineEnd, + insetBlockEnd: token.floatButtonInsetBlockEnd, + boxShadow: token.boxShadowSecondary, + // Pure Panel + '&-pure': { + position: 'relative', + inset: 'auto', + }, + '&:empty': { + display: 'none', + }, + [`${antCls}-badge`]: { + width: '100%', + height: '100%', + [`${antCls}-badge-count`]: { + transform: 'translate(0, 0)', + transformOrigin: 'center', + top: calc(badgeOffset).mul(-1).equal(), + insetInlineEnd: calc(badgeOffset).mul(-1).equal(), + }, + }, + [`${componentCls}-body`]: { + width: '100%', + height: '100%', + display: 'flex', + justifyContent: 'center', + alignItems: 'center', + transition: `all ${token.motionDurationMid}`, + [`${componentCls}-content`]: { + overflow: 'hidden', + textAlign: 'center', + minHeight: floatButtonSize, + display: 'flex', + flexDirection: 'column', + justifyContent: 'center', + alignItems: 'center', + padding: `${unit(calc(floatButtonBodyPadding).div(2).equal())} ${unit( + floatButtonBodyPadding, + )}`, + [`${componentCls}-icon`]: { + textAlign: 'center', + margin: 'auto', + width: floatButtonIconSize, + fontSize: floatButtonIconSize, + lineHeight: 1, + }, + }, + }, + }, + + [`${componentCls}-rtl`]: { + direction: 'rtl', + }, + [`${componentCls}-circle`]: { + height: floatButtonSize, + borderRadius: '50%', + [`${antCls}-badge`]: { + [`${antCls}-badge-dot`]: { + top: dotOffsetInCircle, + insetInlineEnd: dotOffsetInCircle, + }, + }, + [`${componentCls}-body`]: { + borderRadius: '50%', + }, + }, + [`${componentCls}-square`]: { + height: 'auto', + minHeight: floatButtonSize, + borderRadius: borderRadiusLG, + [`${antCls}-badge`]: { + [`${antCls}-badge-dot`]: { + top: dotOffsetInSquare, + insetInlineEnd: dotOffsetInSquare, + }, + }, + [`${componentCls}-body`]: { + height: 'auto', + borderRadius: borderRadiusLG, + }, + }, + [`${componentCls}-default`]: { + backgroundColor: token.floatButtonBackgroundColor, + transition: `background-color ${token.motionDurationMid}`, + [`${componentCls}-body`]: { + backgroundColor: token.floatButtonBackgroundColor, + transition: `background-color ${token.motionDurationMid}`, + '&:hover': { + backgroundColor: token.colorFillContent, + }, + [`${componentCls}-content`]: { + [`${componentCls}-icon`]: { + color: token.colorText, + }, + [`${componentCls}-description`]: { + display: 'flex', + alignItems: 'center', + lineHeight: unit(token.fontSizeLG), + color: token.colorText, + fontSize: token.fontSizeSM, + }, + }, + }, + }, + [`${componentCls}-primary`]: { + backgroundColor: token.colorPrimary, + [`${componentCls}-body`]: { + backgroundColor: token.colorPrimary, + transition: `background-color ${token.motionDurationMid}`, + '&:hover': { + backgroundColor: token.colorPrimaryHover, + }, + [`${componentCls}-content`]: { + [`${componentCls}-icon`]: { + color: token.colorTextLightSolid, + }, + [`${componentCls}-description`]: { + display: 'flex', + alignItems: 'center', + lineHeight: unit(token.fontSizeLG), + color: token.colorTextLightSolid, + fontSize: token.fontSizeSM, + }, + }, + }, + }, + }; +}; + +// ============================== Export ============================== +export const prepareComponentToken: GetDefaultToken<'FloatButton'> = (token) => ({ + dotOffsetInCircle: getOffset(token.controlHeightLG / 2), + dotOffsetInSquare: getOffset(token.borderRadiusLG), +}); + +export default genStyleHooks( + 'FloatButton', + (token) => { + const { + colorTextLightSolid, + colorBgElevated, + controlHeightLG, + marginXXL, + marginLG, + fontSize, + fontSizeIcon, + controlItemBgHover, + paddingXXS, + calc, + } = token; + + const floatButtonToken = mergeToken(token, { + floatButtonBackgroundColor: colorBgElevated, + floatButtonColor: colorTextLightSolid, + floatButtonHoverBackgroundColor: controlItemBgHover, + floatButtonFontSize: fontSize, + floatButtonIconSize: calc(fontSizeIcon).mul(1.5).equal(), + floatButtonSize: controlHeightLG, + floatButtonInsetBlockEnd: marginXXL, + floatButtonInsetInlineEnd: marginLG, + floatButtonBodySize: calc(controlHeightLG).sub(calc(paddingXXS).mul(2)).equal(), + // 这里的 paddingXXS 是简写,完整逻辑是 (controlHeightLG - (controlHeightLG - paddingXXS * 2)) / 2, + floatButtonBodyPadding: paddingXXS, + badgeOffset: calc(paddingXXS).mul(1.5).equal(), + }); + + return [ + floatButtonGroupStyle(floatButtonToken), + sharedFloatButtonStyle(floatButtonToken), + initFadeMotion(token), + initFloatButtonGroupMotion(floatButtonToken), + ]; + }, + prepareComponentToken, +); diff --git a/packages/meta/src/float-button/util.ts b/packages/meta/src/float-button/util.ts new file mode 100644 index 0000000..1cee7c3 --- /dev/null +++ b/packages/meta/src/float-button/util.ts @@ -0,0 +1,10 @@ +const getOffset = (radius: number): number => { + if (radius === 0) { + return 0; + } + // 如果要考虑通用性,这里应该用三角函数 Math.sin(45) + // 但是这个场景比较特殊,始终是等腰直角三角形,所以直接用 Math.sqrt() 开方即可 + return radius - Math.sqrt(radius ** 2 / 2); +}; + +export default getOffset; diff --git a/packages/meta/src/index.tsx b/packages/meta/src/index.tsx index 1eb8bdc..765bea5 100644 --- a/packages/meta/src/index.tsx +++ b/packages/meta/src/index.tsx @@ -16,10 +16,16 @@ export { default as VideoPlayer } from './VideoPlayer' export type { VideoViewProps, VideoViewRef } from './VideoPlayer' export { default as Tabs } from './tabs' export type { TabPaneProps, TabsProps } from './tabs'; -export { default as Button } from './button' export { default as message } from './message' +export { default as Button } from './button' export type { ArgsProps } from './message' export type { ButtonProps, ButtonGroupProps } from './button'; +export { default as FloatButton } from './float-button'; +export type { + FloatButtonGroupProps, + FloatButtonProps, + FloatButtonRef, +} from './float-button/interface'; export { default as Space } from './space' export { default as Slider } from './slider' export type { SliderBaseProps, SliderMarks, SliderRangeProps, SliderSingleProps, SliderTooltipProps } from './slider'; From 23706fabf95946e9a50dcc5ad0c516eeb19ff9ae Mon Sep 17 00:00:00 2001 From: jiangzhixiong <710328466@qq.com> Date: Thu, 23 May 2024 18:14:32 +0800 Subject: [PATCH 3/5] =?UTF-8?q?feat(@zhst/map):=20=E6=B7=BB=E5=8A=A0marker?= =?UTF-8?q?=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/map/package.json | 3 +- packages/map/src/MapBox.tsx | 181 +- .../map/src/assets/icons/cluster_alarm.png | Bin 0 -> 2097 bytes .../map/src/assets/icons/cluster_escape.png | Bin 0 -> 4680 bytes .../map/src/assets/icons/cluster_normal.png | Bin 0 -> 4734 bytes .../map/src/assets/icons/cluterImage.json | 79 + .../map/src/assets/icons/fecluster_alarm.png | Bin 0 -> 2770 bytes .../map/src/assets/icons/fecluster_escape.png | Bin 0 -> 5249 bytes .../map/src/assets/icons/fecluster_normal.png | Bin 0 -> 2440 bytes .../assets/icons/fecluster_normal_select.png | Bin 0 -> 2674 bytes .../assets/icons/fecluster_uncontrolled.png | Bin 0 -> 2733 bytes packages/map/src/assets/icons/index.ts | 186 + .../map/src/assets/icons/marker_escape.png | Bin 0 -> 5169 bytes .../map/src/assets/icons/marker_monitor.png | Bin 0 -> 2145 bytes .../map/src/assets/icons/marker_normal.png | Bin 0 -> 1999 bytes .../src/assets/icons/marker_normal_select.png | Bin 0 -> 3376 bytes packages/map/src/assets/icons/marker_pos.png | Bin 0 -> 2453 bytes .../map/src/assets/icons/marker_runing.png | Bin 0 -> 2145 bytes .../src/assets/icons/marker_runing_select.png | Bin 0 -> 3376 bytes .../map/src/assets/icons/marker_track.png | Bin 0 -> 1717 bytes .../src/assets/icons/marker_track_virtual.png | Bin 0 -> 3394 bytes .../src/assets/icons/marker_uncontrolled.png | Bin 0 -> 3394 bytes .../map/src/assets/icons/marker_virtual.png | Bin 0 -> 3352 bytes .../assets/icons/marker_virtual_select.png | Bin 0 -> 3376 bytes .../map/src/components/clusters/Clusters.tsx | 64 + .../map/src/components/clusters/index.tsx | 5 + .../map/src/components/clusters/layers.ts | 37 + .../components/drawControl/DrawControl.tsx | 20 +- packages/map/src/components/marker/Marker.tsx | 50 + packages/map/src/components/marker/index.tsx | 8 + packages/map/src/components/marker/pin.tsx | 21 + packages/map/src/components/popup/PopUp.tsx | 49 + packages/map/src/components/popup/index.tsx | 5 + packages/map/src/components/tools/Tools.tsx | 18 +- packages/map/src/demo/.data/bart-station.json | 46 + .../src/demo/.data/feature-example-sf.json | 2262 ++++++++++++ .../map/src/demo/.data/us-election-2016.json | 3109 +++++++++++++++++ packages/map/src/demo/.data/us-income.geojson | 56 + packages/map/src/demo/basic.tsx | 45 +- packages/map/src/interface.ts | 18 - packages/map/src/utils/constants.ts | 2 +- packages/meta/src/image/PreviewGroup.tsx | 81 + .../__snapshots__/demo-extend.test.ts.snap | 826 +++++ .../__tests__/__snapshots__/demo.test.ts.snap | 801 +++++ .../__snapshots__/index.test.tsx.snap | 326 ++ .../src/image/__tests__/demo-extend.test.ts | 3 + .../meta/src/image/__tests__/demo.test.ts | 3 + .../meta/src/image/__tests__/image.test.ts | 5 + .../meta/src/image/__tests__/index.test.tsx | 194 + packages/meta/src/image/demo/basic.md | 7 + packages/meta/src/image/demo/basic.tsx | 11 + .../meta/src/image/demo/component-token.md | 7 + .../meta/src/image/demo/component-token.tsx | 31 + .../meta/src/image/demo/controlled-preview.md | 7 + .../src/image/demo/controlled-preview.tsx | 41 + packages/meta/src/image/demo/fallback.md | 7 + packages/meta/src/image/demo/fallback.tsx | 13 + packages/meta/src/image/demo/imageRender.md | 7 + packages/meta/src/image/demo/imageRender.tsx | 23 + packages/meta/src/image/demo/nested.md | 7 + packages/meta/src/image/demo/nested.tsx | 85 + packages/meta/src/image/demo/placeholder.md | 7 + packages/meta/src/image/demo/placeholder.tsx | 32 + .../image/demo/preview-group-top-progress.md | 7 + .../image/demo/preview-group-top-progress.tsx | 20 + .../src/image/demo/preview-group-visible.md | 7 + .../src/image/demo/preview-group-visible.tsx | 19 + packages/meta/src/image/demo/preview-group.md | 7 + .../meta/src/image/demo/preview-group.tsx | 18 + packages/meta/src/image/demo/preview-mask.md | 17 + packages/meta/src/image/demo/preview-mask.tsx | 21 + packages/meta/src/image/demo/previewSrc.md | 7 + packages/meta/src/image/demo/previewSrc.tsx | 14 + packages/meta/src/image/demo/toolbarRender.md | 35 + .../meta/src/image/demo/toolbarRender.tsx | 59 + packages/meta/src/image/index.tsx | 97 + packages/meta/src/image/index.zh-CN.md | 162 + packages/meta/src/image/style/index.ts | 390 +++ packages/meta/src/index.tsx | 2 + 79 files changed, 9551 insertions(+), 119 deletions(-) create mode 100644 packages/map/src/assets/icons/cluster_alarm.png create mode 100644 packages/map/src/assets/icons/cluster_escape.png create mode 100644 packages/map/src/assets/icons/cluster_normal.png create mode 100644 packages/map/src/assets/icons/cluterImage.json create mode 100644 packages/map/src/assets/icons/fecluster_alarm.png create mode 100644 packages/map/src/assets/icons/fecluster_escape.png create mode 100644 packages/map/src/assets/icons/fecluster_normal.png create mode 100644 packages/map/src/assets/icons/fecluster_normal_select.png create mode 100644 packages/map/src/assets/icons/fecluster_uncontrolled.png create mode 100644 packages/map/src/assets/icons/index.ts create mode 100644 packages/map/src/assets/icons/marker_escape.png create mode 100644 packages/map/src/assets/icons/marker_monitor.png create mode 100644 packages/map/src/assets/icons/marker_normal.png create mode 100644 packages/map/src/assets/icons/marker_normal_select.png create mode 100644 packages/map/src/assets/icons/marker_pos.png create mode 100644 packages/map/src/assets/icons/marker_runing.png create mode 100644 packages/map/src/assets/icons/marker_runing_select.png create mode 100644 packages/map/src/assets/icons/marker_track.png create mode 100644 packages/map/src/assets/icons/marker_track_virtual.png create mode 100644 packages/map/src/assets/icons/marker_uncontrolled.png create mode 100644 packages/map/src/assets/icons/marker_virtual.png create mode 100644 packages/map/src/assets/icons/marker_virtual_select.png create mode 100644 packages/map/src/components/clusters/Clusters.tsx create mode 100644 packages/map/src/components/clusters/index.tsx create mode 100644 packages/map/src/components/clusters/layers.ts create mode 100644 packages/map/src/components/marker/Marker.tsx create mode 100644 packages/map/src/components/marker/index.tsx create mode 100644 packages/map/src/components/marker/pin.tsx create mode 100644 packages/map/src/components/popup/PopUp.tsx create mode 100644 packages/map/src/components/popup/index.tsx create mode 100644 packages/map/src/demo/.data/bart-station.json create mode 100644 packages/map/src/demo/.data/feature-example-sf.json create mode 100644 packages/map/src/demo/.data/us-election-2016.json create mode 100644 packages/map/src/demo/.data/us-income.geojson create mode 100644 packages/meta/src/image/PreviewGroup.tsx create mode 100644 packages/meta/src/image/__tests__/__snapshots__/demo-extend.test.ts.snap create mode 100644 packages/meta/src/image/__tests__/__snapshots__/demo.test.ts.snap create mode 100644 packages/meta/src/image/__tests__/__snapshots__/index.test.tsx.snap create mode 100644 packages/meta/src/image/__tests__/demo-extend.test.ts create mode 100644 packages/meta/src/image/__tests__/demo.test.ts create mode 100644 packages/meta/src/image/__tests__/image.test.ts create mode 100644 packages/meta/src/image/__tests__/index.test.tsx create mode 100644 packages/meta/src/image/demo/basic.md create mode 100644 packages/meta/src/image/demo/basic.tsx create mode 100644 packages/meta/src/image/demo/component-token.md create mode 100644 packages/meta/src/image/demo/component-token.tsx create mode 100644 packages/meta/src/image/demo/controlled-preview.md create mode 100644 packages/meta/src/image/demo/controlled-preview.tsx create mode 100644 packages/meta/src/image/demo/fallback.md create mode 100644 packages/meta/src/image/demo/fallback.tsx create mode 100644 packages/meta/src/image/demo/imageRender.md create mode 100644 packages/meta/src/image/demo/imageRender.tsx create mode 100644 packages/meta/src/image/demo/nested.md create mode 100644 packages/meta/src/image/demo/nested.tsx create mode 100644 packages/meta/src/image/demo/placeholder.md create mode 100644 packages/meta/src/image/demo/placeholder.tsx create mode 100644 packages/meta/src/image/demo/preview-group-top-progress.md create mode 100644 packages/meta/src/image/demo/preview-group-top-progress.tsx create mode 100644 packages/meta/src/image/demo/preview-group-visible.md create mode 100644 packages/meta/src/image/demo/preview-group-visible.tsx create mode 100644 packages/meta/src/image/demo/preview-group.md create mode 100644 packages/meta/src/image/demo/preview-group.tsx create mode 100644 packages/meta/src/image/demo/preview-mask.md create mode 100644 packages/meta/src/image/demo/preview-mask.tsx create mode 100644 packages/meta/src/image/demo/previewSrc.md create mode 100644 packages/meta/src/image/demo/previewSrc.tsx create mode 100644 packages/meta/src/image/demo/toolbarRender.md create mode 100644 packages/meta/src/image/demo/toolbarRender.tsx create mode 100644 packages/meta/src/image/index.tsx create mode 100644 packages/meta/src/image/index.zh-CN.md create mode 100644 packages/meta/src/image/style/index.ts diff --git a/packages/map/package.json b/packages/map/package.json index aa0f776..941577c 100644 --- a/packages/map/package.json +++ b/packages/map/package.json @@ -52,6 +52,7 @@ "react-map-gl": "^7.1.7" }, "devDependencies": { - "@types/mapbox__mapbox-gl-draw": "^1.4.6" + "@types/mapbox__mapbox-gl-draw": "^1.4.6", + "axios": "^1.7.2" } } diff --git a/packages/map/src/MapBox.tsx b/packages/map/src/MapBox.tsx index 1e02e7d..9da0e84 100644 --- a/packages/map/src/MapBox.tsx +++ b/packages/map/src/MapBox.tsx @@ -1,42 +1,105 @@ import 'mapbox-gl/dist/mapbox-gl.css'; import '@mapbox/mapbox-gl-draw/dist/mapbox-gl-draw.css'; -import React, { forwardRef, useEffect, useImperativeHandle, useRef, useState } from 'react'; +import React, { forwardRef, useEffect, useImperativeHandle, useMemo, useRef, useState } from 'react'; import Map from 'react-map-gl'; -import { - // MapboxMap, - MapRef, - // MapStyle -} from "react-map-gl"; +import { CSSProperties } from "react"; +import { MapboxMap, MapRef, MapStyle } from "react-map-gl"; import classnames from 'classnames' -import Tools from './components/tools' +import Tools, { ToolsProps } from './components/tools' import DrawControl, { DrawControlProps, DrawControlRefProps } from './components/drawControl'; -import { MapProps } from './interface'; -import { merge } from './utils'; import { MAP_CENTER, defaultMapConfig } from './utils/constants'; import './index.less'; import mapboxDrawStyle from './utils/drawStyle'; +import Marker from './components/marker'; const componentName = 'zhst-map' -export interface MapRefProps { +export interface IMarkerData { + key: string; + title: string; + population: string; + status: string; + latitude: number; + longitude: number; +} +export interface MapProps extends Partial { + mapboxAccessToken?: string; //token + markerData?: IMarkerData[] + minZoom?: number; //最小层级 + maxZoom?: number; //最大层级 + mapStyle?: MapStyle; //地图样式 + height?: number | string; + width?: string | number; + mapRef?: MapRef; + style?: CSSProperties; + children?: JSX.Element | JSX.Element[] | Array; + mapCenter?: { + longitude: number, latitude: number + } + draw?: boolean; + buttonList?: ToolsProps['buttonList'] + onLoad?: (e: mapboxgl.MapboxEvent) => void; + onDrawCreate?: (e: { features: object[], [key: string]: any }) => void; + onDrawUpdate?: (e: { features: object[], [key: string]: any }) => void; + onDrawDelete?: (e: { features: object[], [key: string]: any }) => void; +} + +export interface MapRefProps { } const MapBox = forwardRef((props, ref) => { const { style = {}, children, - onLoad, mapCenter = MAP_CENTER, height = 600, width = '100%', + draw, + markerData = [], + buttonList = [ + { + label: '圆形框选', + key: 'circle', + icon: 'icon-yuan', + onClick: () => drawControlRef.current?.drawer?.changeMode?.('draw_circle') + }, + { + label: '矩形框选', + key: 'rect', + icon: 'icon-fang', + onClick: () => drawControlRef.current?.drawer?.changeMode?.('draw_rect') + }, + { + label: '多边形框选', + key: 'more', + icon: 'icon-duobianxing', + onClick: () => drawControlRef.current?.drawer?.changeMode?.('draw_polygon') + }, + { + label: '路径框选', + key: 'path', + icon: 'icon-lujingkuangxuannor', + onClick: () => drawControlRef.current?.drawer?.changeMode?.('draw_line_string') + }, + { + label: '清除', + key: 'clear', + icon: 'icon-gongjuxiangguanbi', + onClick: () => drawControlRef.current?.drawer?.deleteAll() + } + ], + onLoad, + onDrawCreate, + onDrawUpdate, + onDrawDelete, ...others } = props || {}; const mapRef = useRef(null) const drawControlRef = useRef(null) // 默认绘制配置 - const [drawConfig, setConfig] = useState({ + const [drawConfig] = useState({ displayControlsDefault: false, position: 'top-left', styles: mapboxDrawStyle, @@ -49,82 +112,54 @@ const MapBox = forwardRef((props, ref) => { defaultMode: 'draw_polygon', }) - const handleDrawCreate = e => { - console.log('handleDrawCreate', e) - } + const initMarker = useMemo( + () => { + return markerData.map((_item, index) => ( + + )) + }, + [markerData] + ); - const handleDrawUpdate = e => { - console.log('handleDrawUpdate', e) - } - - const handleDrawDelete = e => { - console.log('handleDrawDelete', e) - } - - useEffect(() => { - console.log('drawControlRef', drawControlRef.current?.drawer?.deleteAll()) - }, []) - - useImperativeHandle(ref, () => ({})) + useImperativeHandle(ref, () => ({ + mapRef: mapRef.current, + drawer: drawControlRef.current?.drawer, + })) return ( //@ts-ignore
drawControlRef.current?.drawer?.changeMode?.('draw_circle') - }, - { - label: '矩形框选', - key: 'rect', - icon: 'icon-fang', - onClick: () => drawControlRef.current?.drawer?.changeMode?.('draw_rect') - }, - { - label: '多边形框选', - key: 'more', - icon: 'icon-duobianxing', - onClick: () => drawControlRef.current?.drawer?.changeMode?.('draw_polygon') - }, - { - label: '路径框选', - key: 'path', - icon: 'icon-lujingkuangxuannor', - onClick: () => drawControlRef.current?.drawer?.changeMode?.('draw_line_string') - }, - { - label: '清除', - key: 'clear', - icon: 'icon-cuo', - onClick: () => drawControlRef.current?.drawer?.deleteAll() - } - ]} + buttonList={buttonList} /> + {/* @ts-ignore */} { - onLoad && onLoad(e); - }} + {...defaultMapConfig} + initialViewState={{ ...mapCenter }} + onLoad={onLoad} style={{ width: width, height: height, ...style }} - {...merge(defaultMapConfig, others)} + {...others} > + {initMarker} {/* */} - + {/* ---------------绘制图层--------------------- */} + {draw && ( + + )} {children}
diff --git a/packages/map/src/assets/icons/cluster_alarm.png b/packages/map/src/assets/icons/cluster_alarm.png new file mode 100644 index 0000000000000000000000000000000000000000..395f082f76f549079145d0b6553d5f9131d038e3 GIT binary patch literal 2097 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 KMNUMnLSTYmd(s*J literal 0 HcmV?d00001 diff --git a/packages/map/src/assets/icons/cluster_normal.png b/packages/map/src/assets/icons/cluster_normal.png new file mode 100644 index 0000000000000000000000000000000000000000..ac7f5c59a42303c5e6debf708484af31460082e5 GIT binary patch literal 4734 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 ISA)O#AD=e?wg3PC literal 0 HcmV?d00001 diff --git a/packages/map/src/assets/icons/cluterImage.json b/packages/map/src/assets/icons/cluterImage.json new file mode 100644 index 0000000..9d9e67c --- /dev/null +++ b/packages/map/src/assets/icons/cluterImage.json @@ -0,0 +1,79 @@ +[{ + "key": "UNCLUTER", + "state": [{ + "type": "NORMAL", + "bgImage": "", + "bgColor": "#0099ff", + "frontImage": "", + "frontColor": "red", + "ripple": false, + "redDot": false, + "name": "IMAGE_UNCLUTER_NORMAL" + }, + { + "type": "RUNING", + "backImage": "", + "backColor": "green", + "frontImage": "", + "frontColor": "red", + "ripple": false, + "redDot": false, + "name": "IMAGE_UNCLUTER_RUNING" + }, + { + "type": "NORMAL_SELECT", + "backImage": "", + "backColor": "yellow", + "frontImage": "", + "frontColor": "red", + "ripple": false, + "redDot": false, + "name": "IMAGE_UNCLUTER_NORMAL_SELECT" + }, + { + "type": "RUNING_SELECT", + "backImage": "", + "backColor": "red", + "frontImage": "", + "frontColor": "blue", + "ripple": false, + "redDot": false, + "name": "IMAGE_UNCLUTER_RUNING_SELECT" + } + ] + }, + { + "key": "APPEARCLUTER_RED", + "state": [{ + "type": "BLUE", + "bgImage": "", + "bgColor": "#0099ff", + "frontImage": "", + "frontColor": "#ffffff", + "ripple": false, + "redDot": false, + "name": "IMAGE_APPEARCLUTER_BLUE" + }, + { + "type": "YELLOW", + "bgImage": "", + "bgColor": "#FF9900", + "frontImage": "", + "frontColor": "#ffffff", + "ripple": false, + "redDot": false, + "name": "IMAGE_APPEARCLUTER_YELLOW" + }, + { + "type": "RED", + "bgImage": "", + "bgColor": "#F25051", + "frontImage": "", + "frontColor": "#ffffff", + "ripple": false, + "redDot": false, + "name": "IMAGE_APPEARCLUTER_RED" + } + ] + } +] \ No newline at end of file diff --git a/packages/map/src/assets/icons/fecluster_alarm.png b/packages/map/src/assets/icons/fecluster_alarm.png new file mode 100644 index 0000000000000000000000000000000000000000..777c5ac15975ccc654c55f6e4acd574bc4b43278 GIT binary patch literal 2770 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 Hu0mjfHIyFU literal 0 HcmV?d00001 diff --git a/packages/map/src/assets/icons/fecluster_normal.png b/packages/map/src/assets/icons/fecluster_normal.png new file mode 100644 index 0000000000000000000000000000000000000000..2b65d02f947ccb9d41d575ce2ecd80952833d5ab GIT binary patch literal 2440 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 literal 0 HcmV?d00001 diff --git a/packages/map/src/assets/icons/fecluster_uncontrolled.png b/packages/map/src/assets/icons/fecluster_uncontrolled.png new file mode 100644 index 0000000000000000000000000000000000000000..f91f77f283f3093d5e5c0f1006b7f2b72c80580b GIT binary patch literal 2733 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'./*.png', { eager: true }); +Object.keys(modules).forEach((key) => { + const fileNmae = key.substring(key.lastIndexOf('/') + 1, key.lastIndexOf('.')); + file[`${fileNmae}`] = modules[key].default; +}); + +export const images: { [key: string]: string } = {}; +//图片点规则 +export const unClustersImgExpressions: mapboxgl.SymbolLayout['icon-image'] = ['case']; +export const clustersImgExpressions: mapboxgl.SymbolLayout['icon-image'] = ['case']; +export const feClustersImgExpressions: mapboxgl.SymbolLayout['icon-image'] = ['case']; + +export const feclusterPrefix = 'IMG_FECLUSTER'; +export const unClusterPrefix = 'IMG_UNCLUSTER'; +export const clusterPrefix = 'IMG_CLUSTER'; + +Object.keys(file).forEach((fileNmae) => { + const [prefix, type, status] = fileNmae.split('_'); + const key = `${type.toUpperCase()}${!!status ? `_${status.toUpperCase()}` : ''}`; + if (prefix === 'marker') { + const imageId = `${unClusterPrefix}_${key}`; + images[imageId] = file[fileNmae]; + unClustersImgExpressions.push(['==', ['get', 'type'], key]); + unClustersImgExpressions.push(imageId); + } + if (prefix === 'cluster') { + const imageId = `${clusterPrefix}_${key}`; + images[imageId] = file[fileNmae]; + clustersImgExpressions.push(['==', ['get', 'type'], key]); + clustersImgExpressions.push(imageId); + } + if (prefix === 'fecluster') { + const imageId = `${feclusterPrefix}_${key}`; + images[imageId] = file[fileNmae]; + feClustersImgExpressions.push(['==', ['get', 'type'], key]); + feClustersImgExpressions.push(imageId); + } +}); + +unClustersImgExpressions.push('IMG_UNCLUSTER_NORMAL'); +clustersImgExpressions.push('IMG_CLUSTER_NORMAL'); +feClustersImgExpressions.push('IMG_FECLUSTER_NORMAL'); + +export const imageData = []; +export const converImageByJson = (jsonData) => { + jsonData.map((v) => { + const key = v['key']; + v['state'].length > 0 && + v['state'].map((_v) => { + const backWidth = 60; + const backHeight = 70; + const canvas = document.createElement('canvas'); + canvas.width = backWidth; + canvas.height = backHeight; + const context = canvas.getContext('2d'); + //context.clearRect(0, 0, backWidth, backHeight); + //context.beginPath(); + context.fillStyle = _v['bgColor']; + + context.font = '50px IconFont'; + context.fillText( + eval(('("' + _v['bgImage']).replace('&#x', '\\u').replace(';', '') + '")'), + 0, + backWidth + ); + context.fillStyle = _v['frontColor']; + context.font = '50px IconFont'; + context.fillText( + eval(('("' + _v['frontImage']).replace('&#x', '\\u').replace(';', '') + '")'), + 0, + backWidth + ); + + //画小红点; + if (v['redDot']) { + context.fillStyle = 'red'; + context.arc(20, 5, 3, Math.PI * 2, 0, true); + context.fill(); + } + const targetImage = new Image(); + targetImage.src = canvas.toDataURL('image/png'); + imageData.push({ + name: _v['name'], + targetImage: targetImage, + ripple: _v['ripple'], + }); + }); + }); +}; + +function BezierEllipse2(ctx, x, y, a, b) { + ctx.beginPath(); + const k = 0.5522848; + const ox = a * k; // 水平控制点偏移量 + const oy = b * k; // 垂直控制点偏移量

ctx.beginPath(); + //从椭圆的左端点开始顺时针绘制四条三次贝塞尔曲线 + ctx.moveTo(x - a, y); + ctx.bezierCurveTo(x - a, y - oy, x - ox, y - b, x, y - b); + ctx.bezierCurveTo(x + ox, y - b, x + a, y - oy, x + a, y); + ctx.bezierCurveTo(x + a, y + oy, x + ox, y + b, x, y + b); + ctx.bezierCurveTo(x - ox, y + b, x - a, y + oy, x - a, y); + ctx.closePath(); +} + +function rgbToRgba(color, alp) { + const rgbaAttr = color.match(/[\d.]+/g); + if (rgbaAttr.length >= 3) { + const [r, g, b] = rgbaAttr; + // r = rgbaAttr[0]; + // g = rgbaAttr[1]; + // b = rgbaAttr[2]; + return 'rgba(' + r + ',' + g + ',' + b + ',' + alp + ')'; + } +} +export function convertImageToMap(name, map) { + const size = 150; + + const imageObject = imageData.find((v) => v['name'] === name); + const initialImage = imageObject.targetImage; + const hasRipper = imageObject['ripple']; + const pulsingDot = { + width: size, + height: size, + data: new Uint8Array(size * size * 4), + + onAdd: function () { + const canvas = document.createElement('canvas'); + canvas.width = this.width; + canvas.height = this.height; + this.canvas = canvas; + this.context = canvas.getContext('2d'); + }, + + render: function () { + const duration = 1.5 * 1000; //1.5s + const t = (performance.now() % duration) / duration; + const alp = 1 - t; + // const size = 10; + + const outerRadius = (size / 2) * 1 * t; + const outerRadius2 = (size / 2) * 0.6 * t; + const context = this.context; + // draw outer circle + context.clearRect(0, 0, this.width, this.height); + + context.beginPath(); + context.drawImage(initialImage, 0, 0, this.width, this.height); + // context.drawImage('./cluster_escape.png', this.width / 2, this.height / 2, this.width / 2, this.height / 2); + // // context.arc(this.width / 2, this.height / 2, outerRadius, 0, Math.PI * 2); + if (hasRipper) { + BezierEllipse2(context, this.width / 2, this.height / 2, outerRadius, outerRadius / 2); + context.fillStyle = rgbToRgba('rgb(255, 100, 100)', alp); + context.fill(); + + context.beginPath(); + // context.arc(this.width / 2, this.height / 2, outerRadius2, 0, Math.PI * 2); + BezierEllipse2(context, this.width / 2, this.height / 2, outerRadius2, outerRadius2 / 2); + context.fillStyle = rgbToRgba('rgb(255, 100, 100)', alp); + context.fill(); + + // draw inner circle + context.beginPath(); + BezierEllipse2( + context, + this.width / 2, + this.height / 2, + (size / 2) * 0.2, + ((size / 2) * 0.2) / 2 + ); + // context.arc(this.width / 2, this.height / 2, (size / 2) * 1 * 0.1, 0, Math.PI * 2); + // context.fillStyle = 'rgba(255, 100, 100, 1)'; + context.fillStyle = rgbToRgba('rgb(255, 100, 100)', 1); + context.fill(); + } + + this.data = context.getImageData(0, 0, this.width, this.height).data; + + map.triggerRepaint(); + return true; + }, + }; + return pulsingDot; +} diff --git a/packages/map/src/assets/icons/marker_escape.png b/packages/map/src/assets/icons/marker_escape.png new file mode 100644 index 0000000000000000000000000000000000000000..a3577919cdb8cc387985d021a1168731cfa8ec31 GIT binary patch literal 5169 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 literal 0 HcmV?d00001 diff --git a/packages/map/src/assets/icons/marker_monitor.png b/packages/map/src/assets/icons/marker_monitor.png new file mode 100644 index 0000000000000000000000000000000000000000..0572538cd63de595cdc12ee6673b955b46807376 GIT binary patch literal 2145 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 literal 0 HcmV?d00001 diff --git a/packages/map/src/assets/icons/marker_normal_select.png b/packages/map/src/assets/icons/marker_normal_select.png new file mode 100644 index 0000000000000000000000000000000000000000..00eeaab18861a6c49cb4391654e78e0062e48d70 GIT binary patch literal 3376 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 literal 0 HcmV?d00001 diff --git a/packages/map/src/assets/icons/marker_pos.png b/packages/map/src/assets/icons/marker_pos.png new file mode 100644 index 0000000000000000000000000000000000000000..9c0774bed7a65f6ac97c96efb11f298e474cfd97 GIT binary patch literal 2453 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 literal 0 HcmV?d00001 diff --git a/packages/map/src/assets/icons/marker_track.png b/packages/map/src/assets/icons/marker_track.png new file mode 100644 index 0000000000000000000000000000000000000000..2d062490301c4ce491ac2803a62dfaea8cf103af GIT binary patch literal 1717 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 Y2M09rLdzNk&Hw-a07*qoM6N<$g0-Dj3;+NC literal 0 HcmV?d00001 diff --git a/packages/map/src/assets/icons/marker_uncontrolled.png b/packages/map/src/assets/icons/marker_uncontrolled.png new file mode 100644 index 0000000000000000000000000000000000000000..74705402dd857bb4e806ba8c43f4b72aff986fdc GIT binary patch literal 3394 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 Y2M09rLdzNk&Hw-a07*qoM6N<$g0-Dj3;+NC literal 0 HcmV?d00001 diff --git a/packages/map/src/assets/icons/marker_virtual.png b/packages/map/src/assets/icons/marker_virtual.png new file mode 100644 index 0000000000000000000000000000000000000000..cf271bd35ca33b1967a3b3993346326973382e14 GIT binary patch literal 3352 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 literal 0 HcmV?d00001 diff --git a/packages/map/src/components/clusters/Clusters.tsx b/packages/map/src/components/clusters/Clusters.tsx new file mode 100644 index 0000000..4b360dd --- /dev/null +++ b/packages/map/src/components/clusters/Clusters.tsx @@ -0,0 +1,64 @@ +/** + * Created by jiangzhixiong on 2024/05/23 + */ +import React, { forwardRef, useContext, useImperativeHandle } from 'react' +import { Source, Layer, SourceProps } from 'react-map-gl'; +import { ConfigProvider } from '@zhst/meta' +import {clusterLayer, clusterCountLayer, unclusteredPointLayer} from './layers'; + +const { ConfigContext } = ConfigProvider + +export interface ClusterProps extends SourceProps { + prefixCls?: string; +} + +export interface ClusterRefProps { +} + +const Cluster = forwardRef((props, ref) => { + const { + prefixCls: customizePrefixCls, + ...rest + } = props + const { getPrefixCls } = useContext(ConfigContext) + const componentName = getPrefixCls('map-cluster', customizePrefixCls); + + // const onClick = event => { + // const feature = event.features[0]; + // const clusterId = feature.properties.cluster_id; + + // const mapboxSource = mapRef.current.getSource('earthquakes') as GeoJSONSource; + + // mapboxSource.getClusterExpansionZoom(clusterId, (err, zoom) => { + // if (err) { + // return; + // } + + // mapRef.current.easeTo({ + // center: feature.geometry.coordinates, + // zoom, + // duration: 500 + // }); + // }); + // }; + + useImperativeHandle(ref, () => ({ + + })) + + return ( + + + + + + ) +}) + +export default Cluster diff --git a/packages/map/src/components/clusters/index.tsx b/packages/map/src/components/clusters/index.tsx new file mode 100644 index 0000000..3775dff --- /dev/null +++ b/packages/map/src/components/clusters/index.tsx @@ -0,0 +1,5 @@ +/** + * Created by jiangzhixiong on 2024/05/23 + */ +export { default as Clusters } from './Clusters' +// export type { ClustersProps, ClustersRefProps } from './Clusters' diff --git a/packages/map/src/components/clusters/layers.ts b/packages/map/src/components/clusters/layers.ts new file mode 100644 index 0000000..b4c060a --- /dev/null +++ b/packages/map/src/components/clusters/layers.ts @@ -0,0 +1,37 @@ +import type {LayerProps} from 'react-map-gl'; + +export const clusterLayer: LayerProps = { + id: 'clusters', + type: 'circle', + source: 'earthquakes', + filter: ['has', 'point_count'], + paint: { + 'circle-color': ['step', ['get', 'point_count'], '#51bbd6', 100, '#f1f075', 750, '#f28cb1'], + 'circle-radius': ['step', ['get', 'point_count'], 20, 100, 30, 750, 40] + } +}; + +export const clusterCountLayer: LayerProps = { + id: 'cluster-count', + type: 'symbol', + source: 'earthquakes', + filter: ['has', 'point_count'], + layout: { + 'text-field': '{point_count_abbreviated}', + 'text-font': ['DIN Offc Pro Medium', 'Arial Unicode MS Bold'], + 'text-size': 12 + } +}; + +export const unclusteredPointLayer: LayerProps = { + id: 'unclustered-point', + type: 'circle', + source: 'earthquakes', + filter: ['!', ['has', 'point_count']], + paint: { + 'circle-color': '#11b4da', + 'circle-radius': 4, + 'circle-stroke-width': 1, + 'circle-stroke-color': '#fff' + } +}; diff --git a/packages/map/src/components/drawControl/DrawControl.tsx b/packages/map/src/components/drawControl/DrawControl.tsx index 62788a0..9cf9a70 100644 --- a/packages/map/src/components/drawControl/DrawControl.tsx +++ b/packages/map/src/components/drawControl/DrawControl.tsx @@ -1,18 +1,19 @@ /** * Created by jiangzhixiong on 2024/05/21 */ - import { forwardRef, useImperativeHandle, useRef, } from 'react' -import MapboxDraw, { MapboxDrawOptions } from '@mapbox/mapbox-gl-draw'; +import MapboxDraw from '@mapbox/mapbox-gl-draw'; import { CircleMode, DragCircleMode, DirectMode, SimpleSelectMode +// @ts-ignore } from 'mapbox-gl-draw-circle' +// @ts-ignore import drawRectMode from 'mapbox-gl-draw-rectangle-mode' +// @ts-ignore import drawStaticMode from '@mapbox/mapbox-gl-draw-static-mode' -// import * as MapboxDrawGeodesic from 'mapbox-gl-draw-geodesic'; import { useControl } from 'react-map-gl'; import type { ControlPosition } from 'react-map-gl'; import { MapContextValue } from 'react-map-gl/dist/esm/components/map'; @@ -32,7 +33,7 @@ export interface DrawControlRefProps { } const DrawControl = forwardRef((props, ref) => { - const drawRef = useRef(null) + const drawRef = useRef(null) useControl( () => { @@ -51,16 +52,15 @@ const DrawControl = forwardRef((props, re ...props } ) - // 修改模式 - // drawIns?.changeMode('simple_select'); + // @ts-ignore drawRef.current = draw return draw }, (context: MapContextValue) => { const { map } = context - map.on('draw.create', props.onCreate); - map.on('draw.update', props.onUpdate); - map.on('draw.delete', props.onDelete); + map.on('draw.create', e => props.onCreate?.(e)); + map.on('draw.update', e => props.onUpdate?.(e)); + map.on('draw.delete', e => props.onDelete?.(e)); }, (context: MapContextValue) => { const { map } = context @@ -74,7 +74,7 @@ const DrawControl = forwardRef((props, re ); useImperativeHandle(ref, () => ({ - drawer: drawRef.current + drawer: drawRef.current! })) return null; diff --git a/packages/map/src/components/marker/Marker.tsx b/packages/map/src/components/marker/Marker.tsx new file mode 100644 index 0000000..deeca9f --- /dev/null +++ b/packages/map/src/components/marker/Marker.tsx @@ -0,0 +1,50 @@ +/** + * Created by jiangzhixiong on 2024/05/23 + */ +import React, { forwardRef, useContext, useImperativeHandle } from 'react' +import { + Marker as MapboxMarker, + MarkerProps as MapboxMarkerProps +} from 'react-map-gl' +import { ConfigProvider, Image } from '@zhst/meta' +import markerPic from '../../assets/icons/marker_monitor.png' + +const { ConfigContext } = ConfigProvider + +export interface MarkerProps extends MapboxMarkerProps { + prefixCls?: string; + key: string; +} + +export interface MarkerRefProps { +} + +const Marker = forwardRef((props, ref) => { + const { + prefixCls: customizePrefixCls, + key, + longitude, + latitude, + onClick + } = props + const { getPrefixCls } = useContext(ConfigContext) + const componentName = getPrefixCls('-map-marker', customizePrefixCls); + + useImperativeHandle(ref, () => ({ + + })) + + return ( + + + + ) +}) + +export default Marker diff --git a/packages/map/src/components/marker/index.tsx b/packages/map/src/components/marker/index.tsx new file mode 100644 index 0000000..e2a7138 --- /dev/null +++ b/packages/map/src/components/marker/index.tsx @@ -0,0 +1,8 @@ +/** + * Created by jiangzhixiong on 2024/05/23 + */ +import Marker from './Marker' + +export type { MarkerProps, MarkerRefProps } from './Marker' + +export default Marker diff --git a/packages/map/src/components/marker/pin.tsx b/packages/map/src/components/marker/pin.tsx new file mode 100644 index 0000000..c669333 --- /dev/null +++ b/packages/map/src/components/marker/pin.tsx @@ -0,0 +1,21 @@ +import * as React from 'react'; + +const ICON = `M20.2,15.7L20.2,15.7c1.1-1.6,1.8-3.6,1.8-5.7c0-5.6-4.5-10-10-10S2,4.5,2,10c0,2,0.6,3.9,1.6,5.4c0,0.1,0.1,0.2,0.2,0.3 + c0,0,0.1,0.1,0.1,0.2c0.2,0.3,0.4,0.6,0.7,0.9c2.6,3.1,7.4,7.6,7.4,7.6s4.8-4.5,7.4-7.5c0.2-0.3,0.5-0.6,0.7-0.9 + C20.1,15.8,20.2,15.8,20.2,15.7z`; + +const pinStyle = { + cursor: 'pointer', + fill: '#d00', + stroke: 'none' +}; + +function Pin({size = 20}) { + return ( + + + + ); +} + +export default React.memo(Pin); diff --git a/packages/map/src/components/popup/PopUp.tsx b/packages/map/src/components/popup/PopUp.tsx new file mode 100644 index 0000000..e1efd0a --- /dev/null +++ b/packages/map/src/components/popup/PopUp.tsx @@ -0,0 +1,49 @@ +/** + * Created by jiangzhixiong on 2024/05/23 + */ +import React, { forwardRef, useContext, useImperativeHandle } from 'react' +import { + PopUp as MapboxPopUp, + PopupProps as MapboxPopupProps +} from 'react-map-gl' +import { ConfigProvider } from '@zhst/meta' + +const { ConfigContext } = ConfigProvider + +export interface PopUpProps extends MapboxPopupProps { + prefixCls?: string; + size?: number; +} + +export interface PopUpRefProps { +} + +const PopUp = forwardRef((props, ref) => { + const { + longitude, + latitude, + onClose, + prefixCls: customizePrefixCls + } = props + const { getPrefixCls } = useContext(ConfigContext) + const componentName = getPrefixCls('map-popup', customizePrefixCls); + + useImperativeHandle(ref, () => ({ + + })) + + return ( + +

+ popup +
+ + ) +}) + +export default PopUp diff --git a/packages/map/src/components/popup/index.tsx b/packages/map/src/components/popup/index.tsx new file mode 100644 index 0000000..166a7c3 --- /dev/null +++ b/packages/map/src/components/popup/index.tsx @@ -0,0 +1,5 @@ +/** + * Created by jiangzhixiong on 2024/05/23 + */ +export { default as PopUp } from './PopUp' +export type { PopUpProps, PopUpRefProps } from './PopUp' diff --git a/packages/map/src/components/tools/Tools.tsx b/packages/map/src/components/tools/Tools.tsx index 299a7c5..3e009b8 100644 --- a/packages/map/src/components/tools/Tools.tsx +++ b/packages/map/src/components/tools/Tools.tsx @@ -43,16 +43,14 @@ const Tools = forwardRef((props, ref) => {
    {buttonList.map((item) => ( - <> -
  • - {typeof item.icon === 'string' ? ( - - ) : ( - item.icon - )} - {item.label} -
  • - +
  • + {typeof item.icon === 'string' ? ( + + ) : ( + item.icon + )} + {item.label} +
  • ))}
, +
+ + +
, +] +`; + +exports[`renders components/image/demo/controlled-preview.tsx extend context correctly 2`] = `[]`; + +exports[`renders components/image/demo/fallback.tsx extend context correctly 1`] = ` +
+ +
+
+ + + + Preview +
+
+
+`; + +exports[`renders components/image/demo/fallback.tsx extend context correctly 2`] = `[]`; + +exports[`renders components/image/demo/imageRender.tsx extend context correctly 1`] = ` +
+ +
+
+ + + + Preview +
+
+
+`; + +exports[`renders components/image/demo/imageRender.tsx extend context correctly 2`] = `[]`; + +exports[`renders components/image/demo/nested.tsx extend context correctly 1`] = ` + +`; + +exports[`renders components/image/demo/nested.tsx extend context correctly 2`] = `[]`; + +exports[`renders components/image/demo/placeholder.tsx extend context correctly 1`] = ` +
+
+
+ + +
+
+ + + + Preview +
+
+
+
+
+ +
+
+`; + +exports[`renders components/image/demo/placeholder.tsx extend context correctly 2`] = `[]`; + +exports[`renders components/image/demo/preview-group.tsx extend context correctly 1`] = ` +Array [ +
+ +
+
+ + + + Preview +
+
+
, +
+ +
+
+ + + + Preview +
+
+
, +] +`; + +exports[`renders components/image/demo/preview-group.tsx extend context correctly 2`] = `[]`; + +exports[`renders components/image/demo/preview-group-top-progress.tsx extend context correctly 1`] = ` +Array [ +
+ +
+
+ + + + Preview +
+
+
, +
+ +
+
+ + + + Preview +
+
+
, +
+ +
+
+ + + + Preview +
+
+
, +] +`; + +exports[`renders components/image/demo/preview-group-top-progress.tsx extend context correctly 2`] = `[]`; + +exports[`renders components/image/demo/preview-group-visible.tsx extend context correctly 1`] = ` +
+ +
+
+ + + + Preview +
+
+
+`; + +exports[`renders components/image/demo/preview-group-visible.tsx extend context correctly 2`] = `[]`; + +exports[`renders components/image/demo/preview-mask.tsx extend context correctly 1`] = ` +
+ +
+
+
+ + + +
+
+ 示例 +
+
+
+
+`; + +exports[`renders components/image/demo/preview-mask.tsx extend context correctly 2`] = `[]`; + +exports[`renders components/image/demo/previewSrc.tsx extend context correctly 1`] = ` +
+ +
+
+ + + + Preview +
+
+
+`; + +exports[`renders components/image/demo/previewSrc.tsx extend context correctly 2`] = `[]`; + +exports[`renders components/image/demo/toolbarRender.tsx extend context correctly 1`] = ` +
+ +
+
+ + + + Preview +
+
+
+`; + +exports[`renders components/image/demo/toolbarRender.tsx extend context correctly 2`] = `[]`; diff --git a/packages/meta/src/image/__tests__/__snapshots__/demo.test.ts.snap b/packages/meta/src/image/__tests__/__snapshots__/demo.test.ts.snap new file mode 100644 index 0000000..af6eb9c --- /dev/null +++ b/packages/meta/src/image/__tests__/__snapshots__/demo.test.ts.snap @@ -0,0 +1,801 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`renders components/image/demo/basic.tsx correctly 1`] = ` +
+ +
+
+ + + + Preview +
+
+
+`; + +exports[`renders components/image/demo/component-token.tsx correctly 1`] = ` +Array [ +
+ +
+
+ + + + Preview +
+
+
, +
+ +
+
+ + + + Preview +
+
+
, +] +`; + +exports[`renders components/image/demo/controlled-preview.tsx correctly 1`] = ` +Array [ +
+ scaleStep: + +
+
+ + + + + + + + + + +
+
+ +
+
+
, +
, + , +
+ + +
, +] +`; + +exports[`renders components/image/demo/fallback.tsx correctly 1`] = ` +
+ +
+
+ + + + Preview +
+
+
+`; + +exports[`renders components/image/demo/imageRender.tsx correctly 1`] = ` +
+ +
+
+ + + + Preview +
+
+
+`; + +exports[`renders components/image/demo/nested.tsx correctly 1`] = ` + +`; + +exports[`renders components/image/demo/placeholder.tsx correctly 1`] = ` +
+
+
+ + +
+
+ + + + Preview +
+
+
+
+
+ +
+
+`; + +exports[`renders components/image/demo/preview-group.tsx correctly 1`] = ` +Array [ +
+ +
+
+ + + + Preview +
+
+
, +
+ +
+
+ + + + Preview +
+
+
, +] +`; + +exports[`renders components/image/demo/preview-group-top-progress.tsx correctly 1`] = ` +Array [ +
+ +
+
+ + + + Preview +
+
+
, +
+ +
+
+ + + + Preview +
+
+
, +
+ +
+
+ + + + Preview +
+
+
, +] +`; + +exports[`renders components/image/demo/preview-group-visible.tsx correctly 1`] = ` +
+ +
+
+ + + + Preview +
+
+
+`; + +exports[`renders components/image/demo/preview-mask.tsx correctly 1`] = ` +
+ +
+
+
+ + + +
+
+ 示例 +
+
+
+
+`; + +exports[`renders components/image/demo/previewSrc.tsx correctly 1`] = ` +
+ +
+
+ + + + Preview +
+
+
+`; + +exports[`renders components/image/demo/toolbarRender.tsx correctly 1`] = ` +
+ +
+
+ + + + Preview +
+
+
+`; diff --git a/packages/meta/src/image/__tests__/__snapshots__/index.test.tsx.snap b/packages/meta/src/image/__tests__/__snapshots__/index.test.tsx.snap new file mode 100644 index 0000000..186df35 --- /dev/null +++ b/packages/meta/src/image/__tests__/__snapshots__/index.test.tsx.snap @@ -0,0 +1,326 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Image Default Group preview props 1`] = ` + +
+
+ +
+
+ + + + Preview +
+
+
+
+
+ + +
+
+
+
+
+ +
+ +`; + +exports[`Image rtl render component should be rendered correctly in RTL direction 1`] = ` +
+ +
+
+ + + + Preview +
+
+
+`; diff --git a/packages/meta/src/image/__tests__/demo-extend.test.ts b/packages/meta/src/image/__tests__/demo-extend.test.ts new file mode 100644 index 0000000..26e2be1 --- /dev/null +++ b/packages/meta/src/image/__tests__/demo-extend.test.ts @@ -0,0 +1,3 @@ +import { extendTest } from '../../../tests/shared/demoTest'; + +extendTest('image'); diff --git a/packages/meta/src/image/__tests__/demo.test.ts b/packages/meta/src/image/__tests__/demo.test.ts new file mode 100644 index 0000000..639111d --- /dev/null +++ b/packages/meta/src/image/__tests__/demo.test.ts @@ -0,0 +1,3 @@ +import demoTest from '../../../tests/shared/demoTest'; + +demoTest('image'); diff --git a/packages/meta/src/image/__tests__/image.test.ts b/packages/meta/src/image/__tests__/image.test.ts new file mode 100644 index 0000000..2e0739b --- /dev/null +++ b/packages/meta/src/image/__tests__/image.test.ts @@ -0,0 +1,5 @@ +import { imageDemoTest } from '../../../tests/shared/imageTest'; + +describe('Image image', () => { + imageDemoTest('image'); +}); diff --git a/packages/meta/src/image/__tests__/index.test.tsx b/packages/meta/src/image/__tests__/index.test.tsx new file mode 100644 index 0000000..e523ba2 --- /dev/null +++ b/packages/meta/src/image/__tests__/index.test.tsx @@ -0,0 +1,194 @@ +import React from 'react'; +import { Modal } from 'antd'; + +import Image from '..'; +import mountTest from '../../../tests/shared/mountTest'; +import rtlTest from '../../../tests/shared/rtlTest'; +import { fireEvent, render } from '../../../tests/utils'; +import ConfigProvider from '../../config-provider'; + +const src = 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png'; + +describe('Image', () => { + mountTest(Image); + rtlTest(Image); + it('Image preview props set false', () => { + const { container } = render(); + + fireEvent.click(container.querySelector('.ant-image')!); + expect(container.querySelector('.ant-image-preview-root')).toBe(null); + }); + it('Group preview props set false', () => { + const { container } = render( + + + , + ); + + fireEvent.click(container.querySelector('.ant-image')!); + + expect(container.querySelector('.ant-image-preview-root')).toBe(null); + }); + + it('Default preview props', () => { + const { container, baseElement } = render(); + + fireEvent.click(container.querySelector('.ant-image')!); + + expect(baseElement.querySelector('.ant-image-preview-mask')).toHaveClass('ant-fade'); + expect(baseElement.querySelector('.ant-image-preview')).toHaveClass('ant-zoom'); + }); + it('Default Group preview props', () => { + const { container, baseElement } = render( + + + , + ); + + fireEvent.click(container.querySelector('.ant-image')!); + + expect(baseElement.querySelector('.ant-image-preview-mask')).toHaveClass('ant-fade'); + expect(baseElement.querySelector('.ant-image-preview')).toHaveClass('ant-zoom'); + expect(baseElement).toMatchSnapshot(); + }); + it('Customize preview props', () => { + const { container, baseElement } = render( + , + ); + + fireEvent.click(container.querySelector('.ant-image')!); + + expect(container.querySelector('.ant-image-preview-root')).not.toBe(null); + + expect(baseElement.querySelector('.ant-image-preview')).toHaveClass('abc'); + expect(baseElement.querySelector('.ant-image-preview-mask')).toHaveClass('def'); + }); + it('Customize Group preview props', () => { + const { container, baseElement } = render( + + + , + ); + + fireEvent.click(container.querySelector('.ant-image')!); + + expect(baseElement.querySelector('.ant-image-preview')).toHaveClass('abc'); + expect(baseElement.querySelector('.ant-image-preview-mask')).toHaveClass('def'); + }); + it('ConfigProvider getPopupContainer', () => { + const { container, baseElement } = render( + <> +
+ document.querySelector('.container')!}> + + + , + ); + fireEvent.click(container.querySelector('.ant-image')!); + expect(baseElement.querySelector('.container')?.children.length).not.toBe(0); + }); + it('Preview forceRender props', async () => { + const onLoadCb = jest.fn(); + const PreviewImage: React.FC = () => ( + + ); + const { baseElement } = render(); + expect(baseElement.querySelector('.ant-image-preview-root')).not.toBe(null); + baseElement.querySelector('.ant-image-preview-img')?.addEventListener('load', onLoadCb); + fireEvent.load(baseElement.querySelector('.ant-image-preview-img')!); + expect(onLoadCb).toHaveBeenCalled(); + }); + it('Preview should support rootClassName', () => { + const { container, baseElement } = render( + + + , + ); + + fireEvent.click(container.querySelector('.ant-image')!); + + expect(baseElement.querySelector('.test-root-class')).toBeTruthy(); + }); + it('Image.PreviewGroup preview in a nested modal where z-index Settings should be correct', () => { + const App = () => ( + + + + + + + + + + + + ); + const { baseElement } = render(); + + fireEvent.click(baseElement.querySelector('.ant-image')!); + + expect( + ( + baseElement.querySelector( + '.test-image-preview-class .ant-image-preview-wrap', + ) as HTMLElement + ).style.zIndex, + ).toBe('1301'); + expect( + ( + baseElement.querySelector( + '.test-image-preview-class.ant-image-preview-operations-wrapper', + ) as HTMLElement + ).style.zIndex, + ).toBe('1302'); + + fireEvent.click(baseElement.querySelectorAll('.ant-image')[1]!); + + expect( + ( + baseElement.querySelector( + '.test-image-preview-group-class .ant-image-preview-wrap', + ) as HTMLElement + ).style.zIndex, + ).toBe('1301'); + expect( + ( + baseElement.querySelector( + '.test-image-preview-group-class.ant-image-preview-operations-wrapper', + ) as HTMLElement + ).style.zIndex, + ).toBe('1302'); + }); +}); diff --git a/packages/meta/src/image/demo/basic.md b/packages/meta/src/image/demo/basic.md new file mode 100644 index 0000000..d61b945 --- /dev/null +++ b/packages/meta/src/image/demo/basic.md @@ -0,0 +1,7 @@ +## zh-CN + +单击图像可以放大显示。 + +## en-US + +Click the image to zoom in. diff --git a/packages/meta/src/image/demo/basic.tsx b/packages/meta/src/image/demo/basic.tsx new file mode 100644 index 0000000..30a75d9 --- /dev/null +++ b/packages/meta/src/image/demo/basic.tsx @@ -0,0 +1,11 @@ +import React from 'react'; +import { Image } from 'antd'; + +const App: React.FC = () => ( + +); + +export default App; diff --git a/packages/meta/src/image/demo/component-token.md b/packages/meta/src/image/demo/component-token.md new file mode 100644 index 0000000..135a6b2 --- /dev/null +++ b/packages/meta/src/image/demo/component-token.md @@ -0,0 +1,7 @@ +## zh-CN + +自定义组件 Token。 + +## en-US + +Custom component token. diff --git a/packages/meta/src/image/demo/component-token.tsx b/packages/meta/src/image/demo/component-token.tsx new file mode 100644 index 0000000..b2843ad --- /dev/null +++ b/packages/meta/src/image/demo/component-token.tsx @@ -0,0 +1,31 @@ +import React from 'react'; +import { ConfigProvider, Image } from 'antd'; + +const App: React.FC = () => ( + + `当前 ${current} / 总计 ${total}` }} + > + + + + +); + +export default App; diff --git a/packages/meta/src/image/demo/controlled-preview.md b/packages/meta/src/image/demo/controlled-preview.md new file mode 100644 index 0000000..e6edd79 --- /dev/null +++ b/packages/meta/src/image/demo/controlled-preview.md @@ -0,0 +1,7 @@ +## zh-CN + +可以使预览受控。 + +## en-US + +You can make preview controlled. diff --git a/packages/meta/src/image/demo/controlled-preview.tsx b/packages/meta/src/image/demo/controlled-preview.tsx new file mode 100644 index 0000000..252e282 --- /dev/null +++ b/packages/meta/src/image/demo/controlled-preview.tsx @@ -0,0 +1,41 @@ +import React, { useState } from 'react'; +import { Button, Image, InputNumber } from 'antd'; + +const App: React.FC = () => { + const [visible, setVisible] = useState(false); + const [scaleStep, setScaleStep] = useState(0.5); + + return ( + <> +
+ scaleStep:{' '} + setScaleStep(val!)} + /> +
+
+ + { + setVisible(value); + }, + }} + /> + + ); +}; + +export default App; diff --git a/packages/meta/src/image/demo/fallback.md b/packages/meta/src/image/demo/fallback.md new file mode 100644 index 0000000..45c2957 --- /dev/null +++ b/packages/meta/src/image/demo/fallback.md @@ -0,0 +1,7 @@ +## zh-CN + +加载失败显示图像占位符。 + +## en-US + +Load failed to display image placeholder. diff --git a/packages/meta/src/image/demo/fallback.tsx b/packages/meta/src/image/demo/fallback.tsx new file mode 100644 index 0000000..7c2e8ce --- /dev/null +++ b/packages/meta/src/image/demo/fallback.tsx @@ -0,0 +1,13 @@ +import React from 'react'; +import { Image } from 'antd'; + +const App: React.FC = () => ( + +); + +export default App; diff --git a/packages/meta/src/image/demo/imageRender.md b/packages/meta/src/image/demo/imageRender.md new file mode 100644 index 0000000..be37917 --- /dev/null +++ b/packages/meta/src/image/demo/imageRender.md @@ -0,0 +1,7 @@ +## zh-CN + +可以自定义预览内容。 + +## en-US + +You can customize the preview content. diff --git a/packages/meta/src/image/demo/imageRender.tsx b/packages/meta/src/image/demo/imageRender.tsx new file mode 100644 index 0000000..e667a44 --- /dev/null +++ b/packages/meta/src/image/demo/imageRender.tsx @@ -0,0 +1,23 @@ +import React from 'react'; +import { Image } from 'antd'; + +const App: React.FC = () => ( + ( +