@hzab/list-render 1.8.2 → 1.8.3-beta1
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/CHANGELOG.md +6 -0
- package/README.md +30 -28
- package/package.json +3 -3
- package/src/DetailModal/index.jsx +112 -0
- package/src/DetailModal/index.less +2 -0
- package/src/FormModal/index.less +8 -0
- package/src/{form-dialog/index.jsx → FormModal/index.tsx} +56 -33
- package/src/common/utils.js +59 -5
- package/src/components/PrefixNode/index.less +9 -0
- package/src/components/PrefixNode/index.tsx +61 -0
- package/src/components/Tags/index.less +3 -0
- package/src/components/Tags/index.tsx +37 -0
- package/src/list-render.jsx +48 -29
- package/src/table-render/index.jsx +36 -4
- package/src/form-dialog/index.less +0 -2
package/CHANGELOG.md
CHANGED
package/README.md
CHANGED
|
@@ -74,9 +74,11 @@ const listDM = useMemo(
|
|
|
74
74
|
| tableProps | Object | | {} | 直接传给 Table 的 props,相关 API 可直接参考 antd table 组件 |
|
|
75
75
|
| fetchOnEdit | Boolean | | true | 展示编辑弹框时,是否会调用一次详情接口进行回填;若为 false,则会使用表格列表接口返回的 row 数据进行回填 |
|
|
76
76
|
| fetchById | Boolean | | true | 编辑中的详情请求,是否使用 id 作为入参的 key |
|
|
77
|
-
|
|
|
78
|
-
|
|
|
79
|
-
|
|
|
77
|
+
| modalMode | string | | dialog | 新增/编辑表单、详情 展示模式: dialog drawer |
|
|
78
|
+
| modalConf | Object | | {} | modal/Drawer 配置对象 |
|
|
79
|
+
| modalDetailProps | Object | | {} | modal descriptions 配置对象 |
|
|
80
|
+
| modalFormProps | Object | | {} | modal/drawer fromRender 配置对象 |
|
|
81
|
+
| modalProps | Object | | {} | modal/drawer 配置对象 |
|
|
80
82
|
| schemaScope | Object | | {} | formRender schemaScope props |
|
|
81
83
|
| components | Object | | {} | formRender components props 自定义组件 |
|
|
82
84
|
| detailComponents | Object | | {} | descriptions components props 自定义组件 |
|
|
@@ -89,28 +91,29 @@ const listDM = useMemo(
|
|
|
89
91
|
| onCreateSuc | Function | | - | 新增成功返回的回调 |
|
|
90
92
|
| onEditSuc | Function | | - | 编辑成功返回的回调 |
|
|
91
93
|
| onDelSuc | Function | | - | 删除成功返回的回调 |
|
|
92
|
-
|
|
|
93
|
-
|
|
|
94
|
+
| onFormModalClose | Function | | - | 表单弹窗关闭回调 |
|
|
95
|
+
| modalFormMount | Function | | - | 新增、编辑弹窗 Form 渲染完成回调 |
|
|
94
96
|
| msgConf | Object | | {} | 新增、编辑、删除、列表查询,详情查询的报错 msg 提示设置 |
|
|
95
97
|
| i18n | Object | | {} | 文案配置 |
|
|
96
98
|
| queryFormInitialValues | Object | | {} | 列表上方查询 Form 默认值 |
|
|
97
99
|
| queryFormIsExtendModelQuery | Boolean | | false | 列表上方查询 Form 默认值是否继承 data-model.query 置 |
|
|
100
|
+
| useFormData | boolean | 否 | | 是否使用 form data 提交数据 |
|
|
98
101
|
|
|
99
102
|
- fetchOnEdit 展示编辑弹框时,是否会调用一次详情接口进行回填(某些场景下,列表接口只返回部分部分字段,只有详情接口会返回全部字段);若为 false,则会使用表格列表接口返回的 row 数据进行回填
|
|
100
103
|
|
|
101
104
|
#### tableConf
|
|
102
105
|
|
|
103
|
-
| 属性名称
|
|
104
|
-
|
|
|
105
|
-
| colConf
|
|
106
|
-
| rowSelection
|
|
107
|
-
| scroll
|
|
108
|
-
| expandable
|
|
109
|
-
| onRow
|
|
110
|
-
| orderColType
|
|
111
|
-
| orderColWidth
|
|
112
|
-
| tableEmptyValue
|
|
113
|
-
| isTableSortXIdex | Boolean
|
|
106
|
+
| 属性名称 | 属性类型 | 必须 | 默认值 | 描述 |
|
|
107
|
+
| ---------------- | ------------- | ---- | --------- | -------------------------------------------------------------------------------------------- |
|
|
108
|
+
| colConf | Object | | {} | 指定各列的配置(比如列宽),key 为字段的 name。可以指定名为 “\_$actions”的字段来设置“操作”列 |
|
|
109
|
+
| rowSelection | Object | | {} | 选择功能的配置。参考 antd table rowSelection 参数 |
|
|
110
|
+
| scroll | Object | | {} | 表格是否可滚动,也可以指定滚动区域的宽、高。参考 antd table scroll 参数 |
|
|
111
|
+
| expandable | Object | | {} | 配置展开属性。参考 antd table expandable 参数 |
|
|
112
|
+
| onRow | Object | | {} | 设置行属性。参考 antd table onRow 参数 |
|
|
113
|
+
| orderColType | string | | - | 序号列数据类型:page(按当前页的序号)、all(按所有页的序号) |
|
|
114
|
+
| orderColWidth | string/number | | - | 序号列 width 的参数 |
|
|
115
|
+
| tableEmptyValue | string/number | | undefined | table 列表空值展示数 |
|
|
116
|
+
| isTableSortXIdex | Boolean | | undefined | table 列表列排序是否按照 x-index 排序 |
|
|
114
117
|
|
|
115
118
|
##### tableConf.colConf[xxx]
|
|
116
119
|
|
|
@@ -159,7 +162,7 @@ const listDM = useMemo(
|
|
|
159
162
|
| ------------------ | -------- | ---- | ------ | ------------------------------------ |
|
|
160
163
|
| headerActionPrefix | Function | 否 | | 新增按钮左侧插槽 |
|
|
161
164
|
| headerActionSuffix | Function | 否 | | 新增按钮右侧插槽 |
|
|
162
|
-
| HeaderOthersSuffix | Function | 否 | | 表格和搜索项之间的插槽
|
|
165
|
+
| HeaderOthersSuffix | Function | 否 | | 表格和搜索项之间的插槽 |
|
|
163
166
|
| tableActionsSlot | Function | 否 | | 操作列插槽,会覆盖操作列 |
|
|
164
167
|
| actionPrefixSlot | Function | 否 | | 操作列 编辑按钮左侧插槽 |
|
|
165
168
|
| actionCenterSlot | Function | 否 | | 操作列 编辑、删除按钮中间插槽 |
|
|
@@ -203,7 +206,6 @@ const Slots = {
|
|
|
203
206
|
| cancelText | string | 否 | | 弹窗底部取消按钮文案 |
|
|
204
207
|
| footer | Array | 否 | | 自定义弹窗底部按钮 |
|
|
205
208
|
| beforeSubmit | Function | 否 | | 提交前的回调, return false; 表示拦截,不进行请求。 |
|
|
206
|
-
| useFormData | boolean | 否 | | 是否使用 form data 提交数据 |
|
|
207
209
|
|
|
208
210
|
#### paginationConf
|
|
209
211
|
|
|
@@ -223,16 +225,16 @@ const Slots = {
|
|
|
223
225
|
|
|
224
226
|
- 可使用 ref 获取并触发执行
|
|
225
227
|
|
|
226
|
-
| 函数名
|
|
227
|
-
|
|
|
228
|
-
| onSearch
|
|
229
|
-
| getList
|
|
230
|
-
| forceUpdate
|
|
231
|
-
|
|
|
232
|
-
| queryRef
|
|
233
|
-
| onCreate
|
|
234
|
-
| onEdit
|
|
235
|
-
| onDel
|
|
228
|
+
| 函数名 | 参数 | 说明 |
|
|
229
|
+
| ------------ | ----- | -------------------------------------------- |
|
|
230
|
+
| onSearch | query | 重置页码至 1,并刷新列表 |
|
|
231
|
+
| getList | query | 获取当前页列表数据 |
|
|
232
|
+
| forceUpdate | - | 强制重渲染列表,解决枚举数据渲染不正常的问题 |
|
|
233
|
+
| formModalRef | - | 新增、编辑 弹窗 form-modal 的 ref |
|
|
234
|
+
| queryRef | - | 筛选条件 query-render 的 ref |
|
|
235
|
+
| onCreate | - | 手动触发新增按钮相关操作 |
|
|
236
|
+
| onEdit | row | 手动触发编辑按钮相关操作 |
|
|
237
|
+
| onDel | row | 手动触发删除按钮相关操作 |
|
|
236
238
|
|
|
237
239
|
# Schema
|
|
238
240
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hzab/list-render",
|
|
3
|
-
"version": "1.8.
|
|
3
|
+
"version": "1.8.3-beta1",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "src",
|
|
6
6
|
"scripts": {
|
|
@@ -20,10 +20,10 @@
|
|
|
20
20
|
"license": "ISC",
|
|
21
21
|
"devDependencies": {
|
|
22
22
|
"@ant-design/icons": "^4.8.1",
|
|
23
|
-
"@hzab/data-model": "^1.
|
|
23
|
+
"@hzab/data-model": "^1.6.0",
|
|
24
24
|
"@hzab/form-render": "^1.1.5",
|
|
25
25
|
"@hzab/schema-descriptions": "^1.0.0",
|
|
26
|
-
"@hzab/webpack-config": "0.
|
|
26
|
+
"@hzab/webpack-config": "^0.7.2",
|
|
27
27
|
"@types/react": "^17.0.62",
|
|
28
28
|
"@types/react-dom": "^17.0.20",
|
|
29
29
|
"antd": "^4.24.12",
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { forwardRef, useEffect, useImperativeHandle, useRef, useState } from "react";
|
|
2
|
+
|
|
3
|
+
import { Modal, Drawer, Button } from "antd";
|
|
4
|
+
import Descriptions from "@hzab/schema-descriptions";
|
|
5
|
+
|
|
6
|
+
import "./index.less";
|
|
7
|
+
|
|
8
|
+
let _formData = null;
|
|
9
|
+
|
|
10
|
+
function DetailModal(props, parentRef) {
|
|
11
|
+
const { modalMode, Slots = {}, modalConf = {}, modalProps = {} } = props;
|
|
12
|
+
const [open, setOpen] = useState(false);
|
|
13
|
+
const [data, setData] = useState({});
|
|
14
|
+
const formRef = useRef();
|
|
15
|
+
function show(formData = props.formInitialValues) {
|
|
16
|
+
setOpen(true);
|
|
17
|
+
// 处理 formRef.current 为 undefined 的问题
|
|
18
|
+
setData(formData);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function close() {
|
|
22
|
+
props.onClose && props.onClose();
|
|
23
|
+
setOpen(false);
|
|
24
|
+
formRef.current?.formRender?.reset();
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
useImperativeHandle(parentRef, () => ({
|
|
28
|
+
show,
|
|
29
|
+
close,
|
|
30
|
+
cancel: close,
|
|
31
|
+
formRef,
|
|
32
|
+
}));
|
|
33
|
+
|
|
34
|
+
let footer = undefined;
|
|
35
|
+
const options = {
|
|
36
|
+
close,
|
|
37
|
+
form: formRef.current?.formRender,
|
|
38
|
+
scenario: "detail",
|
|
39
|
+
};
|
|
40
|
+
if (modalConf?.footer) {
|
|
41
|
+
footer = typeof modalConf?.footer === "function" ? modalConf.footer({ ...options, options }) : modalConf?.footer;
|
|
42
|
+
} else {
|
|
43
|
+
footer = [];
|
|
44
|
+
|
|
45
|
+
if (Slots.modalFooterPre) {
|
|
46
|
+
footer.push(<Slots.modalFooterPre key="pre" options={options} />);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
footer.push(
|
|
50
|
+
<Button key="confirm" onClick={close}>
|
|
51
|
+
{modalConf.okText || "关 闭"}
|
|
52
|
+
</Button>,
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
if (Slots.modalFooterSuffix) {
|
|
56
|
+
footer.push(<Slots.modalFooterSuffix key="suffix" options={options} />);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* 解决 show 函数中 formRef.current 为 undefined 的问题
|
|
62
|
+
*/
|
|
63
|
+
function didMount() {
|
|
64
|
+
props.modalFormMount && props.modalFormMount();
|
|
65
|
+
if (_formData) {
|
|
66
|
+
setData(_formData);
|
|
67
|
+
_formData = null;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const CModal = modalMode === "drawer" ? Drawer : Modal;
|
|
72
|
+
const _modalProps = {
|
|
73
|
+
className: "detail-modal",
|
|
74
|
+
wrapClassName: "detail-modal",
|
|
75
|
+
title: "详情",
|
|
76
|
+
visible: open,
|
|
77
|
+
open: open,
|
|
78
|
+
onClose: close,
|
|
79
|
+
onCancel: close,
|
|
80
|
+
footer: footer,
|
|
81
|
+
maskClosable: modalProps.maskClosable || false,
|
|
82
|
+
width: modalConf?.width ?? 720,
|
|
83
|
+
// 解决弹窗不销毁,表单远程数据没有更新的问题
|
|
84
|
+
destroyOnClose: true,
|
|
85
|
+
...modalProps,
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
return (
|
|
89
|
+
<CModal {..._modalProps}>
|
|
90
|
+
<Descriptions
|
|
91
|
+
{...props.detailProps}
|
|
92
|
+
components={props.components}
|
|
93
|
+
schema={props.schema}
|
|
94
|
+
schemaScope={{
|
|
95
|
+
scenario: "detail",
|
|
96
|
+
...(props.schemaScope || {}),
|
|
97
|
+
}}
|
|
98
|
+
data={data}
|
|
99
|
+
/>
|
|
100
|
+
<DidMount didMount={didMount} />
|
|
101
|
+
</CModal>
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function DidMount(props) {
|
|
106
|
+
useEffect(() => {
|
|
107
|
+
props.didMount();
|
|
108
|
+
}, []);
|
|
109
|
+
return null;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export default forwardRef(DetailModal);
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { forwardRef, useEffect, useImperativeHandle, useRef, useState, useMemo } from "react";
|
|
2
|
+
import _ from "lodash";
|
|
2
3
|
|
|
3
|
-
import { Modal, Button, message } from "antd";
|
|
4
|
+
import { Modal, Drawer, Button, message } from "antd";
|
|
4
5
|
|
|
5
6
|
import FormRender from "@hzab/form-render";
|
|
6
7
|
|
|
@@ -8,13 +9,29 @@ import "./index.less";
|
|
|
8
9
|
|
|
9
10
|
let _formData = null;
|
|
10
11
|
|
|
11
|
-
|
|
12
|
-
|
|
12
|
+
export interface IFormRender {
|
|
13
|
+
setValues: Function;
|
|
14
|
+
reset: Function;
|
|
15
|
+
validate: Function;
|
|
16
|
+
values: Object;
|
|
17
|
+
}
|
|
18
|
+
export interface IFormRef {
|
|
19
|
+
formRender: IFormRender;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* 表单弹层,包含弹窗、抽屉两种模式
|
|
24
|
+
* @param props
|
|
25
|
+
* @param parentRef
|
|
26
|
+
* @returns
|
|
27
|
+
*/
|
|
28
|
+
export function FormModal(props, parentRef) {
|
|
29
|
+
const { modalMode, Slots = {}, modalConf = {}, modalProps = {} } = props;
|
|
13
30
|
const [loading, setLoading] = useState(false);
|
|
14
31
|
const [title, setTitle] = useState("新增");
|
|
15
32
|
const [scenario, setScenario] = useState("create");
|
|
16
33
|
const [open, setOpen] = useState(false);
|
|
17
|
-
const formRef = useRef();
|
|
34
|
+
const formRef = useRef<IFormRef>();
|
|
18
35
|
|
|
19
36
|
const FormSlot = useMemo(() => props.Slots?.FormSlot, [props.Slots?.FormSlot]);
|
|
20
37
|
|
|
@@ -48,8 +65,8 @@ function FormDialog(props, parentRef) {
|
|
|
48
65
|
function onOk() {
|
|
49
66
|
validate().then(async () => {
|
|
50
67
|
const submitForm = _.cloneDeep(await formRef.current?.formRender?.values);
|
|
51
|
-
if (
|
|
52
|
-
const isContinue = await
|
|
68
|
+
if (modalConf.beforeSubmit) {
|
|
69
|
+
const isContinue = await modalConf.beforeSubmit(submitForm, {
|
|
53
70
|
cancel: close,
|
|
54
71
|
formRef,
|
|
55
72
|
scenario,
|
|
@@ -74,7 +91,7 @@ function FormDialog(props, parentRef) {
|
|
|
74
91
|
* 校验表单
|
|
75
92
|
* @returns
|
|
76
93
|
*/
|
|
77
|
-
function validate(hideMessage) {
|
|
94
|
+
function validate(hideMessage = false) {
|
|
78
95
|
return new Promise((resolve, reject) => {
|
|
79
96
|
formRef.current?.formRender
|
|
80
97
|
?.validate()
|
|
@@ -98,33 +115,33 @@ function FormDialog(props, parentRef) {
|
|
|
98
115
|
validate,
|
|
99
116
|
scenario,
|
|
100
117
|
};
|
|
101
|
-
if (
|
|
102
|
-
footer = typeof
|
|
118
|
+
if (modalConf?.footer) {
|
|
119
|
+
footer = typeof modalConf?.footer === "function" ? modalConf.footer({ ...options, options }) : modalConf?.footer;
|
|
103
120
|
} else {
|
|
104
121
|
footer = [];
|
|
105
122
|
|
|
106
|
-
if (Slots.
|
|
107
|
-
footer.push(<Slots.
|
|
123
|
+
if (Slots.modalFooterPre) {
|
|
124
|
+
footer.push(<Slots.modalFooterPre key="pre" options={options} />);
|
|
108
125
|
}
|
|
109
126
|
|
|
110
127
|
footer.push(
|
|
111
128
|
<Button key="cancel" onClick={close}>
|
|
112
|
-
{
|
|
129
|
+
{modalConf.cancelText || "取 消"}
|
|
113
130
|
</Button>,
|
|
114
131
|
);
|
|
115
132
|
|
|
116
|
-
if (Slots.
|
|
117
|
-
footer.push(<Slots.
|
|
133
|
+
if (Slots.modalFooterCenter) {
|
|
134
|
+
footer.push(<Slots.modalFooterCenter key="center" options={options} />);
|
|
118
135
|
}
|
|
119
136
|
|
|
120
137
|
footer.push(
|
|
121
138
|
<Button key="confirm" type="primary" onClick={onOk} loading={loading}>
|
|
122
|
-
{
|
|
139
|
+
{modalConf.okText || "确 定"}
|
|
123
140
|
</Button>,
|
|
124
141
|
);
|
|
125
142
|
|
|
126
|
-
if (Slots.
|
|
127
|
-
footer.push(<Slots.
|
|
143
|
+
if (Slots.modalFooterSuffix) {
|
|
144
|
+
footer.push(<Slots.modalFooterSuffix key="suffix" options={options} />);
|
|
128
145
|
}
|
|
129
146
|
}
|
|
130
147
|
|
|
@@ -132,27 +149,33 @@ function FormDialog(props, parentRef) {
|
|
|
132
149
|
* 解决 show 函数中 formRef.current 为 undefined 的问题
|
|
133
150
|
*/
|
|
134
151
|
function didMount() {
|
|
135
|
-
props.
|
|
152
|
+
props.modalFormMount && props.modalFormMount();
|
|
136
153
|
if (_formData) {
|
|
137
154
|
formRef.current?.formRender?.setValues(_formData);
|
|
138
155
|
_formData = null;
|
|
139
156
|
}
|
|
140
157
|
}
|
|
141
158
|
|
|
159
|
+
const CModal = modalMode === "drawer" ? Drawer : Modal;
|
|
160
|
+
const _modalProps = {
|
|
161
|
+
className: "form-modal",
|
|
162
|
+
wrapClassName: "form-modal",
|
|
163
|
+
title: title,
|
|
164
|
+
visible: open,
|
|
165
|
+
open: open,
|
|
166
|
+
onClose: close,
|
|
167
|
+
onCancel: close,
|
|
168
|
+
onOk: onOk,
|
|
169
|
+
footer: footer,
|
|
170
|
+
maskClosable: modalProps.maskClosable || false,
|
|
171
|
+
width: modalConf?.width ?? 720,
|
|
172
|
+
// 解决弹窗不销毁,表单远程数据没有更新的问题
|
|
173
|
+
destroyOnClose: true,
|
|
174
|
+
...modalProps,
|
|
175
|
+
};
|
|
176
|
+
|
|
142
177
|
return (
|
|
143
|
-
<
|
|
144
|
-
wrapClassName="form-dialog"
|
|
145
|
-
title={title}
|
|
146
|
-
visible={open}
|
|
147
|
-
onCancel={close}
|
|
148
|
-
onOk={onOk}
|
|
149
|
-
footer={footer}
|
|
150
|
-
{...modalProps}
|
|
151
|
-
maskClosable={modalProps.maskClosable || false}
|
|
152
|
-
width={dialogConf?.width}
|
|
153
|
-
// 解决弹窗不销毁,表单远程数据没有更新的问题
|
|
154
|
-
destroyOnClose
|
|
155
|
-
>
|
|
178
|
+
<CModal {..._modalProps}>
|
|
156
179
|
{FormSlot ? (
|
|
157
180
|
<FormSlot {...props} ref={formRef} scenario={scenario} schema={props.schema?.schema} />
|
|
158
181
|
) : (
|
|
@@ -168,7 +191,7 @@ function FormDialog(props, parentRef) {
|
|
|
168
191
|
></FormRender>
|
|
169
192
|
)}
|
|
170
193
|
<DidMount didMount={didMount} />
|
|
171
|
-
</
|
|
194
|
+
</CModal>
|
|
172
195
|
);
|
|
173
196
|
}
|
|
174
197
|
|
|
@@ -179,4 +202,4 @@ function DidMount(props) {
|
|
|
179
202
|
return null;
|
|
180
203
|
}
|
|
181
204
|
|
|
182
|
-
export default forwardRef(
|
|
205
|
+
export default forwardRef(FormModal);
|
package/src/common/utils.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { Schema } from "@formily/json-schema";
|
|
1
2
|
import _ from "lodash";
|
|
2
3
|
import dayjs from "dayjs";
|
|
3
4
|
import advancedFormat from "dayjs/plugin/advancedFormat";
|
|
@@ -23,8 +24,9 @@ const dateFormatEnum = {
|
|
|
23
24
|
* @returns
|
|
24
25
|
*/
|
|
25
26
|
export function getVal(field = {}, data = {}, opt = {}) {
|
|
27
|
+
const {} = opt;
|
|
26
28
|
let val = _.get(data, field.name);
|
|
27
|
-
const {
|
|
29
|
+
const {} = field || {};
|
|
28
30
|
const xComponent = field["x-component"];
|
|
29
31
|
const xComponentProps = field["x-component-props"] || {};
|
|
30
32
|
|
|
@@ -89,9 +91,11 @@ export function getDateVal(val, format) {
|
|
|
89
91
|
return dayjs(val).format(format);
|
|
90
92
|
}
|
|
91
93
|
|
|
92
|
-
export function getFieldList(_schema, fieldList = [], opt = {},isTableSortXIdex = false) {
|
|
94
|
+
export function getFieldList(_schema, fieldList = [], opt = {}, isTableSortXIdex = false) {
|
|
93
95
|
const schema = _schema?.schema || _schema;
|
|
94
96
|
|
|
97
|
+
// 解决 schema 字符串可执行代码、变量等
|
|
98
|
+
const properties = Schema.getOrderProperties(schema);
|
|
95
99
|
const { boxList = [] } = opt || {};
|
|
96
100
|
let _boxList = ["FormGrid", "FormGrid.GridColumn", "Card"];
|
|
97
101
|
if (Array.isArray(boxList)) {
|
|
@@ -101,10 +105,10 @@ export function getFieldList(_schema, fieldList = [], opt = {},isTableSortXIdex
|
|
|
101
105
|
}
|
|
102
106
|
|
|
103
107
|
schema?.properties &&
|
|
104
|
-
|
|
105
|
-
const field = schema
|
|
108
|
+
properties.forEach((item) => {
|
|
109
|
+
const field = item.schema;
|
|
106
110
|
if (!field.name) {
|
|
107
|
-
field.name = key;
|
|
111
|
+
field.name = item.key;
|
|
108
112
|
}
|
|
109
113
|
const componentName = field["x-component"];
|
|
110
114
|
if (_boxList.includes(componentName)) {
|
|
@@ -142,3 +146,53 @@ export function objToFormData(data) {
|
|
|
142
146
|
});
|
|
143
147
|
return formData;
|
|
144
148
|
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* 根据 val 获取对应的 options 项数据
|
|
152
|
+
* @param {string|number|boolean} val
|
|
153
|
+
* @param {Object} field
|
|
154
|
+
*/
|
|
155
|
+
export const getFieldOptItByVal = function (val, field, opt) {
|
|
156
|
+
if (!field) {
|
|
157
|
+
return {};
|
|
158
|
+
}
|
|
159
|
+
const options = Array.isArray(field.enum)
|
|
160
|
+
? field.enum
|
|
161
|
+
: Array.isArray(field.options)
|
|
162
|
+
? field.options
|
|
163
|
+
: field.dataSource || [];
|
|
164
|
+
return getOptItByVal(val, options, { fieldNames: field.fieldNames, ...opt });
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* 根据 val 获取对应的 options 项数据,并进行数据归一化处理
|
|
169
|
+
* value, label, children
|
|
170
|
+
* @param {string|number|boolean} val
|
|
171
|
+
* @param {Object} field
|
|
172
|
+
*/
|
|
173
|
+
export const getOptItByVal = function (val, options, opt) {
|
|
174
|
+
const { fieldNames } = opt || {};
|
|
175
|
+
const { value = "value", label = "label", children = "children" } = fieldNames || {};
|
|
176
|
+
for (let i = 0; i < options.length; i++) {
|
|
177
|
+
const it = options[i];
|
|
178
|
+
if (it?.[value] === val) {
|
|
179
|
+
return {
|
|
180
|
+
...it,
|
|
181
|
+
value: it?.[value],
|
|
182
|
+
label: it?.[label],
|
|
183
|
+
children: it?.[children],
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
// 递归处理
|
|
187
|
+
const _children = it?.[children];
|
|
188
|
+
if (Array.isArray(_children) && _children.length > 0) {
|
|
189
|
+
const child = getOptItByVal(val, _children, opt);
|
|
190
|
+
return {
|
|
191
|
+
...child,
|
|
192
|
+
value: child?.[value],
|
|
193
|
+
label: child?.[label],
|
|
194
|
+
children: child?.[children],
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
};
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { ReactNode } from "react";
|
|
2
|
+
import _ from "lodash";
|
|
3
|
+
|
|
4
|
+
import { getFieldOptItByVal } from "../../common/utils";
|
|
5
|
+
|
|
6
|
+
import "./index.less";
|
|
7
|
+
|
|
8
|
+
export interface IPrefixNodeProps {
|
|
9
|
+
showPrefixNode?: boolean | Object;
|
|
10
|
+
value: any;
|
|
11
|
+
field?: Object;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface ITagOption {
|
|
15
|
+
value: string | number | boolean;
|
|
16
|
+
label: string | number;
|
|
17
|
+
children?: [ITagOption];
|
|
18
|
+
color: string;
|
|
19
|
+
icon: ReactNode | string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export const defaultSwitchEnum = [
|
|
23
|
+
{
|
|
24
|
+
value: true,
|
|
25
|
+
label: "是",
|
|
26
|
+
color: "#00b42a",
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
value: false,
|
|
30
|
+
label: "否",
|
|
31
|
+
color: "#c9cdd4",
|
|
32
|
+
},
|
|
33
|
+
];
|
|
34
|
+
|
|
35
|
+
export const PrefixNode = (props) => {
|
|
36
|
+
const { value, field } = props;
|
|
37
|
+
let showVal = Array.isArray(value) ? value : [value];
|
|
38
|
+
|
|
39
|
+
const _field = { enum: defaultSwitchEnum, ...field };
|
|
40
|
+
|
|
41
|
+
return showVal?.map((it, i) => {
|
|
42
|
+
const option = getFieldOptItByVal(it, _field) || {};
|
|
43
|
+
const PreNode = option.PrefixNode;
|
|
44
|
+
console.log(it, option);
|
|
45
|
+
|
|
46
|
+
return (
|
|
47
|
+
<div key={it + "" + i} className="table-cell-prefix-box">
|
|
48
|
+
{PreNode ? (
|
|
49
|
+
<PreNode />
|
|
50
|
+
) : option.icon ? (
|
|
51
|
+
option.icon
|
|
52
|
+
) : (
|
|
53
|
+
<div className="prefix-node" style={{ backgroundColor: option.color }}></div>
|
|
54
|
+
)}
|
|
55
|
+
{option.label || it}
|
|
56
|
+
</div>
|
|
57
|
+
);
|
|
58
|
+
});
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
export default PrefixNode;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { ReactNode } from "react";
|
|
2
|
+
import { Tag } from "antd";
|
|
3
|
+
import _ from "lodash";
|
|
4
|
+
|
|
5
|
+
import { getFieldOptItByVal } from "../../common/utils";
|
|
6
|
+
|
|
7
|
+
import "./index.less";
|
|
8
|
+
|
|
9
|
+
export interface ITagsProps {
|
|
10
|
+
showTags?: boolean | Object;
|
|
11
|
+
value: any;
|
|
12
|
+
field?: Object;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface ITagOption {
|
|
16
|
+
value: string | number | boolean;
|
|
17
|
+
label: string | number;
|
|
18
|
+
children?: [ITagOption];
|
|
19
|
+
color: string;
|
|
20
|
+
icon: ReactNode | string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export const Tags = (props) => {
|
|
24
|
+
const { value, field } = props;
|
|
25
|
+
let showVal = Array.isArray(value) ? value : [value];
|
|
26
|
+
|
|
27
|
+
return showVal?.map((it, i) => {
|
|
28
|
+
const option = getFieldOptItByVal(it, field) || {};
|
|
29
|
+
return (
|
|
30
|
+
<Tag key={it + "" + i} className="table-cell-tag" icon={option.icon} color={option.color}>
|
|
31
|
+
{option.label || it}
|
|
32
|
+
</Tag>
|
|
33
|
+
);
|
|
34
|
+
});
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export default Tags;
|
package/src/list-render.jsx
CHANGED
|
@@ -9,8 +9,8 @@ import _ from "lodash";
|
|
|
9
9
|
import QueryRender from "./query-render";
|
|
10
10
|
import Pagination from "./pagination-render";
|
|
11
11
|
import TableRender from "./table-render";
|
|
12
|
-
import
|
|
13
|
-
import
|
|
12
|
+
import FormModal from "./FormModal";
|
|
13
|
+
import DetailModal from "./DetailModal";
|
|
14
14
|
|
|
15
15
|
import { objToFormData } from "./common/utils";
|
|
16
16
|
|
|
@@ -23,8 +23,17 @@ const ListRender = forwardRef(function (props, parentRef) {
|
|
|
23
23
|
queryFormIsExtendModelQuery = false,
|
|
24
24
|
queryFormInitialValues = {},
|
|
25
25
|
dialogConf = {},
|
|
26
|
+
modalConf = {},
|
|
26
27
|
/** 编辑接口使用 patch 发起请求 */
|
|
27
28
|
isPatchUpdate = false,
|
|
29
|
+
/**
|
|
30
|
+
* 表单、详情 展示模式: dialog drawer
|
|
31
|
+
*/
|
|
32
|
+
modalMode = "dialog",
|
|
33
|
+
/**
|
|
34
|
+
* 表单提交是否使用 FormData 格式
|
|
35
|
+
*/
|
|
36
|
+
useFormData: _useFormData,
|
|
28
37
|
} = props;
|
|
29
38
|
const { createText = props.createText } = i18n || {};
|
|
30
39
|
const [total, setTotal] = useState(0);
|
|
@@ -32,18 +41,20 @@ const ListRender = forwardRef(function (props, parentRef) {
|
|
|
32
41
|
const [formState, setFormState] = useState("");
|
|
33
42
|
const [rowId, setRowId] = useState(0);
|
|
34
43
|
const [listLoading, setListLoading] = useState(false);
|
|
35
|
-
const
|
|
36
|
-
const
|
|
44
|
+
const formModalRef = useRef();
|
|
45
|
+
const detailModalRef = useRef();
|
|
37
46
|
const queryRef = useRef();
|
|
38
47
|
const modelQueryRef = useRef({});
|
|
39
48
|
const formQueryRef = useRef({});
|
|
40
49
|
const paginationQueryRef = useRef({ pageNum: 1, pageSize: 10 });
|
|
50
|
+
const useFormData = _useFormData ?? dialogConf.useFormData ?? modalConf?.useFormData;
|
|
41
51
|
|
|
42
52
|
useImperativeHandle(parentRef, () => ({
|
|
43
53
|
getList,
|
|
44
54
|
onSearch,
|
|
45
55
|
forceUpdate,
|
|
46
|
-
|
|
56
|
+
formModalRef,
|
|
57
|
+
formDialogRef: formModalRef,
|
|
47
58
|
queryRef,
|
|
48
59
|
onCreate,
|
|
49
60
|
onEdit,
|
|
@@ -165,12 +176,12 @@ const ListRender = forwardRef(function (props, parentRef) {
|
|
|
165
176
|
|
|
166
177
|
function onCreate() {
|
|
167
178
|
setFormState("create");
|
|
168
|
-
|
|
179
|
+
formModalRef.current.show();
|
|
169
180
|
}
|
|
170
181
|
|
|
171
182
|
async function onCreateSubmit(data) {
|
|
172
183
|
let _data = typeof model?.createMap === "function" ? model.createMap(data) : data;
|
|
173
|
-
if (
|
|
184
|
+
if (useFormData) {
|
|
174
185
|
_data = objToFormData(_data);
|
|
175
186
|
}
|
|
176
187
|
return model
|
|
@@ -211,7 +222,7 @@ const ListRender = forwardRef(function (props, parentRef) {
|
|
|
211
222
|
|
|
212
223
|
function handleDetail(data, id) {
|
|
213
224
|
setRowId(id);
|
|
214
|
-
|
|
225
|
+
detailModalRef.current.show(data);
|
|
215
226
|
}
|
|
216
227
|
|
|
217
228
|
function onEdit(row, idx) {
|
|
@@ -239,13 +250,11 @@ const ListRender = forwardRef(function (props, parentRef) {
|
|
|
239
250
|
function handleEdit(data, id) {
|
|
240
251
|
setRowId(id);
|
|
241
252
|
setFormState("edit");
|
|
242
|
-
|
|
253
|
+
formModalRef.current.show(data, "编辑", "edit");
|
|
243
254
|
}
|
|
244
255
|
|
|
245
256
|
async function onEditSubmit(data) {
|
|
246
257
|
let _data = data;
|
|
247
|
-
console.log("isPatchUpdate", isPatchUpdate);
|
|
248
|
-
|
|
249
258
|
if (isPatchUpdate) {
|
|
250
259
|
if (model?.patchMap === "function") {
|
|
251
260
|
_data = model.patchMap(data);
|
|
@@ -254,7 +263,7 @@ const ListRender = forwardRef(function (props, parentRef) {
|
|
|
254
263
|
_data = model.updateMap(data);
|
|
255
264
|
}
|
|
256
265
|
|
|
257
|
-
if (
|
|
266
|
+
if (useFormData) {
|
|
258
267
|
_data = objToFormData(_data);
|
|
259
268
|
}
|
|
260
269
|
const request = isPatchUpdate ? model?.patch : model?.update;
|
|
@@ -287,6 +296,12 @@ const ListRender = forwardRef(function (props, parentRef) {
|
|
|
287
296
|
|
|
288
297
|
const { Slots = {} } = props;
|
|
289
298
|
|
|
299
|
+
const _modalConf = { ...props.dialogConf, ...modalConf };
|
|
300
|
+
const formProps = { ...props.dialogFormProps, ...props.modalFormProps };
|
|
301
|
+
const onFormModalClose = props.onFormDialogClose ?? props.onFormModalClose;
|
|
302
|
+
const modalProps = { ...props.dialogProps, ...props.modalProps };
|
|
303
|
+
const modalFormMount = props.dialogFormMount ?? props.modalFormMount;
|
|
304
|
+
|
|
290
305
|
return (
|
|
291
306
|
<div className={`list-render ${props.className}`}>
|
|
292
307
|
<div className={`list-header ${props.verticalHeader ? "vertical-header" : ""}`}>
|
|
@@ -357,33 +372,37 @@ const ListRender = forwardRef(function (props, parentRef) {
|
|
|
357
372
|
i18n={i18n}
|
|
358
373
|
/>
|
|
359
374
|
) : null}
|
|
360
|
-
|
|
361
|
-
|
|
375
|
+
|
|
376
|
+
<FormModal
|
|
377
|
+
ref={formModalRef}
|
|
362
378
|
schema={schema}
|
|
363
|
-
dialogConf={props.dialogConf}
|
|
364
379
|
formInitialValues={props.formInitialValues}
|
|
365
|
-
onClose={props.onFormDialogClose}
|
|
366
|
-
onSubmit={formState === "edit" ? onEditSubmit : onCreateSubmit}
|
|
367
|
-
Slots={props.Slots}
|
|
368
|
-
modalProps={props.dialogModalProps}
|
|
369
|
-
formProps={props.dialogFormProps}
|
|
370
380
|
components={props.components}
|
|
371
381
|
schemaScope={props.schemaScope}
|
|
372
|
-
|
|
382
|
+
formProps={formProps}
|
|
383
|
+
modalMode={modalMode}
|
|
384
|
+
modalConf={_modalConf}
|
|
385
|
+
onClose={onFormModalClose}
|
|
386
|
+
onSubmit={formState === "edit" ? onEditSubmit : onCreateSubmit}
|
|
387
|
+
Slots={props.Slots}
|
|
388
|
+
modalProps={modalProps}
|
|
389
|
+
modalFormMount={modalFormMount}
|
|
373
390
|
i18n={i18n}
|
|
374
391
|
/>
|
|
375
|
-
|
|
376
|
-
|
|
392
|
+
|
|
393
|
+
<DetailModal
|
|
394
|
+
ref={detailModalRef}
|
|
377
395
|
schema={schema}
|
|
378
|
-
dialogConf={props.dialogConf}
|
|
379
396
|
formInitialValues={props.formInitialValues}
|
|
380
|
-
onClose={props.onFormDialogClose}
|
|
381
|
-
Slots={props.Slots}
|
|
382
|
-
detailProps={props.dialogDetailProps}
|
|
383
|
-
modalProps={props.dialogModalProps}
|
|
384
397
|
components={props.detailComponents}
|
|
385
398
|
schemaScope={props.schemaScope}
|
|
386
|
-
|
|
399
|
+
detailProps={props.dialogDetailProps || props.modalDetailProps}
|
|
400
|
+
modalMode={modalMode}
|
|
401
|
+
modalConf={_modalConf}
|
|
402
|
+
onClose={onFormModalClose}
|
|
403
|
+
Slots={props.Slots}
|
|
404
|
+
modalProps={modalProps}
|
|
405
|
+
modalFormMount={modalFormMount}
|
|
387
406
|
/>
|
|
388
407
|
</div>
|
|
389
408
|
);
|
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
import { useEffect, useState, useMemo, useCallback, useRef } from "react";
|
|
2
2
|
import { Table, Button, Popconfirm, Tooltip } from "antd";
|
|
3
|
+
import { Schema } from "@formily/json-schema";
|
|
3
4
|
import { QuestionCircleOutlined } from "@ant-design/icons";
|
|
5
|
+
import _ from "lodash";
|
|
4
6
|
|
|
5
7
|
import { FormilyField } from "../components/Formily/FormilyField";
|
|
8
|
+
import Tags from "../components/Tags";
|
|
9
|
+
import PrefixNode from "../components/PrefixNode";
|
|
6
10
|
// getColRender 使用 observer 包裹一层 column 的 render 函数,解决数据无法响应的问题
|
|
7
11
|
import { getColRender } from "../common/formily-utils";
|
|
8
12
|
import { getVal, getFieldList } from "../common/utils";
|
|
@@ -15,7 +19,7 @@ const scenario = "table-render";
|
|
|
15
19
|
function TableRender(props) {
|
|
16
20
|
const { config = {}, query = {}, i18n } = props;
|
|
17
21
|
const { tableDel = "删除", tableEdit = "编辑", tableDelTip = "确认删除该项?", tableDetail = "详情" } = i18n || {};
|
|
18
|
-
const { orderColType, orderColWidth, tableEmptyValue, isTableSortXIdex = false} = config || {};
|
|
22
|
+
const { orderColType, orderColWidth, tableEmptyValue, isTableSortXIdex = false } = config || {};
|
|
19
23
|
const [columns, setColumns] = useState([]);
|
|
20
24
|
|
|
21
25
|
const formilyRef = useRef({
|
|
@@ -58,6 +62,7 @@ function TableRender(props) {
|
|
|
58
62
|
const fieldSchemas = formilyRef.current?.fields;
|
|
59
63
|
if (field.inTable !== false) {
|
|
60
64
|
const { name, title } = field;
|
|
65
|
+
const comName = field["x-component"];
|
|
61
66
|
|
|
62
67
|
const decoratorProps = field["x-decorator-props"] || {};
|
|
63
68
|
let _title = title;
|
|
@@ -93,11 +98,13 @@ function TableRender(props) {
|
|
|
93
98
|
};
|
|
94
99
|
} else {
|
|
95
100
|
colRender = function (text, record, index, ...args) {
|
|
96
|
-
const { width, ellipsis, emptyValue } = _colConf || {};
|
|
101
|
+
const { width, ellipsis, emptyValue, showTags, showPrefixNode } = _colConf || {};
|
|
97
102
|
const schemaDefaultValue = fieldSchemas[name]?.componentProps?.emptyValue;
|
|
98
103
|
const defaultValue = emptyValue ?? schemaDefaultValue ?? tableEmptyValue ?? "";
|
|
99
104
|
|
|
100
|
-
let val = getVal({ ...field, ...fieldSchemas[name] }, record, {
|
|
105
|
+
let val = getVal({ ...field, ...fieldSchemas[name] }, record, {
|
|
106
|
+
fieldSchema: fieldSchemas?.[name],
|
|
107
|
+
});
|
|
101
108
|
|
|
102
109
|
if (val === "" || val === undefined || val === null) {
|
|
103
110
|
val = defaultValue;
|
|
@@ -115,13 +122,37 @@ function TableRender(props) {
|
|
|
115
122
|
</Tooltip>
|
|
116
123
|
);
|
|
117
124
|
}
|
|
125
|
+
// 使用 tag 渲染内容
|
|
126
|
+
if (showTags) {
|
|
127
|
+
// field 通过 fieldSchemas 获取最新的 field 数据
|
|
128
|
+
return (
|
|
129
|
+
<Tags
|
|
130
|
+
value={_.get(record, name)}
|
|
131
|
+
config={typeof showTags === "object" ? showTags : undefined}
|
|
132
|
+
field={{ ...field, ...fieldSchemas?.[name] }}
|
|
133
|
+
/>
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
// 使用 前缀节点 渲染内容
|
|
137
|
+
if (showPrefixNode || (showPrefixNode !== false && comName === "Switch")) {
|
|
138
|
+
// field 通过 fieldSchemas 获取最新的 field 数据
|
|
139
|
+
return (
|
|
140
|
+
<PrefixNode
|
|
141
|
+
value={_.get(record, name)}
|
|
142
|
+
config={typeof showPrefixNode === "object" ? showPrefixNode : undefined}
|
|
143
|
+
field={{ ...field, ...fieldSchemas?.[name] }}
|
|
144
|
+
/>
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
|
|
118
148
|
return content;
|
|
119
149
|
};
|
|
120
150
|
}
|
|
121
151
|
|
|
122
152
|
columns.push({
|
|
123
153
|
..._colConf,
|
|
124
|
-
onCell: (record, rowIndex) =>
|
|
154
|
+
onCell: (record, rowIndex) =>
|
|
155
|
+
_colConf?.onCell?.({ ...record, _field: { ...field, ...(fieldSchemas?.[name] || {}) } }, rowIndex) || {},
|
|
125
156
|
title: _title,
|
|
126
157
|
key: name,
|
|
127
158
|
dataIndex: name,
|
|
@@ -153,6 +184,7 @@ function TableRender(props) {
|
|
|
153
184
|
//详情按钮权限
|
|
154
185
|
const _hasDetail =
|
|
155
186
|
hasDetail && typeof hasDetail === "function" ? hasDetail(record, index) : hasDetail !== false;
|
|
187
|
+
console.log("_hasDetail", hasDetail, _hasDetail);
|
|
156
188
|
|
|
157
189
|
//删除按钮权限
|
|
158
190
|
const _hasDel = hasDel && typeof hasDel === "function" ? hasDel(record, index) : hasDel !== false;
|