neo-cmp-cli 1.3.6 → 1.3.8

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 (33) hide show
  1. package/README.md +5 -5
  2. package/package.json +1 -1
  3. package/src/template/antd-custom-cmp-template/package.json +2 -2
  4. package/src/template/echarts-custom-cmp-template/package.json +2 -2
  5. package/src/template/echarts-custom-cmp-template/src/components/map-widget/README.md +125 -0
  6. package/src/template/echarts-custom-cmp-template/src/components/map-widget/USAGE.md +190 -0
  7. package/src/template/echarts-custom-cmp-template/src/components/map-widget/index.tsx +385 -0
  8. package/src/template/echarts-custom-cmp-template/src/components/map-widget/model.ts +105 -0
  9. package/src/template/echarts-custom-cmp-template/src/components/map-widget/style.scss +192 -0
  10. package/src/template/echarts-custom-cmp-template/src/utils/url.ts +82 -0
  11. package/src/template/neo-custom-cmp-template/neo.config.js +4 -4
  12. package/src/template/neo-custom-cmp-template/package.json +2 -2
  13. package/src/template/neo-custom-cmp-template/src/assets/img/custom-form.svg +1 -0
  14. package/src/template/neo-custom-cmp-template/src/assets/img/data-list.svg +1 -0
  15. package/src/template/react-custom-cmp-template/package.json +2 -2
  16. package/src/template/react-ts-custom-cmp-template/neo.config.js +4 -4
  17. package/src/template/react-ts-custom-cmp-template/package.json +3 -3
  18. package/src/template/react-ts-custom-cmp-template/src/assets/img/map.svg +1 -0
  19. package/src/template/vue2-custom-cmp-template/package.json +2 -2
  20. package/src/template/echarts-custom-cmp-template/src/components/info-card/index.tsx +0 -69
  21. package/src/template/echarts-custom-cmp-template/src/components/info-card/model.ts +0 -78
  22. package/src/template/echarts-custom-cmp-template/src/components/info-card/style.scss +0 -105
  23. package/src/template/neo-custom-cmp-template/src/components/contact-card-list/README.md +0 -61
  24. package/src/template/neo-custom-cmp-template/src/components/contact-card-list/index.tsx +0 -191
  25. package/src/template/neo-custom-cmp-template/src/components/contact-card-list/model.ts +0 -56
  26. package/src/template/neo-custom-cmp-template/src/components/contact-card-list/style.scss +0 -260
  27. package/src/template/neo-custom-cmp-template/src/components/contact-form/README.md +0 -94
  28. package/src/template/neo-custom-cmp-template/src/components/contact-form/index.tsx +0 -249
  29. package/src/template/neo-custom-cmp-template/src/components/contact-form/model.ts +0 -63
  30. package/src/template/neo-custom-cmp-template/src/components/contact-form/style.scss +0 -120
  31. package/src/template/react-ts-custom-cmp-template/src/components/info-card/index.tsx +0 -69
  32. package/src/template/react-ts-custom-cmp-template/src/components/info-card/model.ts +0 -78
  33. package/src/template/react-ts-custom-cmp-template/src/components/info-card/style.scss +0 -105
