@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.
package/package.json ADDED
@@ -0,0 +1,71 @@
1
+ {
2
+ "name": "@hzab/list-render",
3
+ "version": "0.0.1",
4
+ "description": "",
5
+ "main": "lib",
6
+ "scripts": {
7
+ "dev": "webpack serve -c ./config/webpack.config.js --env development",
8
+ "build": "webpack -c ./config/webpack.config.js --env production"
9
+ },
10
+ "files": [
11
+ "lib",
12
+ "src"
13
+ ],
14
+ "keywords": [],
15
+ "author": "CaiYansong",
16
+ "license": "ISC",
17
+ "devDependencies": {
18
+ "@babel/core": "^7.20.5",
19
+ "@babel/plugin-proposal-class-properties": "^7.18.6",
20
+ "@babel/plugin-proposal-decorators": "^7.20.7",
21
+ "@babel/plugin-transform-runtime": "^7.19.6",
22
+ "@babel/preset-env": "^7.20.2",
23
+ "@babel/preset-react": "^7.18.6",
24
+ "@babel/preset-typescript": "^7.18.6",
25
+ "@babel/runtime": "^7.20.6",
26
+ "@types/react": "^18.0.26",
27
+ "@types/react-dom": "^18.0.9",
28
+ "antd": "^4.14.0",
29
+ "autoprefixer": "^10.4.13",
30
+ "babel-loader": "^9.1.0",
31
+ "clean-webpack-plugin": "^4.0.0",
32
+ "copy-webpack-plugin": "^11.0.0",
33
+ "css-loader": "^6.7.3",
34
+ "css-minimizer-webpack-plugin": "^4.2.2",
35
+ "eslint": "^8.30.0",
36
+ "eslint-webpack-plugin": "^3.2.0",
37
+ "file-loader": "^6.2.0",
38
+ "fork-ts-checker-webpack-plugin": "^7.2.14",
39
+ "html-webpack-plugin": "^5.5.0",
40
+ "less": "^4.1.3",
41
+ "less-loader": "^11.1.0",
42
+ "mini-css-extract-plugin": "^2.7.2",
43
+ "postcss-loader": "^7.0.2",
44
+ "react": "^18.2.0",
45
+ "react-dom": "^18.2.0",
46
+ "react-router-dom": "^6.8.1",
47
+ "style-loader": "^3.3.1",
48
+ "terser-webpack-plugin": "^5.3.6",
49
+ "typescript": "^4.9.4",
50
+ "webpack": "^5.75.0",
51
+ "webpack-cli": "^5.0.1",
52
+ "webpack-dev-server": "^4.11.1",
53
+ "webpackbar": "^5.0.2"
54
+ },
55
+ "peerDependencies": {
56
+ "antd": "<=4.22.8",
57
+ "react": ">=16.8.0",
58
+ "react-dom": ">=16.8.0"
59
+ },
60
+ "dependencies": {
61
+ "@formily/antd": "^2.2.20",
62
+ "@formily/core": "^2.2.20",
63
+ "@formily/react": "^2.2.20",
64
+ "@hzab/form-render": "^0.0.1",
65
+ "axios": "^1.4.0",
66
+ "lodash": "^4.17.21"
67
+ },
68
+ "directories": {
69
+ "lib": "lib"
70
+ }
71
+ }
@@ -0,0 +1,219 @@
1
+ import _ from "lodash";
2
+ import axios from "axios";
3
+
4
+ class DataModel {
5
+ constructor(params) {
6
+ const {
7
+ ctx,
8
+ query,
9
+ createApi,
10
+ createMap,
11
+ getApi,
12
+ getMap,
13
+ getListApi,
14
+ getListMap,
15
+ getListFunc,
16
+ updateApi,
17
+ updateMap,
18
+ deleteApi,
19
+ multipleDeleteApi,
20
+ axios: as,
21
+ axiosConf,
22
+ } = params;
23
+
24
+ this.ctx = ctx || {};
25
+ this.query = query || {};
26
+ this.axios = as || axios;
27
+ this.axiosConf = axiosConf || {};
28
+
29
+ this.createApi = createApi;
30
+ this.createMap = createMap;
31
+ this.getApi = getApi;
32
+ this.getMap = getMap;
33
+ this.getListApi = getListApi;
34
+ this.getListMap = getListMap;
35
+ this.getListFunc = getListFunc;
36
+ this.updateApi = updateApi;
37
+ this.updateMap = updateMap;
38
+ this.deleteApi = deleteApi;
39
+ this.multipleDeleteApi = multipleDeleteApi;
40
+ }
41
+
42
+ getApiUrl(api, record, ctx = this.ctx) {
43
+ if (!api) {
44
+ throw new Error("Error getApiUrl api 不能为空", api, record, ctx);
45
+ }
46
+ let apiUrl = api;
47
+ const params = _.merge({}, record, ctx);
48
+ _.each(params, (value, key) => {
49
+ if (!_.isString(value) || !_.isNumber(value) || _.isBoolean(value)) {
50
+ apiUrl = apiUrl.replace(`:${key}`, value);
51
+ }
52
+ });
53
+ return apiUrl;
54
+ }
55
+
56
+ get(q = {}, ctx = {}) {
57
+ let query = _.merge({}, this.query, q);
58
+ query = _.pickBy(query, (val) => !_.isNil(val) && val !== "");
59
+
60
+ return new Promise((resolve, reject) => {
61
+ const apiUrl = this.getApiUrl(this.getApi, query, ctx);
62
+ this.axios
63
+ .get(apiUrl, {
64
+ ...this.axiosConf,
65
+ params: query,
66
+ })
67
+ .then((response) => {
68
+ this.handleRes(
69
+ response,
70
+ (res) => {
71
+ if (this.getMap) {
72
+ res = this.getMap(res);
73
+ }
74
+ resolve(res);
75
+ },
76
+ reject,
77
+ );
78
+ })
79
+ .catch((err) => this.errorHandler(err, reject));
80
+ });
81
+ }
82
+
83
+ async getList(q, ctx) {
84
+ let query = _.merge({}, this.query, q);
85
+ query = _.pickBy(query, (val) => !_.isNil(val) && val !== "");
86
+
87
+ let resultList = null;
88
+ if (this.getListFunc) {
89
+ resultList = await this.getListFunc(query);
90
+ } else {
91
+ const getPro = new Promise((resolve, reject) => {
92
+ const apiUrl = this.getApiUrl(this.getListApi, q, ctx);
93
+ this.axios
94
+ .get(apiUrl, {
95
+ ...this.axiosConf,
96
+ params: query,
97
+ })
98
+ .then((response) => {
99
+ this.handleRes(
100
+ response,
101
+ (res) => {
102
+ // data: { list: [], pagination: { current: 0, total: 1 } }
103
+ if (this.getListMap) {
104
+ res.list = res.list.map((record) => this.getListMap(record));
105
+ }
106
+ resolve(res);
107
+ },
108
+ reject,
109
+ );
110
+ })
111
+ .catch((err) => this.errorHandler(err, reject));
112
+ });
113
+ resultList = await getPro;
114
+ }
115
+ return resultList;
116
+ }
117
+
118
+ create(params, ctx) {
119
+ return new Promise((resolve, reject) => {
120
+ const apiUrl = this.getApiUrl(this.createApi, params, ctx);
121
+ const opt = { ...this.axiosConf };
122
+ if (params instanceof FormData) {
123
+ opt.headers = { "Content-Type": "multipart/form-data" };
124
+ }
125
+ this.axios
126
+ .post(apiUrl, params, opt)
127
+ .then((response) => {
128
+ this.handleRes(response, resolve, reject);
129
+ })
130
+ .catch((err) => this.errorHandler(err, reject));
131
+ });
132
+ }
133
+
134
+ update(params, ctx) {
135
+ return new Promise((resolve, reject) => {
136
+ const apiUrl = this.getApiUrl(this.updateApi, params, ctx);
137
+ const opt = { ...this.axiosConf };
138
+ if (params instanceof FormData) {
139
+ opt.headers = { "Content-Type": "multipart/form-data" };
140
+ }
141
+ this.axios
142
+ .put(apiUrl, params, opt)
143
+ .then((response) => {
144
+ this.handleRes(response, resolve, reject);
145
+ })
146
+ .catch((err) => this.errorHandler(err, reject));
147
+ });
148
+ }
149
+
150
+ delete(params, ctx) {
151
+ return new Promise((resolve, reject) => {
152
+ const apiUrl = this.getApiUrl(this.deleteApi, params, ctx);
153
+ this.axios
154
+ .delete(apiUrl, { ...this.axiosConf, ...params })
155
+ .then((response) => {
156
+ this.handleRes(response, resolve, reject);
157
+ })
158
+ .catch((err) => this.errorHandler(err, reject));
159
+ });
160
+ }
161
+
162
+ multipleDelete(params, ctx) {
163
+ return new Promise((resolve, reject) => {
164
+ const apiUrl = this.getApiUrl(this.multipleDeleteApi, params, ctx);
165
+ this.axios
166
+ .delete(apiUrl, { ...this.axiosConf, ...params })
167
+ .then((response) => {
168
+ this.handleRes(response, resolve, reject);
169
+ })
170
+ .catch((err) => this.errorHandler(err, reject));
171
+ });
172
+ }
173
+
174
+ handleRes(response, resolve, reject) {
175
+ if (typeof response !== "object") {
176
+ reject(new Error("response not object"));
177
+ return;
178
+ }
179
+ let _res = response;
180
+ if (_res.data && typeof _res.data?.data?.code === "number") {
181
+ _res = _res.data;
182
+ }
183
+ const { code, message, data, msg } = _res;
184
+ if (code === 200) {
185
+ let _data = data ?? {};
186
+ if (_.isObject(_data) && _data.message === undefined) {
187
+ // 前缀 _ 避免与 data 里已有的 message 冲突
188
+ _data._message = message || msg;
189
+ }
190
+ resolve(_data);
191
+ } else {
192
+ const error = new Error(message || msg);
193
+ error.code = code;
194
+ error.response = response;
195
+ error._message = message || msg;
196
+ reject(error);
197
+ }
198
+ }
199
+
200
+ errorHandler(err, reject) {
201
+ const response = err.response || err;
202
+ if (response) {
203
+ const message = (response.data && (response.data.message || response.data.msg)) || response.msg;
204
+ const error = new Error(message || response.statusText || "未知错误");
205
+ error.code = response.status;
206
+ error.response = response;
207
+ if (message) {
208
+ // 前缀 _ 避免与 data 里已有的 message 冲突
209
+ error._message = message;
210
+ }
211
+ return reject(error);
212
+ }
213
+ return reject(err);
214
+ }
215
+ }
216
+
217
+ export { axios };
218
+
219
+ export default DataModel;
@@ -0,0 +1,80 @@
1
+ import _ from "lodash";
2
+ import dayjs from "dayjs";
3
+
4
+ const dateFormatEnum = {
5
+ time: "YYYY-MM-DD HH:mm:ss",
6
+ date: "YYYY-MM-DD",
7
+ };
8
+
9
+ /**
10
+ * 获取 field 对应的值
11
+ * @param {Object} field schema 项
12
+ * @param {Object} data 数据
13
+ * @param {Object} opt 配置项
14
+ * @returns
15
+ */
16
+ export function getVal(field = {}, data = {}, opt = {}) {
17
+ let val = _.get(data, field.name);
18
+ const { type } = field || {};
19
+ const xComponent = field["x-component"];
20
+ const xComponentProps = field["x-component-props"] || {};
21
+
22
+ if (val && (type === "date-picker" || xComponent === "DatePicker" || xComponent === "DatePicker.RangePicker")) {
23
+ const { picker, showTime } = xComponentProps || {};
24
+ const mode = picker === "date" && showTime === true ? "time" : picker || "date";
25
+ const _formatEnum = opt.dateFormatEnum || dateFormatEnum;
26
+ const format = xComponentProps?.format || _formatEnum[mode];
27
+
28
+ if (Array.isArray(val)) {
29
+ return val.map((it) => getDateVal(it, format)).join(" ~ ");
30
+ }
31
+ return getDateVal(val, format);
32
+ }
33
+
34
+ if (val && (type === "time-picker" || xComponent === "TimePicker" || xComponent === "TimePicker.RangePicker")) {
35
+ const format = xComponentProps?.format || "HH:mm:ss";
36
+ if (Array.isArray(val)) {
37
+ return val.map((it) => dayjs(it).format(format)).join(" ~ ");
38
+ }
39
+ return dayjs(val).format(format);
40
+ }
41
+
42
+ if (type === "switch") {
43
+ if (val === undefined || val === false || val === field.inactiveValue) {
44
+ return field.inactiveText || "否";
45
+ }
46
+ if (val === true || val === field.activeValue) {
47
+ return field.activeText || "是";
48
+ }
49
+ return val;
50
+ }
51
+ if (typeof field.relation === "object") {
52
+ const { key, name, label } = field.relation;
53
+ return data[key]?.[name];
54
+ }
55
+
56
+ if (
57
+ (xComponent === "Select" || xComponent === "Radio.Group" || xComponent === "Checkbox.Group") &&
58
+ Array.isArray(field.enum)
59
+ ) {
60
+ if (!Array.isArray(val)) {
61
+ return field.enum?.find((option) => option.value === val)?.label || val;
62
+ } else if (xComponentProps.mode === "multiple" && Array.isArray(val)) {
63
+ let _val = [];
64
+ val.forEach((valIt) => {
65
+ _val.push(field.enum?.find((option) => option.value === valIt)?.label || valIt);
66
+ });
67
+ val = _val;
68
+ }
69
+ }
70
+
71
+ if (Array.isArray(val)) {
72
+ return val.join("、");
73
+ }
74
+
75
+ return val;
76
+ }
77
+
78
+ export function getDateVal(val, format) {
79
+ return dayjs(val).format(format);
80
+ }
@@ -0,0 +1,173 @@
1
+ import { forwardRef, useEffect, useImperativeHandle, useRef, useState, useMemo } from "react";
2
+
3
+ import { Modal, Button, message } from "antd";
4
+
5
+ import FormRender from "@hzab/form-render";
6
+
7
+ import "./index.less";
8
+
9
+ let _formData = null;
10
+
11
+ function FormDialog(props, parentRef) {
12
+ const { Slots = {}, dialogConf = {} } = props;
13
+ const [title, setTitle] = useState("新增");
14
+ const [open, setOpen] = useState(false);
15
+ const formRef = useRef();
16
+
17
+ const FormSlot = useMemo(() => props.Slots?.FormSlot, [props.Slots?.FormSlot]);
18
+
19
+ const resolveCB = useRef();
20
+ const rejectCB = useRef();
21
+
22
+ function show(formData = props.formInitialValues, title) {
23
+ setOpen(true);
24
+ if (formRef.current?.formRender?.setValues) {
25
+ formRef.current?.formRender?.setValues(formData);
26
+ _formData = null;
27
+ } else {
28
+ _formData = formData;
29
+ }
30
+ console.log("formRef.current?.formRender", formRef.current?.formRender);
31
+ setTitle(title || "新增");
32
+ return new Promise((resolve, reject) => {
33
+ resolveCB.current = resolve;
34
+ rejectCB.current = reject;
35
+ });
36
+ }
37
+
38
+ function close() {
39
+ setOpen(false);
40
+ formRef.current?.formRender?.reset();
41
+ }
42
+
43
+ function cancel() {
44
+ close();
45
+ rejectCB.current && rejectCB.current();
46
+ }
47
+
48
+ useImperativeHandle(parentRef, () => ({
49
+ show,
50
+ close,
51
+ cancel,
52
+ onOk,
53
+ }));
54
+
55
+ function onOk() {
56
+ validate().then(async () => {
57
+ const submitForm = _.cloneDeep(await formRef.current?.formRender?.values);
58
+ if (dialogConf.beforeSubmit) {
59
+ const isContinue = await dialogConf.beforeSubmit(submitForm, {
60
+ cancel,
61
+ });
62
+ if (isContinue === false) {
63
+ return;
64
+ }
65
+ }
66
+
67
+ resolveCB.current && resolveCB.current(submitForm);
68
+ props.onSubmit && props.onSubmit(submitForm);
69
+ close();
70
+ });
71
+ }
72
+
73
+ /**
74
+ * 校验表单
75
+ * @returns
76
+ */
77
+ function validate(hideMessage) {
78
+ return new Promise((resolve, reject) => {
79
+ formRef.current?.formRender
80
+ ?.validate()
81
+ .then((values) => {
82
+ resolve(values);
83
+ })
84
+ .catch((err) => {
85
+ reject(err);
86
+ console.error("Error validate: ", err);
87
+ !hideMessage && message.error("输入有误!");
88
+ });
89
+ });
90
+ }
91
+
92
+ let footer = undefined;
93
+ if (dialogConf?.footer) {
94
+ footer = dialogConf?.footer;
95
+ } else {
96
+ footer = [];
97
+ const options = {
98
+ cancel,
99
+ onOk,
100
+ close,
101
+ form: formRef.current?.formRender,
102
+ validate,
103
+ };
104
+
105
+ if (Slots.dialogFooterPre) {
106
+ footer.push(<Slots.dialogFooterPre key="pre" options={options} />);
107
+ }
108
+
109
+ footer.push(
110
+ <Button key="cancel" onClick={cancel}>
111
+ {dialogConf.cancelText || "取 消"}
112
+ </Button>,
113
+ );
114
+
115
+ if (Slots.dialogFooterCenter) {
116
+ footer.push(<Slots.dialogFooterCenter key="center" options={options} />);
117
+ }
118
+
119
+ footer.push(
120
+ <Button key="confirm" type="primary" onClick={onOk}>
121
+ {dialogConf.okText || "确 定"}
122
+ </Button>,
123
+ );
124
+
125
+ if (Slots.dialogFooterSuffix) {
126
+ footer.push(<Slots.dialogFooterSuffix key="suffix" options={options} />);
127
+ }
128
+ }
129
+
130
+ /**
131
+ * 解决 show 函数中 formRef.current 为 undefined 的问题
132
+ */
133
+ function didMount() {
134
+ if (_formData) {
135
+ formRef.current?.formRender?.setValues(_formData);
136
+ _formData = null;
137
+ }
138
+ }
139
+
140
+ return (
141
+ <Modal
142
+ wrapClassName="form-dialog"
143
+ title={title}
144
+ visible={open}
145
+ onCancel={cancel}
146
+ onOk={onOk}
147
+ footer={footer}
148
+ width={dialogConf?.width}
149
+ destroyOnClose
150
+ >
151
+ {FormSlot ? (
152
+ <FormSlot ref={formRef} schema={props.schema?.schema} resolveCB={resolveCB} rejectCB={rejectCB} />
153
+ ) : (
154
+ <FormRender
155
+ ref={formRef}
156
+ schema={props.schema}
157
+ schemaScope={props.schemaScope}
158
+ xComponents={props.xComponents}
159
+ ></FormRender>
160
+ )}
161
+ <DidMount didMount={didMount} />
162
+ </Modal>
163
+ );
164
+ }
165
+
166
+ function DidMount(props) {
167
+ useEffect(() => {
168
+ props.didMount();
169
+ }, []);
170
+ return;
171
+ }
172
+
173
+ export default forwardRef(FormDialog);
@@ -0,0 +1,2 @@
1
+ .form-dialog {
2
+ }
package/src/index.js ADDED
@@ -0,0 +1,7 @@
1
+ import ListRender from "./list-render.jsx";
2
+
3
+ import DataModel from "./common/data-model.js";
4
+
5
+ export { DataModel };
6
+
7
+ export default ListRender;
package/src/index.less ADDED
@@ -0,0 +1,14 @@
1
+ .list-render {
2
+ .list-header {
3
+ display: flex;
4
+ justify-content: space-between;
5
+ align-items: center;
6
+ margin-bottom: 12px;
7
+ .header-actions-render {
8
+ text-align: right;
9
+ .ant-btn + .ant-btn {
10
+ margin-left: 12px;
11
+ }
12
+ }
13
+ }
14
+ }