neo-cmp-cli 1.2.12 → 1.2.15

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 (30) hide show
  1. package/package.json +1 -1
  2. package/src/module/main.js +4 -4
  3. package/src/plugins/AddNeoRequirePlugin.js +3 -12
  4. package/src/template/antd-custom-cmp-template/package.json +1 -1
  5. package/src/template/develop/neo-custom-cmp-template/package.json +1 -1
  6. package/src/template/echarts-custom-cmp-template/package.json +1 -1
  7. package/src/template/neo-custom-cmp-template/README.md +1 -1
  8. package/src/template/neo-custom-cmp-template/package.json +2 -2
  9. package/src/template/neo-custom-cmp-template/src/assets/img/detail.svg +1 -0
  10. package/src/template/neo-custom-cmp-template/src/components/contact-card-list/README.md +2 -7
  11. package/src/template/neo-custom-cmp-template/src/components/contact-card-list/index.tsx +13 -1
  12. package/src/template/neo-custom-cmp-template/src/components/contact-form/index.tsx +2 -3
  13. package/src/template/neo-custom-cmp-template/src/components/entity-detail/README.md +193 -0
  14. package/src/template/neo-custom-cmp-template/src/components/entity-detail/index.tsx +325 -0
  15. package/src/template/neo-custom-cmp-template/src/components/entity-detail/model.ts +125 -0
  16. package/src/template/neo-custom-cmp-template/src/components/entity-detail/style.scss +292 -0
  17. package/src/template/neo-custom-cmp-template/src/components/object-card-list/README.md +61 -0
  18. package/src/template/neo-custom-cmp-template/src/components/object-card-list/index.tsx +201 -0
  19. package/src/template/neo-custom-cmp-template/src/components/object-card-list/model.ts +66 -0
  20. package/src/template/neo-custom-cmp-template/src/components/object-card-list/style.scss +260 -0
  21. package/src/template/neo-custom-cmp-template/src/components/xobject-table/README.md +3 -11
  22. package/src/template/neo-custom-cmp-template/src/components/xobject-table/index.tsx +76 -58
  23. package/src/template/neo-custom-cmp-template/src/components/xobject-table/model.ts +21 -3
  24. package/src/template/react-custom-cmp-template/package.json +1 -1
  25. package/src/template/react-ts-custom-cmp-template/package.json +1 -1
  26. package/src/template/vue2-custom-cmp-template/package.json +1 -1
  27. package/src/template/neo-custom-cmp-template/src/components/xobject-table-v2/README.md +0 -108
  28. package/src/template/neo-custom-cmp-template/src/components/xobject-table-v2/index.tsx +0 -729
  29. package/src/template/neo-custom-cmp-template/src/components/xobject-table-v2/model.ts +0 -122
  30. package/src/template/neo-custom-cmp-template/src/components/xobject-table-v2/style.scss +0 -304