@@ -0,0 +1,385 @@
1
+ import * as React from 'react';
2
+ import { getUrlParam } from '../../utils/url';
3
+ import './style.scss';
4
+
5
+ // 扩展 Window 接口以支持 AMap
6
+ declare global {
7
+ interface Window {
8
+ AMap: any;
9
+ }
10
+ }
11
+
12
+ interface MapWidgetProps {
13
+ // 经度
14
+ longitude?: number;
15
+ // 纬度
16
+ latitude?: number;
17
+ // 位置名称(支持地址搜索)
18
+ locationName?: string;
19
+ // 地图缩放级别 (3-18)
20
+ zoom?: number;
21
+ // 标记点标题
22
+ markerTitle?: string;
23
+ // 地图高度
24
+ height?: number;
25
+ data?: any;
26
+ }
27
+
28
+ interface MapWidgetState {
29
+ isLoading: boolean;
30
+ error?: string;
31
+ currentAddress?: string;
32
+ urlLocationName?: string;
33
+ }
34
+
35
+ /**
36
+ * 地图展示组件
37
+ * 支持传入经纬度坐标或位置名称,展示对应的地图区域并标注当前位置
38
+ * 基于高德地图API实现
39
+ */
40
+ export default class MapWidget extends React.PureComponent<
41
+ MapWidgetProps,
42
+ MapWidgetState
43
+ > {
44
+ private map: any = null;
45
+
46
+ private AMap: any = null;
47
+
48
+ private marker: any = null;
49
+
50
+ private mapContainer: string = 'map-widget-container';
51
+
52
+ private locationName?: string = '';
53
+
54
+ constructor(props: MapWidgetProps) {
55
+ super(props);
56
+
57
+ this.state = {
58
+ isLoading: false,
59
+ error: undefined,
60
+ currentAddress: '',
61
+ urlLocationName: getUrlParam('location') || props.locationName || '',
62
+ };
63
+ }
64
+
65
+ componentDidMount() {
66
+ this.loadAMapScript();
67
+ // 监听 URL 变化
68
+ window.addEventListener('popstate', this.handleUrlChange);
69
+ window.addEventListener('hashchange', this.handleUrlChange);
70
+ }
71
+
72
+ componentDidUpdate(prevProps: Readonly<MapWidgetProps>): void {
73
+ const { longitude, latitude, locationName, zoom = 14 } = this.props;
74
+ const {
75
+ longitude: prevLongitude,
76
+ latitude: prevLatitude,
77
+ zoom: prevZoom = 14,
78
+ locationName: prevLocationName,
79
+ } = prevProps;
80
+ if (
81
+ longitude !== prevLongitude ||
82
+ latitude !== prevLatitude ||
83
+ locationName !== prevLocationName ||
84
+ zoom !== prevZoom
85
+ ) {
86
+ this.initMap(locationName);
87
+ }
88
+ }
89
+
90
+ componentWillUnmount() {
91
+ // 移除 URL 监听器
92
+ window.removeEventListener('popstate', this.handleUrlChange);
93
+ window.removeEventListener('hashchange', this.handleUrlChange);
94
+
95
+ if (this.map) {
96
+ this.map.destroy();
97
+ this.map = null;
98
+ }
99
+ }
100
+
101
+ /**
102
+ * 处理 URL 变化事件
103
+ */
104
+ handleUrlChange = () => {
105
+ const newLocationName = getUrlParam('location');
106
+
107
+ // 如果 URL 中的 location 参数发生变化,则重新初始化地图
108
+ if (newLocationName !== this.locationName) {
109
+ this.setState(
110
+ {
111
+ urlLocationName: newLocationName,
112
+ error: undefined,
113
+ },
114
+ () => {
115
+ // 重新初始化地图
116
+ this.initMap(newLocationName);
117
+ },
118
+ );
119
+ }
120
+ };
121
+
122
+ /**
123
+ * 动态加载高德地图脚本
124
+ */
125
+ loadAMapScript = () => {
126
+ // 检查是否已加载
127
+ if (window.AMap) {
128
+ this.AMap = window.AMap;
129
+ this.initMap();
130
+ return;
131
+ }
132
+
133
+ this.setState({
134
+ isLoading: true,
135
+ });
136
+
137
+ // 使用 NeoCRM 部署的高德地图脚本: 使用企业内部部署的高德地图脚本,无需单独申请 API Key
138
+ const scriptUrl = 'https://neors.ingageapp.com/base/js/amap_2.0.5.21.js';
139
+ // 备用方案:使用高德官方API(需要申请key)
140
+ // const amapKey = 'your-amap-key-here';
141
+ // const scriptUrl = `https://webapi.amap.com/maps?v=2.0&key=${amapKey}&plugin=AMap.Geocoder,AMap.Marker`;
142
+
143
+ const script = document.createElement('script');
144
+ script.type = 'text/javascript';
145
+ script.src = scriptUrl;
146
+ script.async = true;
147
+
148
+ script.onload = () => {
149
+ this.AMap = window.AMap;
150
+ this.initMap();
151
+
152
+ this.setState({
153
+ isLoading: false,
154
+ });
155
+ };
156
+
157
+ script.onerror = () => {
158
+ this.setState({
159
+ isLoading: false,
160
+ error: '地图加载失败,请检查网络连接或配置地图API Key',
161
+ });
162
+ };
163
+
164
+ document.head.appendChild(script);
165
+ };
166
+
167
+ /**
168
+ * 初始化地图
169
+ */
170
+ initMap = (_locationName?: string) => {
171
+ const { longitude, latitude, locationName, zoom = 14 } = this.props;
172
+ const { urlLocationName } = this.state;
173
+
174
+ // 优先使用 URL 中的 location 参数
175
+ const finalLocationName = _locationName || urlLocationName || locationName;
176
+
177
+ this.locationName = finalLocationName;
178
+
179
+ // 如果提供了位置名称(URL 或 props),先进行地理编码
180
+ if (finalLocationName) {
181
+ this.geocodeAddress(finalLocationName, zoom);
182
+ }
183
+ // 如果提供了经纬度,直接初始化地图
184
+ else if (longitude !== undefined && latitude !== undefined) {
185
+ this.createMap([longitude, latitude], zoom);
186
+ }
187
+ // 默认显示北京天安门
188
+ else {
189
+ this.createMap([116.397428, 39.90923], zoom);
190
+ }
191
+ };
192
+
193
+ /**
194
+ * 地理编码:先将地址转换为经纬度,再创建地图
195
+ */
196
+ geocodeAddress = (address: string, zoom: number) => {
197
+ if (!this.AMap) {
198
+ return;
199
+ }
200
+
201
+ // 动态加载 Geocoder 插件
202
+ this.AMap.plugin('AMap.Geocoder', () => {
203
+ const geocoder = new this.AMap.Geocoder();
204
+
205
+ geocoder.getLocation(address, (status: string, result: any) => {
206
+ if (status === 'complete' && result.geocodes.length > 0) {
207
+ const location = result.geocodes[0].location;
208
+ this.createMap([location.lng, location.lat], zoom);
209
+ this.setState({
210
+ currentAddress: result.geocodes[0].formattedAddress,
211
+ });
212
+ } else {
213
+ this.setState({
214
+ error: `地址解析失败: ${address}`,
215
+ });
216
+ // 使用默认位置
217
+ this.createMap([116.397428, 39.90923], zoom);
218
+ }
219
+ });
220
+ });
221
+ };
222
+
223
+ /**
224
+ * 创建地图实例
225
+ */
226
+ createMap = (center: [number, number], zoom: number) => {
227
+ if (!this.AMap) {
228
+ return;
229
+ }
230
+
231
+ try {
232
+ // 创建地图实例
233
+ this.map = new this.AMap.Map(this.mapContainer, {
234
+ zoom,
235
+ center,
236
+ resizeEnable: true,
237
+ viewMode: '2D',
238
+ });
239
+
240
+ // 添加标记点
241
+ this.addMarker(center);
242
+
243
+ // 获取当前位置的地址信息
244
+ this.getAddressByLocation(center);
245
+ } catch (error) {
246
+ console.log('地图初始化失败 / error:', error);
247
+ this.setState({
248
+ error: '地图初始化失败',
249
+ });
250
+ }
251
+ };
252
+
253
+ /**
254
+ * 添加标记点
255
+ */
256
+ addMarker = (position: [number, number]) => {
257
+ if (!this.AMap || !this.map) {
258
+ return;
259
+ }
260
+
261
+ const { markerTitle } = this.props;
262
+
263
+ // 移除已存在的标记
264
+ if (this.marker) {
265
+ this.map.remove(this.marker);
266
+ }
267
+
268
+ // 创建自定义红色图标
269
+ const redIcon = new this.AMap.Icon({
270
+ size: new this.AMap.Size(50, 68),
271
+ image:
272
+ '//a.amap.com/jsapi_demos/static/demo-center/icons/poi-marker-red.png',
273
+ imageSize: new this.AMap.Size(50, 68),
274
+ });
275
+
276
+ // 创建标记点(红色)
277
+ this.marker = new this.AMap.Marker({
278
+ position,
279
+ title: markerTitle || '当前位置',
280
+ map: this.map,
281
+ anchor: 'bottom-center',
282
+ icon: redIcon,
283
+ });
284
+
285
+ // 点击标记显示信息窗体
286
+ this.marker.on('click', () => {
287
+ const infoWindow = new this.AMap.InfoWindow({
288
+ content: `<div class="map-info-window">
289
+ <div class="info-title">${markerTitle || '当前位置'}</div>
290
+ <div class="info-address">${
291
+ this.state.currentAddress || '正在获取地址...'
292
+ }</div>
293
+ </div>`,
294
+ offset: new this.AMap.Pixel(0, -30),
295
+ });
296
+ infoWindow.open(this.map, position);
297
+ });
298
+ };
299
+
300
+ /**
301
+ * 逆地理编码:根据经纬度获取地址信息
302
+ */
303
+ getAddressByLocation = (location: [number, number]) => {
304
+ if (!this.AMap) {
305
+ return;
306
+ }
307
+
308
+ // 动态加载 Geocoder 插件
309
+ this.AMap.plugin('AMap.Geocoder', () => {
310
+ const geocoder = new this.AMap.Geocoder();
311
+
312
+ geocoder.getAddress(location, (status: string, result: any) => {
313
+ if (status === 'complete' && result.regeocode) {
314
+ this.setState({
315
+ currentAddress: result.regeocode.formattedAddress,
316
+ });
317
+ }
318
+ });
319
+ });
320
+ };
321
+
322
+ render() {
323
+ const { height = 400, data } = this.props;
324
+ const { isLoading, error } = this.state;
325
+
326
+ const curAmisData = data || {};
327
+ const systemInfo = curAmisData.__NeoSystemInfo || {};
328
+
329
+ console.log('this.props:', this.props);
330
+
331
+ return (
332
+ <div className="map-widget-wrapper">
333
+ {/*
334
+ // 地图头部: 显示地图标题和当前位置地址
335
+ <div className="map-widget-header">
336
+ <h3 className="map-widget-title">
337
+ 地图展示
338
+ {systemInfo.tenantName ? `【${systemInfo.tenantName}】` : ''}
339
+ </h3>
340
+ {this.state.currentAddress && (
341
+ <div className="map-widget-address">
342
+ <span className="address-icon">📍</span>
343
+ <span className="address-text">{this.state.currentAddress}</span>
344
+ </div>
345
+ )}
346
+ </div>
347
+ */}
348
+ <div className="map-widget-container-wrapper" style={{ height }}>
349
+ {isLoading && (
350
+ <div className="map-loading">
351
+ <div className="loading-spinner"></div>
352
+ <div className="loading-text">地图加载中...</div>
353
+ </div>
354
+ )}
355
+
356
+ {error && (
357
+ <div className="map-error">
358
+ <div className="error-icon">⚠️</div>
359
+ <div className="error-text">{error}</div>
360
+ </div>
361
+ )}
362
+
363
+ <div
364
+ id={this.mapContainer}
365
+ className="map-widget-container"
366
+ style={{ display: isLoading || error ? 'none' : 'block' }}
367
+ ></div>
368
+ </div>
369
+
370
+ <div className="map-widget-footer">
371
+ <div className="map-info">
372
+ <span className="info-label">经度:</span>
373
+ <span className="info-value">{this.props.longitude || '-'}</span>
374
+ <span className="info-separator">|</span>
375
+ <span className="info-label">纬度:</span>
376
+ <span className="info-value">{this.props.latitude || '-'}</span>
377
+ <span className="info-separator">|</span>
378
+ <span className="info-label">缩放:</span>
379
+ <span className="info-value">{this.props.zoom || 14}</span>
380
+ </div>
381
+ </div>
382
+ </div>
383
+ );
384
+ }
385
+ }
@@ -0,0 +1,105 @@
1
+ /**
2
+ * @file 地图组件对接编辑器的描述文件
3
+ */
4
+ export class MapWidgetModel {
5
+ /**
6
+ * cmpType 为自定义组件名称,用于标识组件的唯一性
7
+ * 在构建时根据当前组件目录名称自动生成
8
+ */
9
+ // cmpType: string = 'map-widget';
10
+
11
+ // 组件名称,用于设置在编辑器左侧组件面板中展示的名称
12
+ label: string = '地图组件';
13
+
14
+ // 组件描述,用于设置在编辑器左侧组件面板中展示的描述
15
+ description: string = '地图展示组件,支持传入经纬度或地址名称';
16
+
17
+ // 分类标签,用于设置在编辑器左侧组件面板哪个分类中展示(可设置多个分类标签)
18
+ tags: string[] = ['自定义组件'];
19
+
20
+ // 组件图标,用于设置在编辑器左侧组件面板中展示的图标
21
+ iconSrc: string = 'https://custom-widgets.bj.bcebos.com/map.svg';
22
+
23
+ // 初次插入页面的默认属性数据
24
+ defaultComProps = {
25
+ label: '地图组件',
26
+ // longitude: 116.397428, // 北京天安门经度
27
+ // latitude: 39.90923, // 北京天安门纬度
28
+ locationName: '北京市天安门广场', // 位置名称(可选)
29
+ zoom: 14, // 缩放级别
30
+ markerTitle: '当前位置', // 标记点标题
31
+ height: 400, // 地图高度
32
+ };
33
+
34
+ // 设计器端预览时展示的默认数据
35
+ previewComProps = {
36
+ label: '地图组件',
37
+ };
38
+
39
+ /**
40
+ * 组件面板配置,用于生成编辑器右侧属性配置面板内容
41
+ */
42
+ propsSchema = [
43
+ {
44
+ type: 'text',
45
+ name: 'locationName',
46
+ label: '位置名称',
47
+ placeholder: '例如:北京市天安门广场',
48
+ description: '输入地址名称自动定位(优先级高于经纬度)',
49
+ },
50
+ {
51
+ type: 'number',
52
+ name: 'longitude',
53
+ label: '经度',
54
+ // value: 116.397428,
55
+ min: -180,
56
+ max: 180,
57
+ step: 0.000001,
58
+ precision: 6,
59
+ },
60
+ {
61
+ type: 'number',
62
+ name: 'latitude',
63
+ label: '纬度',
64
+ // value: 39.90923,
65
+ min: -90,
66
+ max: 90,
67
+ step: 0.000001,
68
+ precision: 6,
69
+ },
70
+ {
71
+ type: 'text',
72
+ name: 'markerTitle',
73
+ label: '标记点标题',
74
+ value: '当前位置',
75
+ },
76
+ {
77
+ type: 'number',
78
+ name: 'zoom',
79
+ label: '缩放级别',
80
+ value: 14,
81
+ min: 3,
82
+ max: 18,
83
+ description: '地图缩放级别,3-18之间,数值越大越详细',
84
+ },
85
+ {
86
+ type: 'number',
87
+ name: 'height',
88
+ label: '地图高度',
89
+ value: 400,
90
+ min: 200,
91
+ max: 800,
92
+ step: 50,
93
+ description: '地图容器的高度(像素)',
94
+ },
95
+ ];
96
+
97
+ // 支持 函数式写法:propsSchemaCreator,com 为组件实例。优先级比 propsSchema 高
98
+ /*
99
+ propsSchemaCreator = (com: any) => {
100
+ return [];
101
+ };
102
+ */
103
+ }
104
+
105
+ export default MapWidgetModel;
@@ -0,0 +1,192 @@
1
+ // 地图组件样式文件
2
+ .map-widget-wrapper {
3
+ width: 100%;
4
+ background: #fff;
5
+ border-radius: 8px;
6
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
7
+ overflow: hidden;
8
+
9
+ .map-widget-header {
10
+ padding: 16px 20px;
11
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
12
+ color: #fff;
13
+
14
+ .map-widget-title {
15
+ margin: 0 0 8px 0;
16
+ font-size: 18px;
17
+ font-weight: 600;
18
+ }
19
+
20
+ .map-widget-address {
21
+ display: flex;
22
+ align-items: center;
23
+ font-size: 14px;
24
+ opacity: 0.95;
25
+
26
+ .address-icon {
27
+ margin-right: 6px;
28
+ font-size: 16px;
29
+ }
30
+
31
+ .address-text {
32
+ flex: 1;
33
+ overflow: hidden;
34
+ text-overflow: ellipsis;
35
+ white-space: nowrap;
36
+ }
37
+ }
38
+ }
39
+
40
+ .map-widget-container-wrapper {
41
+ position: relative;
42
+ background: #f5f5f5;
43
+
44
+ .map-widget-container {
45
+ width: 100%;
46
+ height: 100%;
47
+ }
48
+
49
+ .map-loading {
50
+ display: flex;
51
+ flex-direction: column;
52
+ align-items: center;
53
+ justify-content: center;
54
+ height: 100%;
55
+ background: #fafafa;
56
+
57
+ .loading-spinner {
58
+ width: 40px;
59
+ height: 40px;
60
+ border: 4px solid #e0e0e0;
61
+ border-top-color: #667eea;
62
+ border-radius: 50%;
63
+ animation: spin 1s linear infinite;
64
+ }
65
+
66
+ .loading-text {
67
+ margin-top: 16px;
68
+ color: #666;
69
+ font-size: 14px;
70
+ }
71
+ }
72
+
73
+ .map-error {
74
+ display: flex;
75
+ flex-direction: column;
76
+ align-items: center;
77
+ justify-content: center;
78
+ height: 100%;
79
+ background: #fff5f5;
80
+
81
+ .error-icon {
82
+ font-size: 48px;
83
+ margin-bottom: 16px;
84
+ }
85
+
86
+ .error-text {
87
+ color: #f56c6c;
88
+ font-size: 14px;
89
+ text-align: center;
90
+ padding: 0 20px;
91
+ }
92
+ }
93
+ }
94
+
95
+ .map-widget-footer {
96
+ padding: 12px 20px;
97
+ background: #fafafa;
98
+ border-top: 1px solid #e8e8e8;
99
+
100
+ .map-info {
101
+ display: flex;
102
+ align-items: center;
103
+ justify-content: center;
104
+ font-size: 12px;
105
+ color: #666;
106
+
107
+ .info-label {
108
+ color: #999;
109
+ margin-right: 4px;
110
+ }
111
+
112
+ .info-value {
113
+ color: #333;
114
+ font-weight: 500;
115
+ }
116
+
117
+ .info-separator {
118
+ margin: 0 12px;
119
+ color: #d9d9d9;
120
+ }
121
+ }
122
+ }
123
+
124
+ // 信息窗体样式
125
+ .map-info-window {
126
+ padding: 12px;
127
+ min-width: 200px;
128
+
129
+ .info-title {
130
+ font-size: 14px;
131
+ font-weight: 600;
132
+ color: #333;
133
+ margin-bottom: 8px;
134
+ }
135
+
136
+ .info-address {
137
+ font-size: 12px;
138
+ color: #666;
139
+ line-height: 1.5;
140
+ }
141
+ }
142
+ }
143
+
144
+ // 加载动画
145
+ @keyframes spin {
146
+ 0% {
147
+ transform: rotate(0deg);
148
+ }
149
+ 100% {
150
+ transform: rotate(360deg);
151
+ }
152
+ }
153
+
154
+ // 响应式适配
155
+ @media (max-width: 768px) {
156
+ .map-widget-wrapper {
157
+ .map-widget-header {
158
+ padding: 12px 16px;
159
+
160
+ .map-widget-title {
161
+ font-size: 16px;
162
+ }
163
+
164
+ .map-widget-address {
165
+ font-size: 12px;
166
+ }
167
+ }
168
+
169
+ .map-widget-footer {
170
+ padding: 10px 16px;
171
+
172
+ .map-info {
173
+ font-size: 11px;
174
+
175
+ .info-separator {
176
+ margin: 0 8px;
177
+ }
178
+ }
179
+ }
180
+ }
181
+ }
182
+
183
+ // 高德地图样式覆盖
184
+ .amap-logo,
185
+ .amap-copyright {
186
+ display: none !important;
187
+ }
188
+
189
+ .amap-marker-label {
190
+ border: none;
191
+ background-color: transparent;
192
+ }