form-driver 0.3.14 → 0.4.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.
@@ -0,0 +1,356 @@
1
+ import React from "react";
2
+ import { Modal } from "antd";
3
+ import Checkbox from "../../widget/DIYCheckbox";
4
+ import SortDrag from "../../widget/SortDrag";
5
+ import _ from "lodash";
6
+ import { MUtil } from "../../../framework/MUtil";
7
+ import { BaseViewer, Viewer } from "../../BaseViewer";
8
+ import { MEnumField, MProp, ValueConst } from "../../../framework/Schema";
9
+ import { MSetType } from "../../../types/MSetType";
10
+ import { MFieldViewer } from "../../../framework/MFieldViewer";
11
+ import { assembly } from "../../../framework/Assembly";
12
+ import { ViewerState } from "../../BaseViewer";
13
+
14
+ // 扩展 ViewerState 接口
15
+ interface ACheckDragState extends ViewerState {
16
+ data: any[];
17
+ }
18
+
19
+ function ACheckBoxLabel(field: MEnumField) {
20
+ if (field.html) {
21
+ return <div dangerouslySetInnerHTML={{ __html: field.html }} />;
22
+ } else {
23
+ return field.label ?? field.value;
24
+ }
25
+ }
26
+
27
+ /**
28
+ * 多选
29
+ * 示例:{label:"1.13 除爱人/对象之外,目前和您一起生活的家庭成员包括(多选):",name:"familyAccompany",type:"set", option: "父亲 母亲 孩子 爱人/对象的父亲 爱人/对象的母亲 兄弟姐妹"},
30
+ * 值:["孩子", "父亲"]
31
+ */
32
+ export class ACheckDrag extends Viewer<ACheckDragState> {
33
+ _enumFields: MEnumField[];
34
+ _enumValues: ValueConst[];
35
+
36
+ /** 这个是开放输入框的值 */
37
+ _inputBoxValue: ValueConst;
38
+
39
+ // 定时器
40
+ timer: any;
41
+ // 输入框 ref
42
+ inputRef: React.RefObject<HTMLInputElement>;
43
+
44
+ checkFields: MEnumField[];
45
+ checkValues: ValueConst[];
46
+ dataRef: any; // 选中数据的 Ref 版本,用于获取最新数据的,避免页面渲染
47
+
48
+ constructor(p: MProp) {
49
+ super(p);
50
+ this.inputRef = React.createRef();
51
+ this.timer = null;
52
+ this._enumFields = MUtil.option(this.props.schema);
53
+ this._enumValues = this._enumFields.map((e) => e.value);
54
+
55
+ const openOpt = p.schema.openOption ?? p.schema.setOpen;
56
+ if (openOpt) {
57
+ this._inputBoxValue =
58
+ _.first(_.difference(super.getValue(), this._enumValues)) ??
59
+ assembly.types[openOpt.type].createDefaultValue(assembly, openOpt);
60
+ }
61
+ const initialCheckFields = openOpt
62
+ ? [
63
+ ...this._enumFields,
64
+
65
+ {
66
+ label: p.schema.openOption.label,
67
+ value: this._inputBoxValue,
68
+ remark: "openOption",
69
+ },
70
+ ]
71
+ : this._enumFields;
72
+ const initialCheckValues = openOpt
73
+ ? [...this._enumValues, this._inputBoxValue]
74
+ : this._enumValues;
75
+ // 初始化扩展的状态,包含父类的基础状态和新增的 data 字段
76
+ this.checkFields = initialCheckFields;
77
+ this.checkValues = initialCheckValues;
78
+ this.dataRef = [...(super.getValue() ?? [])];
79
+ this.state = {
80
+ ctrlVersion: 1,
81
+ noValidate: false,
82
+ data: [...(super.getValue() ?? [])], // 选中状态
83
+ };
84
+ }
85
+
86
+ _createBr() {
87
+ return this.props.schema.layoutHint == "h" ? undefined : (
88
+ <div key={MUtil.unique()} />
89
+ );
90
+ }
91
+
92
+ componentDidUpdate(
93
+ prevProps: Readonly<MProp>,
94
+ prevState: Readonly<ACheckDragState>,
95
+ snapshot?: any
96
+ ): void {
97
+ setTimeout(() => {
98
+ console.log("DRAG: 组件更新", this.checkFields, this.state.data);
99
+ }, 2000);
100
+ }
101
+
102
+ element(ctx) {
103
+ let { data } = this.state;
104
+ const values = [...(data ?? [])];
105
+ const openIndex = MSetType.openValueIndex(this.props.schema, values);
106
+ // console.log("选项顺序更换", {
107
+ // checkFields,
108
+ // data,
109
+ // values,
110
+ // qq: super.getValue(),
111
+ // });
112
+ let checkboxs: any[] = this.checkFields.map((m: any, index) => {
113
+ const isShow = MUtil.isShow(
114
+ this.props.database,
115
+ ctx.rootProps.schema?.objectFields,
116
+ m.showIf
117
+ );
118
+ if (!isShow) return null;
119
+
120
+ const checkIndex = values?.findIndex((e) => e === m.value);
121
+ if (m.remark === "openOption") {
122
+ const key = this._inputBoxValue;
123
+ const checked = values?.findIndex((e) => e === key);
124
+ return [
125
+ <Checkbox
126
+ disabled={this.props.disable}
127
+ key={"openOption"}
128
+ checked={checked !== -1}
129
+ checkedIcon={checked ? checked + 1 : 1}
130
+ onChange={(e) => {
131
+ const max = this.props.schema.max;
132
+ if (max > 0 && e.target.checked) {
133
+ const len = values ? values.length : 0;
134
+ // 选择第 max + 1 项时,提示并组织
135
+ if (len >= this.props.schema.max) {
136
+ Modal.info({
137
+ title: `此题最多只能选择 ${max} 项`,
138
+ okText: "确认",
139
+ icon: null,
140
+ centered: true,
141
+ cancelText: "",
142
+ });
143
+ return;
144
+ }
145
+ }
146
+ const currentCheckValue = MSetType.change(
147
+ e.target.checked,
148
+ key,
149
+ values,
150
+ this.props.schema,
151
+ true
152
+ );
153
+ console.log("当前选中的数据ccc", currentCheckValue);
154
+ this.dataRef = currentCheckValue;
155
+ setTimeout(() => {
156
+ super.changeValue(currentCheckValue);
157
+ this.setState({
158
+ data: currentCheckValue,
159
+ });
160
+ }, 0);
161
+ }}
162
+ >
163
+ <span style={{ marginRight: "10px" }}>
164
+ {this.props.schema.openOption.label ?? "其他"}
165
+ </span>
166
+ <span
167
+ onBlurCapture={(e) => {
168
+ console.log("输入框失去焦点", this.dataRef);
169
+ setTimeout(() => {
170
+ super.changeValue(this.dataRef);
171
+ this.setState({
172
+ data: this.dataRef,
173
+ });
174
+ }, 0);
175
+ }}
176
+ >
177
+ <MFieldViewer
178
+ morph={this.props.morph}
179
+ schema={this.props.schema.openOption}
180
+ database={this}
181
+ path="_inputBoxValue"
182
+ afterChange={(path: string, str: any, final: boolean) => {
183
+ const matchEnum = this.checkFields.find(
184
+ (e) => e.value === str && e.remark !== "openOption"
185
+ );
186
+ console.log("输入框 afterChange", str, values, matchEnum);
187
+ if (matchEnum) {
188
+ // 不能让用户输入某个枚举值
189
+ this._inputBoxValue = "";
190
+ _.remove(values, (e) => !this.checkValues.includes(e));
191
+ if (!values.includes(str)) {
192
+ values.push(str);
193
+ }
194
+
195
+ queueMicrotask(() => {
196
+ super.changeValueEx(values, true, final);
197
+ this.setState({
198
+ data: values,
199
+ });
200
+ });
201
+ } else {
202
+ const idx = values.findIndex((v) => {
203
+ const index = this.checkFields
204
+ .filter((e) => e.remark !== "openOption")
205
+ .findIndex((e) => e.value === v);
206
+ if (index === -1) return true;
207
+ });
208
+ if (!_.isNil(idx) || str === "") {
209
+ this._inputBoxValue = str;
210
+ values[idx] = str;
211
+ this.dataRef = values;
212
+ this.checkFields = this.checkFields.map((e) =>
213
+ e.remark === "openOption" ? { ...e, value: str } : e
214
+ );
215
+ console.log("输入框数据", {
216
+ values,
217
+ checkFields: this.checkFields,
218
+ dataRef: this.dataRef,
219
+ });
220
+ MUtil.set(this.props.database, this.props.path, values);
221
+ }
222
+ }
223
+ }}
224
+ parent={this.props.schema}
225
+ forceValid={false}
226
+ disable={openIndex < 0}
227
+ style={{ width: "inherit" }}
228
+ />
229
+ </span>
230
+ </Checkbox>,
231
+ this._createBr(),
232
+ ];
233
+ }
234
+
235
+ return [
236
+ <Checkbox
237
+ key={m.value}
238
+ disabled={this.props.disable}
239
+ checkedIcon={checkIndex === -1 ? 1 : checkIndex + 1}
240
+ checked={_.includes(values, m.value)}
241
+ onChange={(e) => {
242
+ const currentCheckValue = MSetType.change(
243
+ e.target.checked,
244
+ m.value,
245
+ values,
246
+ this.props.schema,
247
+ true
248
+ );
249
+ console.log("当前变化的 value", values, currentCheckValue);
250
+ const max = this.props.schema.max;
251
+ if (max > 0 && e.target.checked) {
252
+ const len = values ? values.length : 0;
253
+ // 选择第 max + 1 项时,提示并组织
254
+ if (len >= this.props.schema.max) {
255
+ Modal.info({
256
+ title: `此题最多只能选择 ${max} 项`,
257
+ okText: "确认",
258
+ icon: null,
259
+ centered: true,
260
+ cancelText: "",
261
+ });
262
+ return;
263
+ }
264
+ }
265
+ this.dataRef = currentCheckValue;
266
+ queueMicrotask(() => {
267
+ super.changeValue(currentCheckValue);
268
+ this.setState({
269
+ data: currentCheckValue,
270
+ });
271
+ });
272
+ }}
273
+ >
274
+ {ACheckBoxLabel(m)}
275
+ </Checkbox>,
276
+ this._createBr(),
277
+ ];
278
+ });
279
+
280
+ // 定义更换数据源的方法
281
+ const changeOriginDataSource = (newData) => {
282
+ console.log("新数据", newData);
283
+ // 更新排序后的选项数据
284
+ const sortedCheckFields = newData.map((item) => ({
285
+ ...item,
286
+ label: item.label,
287
+ value: item.id,
288
+ }));
289
+ // 更新排序后的选中值(保持原有的选中状态)
290
+ const sortedData = newData
291
+ .filter((item) => item.isChecked)
292
+ .map((item) => item.id);
293
+
294
+ // 同时更新schema中的option顺序,以确保后续操作基于新的排序
295
+ const newSchema = { ...this.props.schema };
296
+ const isHaveOpenOption = sortedCheckFields.filter(
297
+ (e) => e.remark === "openOption"
298
+ );
299
+ console.log("isHaveOpenOption", isHaveOpenOption);
300
+ newSchema.option = isHaveOpenOption
301
+ ? sortedCheckFields?.filter((e) => e.remark !== "openOption")
302
+ : sortedCheckFields;
303
+ newSchema.openOption = isHaveOpenOption
304
+ ? isHaveOpenOption[0]
305
+ : newSchema.openOption;
306
+ // 更新组件状态
307
+ // console.log("DRAG: changeOriginDataSource 排序之后正常应该展示的数据", {
308
+ // sortedData,
309
+ // sortedCheckFields,
310
+ // newSchema,
311
+ // });
312
+ this.checkFields = sortedCheckFields;
313
+ this.dataRef = sortedData;
314
+ setTimeout(() => {
315
+ this.setState({
316
+ data: sortedData,
317
+ });
318
+ MUtil.set(this.props.database, this.props.path, sortedData);
319
+ }, 0);
320
+ };
321
+
322
+ return (
323
+ <SortDrag
324
+ changeOriginDataSource={changeOriginDataSource}
325
+ sortList={(checkboxs ?? [])?.map((cpn, index) => {
326
+ let checkFieldsValue;
327
+ checkFieldsValue = this.checkFields[index]?.value;
328
+ const isOpenOp = this.checkFields[index]?.remark === "openOption";
329
+ if (isOpenOp) {
330
+ checkFieldsValue = this._inputBoxValue;
331
+ }
332
+ console.log("DRAG: 实际传递进如 SortDrag的数据", {
333
+ data,
334
+ dataRef: this.dataRef,
335
+ checkFields: this.checkFields,
336
+ schema: this.props.database,
337
+ isOpenOp,
338
+ openValue: this._inputBoxValue,
339
+ });
340
+
341
+ return {
342
+ isChecked: this.dataRef
343
+ ? this.dataRef?.findIndex((e) => e === checkFieldsValue) !== -1
344
+ : false,
345
+ checkedIndex:
346
+ this.dataRef?.findIndex((e) => e === checkFieldsValue) + 1,
347
+ cpn,
348
+ id: "" + checkFieldsValue,
349
+ label: this.checkFields[index]?.label,
350
+ remark: this.checkFields[index]?.remark,
351
+ };
352
+ })}
353
+ />
354
+ );
355
+ }
356
+ }
@@ -1,37 +1,48 @@
1
1
  import React, { CSSProperties } from "react";