@@ -0,0 +1,325 @@
1
+ import * as React from 'react';
2
+ import {
3
+ Card,
4
+ Row,
5
+ Col,
6
+ Spin,
7
+ Empty,
8
+ Descriptions,
9
+ Button,
10
+ Tag,
11
+ Divider,
12
+ Typography,
13
+ } from 'antd';
14
+ import {
15
+ ReloadOutlined,
16
+ InfoCircleOutlined,
17
+ CheckCircleOutlined,
18
+ CloseCircleOutlined,
19
+ } from '@ant-design/icons';
20
+ // @ts-ignore
21
+ import { xObject } from 'neo-open-api'; // Neo OpenAPI SDK
22
+ import './style.scss';
23
+
24
+ const { Title, Text } = Typography;
25
+
26
+ interface EntityDetailProps {
27
+ title?: string;
28
+ xObjectDetailApi?: {
29
+ xObjectApiKey: string;
30
+ objectId: string;
31
+ };
32
+ columnCount?: number;
33
+ showTitle?: boolean;
34
+ data?: any;
35
+ }
36
+
37
+ interface FieldDescription {
38
+ apiKey: string;
39
+ label: string;
40
+ type: string;
41
+ [key: string]: any;
42
+ }
43
+
44
+ interface EntityDetailState {
45
+ detailData: any;
46
+ fieldDescriptions: FieldDescription[];
47
+ loading: boolean;
48
+ error: string | null;
49
+ }
50
+
51
+ export default class EntityDetail extends React.PureComponent<
52
+ EntityDetailProps,
53
+ EntityDetailState
54
+ > {
55
+ constructor(props: EntityDetailProps) {
56
+ super(props);
57
+
58
+ this.state = {
59
+ detailData: {},
60
+ fieldDescriptions: [],
61
+ loading: false,
62
+ error: null,
63
+ };
64
+
65
+ this.loadEntityDetail = this.loadEntityDetail.bind(this);
66
+ this.loadFieldDescriptions = this.loadFieldDescriptions.bind(this);
67
+ }
68
+
69
+ componentDidMount() {
70
+ this.loadData();
71
+ }
72
+
73
+ componentDidUpdate(prevProps: EntityDetailProps) {
74
+ const { xObjectDetailApi } = this.props;
75
+ if (
76
+ xObjectDetailApi?.xObjectApiKey !==
77
+ prevProps.xObjectDetailApi?.xObjectApiKey ||
78
+ xObjectDetailApi?.objectId !== prevProps.xObjectDetailApi?.objectId
79
+ ) {
80
+ this.loadData();
81
+ }
82
+ }
83
+
84
+ async loadData() {
85
+ const { xObjectDetailApi } = this.props;
86
+ if (!xObjectDetailApi?.xObjectApiKey || !xObjectDetailApi?.objectId) {
87
+ this.setState({
88
+ error: '缺少必要参数:实体类型或数据ID',
89
+ loading: false,
90
+ });
91
+ return;
92
+ }
93
+
94
+ await Promise.all([this.loadFieldDescriptions(), this.loadEntityDetail()]);
95
+ }
96
+
97
+ async loadFieldDescriptions() {
98
+ const { xObjectDetailApi } = this.props;
99
+ if (!xObjectDetailApi?.xObjectApiKey) return;
100
+
101
+ try {
102
+ const result = await xObject.getDesc(xObjectDetailApi.xObjectApiKey);
103
+ if (result?.status) {
104
+ const fields = result.data?.fields || [];
105
+ this.setState({ fieldDescriptions: fields });
106
+ }
107
+ } catch (error: any) {
108
+ console.error('获取字段描述失败:', error);
109
+ }
110
+ }
111
+
112
+ async loadEntityDetail() {
113
+ const xObjectDetailApi: any = this.props.xObjectDetailApi || {};
114
+ if (!xObjectDetailApi.xObjectApiKey || !xObjectDetailApi.objectId) return;
115
+
116
+ this.setState({ loading: true, error: null });
117
+
118
+ try {
119
+ const result = await xObject.get(
120
+ xObjectDetailApi.xObjectApiKey,
121
+ xObjectDetailApi.objectId,
122
+ );
123
+
124
+ if (result?.status) {
125
+ const data = result.data || {};
126
+ this.setState({
127
+ detailData: data,
128
+ loading: false,
129
+ });
130
+ } else {
131
+ this.setState({
132
+ error: result?.msg || '获取详情数据失败',
133
+ loading: false,
134
+ });
135
+ }
136
+ } catch (error: any) {
137
+ this.setState({
138
+ error: error.message || '获取详情数据失败',
139
+ loading: false,
140
+ });
141
+ }
142
+ }
143
+
144
+ getFieldLabel(apiKey: string): string {
145
+ const { fieldDescriptions } = this.state;
146
+ const field = fieldDescriptions.find((f) => f.apiKey === apiKey);
147
+ return field?.label || apiKey;
148
+ }
149
+
150
+ getFieldType(apiKey: string): string {
151
+ const { fieldDescriptions } = this.state;
152
+ const field = fieldDescriptions.find((f) => f.apiKey === apiKey);
153
+ return field?.type || 'text';
154
+ }
155
+
156
+ renderFieldValue(value: any, fieldType: string) {
157
+ if (value === null || value === undefined || value === '') {
158
+ return <Text type="secondary">-</Text>;
159
+ }
160
+
161
+ // 根据字段类型渲染不同的值
162
+ switch (fieldType) {
163
+ case 'boolean':
164
+ return value ? (
165
+ <Tag icon={<CheckCircleOutlined />} color="success">
166
+
167
+ </Tag>
168
+ ) : (
169
+ <Tag icon={<CloseCircleOutlined />} color="default">
170
+
171
+ </Tag>
172
+ );
173
+ case 'date':
174
+ case 'datetime':
175
+ return new Date(value).toLocaleString('zh-CN');
176
+ case 'number':
177
+ case 'currency':
178
+ case 'percent':
179
+ return typeof value === 'number'
180
+ ? value.toLocaleString('zh-CN')
181
+ : value;
182
+ case 'url':
183
+ return (
184
+ <a href={value} target="_blank" rel="noopener noreferrer">
185
+ {value}
186
+ </a>
187
+ );
188
+ case 'email':
189
+ return <a href={`mailto:${value}`}>{value}</a>;
190
+ case 'phone':
191
+ return <a href={`tel:${value}`}>{value}</a>;
192
+ default:
193
+ return String(value);
194
+ }
195
+ }
196
+
197
+ renderDetailContent() {
198
+ const { detailData } = this.state;
199
+ const { columnCount = 3 } = this.props;
200
+
201
+ if (!detailData || Object.keys(detailData).length === 0) {
202
+ return (
203
+ <Empty
204
+ image={Empty.PRESENTED_IMAGE_SIMPLE}
205
+ description="暂无详情数据"
206
+ />
207
+ );
208
+ }
209
+
210
+ // 过滤系统字段和空字段
211
+ const displayFields = Object.keys(detailData).filter(
212
+ (key) =>
213
+ // 可以根据需要自定义过滤规则
214
+ !key.startsWith('_') && detailData[key] !== undefined,
215
+ );
216
+
217
+ // 将字段分成多组,每组对应一列
218
+ const fieldsPerColumn = Math.ceil(displayFields.length / columnCount);
219
+ const columnGroups: string[][] = [];
220
+
221
+ for (let i = 0; i < columnCount; i++) {
222
+ const start = i * fieldsPerColumn;
223
+ const end = start + fieldsPerColumn;
224
+ columnGroups.push(displayFields.slice(start, end));
225
+ }
226
+
227
+ return (
228
+ <Row gutter={[24, 24]}>
229
+ {columnGroups.map((fields, colIndex) => (
230
+ <Col xs={24} sm={24} md={24 / columnCount} key={colIndex}>
231
+ <Card className="detail-column-card" bordered={false} size="small">
232
+ <Descriptions
233
+ column={1}
234
+ size="middle"
235
+ bordered
236
+ labelStyle={{
237
+ fontWeight: 500,
238
+ backgroundColor: '#fafafa',
239
+ width: '35%',
240
+ }}
241
+ contentStyle={{
242
+ backgroundColor: '#ffffff',
243
+ }}
244
+ >
245
+ {fields.map((fieldKey) => {
246
+ const fieldType = this.getFieldType(fieldKey);
247
+ const fieldLabel = this.getFieldLabel(fieldKey);
248
+ const fieldValue = detailData[fieldKey];
249
+
250
+ return (
251
+ <Descriptions.Item label={fieldLabel} key={fieldKey}>
252
+ {this.renderFieldValue(fieldValue, fieldType)}
253
+ </Descriptions.Item>
254
+ );
255
+ })}
256
+ </Descriptions>
257
+ </Card>
258
+ </Col>
259
+ ))}
260
+ </Row>
261
+ );
262
+ }
263
+
264
+ render() {
265
+ const { title, showTitle = true } = this.props;
266
+ const { loading, error } = this.state;
267
+ const curAmisData = this.props.data || {};
268
+ const systemInfo = curAmisData.__NeoSystemInfo || {};
269
+
270
+ return (
271
+ <div className="entity-detail-container">
272
+ {showTitle && (
273
+ <div className="detail-header">
274
+ <div className="header-content">
275
+ <Title level={4} className="header-title">
276
+ <InfoCircleOutlined className="title-icon" />
277
+ {title || '实体数据详情'}
278
+ {systemInfo.tenantName ? (
279
+ <Tag color="blue" style={{ marginLeft: 8 }}>
280
+ {systemInfo.tenantName}
281
+ </Tag>
282
+ ) : null}
283
+ </Title>
284
+ <Button
285
+ type="primary"
286
+ icon={<ReloadOutlined />}
287
+ onClick={this.loadEntityDetail}
288
+ loading={loading}
289
+ className="refresh-button"
290
+ size="small"
291
+ >
292
+ 刷新
293
+ </Button>
294
+ </div>
295
+ <Divider style={{ margin: '12px 0' }} />
296
+ </div>
297
+ )}
298
+
299
+ <div className="detail-content">
300
+ <Spin spinning={loading} tip="加载详情数据中...">
301
+ {error ? (
302
+ <div className="error-container">
303
+ <Empty
304
+ image={Empty.PRESENTED_IMAGE_SIMPLE}
305
+ description={
306
+ <div>
307
+ <div style={{ color: '#ff4d4f', marginBottom: 8 }}>
308
+ {error}
309
+ </div>
310
+ <Button type="primary" onClick={this.loadEntityDetail}>
311
+ 重新加载
312
+ </Button>
313
+ </div>
314
+ }
315
+ />
316
+ </div>
317
+ ) : (
318
+ this.renderDetailContent()
319
+ )}
320
+ </Spin>
321
+ </div>
322
+ </div>
323
+ );
324
+ }
325
+ }
@@ -0,0 +1,125 @@
1
+ /**
2
+ * @file 实体数据详情组件对接编辑器的描述文件
3
+ * @description 定义组件在 Neo 平台编辑器中的配置信息
4
+ */
5
+ export class EntityDetailModel {
6
+ /**
7
+ * 组件类型标识
8
+ * 用于标识组件的唯一性,在构建时根据当前组件目录名称自动生成
9
+ * 注意:此字段在构建时会被自动替换,不需要手动设置
10
+ */
11
+ // cmpType: string = 'entity-detail';
12
+
13
+ /** 组件名称,用于设置在编辑器左侧组件面板中展示的名称 */
14
+ label: string = '实体数据详情';
15
+
16
+ /** 组件描述,用于设置在编辑器左侧组件面板中展示的描述 */
17
+ description: string = '展示实体数据详情信息,支持多列布局和字段类型识别';
18
+
19
+ /** 分类标签,用于设置在编辑器左侧组件面板哪个分类中展示(可设置多个分类标签) */
20
+ tags: string[] = ['自定义组件'];
21
+
22
+ /** 组件图标,用于设置在编辑器左侧组件面板中展示的图标 */
23
+ iconSrc: string = 'https://custom-widgets.bj.bcebos.com/detail.svg';
24
+
25
+ /** 初次插入页面的默认属性数据 */
26
+ defaultComProps = {
27
+ title: '实体数据详情',
28
+ xObjectDetailApi: {
29
+ xObjectApiKey: 'account',
30
+ objectId: '',
31
+ },
32
+ columnCount: 3,
33
+ showTitle: true,
34
+ };
35
+
36
+ /** 设计器端预览时展示的默认数据 */
37
+ previewComProps = {
38
+ title: '实体数据详情',
39
+ columnCount: 3,
40
+ showTitle: true,
41
+ };
42
+
43
+ /**
44
+ * 组件属性配置模式
45
+ * 定义组件在编辑器中可配置的属性
46
+ */
47
+ propsSchema = [
48
+ {
49
+ type: 'text',
50
+ name: 'title',
51
+ label: '标题',
52
+ value: '实体数据详情',
53
+ placeholder: '请输入标题',
54
+ description: '组件顶部显示的标题',
55
+ },
56
+ {
57
+ type: 'switch',
58
+ name: 'showTitle',
59
+ label: '显示标题栏',
60
+ value: true,
61
+ description: '是否显示组件顶部的标题和刷新按钮',
62
+ },
63
+ {
64
+ type: 'xObjectDetailApi', // 用于选取对象业务详情数据的配置项
65
+ name: 'xObjectDetailApi',
66
+ label: '业务详情数据',
67
+ },
68
+ /*
69
+ {
70
+ type: 'xObjectEntityList', // 用于选取实体的配置项
71
+ name: 'xObjectDetailApi.xObjectApiKey',
72
+ label: '实体对象',
73
+ helpText: '请选择要展示详情的实体对象',
74
+ custom: false,
75
+ value: 'account'
76
+ },
77
+ {
78
+ type: 'text',
79
+ name: 'xObjectDetailApi.objectId',
80
+ label: '数据ID',
81
+ value: '',
82
+ placeholder: '请输入数据ID或使用上下文变量',
83
+ description: '要展示的数据记录ID,支持变量:${recordId}',
84
+ },
85
+ */
86
+ {
87
+ type: 'select',
88
+ name: 'columnCount',
89
+ label: '列数',
90
+ value: 3,
91
+ options: [
92
+ {
93
+ label: '1列',
94
+ value: 1,
95
+ },
96
+ {
97
+ label: '2列',
98
+ value: 2,
99
+ },
100
+ {
101
+ label: '3列',
102
+ value: 3,
103
+ },
104
+ {
105
+ label: '4列',
106
+ value: 4,
107
+ },
108
+ ],
109
+ description: '详情页面的列数布局',
110
+ },
111
+ ];
112
+
113
+ /**
114
+ * 支持函数式写法:propsSchemaCreator
115
+ * com 为组件实例,优先级比 propsSchema 高
116
+ * 可以根据组件实例动态生成属性配置
117
+ */
118
+ /*
119
+ propsSchemaCreator = (com: any) => {
120
+ return [];
121
+ };
122
+ */
123
+ }
124
+
125
+ export default EntityDetailModel;