neo-cmp-cli 1.2.12 → 1.2.13
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 +1 -1
- package/src/template/antd-custom-cmp-template/package.json +1 -1
- package/src/template/develop/neo-custom-cmp-template/package.json +1 -1
- package/src/template/echarts-custom-cmp-template/package.json +1 -1
- package/src/template/neo-custom-cmp-template/README.md +1 -1
- package/src/template/neo-custom-cmp-template/package.json +2 -2
- package/src/template/neo-custom-cmp-template/src/assets/img/detail.svg +1 -0
- package/src/template/neo-custom-cmp-template/src/components/contact-card-list/README.md +2 -7
- package/src/template/neo-custom-cmp-template/src/components/contact-card-list/index.tsx +13 -1
- package/src/template/neo-custom-cmp-template/src/components/contact-form/index.tsx +2 -3
- package/src/template/neo-custom-cmp-template/src/components/entity-detail/README.md +193 -0
- package/src/template/neo-custom-cmp-template/src/components/entity-detail/index.tsx +325 -0
- package/src/template/neo-custom-cmp-template/src/components/entity-detail/model.ts +125 -0
- package/src/template/neo-custom-cmp-template/src/components/entity-detail/style.scss +292 -0
- package/src/template/neo-custom-cmp-template/src/components/object-card-list/README.md +61 -0
- package/src/template/neo-custom-cmp-template/src/components/object-card-list/index.tsx +201 -0
- package/src/template/neo-custom-cmp-template/src/components/object-card-list/model.ts +66 -0
- package/src/template/neo-custom-cmp-template/src/components/object-card-list/style.scss +260 -0
- package/src/template/neo-custom-cmp-template/src/components/xobject-table/README.md +3 -11
- package/src/template/neo-custom-cmp-template/src/components/xobject-table/index.tsx +76 -58
- package/src/template/neo-custom-cmp-template/src/components/xobject-table/model.ts +21 -3
- package/src/template/react-custom-cmp-template/package.json +1 -1
- package/src/template/react-ts-custom-cmp-template/package.json +1 -1
- package/src/template/vue2-custom-cmp-template/package.json +1 -1
- package/src/template/neo-custom-cmp-template/src/components/xobject-table-v2/README.md +0 -108
- package/src/template/neo-custom-cmp-template/src/components/xobject-table-v2/index.tsx +0 -729
- package/src/template/neo-custom-cmp-template/src/components/xobject-table-v2/model.ts +0 -122
- package/src/template/neo-custom-cmp-template/src/components/xobject-table-v2/style.scss +0 -304
package/package.json
CHANGED
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
"axios": "^0.27.2",
|
|
45
45
|
"antd": "^4.9.4",
|
|
46
46
|
"lodash": "^4.17.21",
|
|
47
|
-
"neo-open-api": "^1.0.
|
|
47
|
+
"neo-open-api": "^1.0.8"
|
|
48
48
|
},
|
|
49
49
|
"devDependencies": {
|
|
50
50
|
"@commitlint/cli": "^8.3.5",
|
|
@@ -52,7 +52,7 @@
|
|
|
52
52
|
"@types/react": "^16.9.11",
|
|
53
53
|
"@types/react-dom": "^16.9.15",
|
|
54
54
|
"@types/axios": "^0.14.0",
|
|
55
|
-
"neo-cmp-cli": "^1.2.
|
|
55
|
+
"neo-cmp-cli": "^1.2.13",
|
|
56
56
|
"husky": "^4.2.5",
|
|
57
57
|
"lint-staged": "^10.2.9",
|
|
58
58
|
"prettier": "^2.0.5"
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1759211982910" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2424" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M1024 1024H0V0h768v20.608l19.84-19.904 235.52 235.392-19.84 19.904H1024v768zM768 147.2V256h108.8zM896 384h-256V128H128v768h768V384z m-128 192H256V448h512v128z m0 192H256v-128h512v128z" fill="#0764f5" p-id="2425"></path></svg>
|
|
@@ -1,8 +1,4 @@
|
|
|
1
1
|
# ContactCardList 联系人卡片列表组件
|
|
2
|
-
- 使用 cursor 生成的自定义组件。
|
|
3
|
-
|
|
4
|
-
## 组件描述
|
|
5
|
-
|
|
6
2
|
联系人卡片列表组件用于展示联系人信息,以卡片形式展示每个联系人的姓名和手机号。组件使用 Ant Design 的 Card 组件,具有良好的视觉效果和交互体验。
|
|
7
3
|
|
|
8
4
|
## 功能特性
|
|
@@ -10,7 +6,7 @@
|
|
|
10
6
|
- 📱 响应式设计,支持多种屏幕尺寸
|
|
11
7
|
- 🎨 美观的卡片布局,支持悬停效果
|
|
12
8
|
- 🔄 自动加载数据,支持错误重试
|
|
13
|
-
- 📊 使用
|
|
9
|
+
- 📊 使用 neo-open-api 获取 customContact__c 数据
|
|
14
10
|
- 🎯 展示联系人姓名和手机号信息
|
|
15
11
|
- 💫 加载状态和空状态处理
|
|
16
12
|
|
|
@@ -23,11 +19,10 @@
|
|
|
23
19
|
|
|
24
20
|
## 数据源
|
|
25
21
|
|
|
26
|
-
组件通过 `
|
|
22
|
+
组件通过 `neo-open-api` 工具函数获取数据:
|
|
27
23
|
|
|
28
24
|
- **数据表**: `customContact__c`
|
|
29
25
|
- **字段**: `id`, `name`, `phone__c`
|
|
30
|
-
- **API**: `/rest/data/v2/query`
|
|
31
26
|
|
|
32
27
|
## 样式特性
|
|
33
28
|
|
|
@@ -76,6 +76,16 @@ export default class ContactCardList extends React.PureComponent<
|
|
|
76
76
|
}
|
|
77
77
|
}
|
|
78
78
|
|
|
79
|
+
getDataName = (data: any) => {
|
|
80
|
+
let dataNameKey = 'name';
|
|
81
|
+
Object.keys(data).forEach((nameKey: string) => {
|
|
82
|
+
if (nameKey && /Name$/.test(nameKey)) {
|
|
83
|
+
dataNameKey = nameKey;
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
return data[dataNameKey];
|
|
87
|
+
};
|
|
88
|
+
|
|
79
89
|
renderContactCard(contact: ContactData, index: number) {
|
|
80
90
|
return (
|
|
81
91
|
<Col xs={24} sm={12} md={8} lg={6} xl={6} key={contact.id || index}>
|
|
@@ -96,7 +106,9 @@ export default class ContactCardList extends React.PureComponent<
|
|
|
96
106
|
<div className="contact-info">
|
|
97
107
|
<div className="contact-name">
|
|
98
108
|
<UserOutlined className="info-icon" />
|
|
99
|
-
<span className="name-text">
|
|
109
|
+
<span className="name-text">
|
|
110
|
+
{contact.name || this.getDataName(contact) || '未知姓名'}
|
|
111
|
+
</span>
|
|
100
112
|
</div>
|
|
101
113
|
<div className="contact-phone">
|
|
102
114
|
<PhoneOutlined className="info-icon" />
|
|
@@ -109,14 +109,13 @@ export default class ContactForm extends React.PureComponent<
|
|
|
109
109
|
|
|
110
110
|
if (userInfo && userInfo.id) {
|
|
111
111
|
submitData = Object.assign(submitData, {
|
|
112
|
-
userId: userInfo.id,
|
|
113
|
-
dimDepart: userInfo.departId,
|
|
112
|
+
userId: userInfo.id, // 当前用户ID,非必填
|
|
113
|
+
dimDepart: userInfo.departId, // 当前用户所属部门ID,非必填
|
|
114
114
|
});
|
|
115
115
|
}
|
|
116
116
|
|
|
117
117
|
// 调用 API 提交数据
|
|
118
118
|
const response: any = await xObject.create('customContact__c', {
|
|
119
|
-
method: 'POST',
|
|
120
119
|
data: submitData,
|
|
121
120
|
});
|
|
122
121
|
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
# 实体数据详情组件
|
|
2
|
+
实体数据详情组件(entity-detail)是一个用于展示实体数据详细信息的自定义组件。它通过 Neo Open API 获取数据,支持多列布局展示,自动识别字段类型并进行格式化显示。
|
|
3
|
+
|
|
4
|
+
## 功能特性
|
|
5
|
+
|
|
6
|
+
- ✅ **数据获取**:基于 Neo Open API 获取实体数据详情
|
|
7
|
+
- ✅ **多列布局**:支持 1-4 列的灵活布局配置
|
|
8
|
+
- ✅ **字段类型识别**:自动识别并格式化不同类型的字段(布尔值、日期、数字、URL、邮箱、电话等)
|
|
9
|
+
- ✅ **可视化配置**:在编辑器中可视化配置实体类型、数据ID、列数等属性
|
|
10
|
+
- ✅ **响应式设计**:支持不同屏幕尺寸的自适应布局
|
|
11
|
+
- ✅ **刷新功能**:支持手动刷新数据
|
|
12
|
+
- ✅ **错误处理**:完善的错误提示和重试机制
|
|
13
|
+
|
|
14
|
+
## 使用场景
|
|
15
|
+
|
|
16
|
+
- 实体记录详情页展示
|
|
17
|
+
- 数据详情弹窗
|
|
18
|
+
- 审批流程中的数据展示
|
|
19
|
+
- 仪表盘中的详细信息卡片
|
|
20
|
+
|
|
21
|
+
## 组件属性配置
|
|
22
|
+
|
|
23
|
+
| 属性名 | 类型 | 默认值 | 说明 |
|
|
24
|
+
|--------|------|--------|------|
|
|
25
|
+
| title | string | '实体数据详情' | 组件标题 |
|
|
26
|
+
| showTitle | boolean | true | 是否显示标题栏和刷新按钮 |
|
|
27
|
+
| xObjectDetailApi | object | { xObjectApiKey: 'account', objectId: '' } | 业务详情数据配置,包含实体对象API Key和数据ID |
|
|
28
|
+
| xObjectDetailApi.xObjectApiKey | string | 'account' | 实体对象的 API Key |
|
|
29
|
+
| xObjectDetailApi.objectId | string | '' | 要展示的数据记录ID,支持变量如 ${recordId} |
|
|
30
|
+
| columnCount | number | 3 | 详情页面的列数布局(1-4列) |
|
|
31
|
+
|
|
32
|
+
## 字段类型支持
|
|
33
|
+
|
|
34
|
+
组件会根据字段类型自动格式化显示:
|
|
35
|
+
|
|
36
|
+
- **boolean**:显示为带图标的标签(是/否)
|
|
37
|
+
- **date/datetime**:格式化为本地日期时间
|
|
38
|
+
- **number/currency/percent**:使用千分位格式化
|
|
39
|
+
- **url**:显示为可点击的链接
|
|
40
|
+
- **email**:显示为邮箱链接
|
|
41
|
+
- **phone**:显示为电话链接
|
|
42
|
+
- **text**:普通文本显示
|
|
43
|
+
- **空值**:显示为 "-"
|
|
44
|
+
|
|
45
|
+
## 使用示例
|
|
46
|
+
|
|
47
|
+
### 基础用法
|
|
48
|
+
|
|
49
|
+
```jsx
|
|
50
|
+
<EntityDetail
|
|
51
|
+
title="客户详情"
|
|
52
|
+
xObjectDetailApi={{
|
|
53
|
+
xObjectApiKey: "account",
|
|
54
|
+
objectId: "12345"
|
|
55
|
+
}}
|
|
56
|
+
columnCount={3}
|
|
57
|
+
/>
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### 使用变量
|
|
61
|
+
|
|
62
|
+
在 Neo 平台中,可以使用上下文变量:
|
|
63
|
+
|
|
64
|
+
```jsx
|
|
65
|
+
<EntityDetail
|
|
66
|
+
title="当前记录详情"
|
|
67
|
+
xObjectDetailApi={{
|
|
68
|
+
xObjectApiKey: "customContact__c",
|
|
69
|
+
objectId: "${recordId}"
|
|
70
|
+
}}
|
|
71
|
+
columnCount={2}
|
|
72
|
+
/>
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### 隐藏标题栏
|
|
76
|
+
|
|
77
|
+
```jsx
|
|
78
|
+
<EntityDetail
|
|
79
|
+
showTitle={false}
|
|
80
|
+
xObjectDetailApi={{
|
|
81
|
+
xObjectApiKey: "account",
|
|
82
|
+
objectId: "12345"
|
|
83
|
+
}}
|
|
84
|
+
columnCount={1}
|
|
85
|
+
/>
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## 技术实现
|
|
89
|
+
|
|
90
|
+
### 数据获取
|
|
91
|
+
|
|
92
|
+
组件使用以下 API 获取数据:
|
|
93
|
+
|
|
94
|
+
1. **xObject.get**:获取单条实体数据
|
|
95
|
+
2. **xObject.getDesc**:获取实体字段描述信息
|
|
96
|
+
|
|
97
|
+
```typescript
|
|
98
|
+
// 获取详情数据
|
|
99
|
+
const result = await xObject.get(xObjectDetailApi.xObjectApiKey, xObjectDetailApi.objectId);
|
|
100
|
+
|
|
101
|
+
// 获取字段描述
|
|
102
|
+
const descResult = await xObject.getDesc(xObjectDetailApi.xObjectApiKey);
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### 布局方案
|
|
106
|
+
|
|
107
|
+
组件采用 Ant Design 的 Row/Col 栅格系统,根据 `columnCount` 属性动态分配字段到不同的列中:
|
|
108
|
+
|
|
109
|
+
- 1列:每列占 24 格(100%宽度)
|
|
110
|
+
- 2列:每列占 12 格(50%宽度)
|
|
111
|
+
- 3列:每列占 8 格(33.3%宽度)
|
|
112
|
+
- 4列:每列占 6 格(25%宽度)
|
|
113
|
+
|
|
114
|
+
### 响应式适配
|
|
115
|
+
|
|
116
|
+
- **桌面端**(>1200px):按配置的列数显示
|
|
117
|
+
- **平板端**(768px-1200px):3列及以上自动调整为2列
|
|
118
|
+
- **移动端**(<768px):自动调整为单列显示
|
|
119
|
+
|
|
120
|
+
## 样式定制
|
|
121
|
+
|
|
122
|
+
组件提供了丰富的 CSS 类名,可以通过自定义样式进行个性化定制:
|
|
123
|
+
|
|
124
|
+
```scss
|
|
125
|
+
.entity-detail-container {
|
|
126
|
+
// 容器样式
|
|
127
|
+
|
|
128
|
+
.detail-header {
|
|
129
|
+
// 头部样式
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
.detail-content {
|
|
133
|
+
// 内容区域样式
|
|
134
|
+
|
|
135
|
+
.detail-column-card {
|
|
136
|
+
// 列卡片样式
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
## 注意事项
|
|
143
|
+
|
|
144
|
+
1. **xObjectDetailApi 参数必填**:必须提供有效的 `xObjectDetailApi` 对象,包含 `xObjectApiKey` 和 `objectId` 才能正常展示
|
|
145
|
+
2. **权限要求**:确保当前用户有权限访问指定的实体对象
|
|
146
|
+
3. **字段过滤**:组件会自动过滤以下划线开头的系统字段
|
|
147
|
+
4. **性能优化**:大量字段时建议使用2-3列布局以保证用户体验
|
|
148
|
+
5. **属性结构**:使用 `xObjectDetailApi` 对象结构,提供更清晰的数据配置方式
|
|
149
|
+
|
|
150
|
+
## 开发说明
|
|
151
|
+
|
|
152
|
+
### 目录结构
|
|
153
|
+
|
|
154
|
+
```
|
|
155
|
+
entity-detail/
|
|
156
|
+
├── index.tsx # 组件主文件
|
|
157
|
+
├── model.ts # 编辑器配置文件
|
|
158
|
+
├── style.scss # 样式文件
|
|
159
|
+
└── README.md # 组件文档
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
### 依赖包
|
|
163
|
+
|
|
164
|
+
- react: ^16.9.0
|
|
165
|
+
- antd: ^4.9.4
|
|
166
|
+
- neo-open-api: ^1.0.2
|
|
167
|
+
|
|
168
|
+
### 本地开发
|
|
169
|
+
|
|
170
|
+
```bash
|
|
171
|
+
# 预览组件
|
|
172
|
+
npm run preview --cmpType=entity-detail
|
|
173
|
+
|
|
174
|
+
# 构建组件
|
|
175
|
+
npm run build
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
## 更新日志
|
|
179
|
+
|
|
180
|
+
### v1.1.0 (2024-12-19)
|
|
181
|
+
|
|
182
|
+
- **重大更新**:重构属性结构,使用 `xObjectDetailApi` 对象替代分离的 `xObjectApiKey` 和 `objectId` 属性
|
|
183
|
+
- 优化组件属性配置,提供更清晰的数据结构
|
|
184
|
+
- 更新编辑器配置,支持 `xObjectDetailApi` 类型的属性配置
|
|
185
|
+
- 保持向后兼容性,现有功能完全保留
|
|
186
|
+
|
|
187
|
+
### v1.0.0 (2024-09-30)
|
|
188
|
+
|
|
189
|
+
- 初始版本发布
|
|
190
|
+
- 支持基础的实体数据详情展示
|
|
191
|
+
- 支持多列布局配置
|
|
192
|
+
- 支持字段类型识别和格式化
|
|
193
|
+
- 支持响应式布局
|
|
@@ -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
|
+
}
|