form-driver 0.4.0 → 0.4.2
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/dist/m3.css +129 -1
- package/dist/m3.js +1 -1
- package/es/m3.css +129 -1
- package/es/m3.js +1196 -386
- package/lib/m3.css +129 -1
- package/lib/m3.js +1192 -382
- package/package.json +3 -2
- package/src/framework/Assembly.tsx +53 -23
- package/src/framework/Init.tsx +4 -0
- package/src/framework/M3.tsx +2 -2
- package/src/framework/MViewer.tsx +32 -8
- package/src/framework/Validator.ts +131 -68
- package/src/types/MSetType.ts +1 -1
- package/src/types/MWeightType.ts +113 -0
- package/src/ui/BaseViewer.tsx +64 -42
- package/src/ui/editor/basic/AIntBox.tsx +13 -10
- package/src/ui/editor/complex/AArrayGrid.tsx +229 -101
- package/src/ui/editor/complex/ACheckDrag.tsx +46 -40
- package/src/ui/editor/complex/AForm.tsx +273 -125
- package/src/ui/editor/complex/AWeight.tsx +257 -0
- package/src/ui/widget/EnhancedSortDrag.less +142 -0
- package/src/ui/widget/EnhancedSortDrag.tsx +498 -0
- package/src/ui/widget/SortDrag.less +1 -1
- package/src/ui/widget/SortDrag.tsx +1 -14
- package/types/framework/Assembly.d.ts +1 -1
- package/types/framework/MViewer.d.ts +1 -0
- package/types/framework/Validator.d.ts +2 -2
- package/types/types/MWeightType.d.ts +2 -0
- package/types/ui/editor/complex/AArrayGrid.d.ts +1 -1
- package/types/ui/editor/complex/AForm.d.ts +2 -2
- package/types/ui/editor/complex/AWeight.d.ts +42 -0
- package/types/ui/widget/EnhancedSortDrag.d.ts +59 -0
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
import { ReactNode, useState, memo } from "react";
|
|
2
|
+
import { BaseViewer, Viewer } from "../../BaseViewer";
|
|
3
|
+
import { MEnumField, MProp, ValueConst } from "../../../framework/Schema";
|
|
4
|
+
import { ViewerState } from "../../BaseViewer";
|
|
5
|
+
import { Slider, Row, Col, InputNumber } from "antd";
|
|
6
|
+
import { MUtil } from "../../../framework/MUtil";
|
|
7
|
+
|
|
8
|
+
export interface AWeightProps {
|
|
9
|
+
/** 总比值,默认100 */
|
|
10
|
+
total?: number;
|
|
11
|
+
|
|
12
|
+
/** 是否允许小数,默认false只允许整数 */
|
|
13
|
+
allowDecimal?: boolean;
|
|
14
|
+
|
|
15
|
+
/** 小数位数,当allowDecimal为true时有效,默认1 */
|
|
16
|
+
decimalPlaces?: number;
|
|
17
|
+
|
|
18
|
+
/** 最小步长,默认1 */
|
|
19
|
+
step?: number;
|
|
20
|
+
|
|
21
|
+
/** 每个选项的最小比重,默认0 */
|
|
22
|
+
minWeight?: number;
|
|
23
|
+
|
|
24
|
+
/** 每个选项的最大比重,默认等于total */
|
|
25
|
+
maxWeight?: number;
|
|
26
|
+
|
|
27
|
+
/** 是否显示百分比符号,默认true */
|
|
28
|
+
showPercentage?: boolean;
|
|
29
|
+
|
|
30
|
+
/** 是否允许某些选项为0,默认true */
|
|
31
|
+
allowZero?: boolean;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// 扩展 ViewerState 接口
|
|
35
|
+
interface AWeightState extends ViewerState {
|
|
36
|
+
allSliderValues: any;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const mockOptions = [
|
|
40
|
+
{ label: "产品质量", value: "quality" },
|
|
41
|
+
{ label: "服务态度", value: "service" },
|
|
42
|
+
{ label: "价格水平", value: "price" },
|
|
43
|
+
{ label: "配送速度", value: "delivery" },
|
|
44
|
+
];
|
|
45
|
+
|
|
46
|
+
const mockData = {
|
|
47
|
+
quality: 40,
|
|
48
|
+
service: 30,
|
|
49
|
+
price: 20,
|
|
50
|
+
delivery: 10,
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const InputSlider = memo((props: any) => {
|
|
54
|
+
const { value, min, max, onChange: onCpnChange, onAfterChange } = props;
|
|
55
|
+
|
|
56
|
+
const onChange = (newValue: number, isInputChange?: boolean) => {
|
|
57
|
+
onCpnChange(newValue, isInputChange);
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
return (
|
|
61
|
+
<Row gutter={[12, 12]}>
|
|
62
|
+
<Col span={4}>
|
|
63
|
+
<InputNumber
|
|
64
|
+
precision={0}
|
|
65
|
+
style={{ width: "100%" }}
|
|
66
|
+
min={min}
|
|
67
|
+
max={max}
|
|
68
|
+
value={value}
|
|
69
|
+
onChange={(v) => onChange(v, true)}
|
|
70
|
+
onBlur={onAfterChange}
|
|
71
|
+
/>
|
|
72
|
+
</Col>
|
|
73
|
+
<Col span={20}>
|
|
74
|
+
<Slider
|
|
75
|
+
min={min}
|
|
76
|
+
max={max}
|
|
77
|
+
marks={{ [min]: min, [max]: max }}
|
|
78
|
+
onChange={onChange}
|
|
79
|
+
onAfterChange={onAfterChange}
|
|
80
|
+
value={typeof value === "number" ? value : 0}
|
|
81
|
+
/>
|
|
82
|
+
</Col>
|
|
83
|
+
</Row>
|
|
84
|
+
);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
export class AWeight extends Viewer<AWeightState> {
|
|
88
|
+
_enumFields: MEnumField[];
|
|
89
|
+
_enumValues: ValueConst[];
|
|
90
|
+
allSliderValuesChanged: any = [];
|
|
91
|
+
// 添加防抖定时器
|
|
92
|
+
private debounceTimer: any = null;
|
|
93
|
+
private lastUpdateTime: number = 0;
|
|
94
|
+
private readonly THROTTLE_DELAY = 16; // 约 60fps
|
|
95
|
+
private totalWeight: any = 0;
|
|
96
|
+
private allValues: any = [];
|
|
97
|
+
constructor(props: MProp) {
|
|
98
|
+
console.log("AWeight props", props);
|
|
99
|
+
super(props);
|
|
100
|
+
const { schema } = props;
|
|
101
|
+
this._enumFields = MUtil.option(this.props.schema);
|
|
102
|
+
console.log("this._enumFields", this._enumFields);
|
|
103
|
+
this.totalWeight = (schema as any).weight;
|
|
104
|
+
this.allValues =
|
|
105
|
+
super.getValue()?.map((i) => i.value) ??
|
|
106
|
+
new Array(this._enumFields.length).fill(0);
|
|
107
|
+
this.state = {
|
|
108
|
+
allSliderValues: this.allValues,
|
|
109
|
+
ctrlVersion: 1,
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
componentDidMount(): void {
|
|
114
|
+
console.log("滑动组件初始化");
|
|
115
|
+
// const { cpnData } = this.state;
|
|
116
|
+
// const allSliderValues = Object.values(cpnData);
|
|
117
|
+
// this.setState({ allSliderValues });
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
componentDidUpdate(pre, cur): void {
|
|
121
|
+
// console.log("当前数据88", super.getValue());
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// 更新关联的滑动条
|
|
125
|
+
private updateLinkedSliders(
|
|
126
|
+
numbers: number[],
|
|
127
|
+
currentIndex: number,
|
|
128
|
+
isAfterChange?: boolean
|
|
129
|
+
): void {
|
|
130
|
+
const newNumbers = [...numbers];
|
|
131
|
+
const targetIndex = numbers.length - 1;
|
|
132
|
+
const isSelectNumber = this.allSliderValuesChanged
|
|
133
|
+
.slice(0, targetIndex)
|
|
134
|
+
.every((changed) => changed);
|
|
135
|
+
const totalData = numbers.reduce((total, current, index) => {
|
|
136
|
+
if (index !== targetIndex) {
|
|
137
|
+
total += current;
|
|
138
|
+
}
|
|
139
|
+
return total;
|
|
140
|
+
}, 0);
|
|
141
|
+
if (targetIndex !== -1 && currentIndex !== targetIndex && isSelectNumber) {
|
|
142
|
+
const finalValue = this.totalWeight - totalData;
|
|
143
|
+
console.log("totalData", totalData, finalValue);
|
|
144
|
+
if (finalValue >= 0 && finalValue <= this.totalWeight) {
|
|
145
|
+
// 使用插值算法平滑过渡
|
|
146
|
+
const currentValue = numbers[targetIndex];
|
|
147
|
+
const smoothedValue = isAfterChange
|
|
148
|
+
? finalValue
|
|
149
|
+
: Math.floor(this.smoothValue(currentValue, finalValue, 0.3)); // 0.3 是平滑系数
|
|
150
|
+
|
|
151
|
+
newNumbers[targetIndex] = smoothedValue;
|
|
152
|
+
this.setState({
|
|
153
|
+
allSliderValues: newNumbers,
|
|
154
|
+
});
|
|
155
|
+
} else {
|
|
156
|
+
newNumbers[targetIndex] = 0;
|
|
157
|
+
this.setState({
|
|
158
|
+
allSliderValues: newNumbers,
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
} else {
|
|
162
|
+
// 如果不联动更新的情况下,那么就直接更新滑动条
|
|
163
|
+
this.setState({
|
|
164
|
+
allSliderValues: newNumbers,
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
this.allSliderValuesChanged[currentIndex] = true;
|
|
168
|
+
super.changeValue(
|
|
169
|
+
newNumbers.map((n, index) => {
|
|
170
|
+
return {
|
|
171
|
+
label: this._enumFields[index].value,
|
|
172
|
+
value: n,
|
|
173
|
+
};
|
|
174
|
+
})
|
|
175
|
+
);
|
|
176
|
+
console.log("更改后的 weight database", super.getValue());
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// 平滑插值算法
|
|
180
|
+
private smoothValue(current: number, target: number, factor: number): number {
|
|
181
|
+
return current + (target - current) * factor;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
element() {
|
|
185
|
+
const hasAssignWeight = this.state.allSliderValues.reduce(
|
|
186
|
+
(total, current) => total + current,
|
|
187
|
+
0
|
|
188
|
+
);
|
|
189
|
+
return (
|
|
190
|
+
<div style={{ padding: 16 }}>
|
|
191
|
+
{this._enumFields.map((option, index) => {
|
|
192
|
+
return (
|
|
193
|
+
<div key={(option as any).value}>
|
|
194
|
+
<div style={{ marginBottom: 8, fontWeight: "bold" }}>
|
|
195
|
+
{option.label}
|
|
196
|
+
</div>
|
|
197
|
+
<InputSlider
|
|
198
|
+
onChange={(value, isInputChange) => {
|
|
199
|
+
const numbers = [...this.state.allSliderValues];
|
|
200
|
+
numbers[index] = value;
|
|
201
|
+
super.changeValue(
|
|
202
|
+
numbers.map((n, index) => {
|
|
203
|
+
return {
|
|
204
|
+
label: this._enumFields[index].value,
|
|
205
|
+
value: n,
|
|
206
|
+
};
|
|
207
|
+
})
|
|
208
|
+
);
|
|
209
|
+
|
|
210
|
+
// 使用节流控制联动更新频率
|
|
211
|
+
const now = Date.now();
|
|
212
|
+
if (now - this.lastUpdateTime >= this.THROTTLE_DELAY) {
|
|
213
|
+
this.updateLinkedSliders(numbers, index, isInputChange);
|
|
214
|
+
this.lastUpdateTime = now;
|
|
215
|
+
} else {
|
|
216
|
+
// 使用防抖确保最后一次更新被执行
|
|
217
|
+
if (this.debounceTimer) {
|
|
218
|
+
clearTimeout(this.debounceTimer);
|
|
219
|
+
}
|
|
220
|
+
this.debounceTimer = setTimeout(() => {
|
|
221
|
+
this.updateLinkedSliders(numbers, index, isInputChange);
|
|
222
|
+
this.lastUpdateTime = Date.now();
|
|
223
|
+
}, this.THROTTLE_DELAY);
|
|
224
|
+
}
|
|
225
|
+
}}
|
|
226
|
+
value={this.state.allSliderValues[index]}
|
|
227
|
+
onAfterChange={(value) => {
|
|
228
|
+
// 结束更新后,再重设一遍数据,确保数据的准确性
|
|
229
|
+
this.updateLinkedSliders(
|
|
230
|
+
this.state.allSliderValues,
|
|
231
|
+
index,
|
|
232
|
+
true
|
|
233
|
+
);
|
|
234
|
+
}}
|
|
235
|
+
max={this.totalWeight}
|
|
236
|
+
min={0}
|
|
237
|
+
/>
|
|
238
|
+
</div>
|
|
239
|
+
);
|
|
240
|
+
})}
|
|
241
|
+
{this.totalWeight > 0 && this._enumFields.length > 0 ? (
|
|
242
|
+
<div>
|
|
243
|
+
提示:总比重必须为{this.totalWeight},已分配比重:
|
|
244
|
+
<span
|
|
245
|
+
style={{
|
|
246
|
+
color: hasAssignWeight > this.totalWeight ? "red" : "#000",
|
|
247
|
+
}}
|
|
248
|
+
>
|
|
249
|
+
{hasAssignWeight}
|
|
250
|
+
{hasAssignWeight > this.totalWeight ? `,请修改` : ""}
|
|
251
|
+
</span>
|
|
252
|
+
</div>
|
|
253
|
+
) : null}
|
|
254
|
+
</div>
|
|
255
|
+
);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
// 增强版拖拽组件样式
|
|
2
|
+
.enhanced-sortDrag {
|
|
3
|
+
display: flex;
|
|
4
|
+
flex-direction: column;
|
|
5
|
+
gap: 8px;
|
|
6
|
+
|
|
7
|
+
&.direction-horizontal {
|
|
8
|
+
flex-direction: row;
|
|
9
|
+
flex-wrap: wrap;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
.enhanced-dragItem {
|
|
14
|
+
position: relative;
|
|
15
|
+
display: flex;
|
|
16
|
+
align-items: center;
|
|
17
|
+
justify-content: space-between;
|
|
18
|
+
padding: 12px 16px;
|
|
19
|
+
background: #ffffff;
|
|
20
|
+
border: 1px solid #d9d9d9;
|
|
21
|
+
border-radius: 6px;
|
|
22
|
+
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.03);
|
|
23
|
+
transition: all 0.3s ease;
|
|
24
|
+
|
|
25
|
+
// 水平方向布局
|
|
26
|
+
&.direction-horizontal {
|
|
27
|
+
flex: 0 0 auto;
|
|
28
|
+
min-width: 120px;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// 拖拽状态样式
|
|
32
|
+
&.dragging {
|
|
33
|
+
opacity: 0.6;
|
|
34
|
+
transform: scale(0.95);
|
|
35
|
+
box-shadow: 0 4px 12px rgba(24, 144, 255, 0.2);
|
|
36
|
+
z-index: 1000;
|
|
37
|
+
|
|
38
|
+
.enhanced-dragBody {
|
|
39
|
+
opacity: 0.7;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// 放置目标状态样式
|
|
44
|
+
&.dropping {
|
|
45
|
+
background: #f0f8ff;
|
|
46
|
+
border-color: #1890ff;
|
|
47
|
+
box-shadow: 0 2px 8px rgba(24, 144, 255, 0.15);
|
|
48
|
+
border-style: dashed;
|
|
49
|
+
|
|
50
|
+
&::before {
|
|
51
|
+
content: "";
|
|
52
|
+
position: absolute;
|
|
53
|
+
top: 0;
|
|
54
|
+
left: 0;
|
|
55
|
+
right: 0;
|
|
56
|
+
bottom: 0;
|
|
57
|
+
border: 2px dashed #1890ff;
|
|
58
|
+
border-radius: 6px;
|
|
59
|
+
animation: pulse 1.5s infinite;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// 禁用状态样式
|
|
64
|
+
&.disabled {
|
|
65
|
+
background: #f5f5f5;
|
|
66
|
+
border-color: #e8e8e8;
|
|
67
|
+
|
|
68
|
+
.enhanced-dragBody {
|
|
69
|
+
opacity: 0.5;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// 拖拽句柄样式
|
|
74
|
+
.enhanced-dragHandle {
|
|
75
|
+
display: flex;
|
|
76
|
+
align-items: center;
|
|
77
|
+
justify-content: center;
|
|
78
|
+
min-width: 24px;
|
|
79
|
+
height: 24px;
|
|
80
|
+
|
|
81
|
+
&:hover {
|
|
82
|
+
background: rgba(24, 144, 255, 0.1);
|
|
83
|
+
border-radius: 4px;
|
|
84
|
+
transition: all 0.2s ease;
|
|
85
|
+
|
|
86
|
+
.anticon {
|
|
87
|
+
font-size: 16px;
|
|
88
|
+
color: #666;
|
|
89
|
+
|
|
90
|
+
&:hover {
|
|
91
|
+
color: #1890ff;
|
|
92
|
+
transform: scale(1.1);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
.enhanced-dragBody {
|
|
97
|
+
flex: 1;
|
|
98
|
+
transition: all 0.3s ease;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// 拖拽动画
|
|
105
|
+
@keyframes pulse {
|
|
106
|
+
0% {
|
|
107
|
+
opacity: 0.4;
|
|
108
|
+
}
|
|
109
|
+
50% {
|
|
110
|
+
opacity: 0.8;
|
|
111
|
+
}
|
|
112
|
+
100% {
|
|
113
|
+
opacity: 0.4;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// 拖拽预览样式
|
|
118
|
+
.drag-preview {
|
|
119
|
+
background: #ffffff;
|
|
120
|
+
border: 1px solid #1890ff;
|
|
121
|
+
border-radius: 6px;
|
|
122
|
+
box-shadow: 0 6px 16px rgba(24, 144, 255, 0.25);
|
|
123
|
+
transform: rotate(3deg);
|
|
124
|
+
opacity: 0.9;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// 性能优化:减少不必要的重绘
|
|
128
|
+
.enhanced-sortDrag * {
|
|
129
|
+
transform-style: preserve-3d;
|
|
130
|
+
backface-visibility: hidden;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// 响应式设计
|
|
134
|
+
@media (max-width: 768px) {
|
|
135
|
+
.enhanced-dragItem {
|
|
136
|
+
padding: 10px 12px;
|
|
137
|
+
|
|
138
|
+
&.direction-horizontal {
|
|
139
|
+
min-width: 100px;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|