2
2
  import _ from "lodash";
3
- import { BaseViewer } from '../../BaseViewer';
4
- import TextArea from 'antd/lib/input/TextArea';
3
+ import { BaseViewer } from "../../BaseViewer";
4
+ import TextArea from "antd/lib/input/TextArea";
5
5
 
6
6
  /**
7
7
  * 用文本框编辑json object或者json array
8
8
  */
9
9
  export class JsonEditor extends BaseViewer {
10
- error:string;
10
+ error: string;
11
11
 
12
12
  element() {
13
13
  const value = super.getValue();
14
- let styles:CSSProperties = _.merge({minHeight: "10em"}, this.props.style, this.props.schema.style);
15
- if(this.props.hideBorder){
14
+ let styles: CSSProperties = _.merge(
15
+ { minHeight: "10em" },
16
+ this.props.style,
17
+ this.props.schema.style
18
+ );
19
+ if (this.props.hideBorder) {
16
20
  styles.border = "1px solid transparent";
17
21
  }
18
- if(this.error) {
22
+ if (this.error) {
19
23
  styles.border = "1px solid red";
20
24
  }
21
25
 
22
- return <TextArea style={styles} key={this.props.path} defaultValue={JSON.stringify(value,null,2)} onBlur={(v)=>{
23
- try{
24
- this.changeValue(JSON.parse(v.target.value));
25
- }catch(e){
26
- }
27
- }} onChange={(v)=>{
28
- try{
29
- JSON.parse(v.target.value);
30
- this.error = undefined;
31
- }catch(e){
32
- this.error = e.message;
33
- }
34
- this.setState({});
35
- }}/>
26
+ return (
27
+ <TextArea
28
+ style={styles}
29
+ key={this.props.path}
30
+ defaultValue={JSON.stringify(value, null, 2)}
31
+ onBlur={(v) => {
32
+ try {
33
+ this.changeValue(JSON.parse(v.target.value));
34
+ } catch (e) {}
35
+ }}
36
+ onChange={(v) => {
37
+ try {
38
+ JSON.parse(v.target.value);
39
+ this.error = undefined;
40
+ } catch (e: any) {
41
+ this.error = e.message;
42
+ }
43
+ this.setState({});
44
+ }}
45
+ />
46
+ );
36
47
  }
37
- }
48
+ }
@@ -0,0 +1,36 @@
1
+ .diy-checkbox-wrapper {
2
+ &:hover .diy-checkbox-icon {
3
+ transform: scale(1.1);
4
+ }
5
+
6
+ .diy-checkbox-icon {
7
+ display: inline-flex;
8
+ align-items: center;
9
+ justify-content: center;
10
+ width: 16px;
11
+ height: 16px;
12
+ border: 1px solid #d9d9d9;
13
+ border-radius: 2px;
14
+ transition: all 0.3s;
15
+
16
+ &.checked {
17
+ background-color: #1890ff;
18
+ border-color: #1890ff;
19
+ color: white;
20
+ }
21
+
22
+ &.indeterminate {
23
+ background-color: #1890ff;
24
+ border-color: #1890ff;
25
+ color: white;
26
+ }
27
+
28
+ &:hover {
29
+ border-color: #40a9ff;
30
+ }
31
+ }
32
+
33
+ .diy-checkbox-label {
34
+ user-select: none;
35
+ }
36
+ }
@@ -0,0 +1,154 @@
1
+ import React, { ReactNode } from "react";
2
+ import { Checkbox, CheckboxProps } from "antd";
3
+ import { CheckboxChangeEvent } from "antd/es/checkbox";
4
+ import { CheckOutlined } from "@ant-design/icons";
5
+ import "./DIYCheckbox.less";
6
+
7
+ export interface DIYCheckboxProps extends Omit<CheckboxProps, "children"> {
8
+ /** 自定义选中时的图标 */
9
+ checkedIcon?: ReactNode;
10
+ /** 自定义未选中时的图标 */
11
+ uncheckedIcon?: ReactNode;
12
+ /** 自定义半选中时的图标 */
13
+ indeterminateIcon?: ReactNode;
14
+ /** 子元素内容 */
15
+ children?: ReactNode;
16
+ /** 图标大小 */
17
+ iconSize?: number;
18
+ /** 图标颜色 */
19
+ iconColor?: string;
20
+ }
21
+
22
+ const DIYCheckbox: React.FC<DIYCheckboxProps> = ({
23
+ checkedIcon = <CheckOutlined />,
24
+ uncheckedIcon,
25
+ indeterminateIcon,
26
+ children,
27
+ iconSize = 16,
28
+ iconColor = "#fff",
29
+ className = "",
30
+ style = {},
31
+ checked,
32
+ indeterminate,
33
+ disabled,
34
+ ...restProps
35
+ }) => {
36
+ // 根据状态决定显示的图标
37
+ const getCurrentIcon = () => {
38
+ if (indeterminate && indeterminateIcon) {
39
+ return indeterminateIcon;
40
+ }
41
+ if (checked && checkedIcon) {
42
+ return checkedIcon;
43
+ }
44
+ if (!checked && uncheckedIcon) {
45
+ return uncheckedIcon;
46
+ }
47
+ return null;
48
+ };
49
+
50
+ const currentIcon = getCurrentIcon();
51
+
52
+ // 如果没有自定义图标,使用默认的 Checkbox
53
+ if (!currentIcon) {
54
+ return (
55
+ <Checkbox
56
+ className={className}
57
+ style={style}
58
+ checked={checked}
59
+ indeterminate={indeterminate}
60
+ disabled={disabled}
61
+ {...restProps}
62
+ >
63
+ {children}
64
+ </Checkbox>
65
+ );
66
+ }
67
+
68
+ // 处理点击事件,确保正确的状态传递
69
+ const handleClick = (e: React.MouseEvent) => {
70
+ if (disabled) return;
71
+
72
+ // 阻止事件冒泡到隐藏的 input
73
+ e.preventDefault();
74
+ e.stopPropagation();
75
+
76
+ // 如果有 onChange 回调,则调用它并传递正确的 checked 状态
77
+ if (restProps.onChange) {
78
+ const checkboxEvent: CheckboxChangeEvent = {
79
+ target: {
80
+ checked: !checked,
81
+ },
82
+ stopPropagation: () => {},
83
+ preventDefault: () => {},
84
+ nativeEvent: e.nativeEvent,
85
+ };
86
+ restProps.onChange(checkboxEvent);
87
+ }
88
+ };
89
+
90
+ // 处理键盘事件,支持空格键和回车键
91
+ const handleKeyDown = (e: React.KeyboardEvent) => {
92
+ if (disabled) return;
93
+
94
+ if (e.key === " " || e.key === "Enter") {
95
+ e.preventDefault();
96
+ handleClick(e as any);
97
+ }
98
+ };
99
+
100
+ // 自定义图标的样式
101
+ const iconStyle: React.CSSProperties = {
102
+ fontSize: iconSize,
103
+ color: disabled ? "#d9d9d9" : iconColor,
104
+ cursor: disabled ? "not-allowed" : "pointer",
105
+ transition: "all 0.3s",
106
+ };
107
+
108
+ const wrapperStyle: React.CSSProperties = {
109
+ display: "inline-flex",
110
+ alignItems: "center",
111
+ cursor: disabled ? "not-allowed" : "pointer",
112
+ opacity: disabled ? 0.5 : 1,
113
+ ...style,
114
+ };
115
+
116
+ return (
117
+ <label
118
+ className={`diy-checkbox-wrapper ${className}`}
119
+ style={wrapperStyle}
120
+ tabIndex={disabled ? -1 : 0}
121
+ role="checkbox"
122
+ aria-checked={indeterminate ? "mixed" : checked}
123
+ aria-disabled={disabled}
124
+ >
125
+ <span
126
+ className={`diy-checkbox-icon ${checked ? "checked" : ""} ${
127
+ indeterminate ? "indeterminate" : ""
128
+ }`}
129
+ style={iconStyle}
130
+ aria-hidden="true"
131
+ onClick={handleClick}
132
+ onKeyDown={handleKeyDown}
133
+ >
134
+ {currentIcon}
135
+ </span>
136
+ {children && (
137
+ <span className="diy-checkbox-label" style={{ marginLeft: 8 }}>
138
+ {children}
139
+ </span>
140
+ )}
141
+ <input
142
+ type="checkbox"
143
+ checked={checked}
144
+ disabled={disabled}
145
+ style={{ display: "none" }}
146
+ onChange={() => {}} // 防止 React 警告
147
+ tabIndex={-1} // 避免键盘焦点
148
+ aria-hidden="true"
149
+ />
150
+ </label>
151
+ );
152
+ };
153
+
154
+ export default DIYCheckbox;
@@ -0,0 +1,32 @@
1
+ .sortDrag {
2
+ .dragItem {
3
+ display: flex;
4
+ justify-content: space-between;
5
+ padding: 4px 8px;
6
+ background: #fff;
7
+ // border: 1px solid #d9d9d9;
8
+ // border-radius: 4px;
9
+ // box-shadow: 0 1px 2px #00000008;
10
+ font-size: 16px;
11
+ transition: background 0.2s, box-shadow 0.2s;
12
+ cursor: grab;
13
+
14
+ // 拖拽中状态
15
+ &.dragging {
16
+ background: #bae7ff;
17
+ box-shadow: 0 2px 8px #1890ff33;
18
+ }
19
+
20
+ // 拖拽悬停状态
21
+ &.dropping {
22
+ background: #f2f2f2;
23
+ }
24
+ }
25
+
26
+ .dragBody {
27
+ flex: 1;
28
+ }
29
+ .dragHandle {
30
+ width: 40px;
31
+ }
32
+ }