@hzab/list-render 1.8.2 → 1.8.3-beta2

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 CHANGED
@@ -1,3 +1,9 @@
1
+ # @hzab/list-render@1.9.0
2
+
3
+ feat: 列表渲染支持 tag 模式、前缀 Node (前缀圆点)
4
+ feat: Switch 渲染模式优化,使用 前缀 Node (前缀圆点)
5
+ feat: 新增、编辑、详情弹窗支持抽屉模式
6
+
1
7
  # @hzab/list-render@1.8.2
2
8
 
3
9
  fix: query schema props scope 模式问题修复
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
- | dialogConf | Object | | {} | dialog 配置对象 |
78
- | dialogDetailProps | Object | | {} | dialog descriptions 配置对象 |
79
- | dialogFormProps | Object | | {} | dialog fromRender 配置对象 |
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
- | onFormDialogClose | Function | | - | 表单弹窗关闭回调 |
93
- | dialogFormMount | Function | | - | 新增、编辑弹窗 Form 渲染完成回调 |
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 | Object | | {} | 指定各列的配置(比如列宽),key 为字段的 name。可以指定名为 “\_$actions”的字段来设置“操作”列 |
106
- | rowSelection | Object | | {} | 选择功能的配置。参考 antd table rowSelection 参数 |
107
- | scroll | Object | | {} | 表格是否可滚动,也可以指定滚动区域的宽、高。参考 antd table scroll 参数 |
108
- | expandable | Object | | {} | 配置展开属性。参考 antd table expandable 参数 |
109
- | onRow | Object | | {} | 设置行属性。参考 antd table onRow 参数 |
110
- | orderColType | string | | - | 序号列数据类型:page(按当前页的序号)、all(按所有页的序号) |
111
- | orderColWidth | string/number | | - | 序号列 width 的参数 |
112
- | tableEmptyValue | string/number | | undefined | table 列表空值展示数 |
113
- | isTableSortXIdex | Boolean | | undefined | table 列表列排序是否按照 x-index 排序 |
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 | query | 重置页码至 1,并刷新列表 |
229
- | getList | query | 获取当前页列表数据 |
230
- | forceUpdate | - | 强制重渲染列表,解决枚举数据渲染不正常的问题 |
231
- | formDialogRef | - | 新增、编辑 弹窗 form-dialog 的 ref |
232
- | queryRef | - | 筛选条件 query-render 的 ref |
233
- | onCreate | - | 手动触发新增按钮相关操作 |
234
- | onEdit | row | 手动触发编辑按钮相关操作 |
235
- | onDel | row | 手动触发删除按钮相关操作 |
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.2",
3
+ "version": "1.8.3-beta2",
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.5.0",
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.0.12",
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);
@@ -0,0 +1,2 @@
1
+ .detail-modal {
2
+ }
@@ -0,0 +1,8 @@
1
+ .form-modal {
2
+ .ant-drawer-footer,
3
+ ant-modal-footer {
4
+ .ant-btn + .ant-btn {
5
+ margin-left: 12px;
6
+ }
7
+ }
8
+ }
@@ -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
- function FormDialog(props, parentRef) {
12
- const { Slots = {}, dialogConf = {}, modalProps = {} } = props;
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 (dialogConf.beforeSubmit) {
52
- const isContinue = await dialogConf.beforeSubmit(submitForm, {
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 (dialogConf?.footer) {
102
- footer = typeof dialogConf?.footer === "function" ? dialogConf.footer({ ...options, options }) : dialogConf?.footer;
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.dialogFooterPre) {
107
- footer.push(<Slots.dialogFooterPre key="pre" options={options} />);
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
- {dialogConf.cancelText || "取 消"}
129
+ {modalConf.cancelText || "取 消"}
113
130
  </Button>,
114
131
  );
115
132
 
116
- if (Slots.dialogFooterCenter) {
117
- footer.push(<Slots.dialogFooterCenter key="center" options={options} />);
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
- {dialogConf.okText || "确 定"}
139
+ {modalConf.okText || "确 定"}
123
140
  </Button>,
124
141
  );
125
142
 
126
- if (Slots.dialogFooterSuffix) {
127
- footer.push(<Slots.dialogFooterSuffix key="suffix" options={options} />);
143
+ if (Slots.modalFooterSuffix) {
144
+ footer.push(<Slots.modalFooterSuffix key="suffix" options={options} />);
128
145
  }
129
146
  }
130
147
 
@@ -132,29 +149,35 @@ function FormDialog(props, parentRef) {
132
149
  * 解决 show 函数中 formRef.current 为 undefined 的问题
133
150
  */
134
151
  function didMount() {
135
- props.dialogFormMount && props.dialogFormMount();
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
- <Modal
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
- <FormSlot {...props} ref={formRef} scenario={scenario} schema={props.schema?.schema} />
180
+ <FormSlot {...props} formRef={formRef} scenario={scenario} schema={props.schema?.schema} />
158
181
  ) : (
159
182
  <FormRender
160
183
  {...props.formProps}
@@ -168,7 +191,7 @@ function FormDialog(props, parentRef) {
168
191
  ></FormRender>
169
192
  )}
170
193
  <DidMount didMount={didMount} />
171
- </Modal>
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(FormDialog);
205
+ export default forwardRef(FormModal);
@@ -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 { } = field || {};
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
- Object.keys(schema?.properties).forEach((key) => {
105
- const field = schema?.properties[key];
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,9 @@
1
+ .table-cell-prefix-box {
2
+ .prefix-node {
3
+ display: inline-block;
4
+ width: 8px;
5
+ height: 8px;
6
+ margin-right: 8px;
7
+ border-radius: 50%;
8
+ }
9
+ }
@@ -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,3 @@
1
+ .table-cell-tag {
2
+ margin-bottom: 8px;
3
+ }
@@ -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;
@@ -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 FormDialog from "./form-dialog";
13
- import DetailDialog from "./detail-dialog";
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 formDialogRef = useRef();
36
- const detailDialogRef = useRef();
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
- formDialogRef,
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
- formDialogRef.current.show();
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 (dialogConf.useFormData) {
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
- detailDialogRef.current.show(data);
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
- formDialogRef.current.show(data, "编辑", "edit");
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 (dialogConf.useFormData) {
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
- <FormDialog
361
- ref={formDialogRef}
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
- dialogFormMount={props.dialogFormMount}
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
- <DetailDialog
376
- ref={detailDialogRef}
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
- dialogFormMount={props.dialogFormMount}
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, { fieldSchema: fieldSchemas?.[name] });
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) => (_colConf?.onCell?.({...record, _field: { ...field, ...(fieldSchemas?.[name] || {}) }}, 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;
@@ -1,2 +0,0 @@
1
- .form-dialog {
2
- }