@hzab/list-render 1.10.19 → 1.10.20-alpha.0

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/README.md CHANGED
@@ -107,6 +107,16 @@ const listDM = useMemo(
107
107
  | onEditReqVerify | Function | 否 | - | 编辑态保存时额外的规则校验函数,返回 Promise 的 resolve 或 reject 用于继续执行或停止执行 |
108
108
 
109
109
  - fetchOnEdit 展示编辑弹框时,是否会调用一次详情接口进行回填(某些场景下,列表接口只返回部分部分字段,只有详情接口会返回全部字段);若为 false,则会使用表格列表接口返回的 row 数据进行回填
110
+ - editMode
111
+ - modal 弹窗/抽屉编辑;
112
+ - table-edit 整个 table 一起编辑;
113
+ - table-edit-show 整个 table 可 一起编辑,直接显示表单;
114
+ - line 编辑整行,编辑按钮在操作列;
115
+ - line-show 编辑整行,直接显示表单,编辑按钮在操作列;
116
+ - line-cell 编辑整行,操作按钮在单元格;
117
+ - line-cell-show 编辑整行,直接显示表单,操作按钮在单元格;
118
+ - cell 编辑指定单元格
119
+ - cell-show 编辑指定单元格,直接显示表单
110
120
 
111
121
  #### tableConf
112
122
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hzab/list-render",
3
- "version": "1.10.19",
3
+ "version": "1.10.20-alpha.0",
4
4
  "description": "",
5
5
  "main": "src",
6
6
  "scripts": {
@@ -26,12 +26,12 @@
26
26
  "devDependencies": {
27
27
  "@ant-design/icons": "^4.8.1",
28
28
  "@hzab/data-model": "^1.7.4",
29
- "@hzab/form-render": "^1.6.12",
29
+ "@hzab/form-render": "^1.7.12",
30
30
  "@hzab/formily-result-utils": "^1.2.0",
31
31
  "@hzab/permissions": "^1.0.0",
32
32
  "@hzab/schema-descriptions": "^1.3.0",
33
+ "@hzab/utils": "^1.0.16",
33
34
  "@hzab/webpack-config": "^0.7.2",
34
- "@hzab/utils": "^1.0.7",
35
35
  "@types/react": "^17.0.62",
36
36
  "@types/react-dom": "^17.0.20",
37
37
  "antd": "^4.24.12",
@@ -0,0 +1,11 @@
1
+ .edit-array-table {
2
+ .edit-array-table-head {
3
+ display: flex;
4
+ justify-content: space-between;
5
+ .edit-array-table-actions {
6
+ .ant-btn + .ant-btn {
7
+ margin-left: 8px;
8
+ }
9
+ }
10
+ }
11
+ }
@@ -0,0 +1,198 @@
1
+ import { useEffect, useMemo, useRef, useState, forwardRef, useImperativeHandle } from "react";
2
+ import { Button } from "antd";
3
+ import { cloneDeep } from "lodash";
4
+ import { Schema } from "@formily/json-schema";
5
+
6
+ import FormRender from "@hzab/form-render";
7
+ import { useIndex } from "@hzab/form-render/src/components/ArrayBase";
8
+ import SchemaToArrayTable from "../components/Formily/SchemaToArrayTable";
9
+
10
+ import "./index.less";
11
+
12
+ export interface IEditArrayTableProps {
13
+ topProps: {
14
+ editMode?: string;
15
+ fetchOnEdit?: boolean;
16
+ hasAction?: boolean;
17
+ hideMessage?: boolean;
18
+ };
19
+ config?: {
20
+ isDargTable?: boolean;
21
+ orderColType?: string;
22
+ };
23
+ schema: Schema;
24
+ list: Object[];
25
+ components: Object[];
26
+ schemaScope: Object;
27
+ getList: () => Promise<{ list: any[] }>;
28
+ onEditSubmit: (val) => void;
29
+ handleMessage: (msg) => void;
30
+ query: {
31
+ pageNum: number;
32
+ pageSize: number;
33
+ };
34
+ }
35
+
36
+ // TODO: 插槽、权限、拖拽、序号、行选择、列显隐、操作列
37
+ export const EditArrayTable = forwardRef(function (props: IEditArrayTableProps, parentRef) {
38
+ const { topProps, schemaScope, getList, handleMessage, config, query } = props;
39
+ const { hasAction, editMode } = topProps || {};
40
+ const { orderColType = "page" } = config || {};
41
+
42
+ console.log("config", config, query);
43
+
44
+ const [editing, setEditing] = useState(false);
45
+ const [loading, setLoading] = useState(false);
46
+ const formRef = useRef(null);
47
+ const preValRef = useRef(null);
48
+ const queryRef = useRef(props.query);
49
+
50
+ queryRef.current = query;
51
+
52
+ const schema = useMemo(() => {
53
+ const schemaToArrayTable = new SchemaToArrayTable({
54
+ schema: props.schema,
55
+ option: { hasAction, ...config },
56
+ });
57
+ return schemaToArrayTable.handleSchema();
58
+ }, [props.schema]);
59
+
60
+ const components = useMemo(() => {
61
+ return {
62
+ TableActions() {
63
+ return <div>TableActions</div>;
64
+ },
65
+ TableIndex() {
66
+ const index = useIndex();
67
+ // 序号列
68
+ if (orderColType === "page") {
69
+ return index + 1;
70
+ } else if (orderColType == "all") {
71
+ const { pageSize = 10, pageNum = 1 } = queryRef.current || {};
72
+ // 当前页数减1乘以每一页页数再加当前页序号+1
73
+ return (pageNum - 1) * pageSize + (index + 1);
74
+ }
75
+ return null;
76
+ },
77
+ ...props.components,
78
+ };
79
+ }, [props.components, props.query.pageNum, props.query.pageSize]);
80
+
81
+ useImperativeHandle(
82
+ parentRef,
83
+ () => ({
84
+ formRef,
85
+ }),
86
+ [],
87
+ );
88
+
89
+ useEffect(() => {
90
+ if (formRef.current) {
91
+ setVal(props.list);
92
+ }
93
+ }, [props.list]);
94
+
95
+ function getVal() {
96
+ return cloneDeep(formRef.current.formRender.values.arrayTable);
97
+ }
98
+
99
+ function setVal(val) {
100
+ // formRef.current.formRender.reset();
101
+ formRef.current.formRender.setValues({
102
+ arrayTable: cloneDeep(val),
103
+ });
104
+ }
105
+
106
+ async function onEdit() {
107
+ setLoading(true);
108
+ if (topProps.fetchOnEdit !== false) {
109
+ // 获取当前列表最新数据
110
+ return getList()
111
+ .then((res) => {
112
+ handleEdit(res?.list);
113
+ return res;
114
+ })
115
+ .catch((err) => {
116
+ console.error("err", err);
117
+ handleMessage(err._message);
118
+ return Promise.reject(err);
119
+ });
120
+ } else {
121
+ const value = getVal();
122
+ handleEdit(value);
123
+ return Promise.resolve(value);
124
+ }
125
+ }
126
+
127
+ async function handleEdit(data) {
128
+ // cloneDeep 避免受影响
129
+ preValRef.current = cloneDeep(data);
130
+ setVal(data);
131
+ setEditing(true);
132
+ formRef.current.formRender.fields.arrayTable.readPretty = false;
133
+ setLoading(false);
134
+ }
135
+
136
+ async function onSubmit() {
137
+ setLoading(true);
138
+ try {
139
+ // 表单校验
140
+ await formRef.current?.formRender?.validate();
141
+ try {
142
+ await (props.onEditSubmit && props.onEditSubmit(getVal()));
143
+ onClose();
144
+ } catch (error) {}
145
+ } catch (error) {
146
+ console.error("Error validate: ", error);
147
+ !topProps.hideMessage && handleMessage("输入有误!");
148
+ }
149
+ setLoading(false);
150
+ }
151
+
152
+ function onClose() {
153
+ formRef.current.formRender.fields.arrayTable.readPretty = true;
154
+ setEditing(false);
155
+ }
156
+
157
+ function onCancel() {
158
+ onClose();
159
+ // 未提交,取消恢复原始数据
160
+ setVal(preValRef.current);
161
+ }
162
+
163
+ return (
164
+ <div className="edit-array-table">
165
+ <div className="edit-array-table-head">
166
+ <div></div>
167
+ <div className="edit-array-table-actions">
168
+ {editing ? (
169
+ <>
170
+ <Button className="edit-array-table-submit" type="primary" onClick={onSubmit} loading={loading}>
171
+ 提交
172
+ </Button>
173
+ <Button className="edit-array-table-cancel" onClick={onCancel} loading={loading}>
174
+ 取消
175
+ </Button>
176
+ </>
177
+ ) : (
178
+ <Button className="edit-array-table-edit" type="primary" onClick={onEdit} loading={loading}>
179
+ 编辑
180
+ </Button>
181
+ )}
182
+ </div>
183
+ </div>
184
+ <FormRender
185
+ ref={formRef}
186
+ className="edit-array-table-form"
187
+ schema={schema}
188
+ components={components}
189
+ schemaScope={schemaScope}
190
+ initialValues={{}}
191
+ emptyValue={" "}
192
+ formOptions={{ readPretty: editMode !== "table-edit-show" }}
193
+ />
194
+ </div>
195
+ );
196
+ });
197
+
198
+ export default EditArrayTable;
@@ -1,7 +1,9 @@
1
- import React, { useState, useCallback, useRef, useEffect } from "react";
1
+ import React, { useState, useCallback, useRef, useEffect, useMemo } from "react";
2
2
  import { Card, Slider, Rate, Form, Button } from "antd";
3
3
  import { EditOutlined, CheckOutlined, CloseOutlined } from "@ant-design/icons";
4
+ import { createForm, onFormValuesChange, onFieldValueChange } from "@formily/core";
4
5
  import { createSchemaField, FormProvider, FormConsumer } from "@formily/react";
6
+
5
7
  import { cloneDeep } from "lodash";
6
8
 
7
9
  import { antdComponents, customComponents } from "@hzab/form-render";
@@ -13,6 +15,74 @@ export interface Item {
13
15
  address: string;
14
16
  }
15
17
 
18
+ export interface EditableRowProps extends React.HTMLAttributes<HTMLElement> {
19
+ SchemaField: any;
20
+ getField: () => {
21
+ type: string;
22
+ name: string;
23
+ };
24
+ editing: boolean;
25
+ dataIndex: string;
26
+ title: any;
27
+ inputType: "number" | "text";
28
+ record: Item;
29
+ index: number;
30
+ editable: boolean;
31
+ onEdit: (record: Partial<Item> & { key: React.Key }, dataIndex: string, opt: Object) => void;
32
+ onSave: (field: { type: string; name: string }) => void;
33
+ onCancel: () => void;
34
+ topProps: {
35
+ config: { hasEdit: boolean | Function };
36
+ editMode: "modal" | "line" | "line-cell" | "cell";
37
+ formConf: any;
38
+ };
39
+ children: React.ReactNode;
40
+ }
41
+
42
+ export const EditableRow: React.FC<EditableRowProps> = ({
43
+ SchemaField,
44
+ getField,
45
+ editing,
46
+ dataIndex,
47
+ title,
48
+ inputType,
49
+ record,
50
+ index,
51
+ onEdit,
52
+ onSave,
53
+ onCancel,
54
+ editable,
55
+ topProps,
56
+ children,
57
+ ...restProps
58
+ }) => {
59
+ const { formConf = {} } = topProps || {};
60
+ const formRender = useMemo(
61
+ () =>
62
+ createForm({
63
+ initialValues: record,
64
+ // 禁用状态(注意,自定义组件组件自行获取对应的状态)
65
+ readOnly: formConf.readOnly,
66
+ disabled: formConf.disabled,
67
+ ...(formConf.formOptions || {}),
68
+ effects(...args) {
69
+ formConf.onFormValuesChange && onFormValuesChange(formConf.onFormValuesChange);
70
+ formConf.onFieldValueChange && onFieldValueChange("*", formConf.onFieldValueChange);
71
+ formConf.formOptions?.effects && formConf.formOptions?.effects(...args);
72
+ },
73
+ }),
74
+ [],
75
+ );
76
+ // return <tr {...restProps}>{children}</tr>;
77
+ return (
78
+ <FormProvider form={formRender}>
79
+ <Form layout="vertical" component={false}>
80
+ <FormConsumer>{() => <tr {...restProps}>{children}</tr>}</FormConsumer>
81
+ </Form>
82
+ </FormProvider>
83
+ );
84
+ };
85
+
16
86
  export interface EditableCellProps extends React.HTMLAttributes<HTMLElement> {
17
87
  SchemaField: any;
18
88
  getField: () => {
@@ -32,6 +102,7 @@ export interface EditableCellProps extends React.HTMLAttributes<HTMLElement> {
32
102
  topProps: {
33
103
  config: { hasEdit: boolean | Function };
34
104
  editMode: "modal" | "line" | "line-cell" | "cell";
105
+ isEditCell: (record) => boolean;
35
106
  };
36
107
  children: React.ReactNode;
37
108
  }
@@ -56,12 +127,12 @@ export const EditableCell: React.FC<EditableCellProps> = ({
56
127
  const field = getField && getField();
57
128
  const fPattern = field?.["x-pattern"];
58
129
  const fDisplay = field?.["x-display"];
59
- const { editMode = "modal" } = topProps || {};
130
+ const { editMode = "modal", isEditCell } = topProps || {};
60
131
  const { hasEdit } = topProps?.config || {};
61
132
  const fieldRef = useRef();
62
133
 
63
134
  // 弹窗编辑模式
64
- if (editMode === "modal") {
135
+ if (editMode === "modal" || (typeof isEditCell === "function" && !isEditCell(record))) {
65
136
  return <td {...restProps}>{children}</td>;
66
137
  }
67
138
  // 无编辑权限或禁止编辑
@@ -0,0 +1,157 @@
1
+ import React, { useRef } from "react";
2
+ import { EditOutlined, CheckOutlined, CloseOutlined } from "@ant-design/icons";
3
+ import { useForm } from "@formily/react";
4
+
5
+ import { Item, EditMode } from "./type";
6
+
7
+ const submitHideList = ["line"];
8
+
9
+ export interface EditableCellProps extends React.HTMLAttributes<HTMLElement> {
10
+ SchemaField: any;
11
+ getField: () => {
12
+ type: string;
13
+ name: string;
14
+ component: string;
15
+ };
16
+ editing: boolean;
17
+ dataIndex: string;
18
+ title: any;
19
+ inputType: "number" | "text";
20
+ record: Item;
21
+ index: number;
22
+ editable: boolean;
23
+ onEdit: (record: Partial<Item> & { key: React.Key }, dataIndex: string, opt: Object) => void;
24
+ onEditSave: ({ field, id, dataIndex }) => void;
25
+ onCancel: () => void;
26
+ topProps: {
27
+ hasEdit: boolean | Function;
28
+ tableConf: { canEdit: Function };
29
+ editMode: EditMode;
30
+ idKey: "string";
31
+ };
32
+ components: any;
33
+ children: React.ReactNode;
34
+ }
35
+
36
+ export const EditableCell: React.FC<EditableCellProps> = ({
37
+ SchemaField,
38
+ getField,
39
+ editing,
40
+ dataIndex,
41
+ title,
42
+ inputType,
43
+ record,
44
+ index,
45
+ onEdit,
46
+ onEditSave,
47
+ onCancel,
48
+ editable,
49
+ topProps,
50
+ children,
51
+ components,
52
+ ...restProps
53
+ }) => {
54
+ const field = getField && getField();
55
+ const { idKey = "id", hasEdit, editMode = "modal" } = topProps || {};
56
+ const { canEdit } = topProps?.tableConf || {};
57
+
58
+ // 弹窗编辑模式
59
+ if (!field || (typeof canEdit === "function" && canEdit(record, { index, field }))) {
60
+ return <td {...restProps}>{children}</td>;
61
+ }
62
+
63
+ const name = field?.name;
64
+ const fieldRef = useRef();
65
+ const fPattern = field?.["x-pattern"];
66
+ const fDisplay = field?.["x-display"];
67
+ const display = field?.["x-display"];
68
+ const disabled = display === "none" || display === "hidden";
69
+
70
+ const id = record?.[idKey];
71
+ // 无编辑权限或禁止编辑
72
+ const _hasEdit = hasEdit && typeof hasEdit === "function" ? hasEdit(record, index) : hasEdit !== false;
73
+ let _editable =
74
+ !disabled && _hasEdit && editable && !(fPattern === "disabled" || fPattern === "readOnly" || fDisplay === "none");
75
+ // 行内单元格编辑按钮
76
+ const showEditIcon = !editing && _editable && (editMode === "line-cell" || editMode === "cell");
77
+ const showSubmitIcon = !disabled && editing && !submitHideList.includes(editMode);
78
+
79
+ const editOpt = { index, field, fieldRef };
80
+
81
+ const form = useForm();
82
+ if (form.fields[name]) {
83
+ form.fields[name].pattern = disabled || !editing ? "readPretty" : field?.["x-pattern"] ?? "editable";
84
+ }
85
+
86
+ // 使用 Schema 渲染模式
87
+ return (
88
+ <td {...restProps}>
89
+ <div
90
+ className="editable-table-cell table-cell"
91
+ onDoubleClick={(e) => {
92
+ if (editMode == "modal") {
93
+ return;
94
+ }
95
+ if (editing) {
96
+ return;
97
+ }
98
+ e?.preventDefault();
99
+ e?.stopPropagation();
100
+ onEdit(record, dataIndex, editOpt);
101
+ }}
102
+ onKeyDown={(e) => {
103
+ if (e.key === "Enter") {
104
+ e.preventDefault(); // 防止意外的表单提交
105
+ onEditSave && onEditSave({ field, id, dataIndex });
106
+ }
107
+ }}
108
+ >
109
+ <SchemaField
110
+ schema={{
111
+ type: "object",
112
+ properties: {
113
+ [name]: {
114
+ ...field,
115
+ title: "",
116
+ ["x-component-props"]: {
117
+ ...field["x-component-props"],
118
+ ref: (ref) => {
119
+ if (field["x-component-props"] && typeof field["x-component-props"].ref === "function") {
120
+ field["x-component-props"].ref(ref);
121
+ }
122
+ if (ref) {
123
+ fieldRef.current = ref;
124
+ }
125
+ },
126
+ },
127
+ // 外部配置的禁用状态
128
+ "x-pattern": disabled || !editing ? "readPretty" : field?.["x-pattern"] ?? "editable",
129
+ // 外部配置的表单显隐状态,强制为 显示
130
+ "x-display": "visible",
131
+ },
132
+ },
133
+ }}
134
+ />
135
+ {showEditIcon ? (
136
+ <EditOutlined
137
+ className="table-cell-edit-icon"
138
+ onClick={(e) => {
139
+ e?.preventDefault();
140
+ e?.stopPropagation();
141
+ onEdit(record, dataIndex, editOpt);
142
+ }}
143
+ />
144
+ ) : null}
145
+ {showSubmitIcon ? (
146
+ <div className="cell-editing-actions">
147
+ <CheckOutlined
148
+ className="table-cell-confirm-icon"
149
+ onClick={() => onEditSave({ field, id: record[topProps.idKey || "id"], dataIndex })}
150
+ />
151
+ <CloseOutlined className="table-cell-cancel-icon" onClick={onCancel} />
152
+ </div>
153
+ ) : null}
154
+ </div>
155
+ </td>
156
+ );
157
+ };
@@ -0,0 +1,71 @@
1
+ import { memo, useMemo } from "react";
2
+ import { Form } from "antd";
3
+ import { createForm, onFormValuesChange, onFieldValueChange, Field } from "@formily/core";
4
+ import { FormProvider, FormConsumer } from "@formily/react";
5
+ import { PreviewText } from "c-formily-antd/lib/preview-text";
6
+
7
+ import { Item, EditMode } from "./type";
8
+
9
+ export interface EditableRowProps extends React.HTMLAttributes<HTMLElement> {
10
+ rowFormMapRef;
11
+ editingInofRef;
12
+ record: Item;
13
+ editable: boolean;
14
+ disabled: boolean;
15
+ onEditSave;
16
+ topProps: {
17
+ config: { hasEdit: boolean | Function };
18
+ editMode: EditMode;
19
+ idKey: "string";
20
+ onFormValuesChange: (form: any) => void;
21
+ onFieldValueChange: (field, form: any) => void;
22
+ formOptions: {
23
+ effects;
24
+ };
25
+ tableConf: {
26
+ tableEmptyValue;
27
+ };
28
+ };
29
+ children: React.ReactNode;
30
+ }
31
+
32
+ export const EditTableRow: React.FC<EditableRowProps> = (props: EditableRowProps) => {
33
+ const { record, rowFormMapRef, editingInofRef, topProps, onEditSave, ...resetProps } = props;
34
+ const { idKey = "id", editMode } = topProps || {};
35
+ const rowId = props["data-row-key"];
36
+ const rowForm = useMemo(() => {
37
+ if (rowFormMapRef?.current?.get && rowFormMapRef.current.get(rowId)) {
38
+ // @ts-ignore
39
+ rowFormMapRef.current.get(rowId).readPretty = true;
40
+ return rowFormMapRef.current?.get(rowId);
41
+ }
42
+ const form = createForm({
43
+ initialValues: record,
44
+ // 只读模式,非编辑态默认为只读模式
45
+ readPretty: editingInofRef?.current?.editingRowId !== rowId,
46
+ ...(topProps?.formOptions || {}),
47
+ effects(...args) {
48
+ topProps?.onFormValuesChange && onFormValuesChange(topProps.onFormValuesChange);
49
+ onFieldValueChange("*", (field: Field, form) => {
50
+ topProps.onFieldValueChange && topProps.onFieldValueChange(field, form);
51
+ });
52
+ topProps?.formOptions?.effects && topProps.formOptions?.effects(...args);
53
+ },
54
+ });
55
+ if (rowFormMapRef) {
56
+ rowFormMapRef.current.set(rowId, form);
57
+ }
58
+ return form;
59
+ }, [record]);
60
+
61
+ return (
62
+ <FormProvider form={rowForm}>
63
+ {/* 传入空值展示的字符串 */}
64
+ <PreviewText.Placeholder value={topProps?.tableConf?.tableEmptyValue || " "}>
65
+ <Form layout="vertical" component={false}>
66
+ <FormConsumer>{() => <tr {...resetProps}>{props.children}</tr>}</FormConsumer>
67
+ </Form>
68
+ </PreviewText.Placeholder>
69
+ </FormProvider>
70
+ );
71
+ };