@hzab/form-render-mobile 0.0.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.
Files changed (39) hide show
  1. package/README.md +67 -0
  2. package/lib/index.js +46475 -0
  3. package/lib/static/imgs/approachPointIcon_f2eb69df51.png +0 -0
  4. package/lib/static/imgs/layers-2x_8f2c4d1147.png +0 -0
  5. package/lib/static/imgs/layers_416d91365b.png +0 -0
  6. package/lib/static/imgs/marker-icon_2b3e1faf89.png +0 -0
  7. package/package.json +59 -0
  8. package/src/common/schemaHandler.js +55 -0
  9. package/src/components/Cascader/index.jsx +75 -0
  10. package/src/components/Cascader/index.less +3 -0
  11. package/src/components/DatePicker/index.less +3 -0
  12. package/src/components/DatePicker/index.tsx +98 -0
  13. package/src/components/MapPicker/common/approachPointIcon.png +0 -0
  14. package/src/components/MapPicker/common/getCurrentPosition.js +27 -0
  15. package/src/components/MapPicker/common/gpsConvert.js +236 -0
  16. package/src/components/MapPicker/common/map-config.js +7 -0
  17. package/src/components/MapPicker/index.jsx +82 -0
  18. package/src/components/MapPicker/line-picker/car-route.json +426 -0
  19. package/src/components/MapPicker/line-picker/index.jsx +188 -0
  20. package/src/components/MapPicker/line-picker/index.less +41 -0
  21. package/src/components/MapPicker/line-picker/mockLonLat.js +1 -0
  22. package/src/components/MapPicker/location-picker/index.jsx +104 -0
  23. package/src/components/MapPicker/location-picker/index.less +36 -0
  24. package/src/components/NumberPicker/index.jsx +16 -0
  25. package/src/components/NumberPicker/index.less +3 -0
  26. package/src/components/Password/index.jsx +14 -0
  27. package/src/components/Select/index.less +3 -0
  28. package/src/components/Select/index.tsx +60 -0
  29. package/src/components/Text/index.tsx +23 -0
  30. package/src/components/Uploader/common/cordova-camera.js +188 -0
  31. package/src/components/Uploader/common/utils.js +87 -0
  32. package/src/components/Uploader/index.jsx +31 -0
  33. package/src/components/Uploader/uploader.jsx +246 -0
  34. package/src/components/Uploader/uploader.less +36 -0
  35. package/src/components/Uploader/video/index.jsx +37 -0
  36. package/src/components/Uploader/video/index.less +37 -0
  37. package/src/components/index.ts +10 -0
  38. package/src/index.tsx +101 -0
  39. package/src/type.d.ts +15 -0
