zy-react-library 1.0.0 → 1.0.2

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.
@@ -0,0 +1,18 @@
1
+ import type { FC } from "react";
2
+
3
+ export interface MapSelectorProps {
4
+ /** 是否显示弹窗 */
5
+ visible: boolean;
6
+ /** 关闭弹窗回调 */
7
+ onClose: () => void;
8
+ /** 经度值 */
9
+ longitude?: number | string;
10
+ /** 纬度值 */
11
+ latitude?: number | string;
12
+ /** 确认选择回调 */
13
+ onConfirm?: (longitude: number | string, latitude: number | string) => void;
14
+ }
15
+
16
+ declare const MapSelector: FC<MapSelectorProps>;
17
+
18
+ export default MapSelector;
@@ -0,0 +1,204 @@
1
+ import { Button, Col, Form, Input, Modal, Row, Spin } from "antd";
2
+ import { useEffect, useRef, useState } from "react";
3
+
4
+ const MapSelector = ({
5
+ visible,
6
+ onClose,
7
+ longitude,
8
+ latitude,
9
+ onConfirm, // 新增确认回调
10
+ }) => {
11
+ const mapContainerRef = useRef(null);
12
+ const mapInstanceRef = useRef(null);
13
+ const [loading, setLoading] = useState(false);
14
+ const [currentLongitude, setCurrentLongitude] = useState(longitude || "");
15
+ const [currentLatitude, setCurrentLatitude] = useState(latitude || "");
16
+ const [localSearch, setLocalSearch] = useState("");
17
+
18
+ // 当外部经纬度变化时,更新内部状态
19
+ useEffect(() => {
20
+ setCurrentLongitude(longitude || "");
21
+ setCurrentLatitude(latitude || "");
22
+ }, [longitude, latitude]);
23
+
24
+ // 初始化地图
25
+ const initMap = async () => {
26
+ if (!window.BMapGL) {
27
+ console.error("BMapGL is not loaded");
28
+ return;
29
+ }
30
+
31
+ setLoading(true);
32
+
33
+ // 确保DOM已经渲染
34
+ await new Promise(resolve => setTimeout(resolve, 100));
35
+
36
+ if (mapContainerRef.current) {
37
+ // 只有在没有地图实例时才创建新地图
38
+ if (!mapInstanceRef.current) {
39
+ const map = new window.BMapGL.Map(mapContainerRef.current);
40
+ mapInstanceRef.current = map;
41
+
42
+ map.centerAndZoom(
43
+ new window.BMapGL.Point(
44
+ longitude || "119.69457721306945",
45
+ latitude || "39.940504336846665",
46
+ ),
47
+ 16,
48
+ );
49
+
50
+ map.enableScrollWheelZoom(true);
51
+
52
+ // 如果有初始坐标,添加标记
53
+ if (longitude && latitude) {
54
+ const point = new window.BMapGL.Point(longitude, latitude);
55
+ const marker = new window.BMapGL.Marker(point);
56
+ map.addOverlay(marker);
57
+ }
58
+
59
+ // 添加点击事件
60
+ map.addEventListener("click", (event) => {
61
+ map.clearOverlays();
62
+ const point = new window.BMapGL.Point(event.latlng.lng, event.latlng.lat);
63
+ const marker = new window.BMapGL.Marker(point);
64
+ map.addOverlay(marker);
65
+ setCurrentLatitude(event.latlng.lat);
66
+ setCurrentLongitude(event.latlng.lng);
67
+ });
68
+ }
69
+
70
+ setLoading(false);
71
+ }
72
+ };
73
+
74
+ // 搜索功能
75
+ const handleLocalSearch = () => {
76
+ if (localSearch && mapInstanceRef.current) {
77
+ const local = new window.BMapGL.LocalSearch(mapInstanceRef.current, {
78
+ renderOptions: { map: mapInstanceRef.current },
79
+ });
80
+ local.search(localSearch);
81
+ }
82
+ };
83
+
84
+ // 关闭弹窗
85
+ const handleClose = () => {
86
+ setLocalSearch("");
87
+ if (onClose)
88
+ onClose();
89
+ };
90
+
91
+ // 确认选择
92
+ const handleConfirm = () => {
93
+ if (onConfirm) {
94
+ onConfirm(currentLongitude, currentLatitude);
95
+ }
96
+ handleClose();
97
+ };
98
+
99
+ // 监听visible变化
100
+ useEffect(() => {
101
+ let initTimer;
102
+
103
+ if (visible) {
104
+ // 延迟初始化地图,确保DOM完全渲染
105
+ initTimer = setTimeout(() => {
106
+ initMap();
107
+ }, 100);
108
+ }
109
+
110
+ return () => {
111
+ if (initTimer) {
112
+ clearTimeout(initTimer);
113
+ }
114
+ };
115
+ }, [visible]);
116
+
117
+ const handleAfterClose = () => {
118
+ if (mapInstanceRef.current) {
119
+ try {
120
+ mapInstanceRef.current.clearOverlays();
121
+ mapInstanceRef.current.destroy();
122
+ mapInstanceRef.current = null;
123
+ }
124
+ catch (e) {
125
+ console.warn("Error destroying map on unmount:", e);
126
+ }
127
+ mapInstanceRef.current = null;
128
+ }
129
+ };
130
+
131
+ // 组件卸载时清理地图
132
+ useEffect(() => {
133
+ return () => {
134
+ handleAfterClose();
135
+ };
136
+ }, []);
137
+
138
+ return (
139
+ <Modal
140
+ open={visible}
141
+ title="坐标"
142
+ onCancel={handleClose}
143
+ onOk={handleConfirm}
144
+ width={800}
145
+ destroyOnHidden={false}
146
+ afterClose={handleAfterClose}
147
+ >
148
+ <Form labelAlign="right" labelCol={{ span: 6 }} wrapperCol={{ span: 18 }}>
149
+ <Row gutter={24}>
150
+ <Col span={12}>
151
+ <Form.Item label="关键字搜索">
152
+ <Input
153
+ value={localSearch}
154
+ onChange={e => setLocalSearch(e.target.value)}
155
+ allowClear
156
+ />
157
+ </Form.Item>
158
+ </Col>
159
+ <Col span={12}>
160
+ <Form.Item label=" " colon={false}>
161
+ <Button type="primary" onClick={handleLocalSearch}>
162
+ 搜索
163
+ </Button>
164
+ </Form.Item>
165
+ </Col>
166
+ </Row>
167
+ <Row gutter={24}>
168
+ <Col span={12}>
169
+ <Form.Item label="经度">
170
+ <Input disabled value={currentLongitude} />
171
+ </Form.Item>
172
+ </Col>
173
+ <Col span={12}>
174
+ <Form.Item label="纬度">
175
+ <Input disabled value={currentLatitude} />
176
+ </Form.Item>
177
+ </Col>
178
+ </Row>
179
+ </Form>
180
+ <div
181
+ ref={mapContainerRef}
182
+ style={{ width: "100%", height: "500px", position: "relative" }}
183
+ >
184
+ <Spin size="large" tip="地图正在加载中..." spinning={loading}>
185
+ <div style={{
186
+ position: "absolute",
187
+ top: 0,
188
+ left: 0,
189
+ right: 0,
190
+ bottom: 0,
191
+ display: "flex",
192
+ justifyContent: "center",
193
+ alignItems: "center",
194
+ backgroundColor: "rgba(0, 0, 0, 0.5)",
195
+ zIndex: 1000,
196
+ }}
197
+ />
198
+ </Spin>
199
+ </div>
200
+ </Modal>
201
+ );
202
+ };
203
+
204
+ export default MapSelector;
@@ -0,0 +1,16 @@
1
+ import type { FC } from "react";
2
+
3
+ export interface MapProps {
4
+ /** 经度属性名,默认 longitude */
5
+ longitudeProps?: string;
6
+ /** 纬度属性名,默认 latitude */
7
+ latitudeProps?: string;
8
+ /** 经纬度变化回调 */
9
+ onConfirm?: (longitude: number | string, latitude: number | string) => void;
10
+ /** 经纬度是否必填,默认 true */
11
+ required?: boolean;
12
+ }
13
+
14
+ declare const Map: FC<MapProps>;
15
+
16
+ export default Map;
@@ -0,0 +1,61 @@
1
+ import { Button, Col, Form, Input, Row } from "antd";
2
+ import { useState } from "react";
3
+ import MapSelector from "./MapSelector";
4
+
5
+ const Map = (props) => {
6
+ const {
7
+ longitudeProps = "longitude",
8
+ latitudeProps = "latitude",
9
+ onConfirm,
10
+ required = true,
11
+ } = props;
12
+
13
+ const form = Form.useFormInstance();
14
+ const [mapVisible, setMapVisible] = useState(false);
15
+ const [currentLongitude, setCurrentLongitude] = useState(form.getFieldValue(longitudeProps) || "");
16
+ const [currentLatitude, setCurrentLatitude] = useState(form.getFieldValue(latitudeProps) || "");
17
+
18
+ const handleMapConfirm = (longitudeValue, latitudeValue) => {
19
+ setCurrentLongitude(longitudeValue);
20
+ setCurrentLatitude(latitudeValue);
21
+ form.setFieldsValue({
22
+ [longitudeProps]: longitudeValue,
23
+ [latitudeProps]: latitudeValue,
24
+ });
25
+ onConfirm?.(longitudeValue, latitudeValue);
26
+ setMapVisible(false);
27
+ };
28
+
29
+ return (
30
+ <>
31
+ <Row gutter={24}>
32
+ <Col span={12}>
33
+ <Form.Item label="经度" name={longitudeProps} style={{ flex: 1 }} rules={[{ required, message: "请选择经度" }]}>
34
+ <Input disabled />
35
+ </Form.Item>
36
+ </Col>
37
+ <Col span={12}>
38
+ <div style={{ display: "flex" }}>
39
+ <Form.Item label="纬度" name={latitudeProps} style={{ flex: 1 }} rules={[{ required, message: "请选择纬度" }]}>
40
+ <Input disabled />
41
+ </Form.Item>
42
+ <Form.Item label=" " colon={false}>
43
+ <Button type="primary" onClick={() => setMapVisible(true)}>
44
+ 点击定位
45
+ </Button>
46
+ </Form.Item>
47
+ </div>
48
+ </Col>
49
+ </Row>
50
+ <MapSelector
51
+ visible={mapVisible}
52
+ onClose={() => setMapVisible(false)}
53
+ longitude={currentLongitude}
54
+ latitude={currentLatitude}
55
+ onConfirm={handleMapConfirm}
56
+ />
57
+ </>
58
+ );
59
+ };
60
+
61
+ export default Map;
@@ -0,0 +1,18 @@
1
+ import type { CSSProperties, FC } from "react";
2
+
3
+ export interface PdfProps {
4
+ /** pdf 文件地址 */
5
+ file: string;
6
+ /** 是否显示弹窗 */
7
+ visible?: boolean;
8
+ /** 关闭弹窗的方法 */
9
+ onCancel?: () => void;
10
+ /** 是否使用内联模式,true为不使用弹窗,默认为false */
11
+ inline?: boolean;
12
+ /** 内联模式下的样式 */
13
+ style?: CSSProperties;
14
+ }
15
+
16
+ declare const Pdf: FC<PdfProps>;
17
+
18
+ export default Pdf;
@@ -0,0 +1,84 @@
1
+ import { Button, message, Modal, Spin } from "antd";
2
+ import { useState } from "react";
3
+ import { Document, Page, pdfjs } from "react-pdf";
4
+ import { getFileUrl } from "../../utils/index";
5
+
6
+ function Pdf(props) {
7
+ const {
8
+ visible = false,
9
+ onCancel,
10
+ file,
11
+ inline = false,
12
+ style = {},
13
+ } = props;
14
+
15
+ const fileUrl = getFileUrl();
16
+ const [numPages, setNumPages] = useState(0);
17
+ const [pdfWidth, setPdfWidth] = useState(600);
18
+ const [loading, setLoading] = useState(true);
19
+
20
+ pdfjs.GlobalWorkerOptions.workerSrc = new URL(
21
+ "pdfjs-dist/build/pdf.worker.min.mjs",
22
+ import.meta.url,
23
+ ).toString();
24
+
25
+ const onDocumentLoadSuccess = ({ numPages }) => {
26
+ setNumPages(numPages);
27
+ setLoading(false);
28
+ };
29
+
30
+ const onDocumentLoadError = () => {
31
+ setLoading(false);
32
+ message.error("加载 PDF 文件失败");
33
+ if (onCancel)
34
+ onCancel();
35
+ };
36
+
37
+ const onPageLoadSuccess = ({ width }) => {
38
+ setPdfWidth(width);
39
+ };
40
+
41
+ // 内联模式的PDF内容
42
+ const renderPdfContent = () => (
43
+ <>
44
+ {loading && (
45
+ <div style={{ display: "flex", justifyContent: "center", alignItems: "center", height: "80vh" }}>
46
+ <Spin size="large" />
47
+ </div>
48
+ )}
49
+ <div style={{ height: "88vh", overflowY: "auto", padding: "24px", ...style }}>
50
+ <Document
51
+ file={fileUrl + file}
52
+ onLoadSuccess={onDocumentLoadSuccess}
53
+ onLoadError={onDocumentLoadError}
54
+ >
55
+ {
56
+ Array.from({ length: numPages }).map((el, index) => (
57
+ <Page key={`page_${index + 1}`} pageNumber={index + 1} onLoadSuccess={onPageLoadSuccess} />
58
+ ))
59
+ }
60
+ </Document>
61
+ </div>
62
+ </>
63
+ );
64
+
65
+ // 如果是内联模式,直接返回PDF内容
66
+ if (inline) {
67
+ return renderPdfContent();
68
+ }
69
+
70
+ // 默认弹窗模式
71
+ return (
72
+ <Modal
73
+ open={visible}
74
+ width={pdfWidth + 100}
75
+ title="PDF预览"
76
+ onCancel={onCancel}
77
+ footer={<Button onClick={onCancel}>关闭</Button>}
78
+ >
79
+ {renderPdfContent()}
80
+ </Modal>
81
+ );
82
+ }
83
+
84
+ export default Pdf;
@@ -0,0 +1,18 @@
1
+ import type { FC } from "react";
2
+
3
+ export interface PreviewPdfProps {
4
+ /** 文件列表,和 name、url 冲突 */
5
+ files?: Record<string, any>[];
6
+ /** 文件名字段名,传入 files 时会优先查找是否存在 name、fileName */
7
+ nameKey?: string;
8
+ /** 文件路径字段名,传入 files 时会优先查找是否存在 filePath */
9
+ urlKey?: string;
10
+ /** 单个文件名,和 files 冲突 */
11
+ name?: string;
12
+ /** 单个文件路径,和 files 冲突 */
13
+ url?: string;
14
+ }
15
+
16
+ declare const PreviewPdf: FC<PreviewPdfProps>;
17
+
18
+ export default PreviewPdf;
@@ -0,0 +1,76 @@
1
+ import { Button, Space } from "antd";
2
+ import { useState } from "react";
3
+ import Pdf from "../Pdf";
4
+
5
+ const PreviewPdf = (props) => {
6
+ const {
7
+ files = [],
8
+ nameKey = "",
9
+ urlKey = "",
10
+ name = "",
11
+ url = "",
12
+ } = props;
13
+
14
+ const [visible, setVisible] = useState(false);
15
+ const [src, setSrc] = useState("");
16
+
17
+ const previewPdf = (src) => {
18
+ setVisible(true);
19
+ setSrc(src);
20
+ };
21
+
22
+ const onCancel = () => {
23
+ setVisible(false);
24
+ setSrc("");
25
+ };
26
+
27
+ // 单个文件预览模式
28
+ if (files.length === 0 && name && url) {
29
+ return (
30
+ <>
31
+ <Space>
32
+ <span>{name}</span>
33
+ <Button type="primary" size="small" onClick={() => previewPdf(url)}>
34
+ 预览
35
+ </Button>
36
+ </Space>
37
+ <Pdf
38
+ visible={visible}
39
+ file={src}
40
+ onCancel={onCancel}
41
+ />
42
+ </>
43
+ );
44
+ }
45
+
46
+ // 多文件预览模式
47
+ if (files.length > 0 && !name && !url) {
48
+ return (
49
+ <>
50
+ {files.map(item => (
51
+ <div key={item.filePath || item[urlKey]} style={{ marginTop: 5 }}>
52
+ <Space>
53
+ <span>{item.name || item.fileName || item[nameKey]}</span>
54
+ <Button
55
+ type="primary"
56
+ size="small"
57
+ onClick={() => previewPdf(item.filePath || item[urlKey])}
58
+ >
59
+ 预览
60
+ </Button>
61
+ </Space>
62
+ </div>
63
+ ))}
64
+ <Pdf
65
+ visible={visible}
66
+ file={src}
67
+ onCancel={onCancel}
68
+ />
69
+ </>
70
+ );
71
+ }
72
+
73
+ return null;
74
+ };
75
+
76
+ export default PreviewPdf;
@@ -11,7 +11,7 @@ function TablePro(props) {
11
11
  const storeIndex = props.storeIndex || `${window.process.env.app.antd["ant-prefix"]}_${Math.random().toString(36).substring(2)}`;
12
12
  function calcColumns() {
13
13
  showIndex && columns.unshift(getIndexColumn(props.pagination));
14
- return columns.map(item => ({ ...item, align: useAlignCenter ? "center" : "left" }));
14
+ return columns.map(item => ({ align: useAlignCenter ? "center" : "left", ...item }));
15
15
  }
16
16
  return <Table storeIndex={storeIndex} columns={calcColumns()} {...restProps} />;
17
17
  }
@@ -13,7 +13,7 @@ export interface UseTableOptions<TData extends Data, TParams extends Params> ext
13
13
  /** 是否使用存储查询条件,默认是 */
14
14
  useStorageQueryCriteria?: boolean;
15
15
  /** 额外参数 */
16
- params?: FormValues | (() => FormValues);
16
+ params?: Record<string, any> | (() => Record<string, any>);
17
17
  /** 表单数据转换函数,在每次请求之前调用,接收当前搜索的表单项,要求返回一个对象 */
18
18
  transform?: (formData: FormValues) => FormValues;
19
19
  /** 回调函数 */
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "zy-react-library",
3
3
  "private": false,
4
- "version": "1.0.0",
4
+ "version": "1.0.2",
5
5
  "type": "module",
6
6
  "description": "",
7
7
  "author": "LiuJiaNan",
@@ -28,6 +28,7 @@
28
28
  "antd": "^5.27.6",
29
29
  "dayjs": "^1.11.18",
30
30
  "lodash-es": "^4.17.21",
31
- "react": "^18.3.1"
31
+ "react": "^18.3.1",
32
+ "react-pdf": "^10.2.0"
32
33
  }
33
34
  }