@hzab/form-render 1.0.3 → 1.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hzab/form-render",
3
- "version": "1.0.3",
3
+ "version": "1.1.1",
4
4
  "description": "",
5
5
  "main": "lib",
6
6
  "scripts": {
@@ -0,0 +1,39 @@
1
+ .addr-list {
2
+ flex: 1;
3
+ height: 100%;
4
+ min-height: 600px;
5
+ overflow-y: auto;
6
+ .addr-item {
7
+ display: flex;
8
+ justify-content: space-between;
9
+ align-items: center;
10
+
11
+ .item-content {
12
+ display: flex;
13
+ .item-addr{
14
+ flex: 1;
15
+ }
16
+ .item-range {
17
+ flex-shrink: 0;
18
+ color: #999;
19
+ }
20
+ }
21
+
22
+ .action-box {
23
+ min-width: 56px;
24
+ }
25
+ .action-btn {
26
+ margin-right: 12px;
27
+ font-size: 16px;
28
+ &:last-child {
29
+ margin-right: none;
30
+ }
31
+ }
32
+ .edit-btn {
33
+ color: #0080ff;
34
+ }
35
+ .del-btn {
36
+ color: #ff3333;
37
+ }
38
+ }
39
+ }
@@ -0,0 +1,43 @@
1
+ import { Popconfirm } from "antd";
2
+ import { EditOutlined, DeleteOutlined } from "@ant-design/icons";
3
+
4
+ import "./index.less";
5
+
6
+ export const AddrList = (props) => {
7
+ const { list, ItemRender, onItemClick, onEdit, onDel, hasAction = true } = props;
8
+
9
+ const CItemRender = ItemRender ?? LItemRender;
10
+
11
+ return (
12
+ <div className="addr-list">
13
+ {list?.map((it, i) => {
14
+ return (
15
+ <div className="addr-item" key={it.id ?? i} onClick={onItemClick}>
16
+ <CItemRender item={it} index={i} />
17
+ {hasAction && (
18
+ <div className="action-box">
19
+ <EditOutlined className="action-btn edit-btn" onClick={(e) => onEdit(it, i, e)} />
20
+ <Popconfirm title={"确认删除当前项?"} onConfirm={(e) => onDel(it, i, e)}>
21
+ <DeleteOutlined className="action-btn del-btn" />
22
+ </Popconfirm>
23
+ </div>
24
+ )}
25
+ </div>
26
+ );
27
+ })}
28
+ </div>
29
+ );
30
+ };
31
+
32
+ function LItemRender(props) {
33
+ const { item } = props;
34
+ const { address, range } = item || {};
35
+ return (
36
+ <div className="item-content">
37
+ <div className="item-addr">{address}</div>
38
+ {range && <span className="item-range">(范围:{range})</span>}
39
+ </div>
40
+ );
41
+ }
42
+
43
+ export default AddrList;
@@ -0,0 +1,19 @@
1
+ .popup-box {
2
+ position: absolute;
3
+ z-index: 9;
4
+ min-width: 200px;
5
+ padding: 20px;
6
+ border-radius: 4px;
7
+ background-color: #fff;
8
+ box-shadow: 0px 0px 4px 0px rgba(0, 0, 0, 0.25);
9
+
10
+ .popup-footer {
11
+ .ant-btn {
12
+ margin-right: 12px;
13
+
14
+ &:last-child {
15
+ margin-right: none;
16
+ }
17
+ }
18
+ }
19
+ }
@@ -0,0 +1,81 @@
1
+ import { useRef, forwardRef, useImperativeHandle, useState } from "react";
2
+ import { Button } from "antd";
3
+
4
+ import FormRender from "@hzab/form-render";
5
+
6
+ import RangeSchema from "./range.schema.json";
7
+
8
+ import "./index.less";
9
+
10
+ export interface IPopupProps {
11
+ formProps?: {
12
+ schema;
13
+ initialValues?: Object;
14
+ };
15
+ }
16
+
17
+ export interface IData {
18
+ top: number;
19
+ left: number;
20
+ address: string;
21
+ }
22
+
23
+ export const Popup = forwardRef((props: IPopupProps, parentRef) => {
24
+ const { formProps = { schema: RangeSchema } } = props;
25
+
26
+ const [data, setData] = useState<IData>();
27
+
28
+ const formRef: any = useRef();
29
+ const resolveRef = useRef<Function>();
30
+ const rejectRef = useRef<Function>();
31
+
32
+ useImperativeHandle(parentRef, () => ({
33
+ onShow,
34
+ onCancel,
35
+ }));
36
+
37
+ function onShow(data) {
38
+ setData(data ?? {});
39
+ return new Promise((resolve, reject) => {
40
+ resolveRef.current = resolve;
41
+ rejectRef.current = reject;
42
+ });
43
+ }
44
+
45
+ function onCancel() {
46
+ setData(null);
47
+ rejectRef.current();
48
+ }
49
+
50
+ function onConfirm() {
51
+ formRef.current?.formRender?.validate().then(() => {
52
+ resolveRef.current(formRef.current.formRender.values);
53
+ setData(null);
54
+ }).catch(() => {
55
+
56
+ });
57
+ }
58
+
59
+ return data ? (
60
+ <div className="popup-box" style={{ top: data?.top, left: data?.left }}>
61
+ <div className="popup-body">
62
+ {/* TODO: schema 中插入地址字段 text 格式 */}
63
+ <div>地址:{data?.address}</div>
64
+ <FormRender
65
+ ref={formRef}
66
+ {...formProps}
67
+ schema={formProps?.schema ?? RangeSchema}
68
+ initialValues={{ ...formProps?.initialValues, ...data }}
69
+ />
70
+ </div>
71
+ <div className="popup-footer">
72
+ <Button type="primary" onClick={onConfirm}>
73
+ 确定
74
+ </Button>
75
+ <Button onClick={onCancel}>取消</Button>
76
+ </div>
77
+ </div>
78
+ ) : null;
79
+ });
80
+
81
+ export default Popup;
@@ -0,0 +1,21 @@
1
+ {
2
+ "form": {},
3
+ "schema": {
4
+ "type": "object",
5
+ "properties": {
6
+ "range": {
7
+ "type": "string",
8
+ "title": "范围",
9
+ "x-decorator": "FormItem",
10
+ "x-component": "Input",
11
+ "x-validator": [],
12
+ "x-component-props": {},
13
+ "x-decorator-props": {},
14
+ "x-designable-id": "range",
15
+ "name": "range",
16
+ "x-index": 0
17
+ }
18
+ },
19
+ "x-designable-id": "o6m980wykh1"
20
+ }
21
+ }
@@ -0,0 +1,26 @@
1
+ .location-list-picker {
2
+ width: 100%;
3
+ display: flex;
4
+ .location-map-search {
5
+ top: 12px;
6
+ left: 12px;
7
+ }
8
+ .map-box {
9
+ position: relative;
10
+ width: 50%;
11
+ min-height: 600px;
12
+ margin-right: 12px;
13
+ .location-list-picker-notice-bar {
14
+ position: absolute;
15
+ left: 0;
16
+ right: 0;
17
+ bottom: 0;
18
+ z-index: 9;
19
+ }
20
+ .add-btn {
21
+ position: absolute;
22
+ top: 12px;
23
+ right: 12px;
24
+ }
25
+ }
26
+ }
@@ -0,0 +1,221 @@
1
+ // @ts-nocheck
2
+ import { useEffect, useRef, useState } from "react";
3
+ import { Button, message, Alert } from "antd";
4
+ import { observer } from "@formily/react";
5
+
6
+ import { nanoid } from "nanoid";
7
+
8
+ import AddrList from "./components/AddrList";
9
+ import Popup from "./components/Popup";
10
+
11
+ import AMapCom from "../LocationPicker/Map/AMap";
12
+ import MapSearch from "../LocationPicker/components/MapSearch";
13
+ import { MapUtils } from "../LocationPicker/Map/AMap/common/utils";
14
+ import { getAddress } from "../LocationPicker/servers";
15
+
16
+ import "./index.less";
17
+
18
+ export const LocationListPicker = observer((props) => {
19
+ const { value, listProps, popupProps, ItemRender, markerIconConf, isAutoSearch, onChange } = props;
20
+
21
+ const [loading, setLoading] = useState(props.loading || false);
22
+
23
+ const mapUtilsRef = useRef<MapUtils>();
24
+ const listRef = useRef(value);
25
+ const statusRef = useRef();
26
+ const currentItemRef = useRef();
27
+ const popupRef = useRef();
28
+ const isEditingRef = useRef();
29
+
30
+ useEffect(() => {
31
+ return () => {
32
+ // 取消监听
33
+ mapUtilsRef.current?.off("click", onMapClick);
34
+ };
35
+ }, []);
36
+
37
+ useEffect(() => {
38
+ if (!Array.isArray(value) || value?.length == 0 || !mapUtilsRef.current) {
39
+ return;
40
+ }
41
+ // 处理 id
42
+ value?.forEach((it) => {
43
+ if (!it.id) {
44
+ it.id = nanoid();
45
+ }
46
+ });
47
+ listRef.current = value;
48
+ renderMarker();
49
+ }, [value]);
50
+
51
+ function renderMarker() {
52
+ listRef.current?.forEach((it) => {
53
+ mapUtilsRef.current.setMarker(it.longitude, it.latitude, { ...it });
54
+ mapUtilsRef.current.setCircle(it.longitude, it.latitude, it.range, { ...it });
55
+ });
56
+ }
57
+
58
+ function mapInit({ map, mapUtils }) {
59
+ mapUtilsRef.current = mapUtils;
60
+ // 启动监听事件
61
+ mapUtilsRef.current.on("click", onMapClick);
62
+ renderMarker();
63
+ }
64
+
65
+ function onAddClick() {
66
+ if (isEditingRef.current) {
67
+ message.warn("请先完成上次操作");
68
+ return;
69
+ }
70
+ statusRef.current = "add";
71
+ }
72
+
73
+ // 处理 marker、范围 绘制填写逻辑
74
+ async function onMapClick(e) {
75
+ const status = statusRef.current;
76
+ const isAdd = status === "add";
77
+ const isEdit = status === "edit";
78
+
79
+ if (!isAdd && !isEdit) {
80
+ return;
81
+ }
82
+ isEditingRef.current = true;
83
+
84
+ const { lng, lat } = e.lnglat || {};
85
+
86
+ const preItem = currentItemRef.current;
87
+ const id = isAdd ? nanoid() : preItem?.id;
88
+
89
+ let item = {
90
+ id,
91
+ longitude: lng,
92
+ latitude: lat,
93
+ address: "",
94
+ range: null,
95
+ ...preItem,
96
+ };
97
+
98
+ // 解决新增时点击修改重复创建的问题
99
+ if (isAdd) {
100
+ item.longitude = lng;
101
+ item.latitude = lat;
102
+ currentItemRef.current = item;
103
+ }
104
+
105
+ // 接口获取地址
106
+ item.address = await getAddress(lng, lat);
107
+
108
+ // 更新地图状态
109
+ mapUtilsRef.current.setMarker(lng, lat, { ...item });
110
+ // 回显范围
111
+ mapUtilsRef.current.setCircle(lng, lat, item.range, { ...item });
112
+
113
+ // 根据经纬度获取弹窗位置
114
+ const { x, y } = mapUtilsRef.current.map.lngLatToContainer([lng, lat]);
115
+ let res = {};
116
+ try {
117
+ res = await popupRef.current?.onShow({
118
+ top: y,
119
+ left: x,
120
+ address: item.address,
121
+ range: item.range,
122
+ item,
123
+ });
124
+ } catch (error) {
125
+ // 新增状态,取消时清除已添加的 marker circle
126
+ isAdd && mapUtilsRef.current.rmMarker(item.id);
127
+ // 编辑状态恢复数据
128
+ if (isEdit) {
129
+ mapUtilsRef.current.setMarker(preItem.longitude, preItem.latitude, { ...preItem });
130
+ mapUtilsRef.current.setCircle(preItem.longitude, preItem.latitude, preItem.range, { ...preItem });
131
+ }
132
+ clearStatus();
133
+ return;
134
+ }
135
+
136
+ // 设置范围
137
+ mapUtilsRef.current.setCircle(lng, lat, res.range, { ...item });
138
+
139
+ item = Object.assign(item, res);
140
+
141
+ const list = [...listRef.current];
142
+
143
+ if (isAdd) {
144
+ list.push(item);
145
+ } else if (isEdit) {
146
+ const idx = list.findIndex((it) => it.id === id);
147
+ list.splice(idx, 1, item);
148
+ }
149
+
150
+ onChange && onChange(list);
151
+ clearStatus();
152
+ }
153
+
154
+ function clearStatus() {
155
+ isEditingRef.current = false;
156
+ // 清除状态,解决重复创建的问题
157
+ statusRef.current = null;
158
+ // 编辑状态清除数据
159
+ currentItemRef.current = null;
160
+ }
161
+
162
+ function onEdit(item, i) {
163
+ if (isEditingRef.current) {
164
+ message.warn("请先完成上次操作");
165
+ return;
166
+ }
167
+ statusRef.current = "edit";
168
+ currentItemRef.current = item;
169
+ mapUtilsRef.current.setCenter(item.longitude, item.latitude, { immediately: true });
170
+ onMapClick({
171
+ lnglat: {
172
+ lng: item.longitude,
173
+ lat: item.latitude,
174
+ },
175
+ });
176
+ }
177
+
178
+ function onDel(item, i) {
179
+ const list = [...listRef.current];
180
+ list?.splice(i, 1);
181
+ onChange && onChange(list);
182
+ // 清除对应的 marker
183
+ mapUtilsRef.current.rmMarker(item.id);
184
+ // 清除对应的 circle
185
+ mapUtilsRef.current.rmCircle(item.id);
186
+ }
187
+
188
+ function setPoint(lng, lat) {
189
+ mapUtilsRef.current.setCenter(lng, lat, { immediately: true });
190
+ mapUtilsRef.current.map.setZoom(15, true);
191
+ statusRef.current = "add";
192
+ onMapClick({
193
+ lnglat: {
194
+ lng,
195
+ lat,
196
+ },
197
+ });
198
+ }
199
+
200
+ return (
201
+ <div className="location-list-picker">
202
+ <div className="map-box">
203
+ <AMapCom init={mapInit} loading={loading} markerIconConf={markerIconConf} />
204
+ <MapSearch setPoint={setPoint} isAutoSearch={isAutoSearch} />
205
+ <Alert
206
+ className="location-list-picker-notice-bar"
207
+ message={"点击地图上的位置,选择目标地址"}
208
+ type="info"
209
+ showIcon
210
+ />
211
+ <Button className="add-btn" onClick={onAddClick}>
212
+ 新增地址
213
+ </Button>
214
+ <Popup ref={popupRef} {...popupProps} />
215
+ </div>
216
+ <AddrList {...listProps} list={value} ItemRender={ItemRender} onEdit={onEdit} onDel={onDel} />
217
+ </div>
218
+ );
219
+ });
220
+
221
+ export default LocationListPicker;
@@ -1,5 +1,6 @@
1
1
  import AMapLoader from "./loader";
2
2
  import { markerIconBase64 } from "../../../assets/svg-icon";
3
+ import { nanoid } from "nanoid";
3
4
 
4
5
  if (!window._AMapLoaderTemp) {
5
6
  window._AMapLoaderTemp = {};
@@ -38,6 +39,7 @@ export class MapUtils {
38
39
  public map;
39
40
  public AMap;
40
41
  public markers: markersT = {};
42
+ public circles = {};
41
43
  public pickerMarker;
42
44
  public markerIconConf;
43
45
 
@@ -68,14 +70,18 @@ export class MapUtils {
68
70
  * @param lat
69
71
  * @param opt
70
72
  */
71
- setCenter(lon, lat, opt = { map: this.map, AMap: this.AMap || window.AMap }) {
72
- const { map = this.map, AMap = this.AMap || window.AMap } = opt || {};
73
+ setCenter(
74
+ lon,
75
+ lat,
76
+ opt = { immediately: undefined, duration: undefined, map: this.map, AMap: this.AMap || window.AMap },
77
+ ) {
78
+ const { map = this.map, AMap = this.AMap || window.AMap, immediately, duration } = opt || {};
73
79
  if (!(AMap && map)) {
74
80
  throw new Error("请传入地图实例和 AMap");
75
81
  }
76
82
  const position = new window.AMap.LngLat(lon, lat);
77
83
  // 设置中心点
78
- map.setCenter(position);
84
+ map.setCenter(position, immediately, duration);
79
85
  }
80
86
 
81
87
  /**
@@ -86,7 +92,8 @@ export class MapUtils {
86
92
  * @returns
87
93
  */
88
94
  setMarker(lon, lat, opt?: setMarkerOptT) {
89
- const { id = Date.now(), marker, map = this.map, AMap = this.AMap, markerIconConf = {} } = opt || {};
95
+ const { id = nanoid(), marker, map = this.map, AMap = this.AMap, markerIconConf = {} } = opt || {};
96
+
90
97
  if (!(AMap && map)) {
91
98
  throw new Error("请传入地图实例和 AMap");
92
99
  }
@@ -96,6 +103,7 @@ export class MapUtils {
96
103
  }
97
104
  const position = new AMap.LngLat(lon, lat);
98
105
  let _marker = marker || this.markers[id];
106
+
99
107
  // 创建 Marker 或修改位置
100
108
  if (_marker) {
101
109
  // @ts-ignore
@@ -116,12 +124,67 @@ export class MapUtils {
116
124
  position,
117
125
  });
118
126
  this.markers[id] = _m;
127
+
119
128
  map?.add(_m);
120
129
  return _m;
121
130
  }
122
131
 
132
+ /**
133
+ * 删除指定 marker
134
+ * @param id
135
+ */
136
+ rmMarker(id) {
137
+ this.map.remove(this.markers[id]);
138
+ delete this.markers[id];
139
+ }
140
+
141
+ setCircle(lon, lat, radius, opt?) {
142
+ const { id = nanoid(), circle, map = this.map, AMap = this.AMap, circleConf = {} } = opt || {};
143
+ if (!(AMap && map)) {
144
+ throw new Error("请传入地图实例和 AMap");
145
+ }
146
+ if (lon === null || lon === undefined || lat === null || lat === undefined) {
147
+ console.warn("setMarker 请传入正确的经纬度");
148
+ return;
149
+ }
150
+ // circles
151
+
152
+ const center = new AMap.LngLat(lon, lat);
153
+ let _circle = circle || this.circles[id];
154
+
155
+ // 创建 Marker 或修改位置
156
+ if (_circle) {
157
+ // @ts-ignore
158
+ _circle.setCenter(center);
159
+ _circle.setRadius(radius);
160
+ return _circle;
161
+ }
162
+
163
+ //创建圆形 Circle 实例
164
+ const _c = new AMap.Circle({
165
+ center: center, // 圆心
166
+ radius: radius, // 半径
167
+ borderWeight: 1, // 描边的宽度
168
+ strokeColor: "#0080FF", // 轮廓线颜色
169
+ strokeWeight: 1, // 轮廓线宽度
170
+ fillOpacity: 0.1, // 圆形填充透明度
171
+ fillColor: "#0080FF", // 圆形填充颜色
172
+ zIndex: 50, // 圆形的叠加顺序
173
+ ...circleConf,
174
+ });
175
+ this.circles[id] = _c;
176
+ map?.add(_c);
177
+ return _c;
178
+ }
179
+
180
+ rmCircle(id) {
181
+ this.map.remove(this.circles[id]);
182
+ delete this.circles[id];
183
+ }
184
+
123
185
  /**
124
186
  * 创建或修改地图选点的 Marker
187
+ * 在当前 utils 中唯一
125
188
  * @param lon
126
189
  * @param lat
127
190
  * @param opt
@@ -1,4 +1,8 @@
1
1
  .a-map-container {
2
2
  width: 100%;
3
3
  height: 100%;
4
+ .amap-logo,
5
+ .amap-copyright {
6
+ z-index: 5;
7
+ }
4
8
  }
@@ -11,7 +11,7 @@ import React, { useEffect, useState } from "react";
11
11
  import { nanoid } from "nanoid";
12
12
 
13
13
  import { handleInputFileList, getFileName, getFileType } from "./common/utils";
14
- import UploadOss from "./common/ossUpload";
14
+ import LUploadOss from "./common/ossUpload";
15
15
 
16
16
  import PreviewModal, { hasPreviewRender, hasPreviewMedium } from "./components/PreviewModal";
17
17
 
@@ -43,6 +43,9 @@ export function Uploader({ onChange, ...props }) {
43
43
  templateUrl,
44
44
  beforeUploadCheck,
45
45
  templateDownloadText = "模板下载",
46
+ isFileObj = false,
47
+ isFileJson = false,
48
+ UploadOss,
46
49
  } = props;
47
50
 
48
51
  const [fileList, setFileList] = useState([]);
@@ -60,8 +63,14 @@ export function Uploader({ onChange, ...props }) {
60
63
  _file = _file?.originFileObj;
61
64
  }
62
65
  // 从文件对象中获取 url,ossPromise 为上传中的文件,兼容 file 为字符串的情况
63
- let str = (await _file.ossPromise) || _file.url || _file.ossUrl || _file;
64
- _files[i] = str;
66
+
67
+ if (isFileObj) {
68
+ let { ossUrl, url, uid, name, type } = (await _file.ossPromise) || _file;
69
+ _files[i] = { ossUrl, url, uid, name, type };
70
+ } else {
71
+ let str = (await _file.ossPromise) || _file.url || _file.ossUrl || _file;
72
+ _files[i] = str;
73
+ }
65
74
  }
66
75
  }
67
76
  // 处理空数据情况
@@ -71,17 +80,25 @@ export function Uploader({ onChange, ...props }) {
71
80
  // maxCount 为 1 的时候返回结果去除数组
72
81
  _files = _files[0];
73
82
  }
74
- onChange && onChange?.(_files);
83
+ onChange && onChange?.(isFileJson ? JSON.stringify(_files) : _files);
75
84
  };
76
85
 
77
86
  useEffect(() => {
78
87
  if (!value) {
79
88
  return;
80
89
  }
81
- let _list = value;
90
+
91
+ let _list = [];
92
+ try {
93
+ _list = JSON.parse(value);
94
+ } catch (e) {
95
+ _list = value;
96
+ }
97
+
82
98
  if (typeof _list === "string") {
83
99
  _list = value.split(",");
84
100
  }
101
+
85
102
  if (Array.isArray(_list)) {
86
103
  _list = _list.map((res) => {
87
104
  if (typeof res === "string") {
@@ -173,7 +190,8 @@ export function Uploader({ onChange, ...props }) {
173
190
  },
174
191
  customRequest: isOssUpload
175
192
  ? function customRequest({ action, data, file, filename, headers, method, onSuccess, onProgress, onError }) {
176
- const ossUpload = new UploadOss({
193
+ const _UploadOss = UploadOss || LUploadOss;
194
+ const ossUpload = new _UploadOss({
177
195
  serverUrl: ossUrl,
178
196
  ...ossOpt,
179
197
  });
@@ -188,7 +206,7 @@ export function Uploader({ onChange, ...props }) {
188
206
  file.url = res?.data?.data?.fileUrl ?? res?.data?.fileUrl ?? res?.fileUrl;
189
207
  file.ossUrl = file.url;
190
208
  onSuccess(file);
191
- return file.ossUrl;
209
+ return isFileObj ? file : file.ossUrl;
192
210
  })
193
211
  .catch(onError);
194
212
  }
@@ -8,6 +8,7 @@ export * from "./TreeCheckbox";
8
8
  export * from "./DatePicker";
9
9
 
10
10
  export * from "./LocationPicker";
11
+ export * from "./LocationListPicker";
11
12
  export * from "./RichEditor";
12
13
 
13
14
  export { Text };