@@ -0,0 +1,104 @@
1
+ import { useEffect, useRef, useState } from "react";
2
+ import { Marker, TileLayer, Map } from "react-leaflet";
3
+ import "leaflet/dist/leaflet.css";
4
+ import { Button, SpinLoading } from "antd-mobile";
5
+
6
+ import { wgs84togcj02 } from "../common/gpsConvert";
7
+
8
+ import getCurrentPosition from "../common/getCurrentPosition";
9
+ import { mapConfig } from "../common/map-config";
10
+ import pointIconUrl from "../common/approachPointIcon.png";
11
+
12
+ import "./index.less";
13
+
14
+ // 地图选中点图标
15
+ const LeafIcon = new L.Icon({
16
+ iconUrl: pointIconUrl,
17
+ iconSize: [30, 40],
18
+ });
19
+
20
+ let map = null;
21
+
22
+ function ReactLeafletMap(props) {
23
+ const {
24
+ onConfirm,
25
+ onCancel,
26
+ data,
27
+ // TODO: 切换成正确的地址(通过 props | config 传递?)
28
+ mapUrl = "https://map.geoq.cn/ArcGIS/rest/services/ChinaOnlineStreetPurplishBlue/MapServer/tile/{z}/{y}/{x}",
29
+ // mapUrl = "http://20.0.6.168:8083/api/map/org/{z}/{y}/{x}",
30
+ } = props;
31
+ const [point, setPoint] = useState(null);
32
+ const [loading, setLoading] = useState(false);
33
+ const mapRef = useRef();
34
+
35
+ function onClickMap(e) {
36
+ setPoint(e?.latlng);
37
+ }
38
+
39
+ function getPosition() {
40
+ setLoading(true);
41
+ getCurrentPosition()
42
+ .then((position) => {
43
+ const { coords } = position;
44
+ const [lat, lng] = wgs84togcj02(coords.longitude, coords.latitude, true);
45
+ setPoint({ lat, lng });
46
+ map.setView(L.latLng(lat, lng), mapConfig.zoom);
47
+ setLoading(false);
48
+ })
49
+ .catch((err) => {
50
+ console.error("getPosition", err);
51
+ setLoading(false);
52
+ });
53
+ }
54
+
55
+ useEffect(() => {
56
+ map = mapRef?.current?.contextValue?.map;
57
+
58
+ // 数据回填
59
+ if (data && data.lng && data.lat) {
60
+ const { lat, lng } = data;
61
+ map.setView(L.latLng(lat, lng), mapConfig.zoom);
62
+ setPoint({ lat, lng });
63
+ } else {
64
+ // 获取初始位置(当前位置)
65
+ getPosition();
66
+ }
67
+ }, []);
68
+
69
+ function onSave() {
70
+ onConfirm && onConfirm(point);
71
+ }
72
+
73
+ return (
74
+ <div className="location-picker">
75
+ <div className="location-picker-header">
76
+ <span>经度:{point?.lng}</span>
77
+ &nbsp;
78
+ <span>纬度:{point?.lat}</span>
79
+ <span className="get-current-location-btn" onClick={getPosition}>
80
+ 获取当前坐标
81
+ </span>
82
+ </div>
83
+ <Map className="location-picker-map" {...mapConfig} ref={mapRef} onclick={onClickMap}>
84
+ <TileLayer url={mapUrl} />
85
+ {point && <Marker key={`marker`} position={point} icon={LeafIcon} />}
86
+ </Map>
87
+ <div className="location-picker-footer">
88
+ <Button className="footer-btn" onClick={onCancel}>
89
+ 取消
90
+ </Button>
91
+ <Button className="footer-btn" color="primary" fill="solid" onClick={onSave}>
92
+ 确定
93
+ </Button>
94
+ </div>
95
+ {loading ? (
96
+ <div className="loading-wrap">
97
+ <SpinLoading />
98
+ </div>
99
+ ) : null}
100
+ </div>
101
+ );
102
+ }
103
+
104
+ export default ReactLeafletMap;
@@ -0,0 +1,36 @@
1
+ .location-picker {
2
+ position: relative;
3
+ display: flex;
4
+ flex-direction: column;
5
+ height: 100%;
6
+ .location-picker-header {
7
+ display: flex;
8
+ justify-content: space-between;
9
+ padding: 0 20px;
10
+ .get-current-location-btn {
11
+ padding: 0 8px;
12
+ cursor: pointer;
13
+ }
14
+ }
15
+ .location-picker-map {
16
+ flex: 1;
17
+ }
18
+ .location-picker-footer {
19
+ display: flex;
20
+ .footer-btn {
21
+ flex: 1;
22
+ }
23
+ }
24
+ .loading-wrap {
25
+ position: absolute;
26
+ top: 0;
27
+ right: 0;
28
+ bottom: 0;
29
+ left: 0;
30
+ display: flex;
31
+ justify-content: center;
32
+ align-items: center;
33
+ background: rgba(0, 0, 0, 0.55);
34
+ z-index: 1000;
35
+ }
36
+ }
@@ -0,0 +1,16 @@
1
+ import React from "react";
2
+ import { connect, mapProps } from "@formily/react";
3
+ import { Stepper } from "antd-mobile";
4
+
5
+ import "./index.less";
6
+
7
+ function NumberPicker({ precision, className, ...props }) {
8
+ return <Stepper className={`number-picker ${className}`} {...props} digits={precision} />;
9
+ }
10
+
11
+ export default connect(
12
+ NumberPicker,
13
+ mapProps((props, field) => {
14
+ return { ...props, form: field.form, field };
15
+ }),
16
+ );
@@ -0,0 +1,3 @@
1
+ .number-picker {
2
+ width: 100%;
3
+ }
@@ -0,0 +1,14 @@
1
+ import React from "react";
2
+ import { connect, mapProps } from "@formily/react";
3
+ import { Input } from "antd-mobile";
4
+
5
+ function Password(props) {
6
+ return <Input {...props} type="password" />;
7
+ }
8
+
9
+ export default connect(
10
+ Password,
11
+ mapProps((props, field) => {
12
+ return { ...props, form: field.form, field };
13
+ }),
14
+ );
@@ -0,0 +1,3 @@
1
+ .formily-select {
2
+ height: 100%;
3
+ }
@@ -0,0 +1,60 @@
1
+ import { useState, useEffect } from "react";
2
+ import { connect, mapProps } from "@formily/react";
3
+ import { Picker } from "antd-mobile";
4
+
5
+ import "./index.less";
6
+
7
+ function Select(props) {
8
+ const { disabled, readOnly, placeholder = "请选择", field = {}, onChange } = props;
9
+
10
+ const { dataSource } = field;
11
+
12
+ const [_options, setOptions] = useState(dataSource ? [dataSource] : []);
13
+
14
+ const [visible, setVisible] = useState(false);
15
+
16
+ useEffect(() => {
17
+ if (dataSource) {
18
+ setOptions([dataSource]);
19
+ }
20
+ }, [dataSource]);
21
+
22
+ function onClose() {
23
+ setVisible(false);
24
+ }
25
+
26
+ function onClick() {
27
+ if (readOnly || disabled) {
28
+ return;
29
+ }
30
+ setVisible((val) => {
31
+ return !val;
32
+ });
33
+ }
34
+
35
+ function onConfirm(value) {
36
+ onChange && onChange(value);
37
+ }
38
+
39
+ return (
40
+ <div className="formily-select" onClick={onClick}>
41
+ <Picker
42
+ {...props}
43
+ style={{ pointerEvents: readOnly || disabled ? "none" : undefined }}
44
+ columns={_options}
45
+ visible={visible}
46
+ onClose={onClose}
47
+ onConfirm={onConfirm}
48
+ >
49
+ {(value) => (value && value.length > 0 ? value[0]?.label : placeholder)}
50
+ </Picker>
51
+ </div>
52
+ );
53
+ }
54
+
55
+ export default connect(
56
+ Select,
57
+ mapProps((props, field) => {
58
+ return { ...props, form: field.form, field };
59
+ }),
60
+ );
@@ -0,0 +1,23 @@
1
+ import React from "react";
2
+ import { connect, mapProps } from "@formily/react";
3
+
4
+ function Text({ value, mode, content, className, ...props }) {
5
+ const tagName = mode === "normal" || !mode ? "div" : mode;
6
+ console.log("props", props.readOnly);
7
+
8
+ return React.createElement(
9
+ tagName,
10
+ {
11
+ className: `formily-text ${className}`,
12
+ ...props,
13
+ },
14
+ value || content,
15
+ );
16
+ }
17
+
18
+ export default connect(
19
+ Text,
20
+ mapProps((props, field) => {
21
+ return { ...props, form: field.form, field };
22
+ }),
23
+ );
@@ -0,0 +1,188 @@
1
+ export function getImage(_options = {}) {
2
+ return new Promise((resolve, reject) => {
3
+ // TODO: 调整 options 参数
4
+ const options = {
5
+ quality: 0,
6
+ destinationType: 1, // 0-Return base64 encoded string. 1-Return file uri (content://media/external/images/media/2 for Android)
7
+ sourceType: 1, // 0-Photo Library, 1-Camera, 2-Saved Album Choose image only from the device's Camera Roll album
8
+ encodingType: 0, // 0=JPG 1=PNG
9
+ ..._options,
10
+ };
11
+
12
+ navigator.device.capture.captureImage(
13
+ function (mediaFiles) {
14
+ const imgData = mediaFiles[0].localURL;
15
+ let reader;
16
+ let imgBlob;
17
+ window.resolveLocalFileSystemURL(
18
+ imgData,
19
+ function (fileEntry) {
20
+ fileEntry.file(
21
+ function (file) {
22
+ reader = new FileReader();
23
+ reader.onloadend = function (e) {
24
+ imgBlob = new Blob([this.result], { type: "image/jpeg" });
25
+ console.log("imgBlob", imgBlob);
26
+ if (!imgBlob?.name) {
27
+ imgBlob.name = `${Date.now()}.${imgBlob.type?.replace("image/", "")}`;
28
+ }
29
+ if (!imgBlob?.lastModifiedDate) {
30
+ imgBlob.lastModifiedDate = new Date();
31
+ }
32
+ if (!imgBlob?.lastModified) {
33
+ imgBlob.lastModified = imgBlob.lastModifiedDate.getTime();
34
+ }
35
+ if (!imgBlob?.localURL) {
36
+ imgBlob.localURL = mediaFiles[0].localURL;
37
+ }
38
+ resolve(imgBlob);
39
+ // window.__file = imgBlob; // PLACE THE FILE ASSIGNMENT HERE AFTER THE READER HAS INGESTED THE FILE BYTES
40
+ };
41
+ reader.readAsArrayBuffer(file);
42
+ },
43
+ function (err) {
44
+ reject(err);
45
+ console.log("error with photo file");
46
+ },
47
+ );
48
+ },
49
+ function (err) {
50
+ reject(err);
51
+ console.log("error with photo file");
52
+ },
53
+ );
54
+ },
55
+ function (err) {
56
+ reject(err);
57
+ alert("Error taking picture", "Error");
58
+ },
59
+ options,
60
+ );
61
+ });
62
+ }
63
+
64
+ export function getVideo(_options = {}) {
65
+ return new Promise((resolve, reject) => {
66
+ // TODO: 调整 options 参数
67
+ const options = {
68
+ quality: 0,
69
+ destinationType: 1,
70
+ sourceType: 1, // 0:Photo Library, 1=Camera, 2=Saved Album
71
+ encodingType: 0, // 0=JPG 1=PNG
72
+ ..._options,
73
+ };
74
+
75
+ navigator.device.capture.captureVideo(
76
+ function (mediaFiles) {
77
+ const imgData = mediaFiles[0].localURL;
78
+ let reader;
79
+ let imgBlob;
80
+ window.resolveLocalFileSystemURL(
81
+ imgData,
82
+ function (fileEntry) {
83
+ fileEntry.file(
84
+ function (file) {
85
+ reader = new FileReader();
86
+ reader.onloadend = function (e) {
87
+ imgBlob = new Blob([this.result], { type: "video/mp4" });
88
+ console.log("imgBlob", imgBlob);
89
+ if (!imgBlob?.name) {
90
+ imgBlob.name = `${Date.now()}.${imgBlob.type?.replace("video/", "")}`;
91
+ }
92
+ if (!imgBlob?.lastModifiedDate) {
93
+ imgBlob.lastModifiedDate = new Date();
94
+ }
95
+ if (!imgBlob?.lastModified) {
96
+ imgBlob.lastModified = imgBlob.lastModifiedDate.getTime();
97
+ }
98
+ if (!imgBlob?.localURL) {
99
+ imgBlob.localURL = mediaFiles[0].localURL;
100
+ }
101
+ resolve(imgBlob);
102
+ // window.__file = imgBlob; // PLACE THE FILE ASSIGNMENT HERE AFTER THE READER HAS INGESTED THE FILE BYTES
103
+ };
104
+ reader.readAsArrayBuffer(file);
105
+ },
106
+ function (err) {
107
+ reject(err);
108
+ console.log("error with photo file");
109
+ },
110
+ );
111
+ },
112
+ function (err) {
113
+ reject(err);
114
+ console.log("error with photo file");
115
+ },
116
+ );
117
+ },
118
+ function (err) {
119
+ reject(err);
120
+ alert("Error taking picture", "Error");
121
+ },
122
+ options,
123
+ );
124
+ });
125
+ }
126
+
127
+ export function getAudio(_options = {}) {
128
+ return new Promise((resolve, reject) => {
129
+ // TODO: 调整 options 参数
130
+ const options = {
131
+ ..._options,
132
+ };
133
+
134
+ navigator.device.capture.captureAudio(
135
+ function (mediaFiles) {
136
+ const imgData = mediaFiles[0].localURL;
137
+ let reader;
138
+ let audioBlob;
139
+ window.resolveLocalFileSystemURL(
140
+ imgData,
141
+ function (fileEntry) {
142
+ fileEntry.file(
143
+ function (file) {
144
+ // TODO: 文件无法正常读取
145
+ reader = new FileReader();
146
+ reader.onloadend = function (e) {
147
+ audioBlob = new Blob([this.result], { type: "audio/mp3" });
148
+ console.log("audioBlob", audioBlob);
149
+ if (!audioBlob?.name) {
150
+ audioBlob.name = `${Date.now()}.${audioBlob.type?.replace("audio/", "")}`;
151
+ }
152
+ if (!audioBlob?.lastModifiedDate) {
153
+ audioBlob.lastModifiedDate = new Date();
154
+ }
155
+ if (!audioBlob?.lastModified) {
156
+ audioBlob.lastModified = audioBlob.lastModifiedDate.getTime();
157
+ }
158
+ if (!audioBlob?.localURL) {
159
+ audioBlob.localURL = mediaFiles[0].localURL;
160
+ }
161
+ resolve(audioBlob);
162
+ // window.__file = audioBlob; // PLACE THE FILE ASSIGNMENT HERE AFTER THE READER HAS INGESTED THE FILE BYTES
163
+ };
164
+ reader.onerror = function (e) {
165
+ console.log("reader.onerror: ", e);
166
+ };
167
+ reader.readAsArrayBuffer(file);
168
+ },
169
+ function (err) {
170
+ reject(err);
171
+ console.log("error with audio file");
172
+ },
173
+ );
174
+ },
175
+ function (err) {
176
+ reject(err);
177
+ console.log("error with audio file");
178
+ },
179
+ );
180
+ },
181
+ function (err) {
182
+ reject(err);
183
+ alert("Error taking audio", "Error");
184
+ },
185
+ options,
186
+ );
187
+ });
188
+ }
@@ -0,0 +1,87 @@
1
+ /**
2
+ * 建立一个可以存取该 file 的 url
3
+ * @param {Object} file 文件
4
+ * @returns {string} url
5
+ * blob:http://localhost:8000/c9950644-5118-4231-9be7-8183bde1fdc7
6
+ */
7
+ export function getFileURL(file) {
8
+ let url = null;
9
+
10
+ // 下面函数执行的效果是一样的,只是需要针对不同的浏览器执行不同的 js 函数而已
11
+ if (window.createObjectURL != undefined) {
12
+ // basic
13
+ url = window.createObjectURL(file);
14
+ } else if (window.URL != undefined) {
15
+ // mozilla(firefox)
16
+ url = window.URL.createObjectURL(file);
17
+ } else if (window.webkitURL != undefined) {
18
+ // webkit or chrome
19
+ url = window.webkitURL.createObjectURL(file);
20
+ }
21
+
22
+ return url;
23
+ }
24
+
25
+ /**
26
+ * 判断 url 是否带有指定图片后缀
27
+ * @param {string} url
28
+ * @returns
29
+ */
30
+ export function checkImageUrl(url) {
31
+ const imgTypes = [
32
+ "apng",
33
+ "avif",
34
+ "bmp",
35
+ "gif",
36
+ "ico",
37
+ "cur",
38
+ "jpg",
39
+ "jpeg",
40
+ "jfif",
41
+ "pjpeg",
42
+ "pjp",
43
+ "png",
44
+ "svg",
45
+ "tif",
46
+ "tiff",
47
+ "webp",
48
+ ];
49
+ return checkUrlSuffix(url, imgTypes);
50
+ }
51
+
52
+ /**
53
+ * 判断 url 是否带有指定视频后缀
54
+ * @param {string} url
55
+ * @returns
56
+ */
57
+ export function checkVideoUrl(url) {
58
+ const imgTypes = ["3gp", "mpg", "mpeg", "mp4", "m4v", "m4p", "ogv", "ogg", "mov", "webm"];
59
+ return checkUrlSuffix(url, imgTypes);
60
+ }
61
+
62
+ /**
63
+ * 判断 url 是否带有指定音频后缀
64
+ * @param {string} url
65
+ * @returns
66
+ */
67
+ export function checkAudioUrl(url) {
68
+ const imgTypes = ["3gp", "adts", "mpeg", "mp3", "mp4", "ogg", "mov", "webm", "rtp", "amr", "wav"];
69
+ return checkUrlSuffix(url, imgTypes);
70
+ }
71
+
72
+ /**
73
+ * 检查 url 是否带有指定后缀
74
+ * @param {string} url url 地址
75
+ * @param {Array} types 后缀数组
76
+ * @returns
77
+ */
78
+ export function checkUrlSuffix(url, types = [], caseSensitive) {
79
+ if (!url) {
80
+ return false;
81
+ }
82
+ let _url = url?.replace(/\?.+/, "");
83
+ const reg = new RegExp(`\.(${types.join("|")})$`, caseSensitive ? undefined : "i");
84
+ if (reg.test(_url)) {
85
+ return true;
86
+ }
87
+ }
@@ -0,0 +1,31 @@
1
+ import { Form } from "antd-mobile";
2
+ import { connect, mapProps } from "@formily/react";
3
+
4
+ import Uploader from "./uploader";
5
+
6
+ function UploaderCom(props) {
7
+ const { field = {}, form, onChange } = props;
8
+ const { name, mode } = field;
9
+
10
+ function onUploadChange(files) {
11
+ if (field?.autoUpload && props.fieldsConf[name]?.onUpload) {
12
+ props.fieldsConf[name]?.onUpload(files);
13
+ return;
14
+ }
15
+ onChange && onChange(files);
16
+ }
17
+
18
+ const _props = {
19
+ mode: mode,
20
+ ...props,
21
+ };
22
+
23
+ return <Uploader {..._props} onChange={onUploadChange} />;
24
+ }
25
+
26
+ export default connect(
27
+ UploaderCom,
28
+ mapProps((props, field) => {
29
+ return { ...props, form: field.form, field };
30
+ }),
31
+ );