@hzab/list-render 0.0.1

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.
@@ -0,0 +1,240 @@
1
+ /*
2
+ model 不要定义在组件外部,避免出现 query 异常的情况。
3
+ - 异常情况:model 定义在组件函数外部(页面文件最外层),设置搜索条件,切换到其他页面后切换回来,上次设置的搜索条件还存在。
4
+ */
5
+ import { forwardRef, useEffect, useImperativeHandle, useRef, useState } from "react";
6
+ import { Button, message } from "antd";
7
+ import _ from "lodash";
8
+
9
+ import QueryRender from "./query-render";
10
+ import Pagination from "./pagination-render";
11
+ import TableRender from "./table-render";
12
+ import FormDialog from "./form-dialog";
13
+
14
+ import "./index.less";
15
+
16
+ const ListRender = forwardRef(function (props, parentRef) {
17
+ const { idKey = "id" } = props;
18
+ const [total, setTotal] = useState(0);
19
+ const [list, setList] = useState([]);
20
+ const [listLoading, setListLoading] = useState(false);
21
+ const formDialogRef = useRef();
22
+ const queryRef = useRef();
23
+
24
+ useImperativeHandle(parentRef, () => ({
25
+ getList,
26
+ onSearch,
27
+ forceUpdate,
28
+ formDialogRef,
29
+ queryRef,
30
+ }));
31
+
32
+ const { schema = {}, config = {}, model = {} } = props;
33
+
34
+ useEffect(() => {
35
+ if (model) {
36
+ if (!model.query) {
37
+ model.query = {};
38
+ }
39
+ model.query.pageNum = 1;
40
+ }
41
+ !props.closeAutoRequest && getList();
42
+ }, []);
43
+
44
+ useEffect(() => {
45
+ if (model && !model?.query) {
46
+ model.query = {};
47
+ }
48
+ }, [model?.query]);
49
+
50
+ function getList(query = model?.query || {}) {
51
+ if (!model?.getList && Array.isArray(props.list)) {
52
+ setListLoading(true);
53
+ const { list } = props;
54
+ const { pageNum = 1, pageSize = 10 } = model?.query || {};
55
+ setList(list.slice(pageSize * (pageNum - 1), pageNum * pageSize));
56
+ setTotal(list.length);
57
+ props.onGetListEnd && props.onGetListEnd({ list, pagination: { pageNum, pageSize } });
58
+ setListLoading(false);
59
+ return;
60
+ }
61
+ if (!model?.getList) {
62
+ return;
63
+ }
64
+ setListLoading(true);
65
+
66
+ // remove $timerange
67
+ const _q = _.cloneDeep(query);
68
+ if (_q.$timerange !== undefined) {
69
+ delete _q.$timerange;
70
+ }
71
+
72
+ model
73
+ ?.getList(_q)
74
+ .then((res) => {
75
+ console.log("list res", res);
76
+ setList(res.list);
77
+ setTotal(res.pagination?.total);
78
+ props.onGetListEnd && props.onGetListEnd(res);
79
+ setListLoading(false);
80
+ })
81
+ .catch((err) => {
82
+ console.log(err);
83
+ message.error(err._message || "未知错误");
84
+ setListLoading(false);
85
+ });
86
+ }
87
+
88
+ function onPageChange(page, size) {
89
+ if (model && !model.query) {
90
+ model.query = {};
91
+ }
92
+ model.query.pageNum = page;
93
+ model.query.pageSize = size;
94
+ getList();
95
+ }
96
+
97
+ function onSearch(query) {
98
+ if (model && !model.query) {
99
+ model.query = {};
100
+ }
101
+ model.query.pageNum = 1;
102
+ if (model && model.query && !model.query.pageSize) {
103
+ model.query.pageSize = 10;
104
+ }
105
+ model.query = Object.assign(model.query, query);
106
+ getList(query);
107
+ }
108
+
109
+ function forceUpdate() {
110
+ setList((l) => _.cloneDeep(l));
111
+ }
112
+
113
+ function onCreate() {
114
+ console.log("onCreate");
115
+ formDialogRef.current.show().then(async (form) => {
116
+ console.log("onCreate", form);
117
+ const data = form;
118
+
119
+ model
120
+ ?.create(typeof model?.createMap === "function" ? model.createMap(data) : data)
121
+ .then((res) => {
122
+ onSearch();
123
+ message.success(res._message || "新增成功");
124
+ })
125
+ .catch((err) => {
126
+ console.error("err", err);
127
+ message.error(err._message || "未知错误");
128
+ });
129
+ });
130
+ }
131
+
132
+ function onEdit(row, idx) {
133
+ if (props.fetchOnEdit !== false) {
134
+ model
135
+ .get({ id: row[idKey] })
136
+ .then((res) => {
137
+ handleEdit(res, row[idKey]);
138
+ })
139
+ .catch((err) => {
140
+ console.error("err", err);
141
+ message.error(err._message || "未知错误");
142
+ });
143
+ } else {
144
+ handleEdit(row);
145
+ }
146
+ }
147
+
148
+ function handleEdit(data, id) {
149
+ formDialogRef.current.show(data, "编辑").then(async (form) => {
150
+ const data = form;
151
+ model
152
+ ?.update(typeof model?.updateMap === "function" ? model.updateMap(data) : data, { id })
153
+ .then((res) => {
154
+ getList();
155
+ message.success(res?._message || "编辑成功");
156
+ })
157
+ .catch((err) => {
158
+ console.error("err", err);
159
+ message.error(err._message || "未知错误");
160
+ });
161
+ });
162
+ }
163
+
164
+ function onDel(row, idx) {
165
+ model
166
+ ?.delete({ id: row[idKey] })
167
+ .then((res) => {
168
+ message.success(res._message || "删除成功");
169
+ onSearch();
170
+ })
171
+ .catch((err) => {
172
+ message.error(err._message || "未知错误");
173
+ });
174
+ }
175
+
176
+ const { Slots = {} } = props;
177
+
178
+ return (
179
+ <div className={`list-render ${props.className}`}>
180
+ <div className="list-header">
181
+ {props.hasQuery !== false ? (
182
+ <QueryRender
183
+ ref={queryRef}
184
+ schema={props.schema}
185
+ formConf={props.formConf}
186
+ search={props.search}
187
+ filters={props.filters}
188
+ config={props.queryConf}
189
+ onSearch={onSearch}
190
+ schemaScope={props.schemaScope}
191
+ xComponents={props.xComponents}
192
+ />
193
+ ) : (
194
+ <div className="query-render"></div>
195
+ )}
196
+ <div className="header-actions-render">
197
+ {Slots.headerActionPrefix && <Slots.headerActionPrefix />}
198
+ {props.hasCreate !== false ? (
199
+ <Button onClick={onCreate} type="primary">
200
+ 新增
201
+ </Button>
202
+ ) : null}
203
+ {Slots.headerActionSuffix && <Slots.headerActionSuffix />}
204
+ </div>
205
+ </div>
206
+ <TableRender
207
+ idKey={idKey}
208
+ schema={props.schema?.schema}
209
+ list={list}
210
+ formConf={props.formConf}
211
+ config={props.tableConf}
212
+ hasAction={props.hasAction}
213
+ Slots={props.Slots}
214
+ onEdit={onEdit}
215
+ onDel={onDel}
216
+ loading={listLoading}
217
+ />
218
+ <Pagination onChange={onPageChange} total={total} query={model?.query} config={props.paginationConf} />
219
+ <FormDialog
220
+ ref={formDialogRef}
221
+ schema={schema}
222
+ dialogConf={props.dialogConf}
223
+ formConf={props.formConf}
224
+ formSlots={props.formSlots}
225
+ formInitialValues={props.formInitialValues}
226
+ Slots={props.Slots}
227
+ xComponents={props.xComponents}
228
+ schemaScope={props.schemaScope}
229
+ />
230
+ </div>
231
+ );
232
+ });
233
+
234
+ ListRender.defaultProps = {
235
+ model: {
236
+ query: {},
237
+ },
238
+ };
239
+
240
+ export default ListRender;
@@ -0,0 +1,55 @@
1
+ import { useEffect, useState } from "react";
2
+ import { Pagination } from "antd";
3
+
4
+ import "./index.less";
5
+
6
+ const pageSizeOptions = [10, 20, 50, 100];
7
+
8
+ function PaginationRender(props) {
9
+ const [pageSize, setPageSize] = useState(
10
+ props.query?.pageSize || (props.pageSizeOptions ? props.pageSizeOptions[0] : pageSizeOptions[0]),
11
+ );
12
+ const [pageNum, setPageNumber] = useState(props.query?.pageNum || 1);
13
+
14
+ useEffect(() => {
15
+ const { query } = props;
16
+ if (!query) {
17
+ return;
18
+ }
19
+ if (query.pageSize && query.pageSize !== pageSize) {
20
+ setPageSize(query.pageSize);
21
+ }
22
+ if (query.pageNum && query.pageNum !== pageNum) {
23
+ setPageNumber(query.pageNum);
24
+ }
25
+ }, [props.query?.pageNum, props.query?.pageSize]);
26
+
27
+ function onChange(page, size) {
28
+ let _size = size;
29
+ let _page = page;
30
+ if (pageSize !== size) {
31
+ _page = 1;
32
+ }
33
+ setPageSize(_size);
34
+ setPageNumber(_page);
35
+ props.onChange && props.onChange(_page, _size);
36
+ }
37
+
38
+ return (
39
+ <div className="pagination-render-wrap">
40
+ <Pagination
41
+ {...(props.config || {})}
42
+ className="pagination-render"
43
+ current={pageNum}
44
+ pageSize={pageSize}
45
+ onChange={onChange}
46
+ pageSizeOptions={props.pageSizeOptions || pageSizeOptions}
47
+ total={props.total}
48
+ showTotal={(total) => `总条数 ${total} 条`}
49
+ showSizeChanger
50
+ />
51
+ </div>
52
+ );
53
+ }
54
+
55
+ export default PaginationRender;
@@ -0,0 +1,11 @@
1
+ .pagination-render-wrap {
2
+ display: flex;
3
+ justify-content: flex-end;
4
+ align-items: center;
5
+ padding-top: 20px;
6
+ .pagination-render {
7
+ li {
8
+ margin-bottom: 8px;
9
+ }
10
+ }
11
+ }
@@ -0,0 +1,140 @@
1
+ import { useState, useRef, useEffect, useImperativeHandle, forwardRef } from "react";
2
+ import { Button } from "antd";
3
+ import dayjs from "dayjs";
4
+ import _ from "lodash";
5
+
6
+ import FormRender from "@hzab/form-render";
7
+
8
+ import "./index.less";
9
+
10
+ function QueryRender(props, parentRef) {
11
+ const [schema, setSchema] = useState({});
12
+ const formRef = useRef();
13
+
14
+ useImperativeHandle(parentRef, () => ({ formRef }));
15
+
16
+ useEffect(() => {
17
+ const queryProperties = {};
18
+ let index = 0;
19
+ if (props.search) {
20
+ queryProperties.search = {
21
+ type: "string",
22
+ title: "",
23
+ "x-decorator": "FormItem",
24
+ "x-component": "Input",
25
+ "x-validator": [],
26
+ "x-component-props": {
27
+ allowClear: true,
28
+ placeholder: props.search,
29
+ },
30
+ "x-decorator-props": {},
31
+ "x-designable-id": "searchInput",
32
+ "x-index": index,
33
+ name: "name",
34
+ description: "",
35
+ };
36
+ index += 1;
37
+ }
38
+
39
+ const { properties = {} } = props.schema?.schema || {};
40
+ console.log("props.filters", props.filters);
41
+ props.filters?.forEach((key) => {
42
+ const item = properties[key];
43
+ if (item) {
44
+ // TODO: 优化
45
+ const itemConf = {
46
+ ...item,
47
+ required: false,
48
+ "x-index": index,
49
+ };
50
+ if (!itemConf["x-component-props"]) {
51
+ itemConf["x-component-props"] = {};
52
+ }
53
+ itemConf["x-component-props"].allowClear = true;
54
+ queryProperties[key] = itemConf;
55
+ index += 1;
56
+ } else if (key === "$timerange") {
57
+ queryProperties[key] = {
58
+ type: "string[]",
59
+ title: "时间范围",
60
+ "x-decorator": "FormItem",
61
+ "x-component": "DatePicker.RangePicker",
62
+ "x-validator": [],
63
+ "x-component-props": {
64
+ picker: "date",
65
+ showTime: true,
66
+ allowClear: true,
67
+ },
68
+ "x-decorator-props": {},
69
+ name: key,
70
+ "x-designable-id": key,
71
+ "x-index": index,
72
+ };
73
+ index += 1;
74
+ }
75
+ });
76
+
77
+ console.log("queryProperties", queryProperties);
78
+
79
+ setSchema({
80
+ form: {},
81
+ schema: {
82
+ type: "object",
83
+ properties: queryProperties,
84
+ "x-designable-id": "querySchema",
85
+ },
86
+ });
87
+ }, [props.search, props.filters, props.schema, props.schema?.schema]);
88
+
89
+ function onSearch() {
90
+ let query = _.cloneDeep(formRef?.current?.formRender?.values);
91
+ if (props.filters?.includes("$timerange")) {
92
+ console.log("query.$timerange?.[0]", query.$timerange?.[0]);
93
+ if (query.$timerange?.[0] instanceof dayjs) {
94
+ query.beginTime = query.$timerange?.[0]?.format("YYYY-MM-DD HH:mm:ss");
95
+ query.endTime = query.$timerange?.[1]?.format("YYYY-MM-DD HH:mm:ss");
96
+ } else {
97
+ query.beginTime = query.$timerange?.[0];
98
+ query.endTime = query.$timerange?.[1];
99
+ }
100
+ delete query.$timerange;
101
+ }
102
+ if (props.config?.queryMap) {
103
+ query = props.config?.queryMap(query);
104
+ }
105
+ props.onSearch && props.onSearch(query);
106
+ }
107
+
108
+ function onReset() {
109
+ formRef.current?.formRender?.reset();
110
+ props.onSearch && props.onSearch(_.cloneDeep(formRef?.current?.formRender?.values));
111
+ }
112
+
113
+ return (
114
+ <div className="query-render">
115
+ {Object.keys(schema?.schema?.properties || {}).length > 0 ? (
116
+ <>
117
+ <FormRender
118
+ ref={formRef}
119
+ layout="inline"
120
+ schema={schema}
121
+ schemaScope={props.schemaScope}
122
+ Slots={() => {
123
+ return (
124
+ <div className="query-operation">
125
+ {/* <Button onClick={onReset}>重置</Button> */}
126
+ <Button type="primary" onClick={onSearch}>
127
+ 搜索
128
+ </Button>
129
+ </div>
130
+ );
131
+ }}
132
+ xComponents={props.xComponents}
133
+ ></FormRender>
134
+ </>
135
+ ) : null}
136
+ </div>
137
+ );
138
+ }
139
+
140
+ export default forwardRef(QueryRender);
@@ -0,0 +1,22 @@
1
+ .query-render {
2
+ .xxm {
3
+ display: inline-block;
4
+ }
5
+ .form-render {
6
+ display: inline-block;
7
+ }
8
+ .ant-form-inline,
9
+ .ant-form-inline > form {
10
+ display: inline-flex;
11
+ .ant-formily-item-control {
12
+ min-width: 120px;
13
+ }
14
+ .ant-picker-range {
15
+ min-width: 340px;
16
+ }
17
+ .ant-formily-item-layout-inline {
18
+ margin-right: 12px;
19
+ margin-bottom: 12px;
20
+ }
21
+ }
22
+ }
@@ -0,0 +1,163 @@
1
+ import { useEffect, useState } from "react";
2
+ import { Table, Button, Popconfirm } from "antd";
3
+
4
+ import { getVal } from "../common/utils";
5
+
6
+ import "./index.less";
7
+
8
+ function TableRender(props) {
9
+ const { config = {} } = props;
10
+ const [columns, setColumns] = useState([]);
11
+
12
+ useEffect(() => {
13
+ if (!(props.schema && props.schema.properties)) {
14
+ return;
15
+ }
16
+ const columns = [];
17
+ /*
18
+ title: "姓名",
19
+ dataIndex: "name",
20
+ key: "name",
21
+ render: function(text, record, index) {},
22
+ width: 'string | number',
23
+ */
24
+ // TODO: 确认是否区分 函数、ReactDom 插槽
25
+ const { Slots = {} } = props;
26
+ const { properties = {} } = props.schema;
27
+ Object.keys(properties).forEach((key) => {
28
+ const field = properties[key];
29
+ if (field.inTable !== false) {
30
+ const { name, title, type } = field;
31
+ let _colConf = {};
32
+
33
+ if (props.config?.colConf && props.config?.colConf[name]) {
34
+ _colConf = props.config?.colConf[name];
35
+ }
36
+
37
+ if (Slots && Slots[name]) {
38
+ columns.push({
39
+ ..._colConf,
40
+ title: title,
41
+ key: name,
42
+ dataIndex: name,
43
+ render: (text, record, index) => {
44
+ const Slot = Slots[name];
45
+ const slotProps = { text, record, index, field };
46
+ return <Slot {...slotProps} />;
47
+ },
48
+ });
49
+ return;
50
+ }
51
+
52
+ let render = function (text, record) {
53
+ return getVal(field, record);
54
+ };
55
+
56
+ if (type === "date-picker" && field.mode === "datetime") {
57
+ render = function (text, record) {
58
+ const dateArr = getVal(field, record)?.split(" ") || [];
59
+ return (
60
+ <>
61
+ <div>{dateArr[0]}</div>
62
+ <div>{dateArr[1]}</div>
63
+ </>
64
+ );
65
+ };
66
+ }
67
+
68
+ columns.push({
69
+ ..._colConf,
70
+ title: title,
71
+ key: name,
72
+ dataIndex: name,
73
+ render(text, record, index, ...args) {
74
+ return (
75
+ <div
76
+ className={`${_colConf?.ellipsis ? "table-cell-ellipsis" : ""}`}
77
+ style={{ width: _colConf?.width, maxWidth: "100%" }}
78
+ title={
79
+ _colConf?.ellipsis === true || _colConf?.ellipsis?.showTitle
80
+ ? render(text, record, index, ...args)
81
+ : undefined
82
+ }
83
+ >
84
+ {render(text, record, index, ...args)}
85
+ </div>
86
+ );
87
+ },
88
+ });
89
+ }
90
+ });
91
+
92
+ if (props.hasAction !== false) {
93
+ const { hasEdit, hasDel } = props.config || {};
94
+
95
+ const _colConf = props.config?.colConf?._$actions || {};
96
+
97
+ columns.push({
98
+ ..._colConf,
99
+ title: "操作",
100
+ key: "_$actions",
101
+ render(text, record, index) {
102
+ const slotProps = { text, record, index };
103
+
104
+ if (Slots?.tableActionsSlot) {
105
+ return <Slots.tableActionsSlot {...slotProps} />;
106
+ }
107
+
108
+ return (
109
+ <div style={{ width: _colConf?.width, maxWidth: "100%" }}>
110
+ {Slots?.actionPrefixSlot && <Slots.actionPrefixSlot {...slotProps} />}
111
+ {hasEdit !== false ? (
112
+ <Button
113
+ type="link"
114
+ onClick={() => {
115
+ props.onEdit && props.onEdit(record, index);
116
+ }}
117
+ >
118
+ 编辑
119
+ </Button>
120
+ ) : null}
121
+ {Slots?.actionCenterSlot && <Slots.actionCenterSlot {...slotProps} />}
122
+ {hasDel !== false ? (
123
+ <Popconfirm
124
+ placement="topRight"
125
+ title={"确认删除该项?"}
126
+ onConfirm={() => {
127
+ props.onDel && props.onDel(record, index);
128
+ }}
129
+ >
130
+ <Button type="link" danger>
131
+ 删除
132
+ </Button>
133
+ </Popconfirm>
134
+ ) : null}
135
+ {Slots?.actionSuffixSlot && <Slots.actionSuffixSlot {...slotProps} />}
136
+ </div>
137
+ );
138
+ },
139
+ });
140
+ }
141
+
142
+ setColumns(columns);
143
+ }, [props.schema]);
144
+
145
+ return (
146
+ <div className="table-render-wrap">
147
+ <Table
148
+ className="table-render"
149
+ rowKey={props.idKey || "id"}
150
+ rowSelection={config?.rowSelection}
151
+ columns={columns}
152
+ dataSource={props.list}
153
+ pagination={false}
154
+ scroll={config.scroll}
155
+ expandable={config.expandable}
156
+ loading={props.loading}
157
+ onRow={config?.onRow}
158
+ />
159
+ </div>
160
+ );
161
+ }
162
+
163
+ export default TableRender;
@@ -0,0 +1,10 @@
1
+ .table-render-wrap {
2
+ .table-render {
3
+ border-top: 1px solid #f0f0f0;
4
+ }
5
+ .table-cell-ellipsis {
6
+ overflow: hidden;
7
+ white-space: nowrap;
8
+ text-overflow: ellipsis;
9
+ }
10
+ }