@neatui/vue 5.1.0 → 5.1.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/MIGRATION-5.0.md +4 -0
- package/package.json +2 -2
- package/src/components/basic/IFollowView.vue +4 -2
- package/src/components/basic/IRouterView.vue +1 -1
- package/src/components/form/Cascader.vue +3 -2
- package/src/components/form/DatePicker.vue +3 -1
- package/src/components/form/DateRangePicker.vue +3 -1
- package/src/components/form/Input.vue +1 -1
- package/src/components/form/InputNumber.vue +1 -1
- package/src/components/form/InputRange.vue +1 -1
- package/src/components/form/MoreSelect.vue +3 -1
- package/src/components/form/MoreSelectTree.vue +3 -1
- package/src/components/form/PageMoreSelect.vue +2 -1
- package/src/components/form/PageSelect.vue +2 -1
- package/src/components/form/SearchMoreSelect.vue +2 -1
- package/src/components/form/SearchSelect.vue +3 -1
- package/src/components/form/Select.vue +3 -1
- package/src/components/form/SelectTree/SelectTree.vue +3 -1
- package/src/components/form/Textarea.vue +1 -1
- package/src/components/form/TimePicker.vue +1 -1
- package/src/components/loader/FormLoader/FormLoader.vue +239 -159
- package/src/components/loader/FormLoader/FormRender.vue +29 -8
- package/src/components/loader/TableLoader/TableLoader.vue +233 -24
|
@@ -101,14 +101,15 @@
|
|
|
101
101
|
<td :colspan="columnCount" :style="virtualPadStyle(virtualSlice.padTop)"></td>
|
|
102
102
|
</tr>
|
|
103
103
|
<tr
|
|
104
|
-
v-for="
|
|
105
|
-
:key="
|
|
106
|
-
:ref="virtualEnabled ? (el: any) => bindVirtualRow(el, item) : undefined"
|
|
104
|
+
v-for="row in bodyRows"
|
|
105
|
+
:key="row.key"
|
|
106
|
+
:ref="virtualEnabled ? (el: any) => bindVirtualRow(el, row.item) : undefined"
|
|
107
107
|
v-bind="rows.attrs"
|
|
108
|
-
v-on="event(rows.event, item, 'td')"
|
|
108
|
+
v-on="event(rows.event, row.item, 'td')"
|
|
109
|
+
:data-level="treeEnabled ? row.level : undefined"
|
|
109
110
|
>
|
|
110
111
|
<td v-if="selected" col-fixed="1" min-width :col-fixed-side="fixedSide('__select')" :style="fixedStyle('__select')">
|
|
111
|
-
<label class="mb-ss" ui-form="@a type:checkbox"><input type="checkbox" v-model="item.__selected" /><span></span></label>
|
|
112
|
+
<label v-if="row.level === 1" class="mb-ss" ui-form="@a type:checkbox"><input type="checkbox" v-model="row.item.__selected" /><span></span></label>
|
|
112
113
|
</td>
|
|
113
114
|
<template v-for="(col, cid) in state.table" :key="cid">
|
|
114
115
|
<td
|
|
@@ -119,12 +120,41 @@
|
|
|
119
120
|
:col-fixed-side="fixedSide(col.field)"
|
|
120
121
|
:style="fixedStyle(col.field)"
|
|
121
122
|
:data-field="col.field"
|
|
123
|
+
:data-tree="row.level > 1 && col.field === treeField ? 1 : undefined"
|
|
122
124
|
>
|
|
123
|
-
<
|
|
125
|
+
<div v-if="treeEnabled && col.field === treeField && row.level === 1" class="table-tree-node" ui-flex="row lm">
|
|
126
|
+
<i
|
|
127
|
+
v-if="childrenOf(row.item).length"
|
|
128
|
+
class="table-tree-toggle ux-none"
|
|
129
|
+
:data-fold="row.item.__folding ? 1 : 0"
|
|
130
|
+
@click.stop="row.item.__folding = !row.item.__folding"
|
|
131
|
+
></i>
|
|
132
|
+
<i v-else class="table-tree-toggle-pad"></i>
|
|
133
|
+
<slot :name="col.field" :col="col" :item="row.item" :idx="row.idx" :parent="row.parent" :level="1"><TableRender :node="col" :data="row.item" /></slot>
|
|
134
|
+
</div>
|
|
135
|
+
<div v-else-if="treeEnabled && col.field === treeField && row.level === 2" class="table-tree-child" :data-last="row.last ? 1 : 0">
|
|
136
|
+
<div v-if="depth > 2" class="table-tree-node" ui-flex="row lm">
|
|
137
|
+
<i
|
|
138
|
+
v-if="childrenOf(row.item).length"
|
|
139
|
+
class="table-tree-toggle ux-none"
|
|
140
|
+
:data-fold="row.item.__folding ? 1 : 0"
|
|
141
|
+
@click.stop="row.item.__folding = !row.item.__folding"
|
|
142
|
+
></i>
|
|
143
|
+
<i v-else class="table-tree-toggle-pad"></i>
|
|
144
|
+
<slot :name="col.field" :col="col" :item="row.item" :idx="row.idx" :parent="row.parent" :level="2"><TableRender v-if="childHas(col, row.item)" :node="col" :data="row.item" /></slot>
|
|
145
|
+
</div>
|
|
146
|
+
<slot v-else :name="col.field" :col="col" :item="row.item" :idx="row.idx" :parent="row.parent" :level="2"><TableRender v-if="childHas(col, row.item)" :node="col" :data="row.item" /></slot>
|
|
147
|
+
</div>
|
|
148
|
+
<div v-else-if="treeEnabled && col.field === treeField" class="table-tree-child" data-pass="1" :data-last="row.parentLast ? 1 : 0">
|
|
149
|
+
<div class="table-tree-child" :data-last="row.last ? 1 : 0">
|
|
150
|
+
<slot :name="col.field" :col="col" :item="row.item" :idx="row.idx" :parent="row.parent" :level="row.level"><TableRender v-if="childHas(col, row.item)" :node="col" :data="row.item" /></slot>
|
|
151
|
+
</div>
|
|
152
|
+
</div>
|
|
153
|
+
<slot v-else :name="col.field" :col="col" :item="row.item" :idx="row.idx" :parent="row.parent" :level="row.level"><TableRender v-if="row.level === 1 || childHas(col, row.item)" :node="col" :data="row.item" /></slot>
|
|
124
154
|
</td>
|
|
125
155
|
</template>
|
|
126
156
|
<td v-if="slots.operate" col-fixed="r" class="ar" :col-fixed-side="fixedSide('__operate')" :style="fixedStyle('__operate')">
|
|
127
|
-
<slot name="operate" :item="item" :idx="idx"></slot>
|
|
157
|
+
<slot name="operate" :item="row.item" :idx="row.idx" :parent="row.parent" :level="row.level"></slot>
|
|
128
158
|
</td>
|
|
129
159
|
</tr>
|
|
130
160
|
<tr v-if="virtualSlice.padBottom" class="table-virtual-pad" aria-hidden="true">
|
|
@@ -158,21 +188,71 @@
|
|
|
158
188
|
</IFollowView>
|
|
159
189
|
</div>
|
|
160
190
|
</li>
|
|
161
|
-
<
|
|
162
|
-
<
|
|
163
|
-
<
|
|
164
|
-
<
|
|
165
|
-
<
|
|
166
|
-
<
|
|
167
|
-
|
|
168
|
-
<
|
|
169
|
-
|
|
170
|
-
|
|
191
|
+
<template v-for="(item, idx) in lists" :key="rowKeyOf(item, idx)">
|
|
192
|
+
<li class="bg-fore r-sm my-sl n-ms">
|
|
193
|
+
<table>
|
|
194
|
+
<tbody>
|
|
195
|
+
<template v-for="(col, cid) in state.table" :key="cid">
|
|
196
|
+
<tr v-if="!col.__close">
|
|
197
|
+
<td class="nowrap vt n-ss">{{ col.label }}:</td>
|
|
198
|
+
<td class="n-ss">
|
|
199
|
+
<TableRender :node="col" :data="item" />
|
|
200
|
+
</td>
|
|
201
|
+
</tr>
|
|
202
|
+
</template>
|
|
203
|
+
</tbody>
|
|
204
|
+
</table>
|
|
205
|
+
<div v-if="slots.operate"><slot name="operate" :item="item" :idx="idx" :parent="noParent" :level="1"></slot></div>
|
|
206
|
+
<p v-if="treeEnabled && childrenOf(item).length" class="mt-ss">
|
|
207
|
+
<span class="ux-none co-main fs-sm" @click="item.__folding = !item.__folding">
|
|
208
|
+
{{ item.__folding ? words['common.expand'] || '展开' : words['common.collapse'] || '收起' }}
|
|
209
|
+
</span>
|
|
210
|
+
</p>
|
|
211
|
+
</li>
|
|
212
|
+
<template v-if="treeEnabled && !item.__folding">
|
|
213
|
+
<template v-for="(child, cidx) in childrenOf(item)" :key="`${rowKeyOf(item, idx)}-${cidx}`">
|
|
214
|
+
<li v-if="childVisible(child)" class="table-tree-item bg-fore r-sm my-sl n-ms">
|
|
215
|
+
<table>
|
|
216
|
+
<tbody>
|
|
217
|
+
<template v-for="(col, cid) in state.table" :key="cid">
|
|
218
|
+
<tr v-if="!col.__close && childHas(col, child)">
|
|
219
|
+
<td class="nowrap vt n-ss">{{ col.label }}:</td>
|
|
220
|
+
<td class="n-ss">
|
|
221
|
+
<TableRender :node="col" :data="child" />
|
|
222
|
+
</td>
|
|
223
|
+
</tr>
|
|
224
|
+
</template>
|
|
225
|
+
</tbody>
|
|
226
|
+
</table>
|
|
227
|
+
<div v-if="slots.operate"><slot name="operate" :item="child" :idx="cidx" :parent="item" :level="2"></slot></div>
|
|
228
|
+
<p v-if="depth > 2 && childrenOf(child).length" class="mt-ss">
|
|
229
|
+
<span class="ux-none co-main fs-sm" @click="child.__folding = !child.__folding">
|
|
230
|
+
{{ child.__folding ? words['common.expand'] || '展开' : words['common.collapse'] || '收起' }}
|
|
231
|
+
</span>
|
|
232
|
+
</p>
|
|
233
|
+
</li>
|
|
234
|
+
<template v-if="depth > 2 && !child.__folding">
|
|
235
|
+
<template v-for="(grand, gidx) in childrenOf(child)" :key="`${rowKeyOf(item, idx)}-${cidx}-${gidx}`">
|
|
236
|
+
<li v-if="childVisible(grand)" class="table-tree-item table-tree-item-3 bg-fore r-sm my-sl n-ms">
|
|
237
|
+
<table>
|
|
238
|
+
<tbody>
|
|
239
|
+
<template v-for="(col, cid) in state.table" :key="cid">
|
|
240
|
+
<tr v-if="!col.__close && childHas(col, grand)">
|
|
241
|
+
<td class="nowrap vt n-ss">{{ col.label }}:</td>
|
|
242
|
+
<td class="n-ss">
|
|
243
|
+
<TableRender :node="col" :data="grand" />
|
|
244
|
+
</td>
|
|
245
|
+
</tr>
|
|
246
|
+
</template>
|
|
247
|
+
</tbody>
|
|
248
|
+
</table>
|
|
249
|
+
<div v-if="slots.operate"><slot name="operate" :item="grand" :idx="gidx" :parent="child" :level="3"></slot></div>
|
|
250
|
+
</li>
|
|
251
|
+
</template>
|
|
171
252
|
</template>
|
|
172
|
-
</
|
|
173
|
-
</
|
|
174
|
-
|
|
175
|
-
</li>
|
|
253
|
+
</template>
|
|
254
|
+
</template>
|
|
255
|
+
</template>
|
|
176
256
|
</ul>
|
|
177
257
|
</div>
|
|
178
258
|
<div v-if="!lists.length" class="ny-ls o-mm" ui-flex="col cm">
|
|
@@ -245,6 +325,12 @@
|
|
|
245
325
|
rowKey?: string | ((item: any, idx: number) => string | number);
|
|
246
326
|
/** 虚拟滚动(仅表格形态):只渲染可视区附近的行,需要容器限定高度 */
|
|
247
327
|
virtual?: boolean | { estimate?: number; overscan?: number };
|
|
328
|
+
/** 子行:字段名或函数,返回子行数组;子行沿用同一套列和插槽,折叠状态记在行的 __folding 上 */
|
|
329
|
+
children?: string | ((item: any) => any[]);
|
|
330
|
+
/** 折叠按钮和树线所在列的 field,默认第一个可见的非固定列 */
|
|
331
|
+
tree?: string;
|
|
332
|
+
/** 树的层数:默认 2(父行 + 子行);3 时子行再按 children 取第三层 */
|
|
333
|
+
depth?: number;
|
|
248
334
|
}
|
|
249
335
|
// 外部入参
|
|
250
336
|
const props: any = withDefaults(defineProps<Props>(), {
|
|
@@ -258,6 +344,7 @@
|
|
|
258
344
|
allSelected: false,
|
|
259
345
|
layout: 'auto',
|
|
260
346
|
virtual: false,
|
|
347
|
+
depth: 2,
|
|
261
348
|
});
|
|
262
349
|
|
|
263
350
|
/**
|
|
@@ -347,7 +434,7 @@
|
|
|
347
434
|
watch(
|
|
348
435
|
() => props.table,
|
|
349
436
|
() => {
|
|
350
|
-
const _cache = env.client ? JSON.parse(window.localStorage.getItem(`${name}_table`) || '{}') : {};
|
|
437
|
+
const _cache = env.client ? JSON.parse(window.localStorage.getItem(`${name.value}_table`) || '{}') : {};
|
|
351
438
|
state.__last = 0;
|
|
352
439
|
state.table = deepcopy(props.table || [])
|
|
353
440
|
?.map((col: TableColumn, idx: number) => {
|
|
@@ -426,7 +513,7 @@
|
|
|
426
513
|
state.__last = idx;
|
|
427
514
|
}
|
|
428
515
|
});
|
|
429
|
-
if (env.client) window.localStorage.setItem(`${name}_table`, JSON.stringify(_table));
|
|
516
|
+
if (env.client) window.localStorage.setItem(`${name.value}_table`, JSON.stringify(_table));
|
|
430
517
|
},
|
|
431
518
|
{ deep: true },
|
|
432
519
|
);
|
|
@@ -540,6 +627,25 @@
|
|
|
540
627
|
fixedCells.clear();
|
|
541
628
|
});
|
|
542
629
|
|
|
630
|
+
// 子行
|
|
631
|
+
const treeEnabled = computed(() => !!props.children);
|
|
632
|
+
const childrenOf = (item: any): any[] => {
|
|
633
|
+
const key = props.children;
|
|
634
|
+
const list = isFunction(key) ? key(item) : key && item ? item[key] : undefined;
|
|
635
|
+
return Array.isArray(list) ? list : [];
|
|
636
|
+
};
|
|
637
|
+
// 父行的 parent 为空;声明成 any,插槽里两层行的 parent 类型才一致
|
|
638
|
+
const noParent: any = undefined;
|
|
639
|
+
// 子行常常只有部分字段,没有的列留空,不显示占位符
|
|
640
|
+
const childHas = (col: TableColumn, child: any) =>
|
|
641
|
+
(!!child && typeof child === 'object' && hasOwnProp(child, col.field)) || (col.value !== undefined && col.value !== null) || !!(col as any).child || !!(col as any).popup;
|
|
642
|
+
const childVisible = (child: any) => state.table.some((col) => !col.__close && childHas(col, child));
|
|
643
|
+
const treeField = computed(() => {
|
|
644
|
+
if (props.tree) return props.tree;
|
|
645
|
+
const visible = state.table.filter((col) => !col.__close);
|
|
646
|
+
return (visible.find((col) => !col.fixed) || visible[0])?.field;
|
|
647
|
+
});
|
|
648
|
+
|
|
543
649
|
// 行标识
|
|
544
650
|
const rowKeyOf = (item: any, idx: number) => {
|
|
545
651
|
const key = props.rowKey;
|
|
@@ -553,7 +659,8 @@
|
|
|
553
659
|
* 行高各不相同(单元格可能换行、放表单控件),按行对象缓存实测高度,
|
|
554
660
|
* 没测过的按已测平均值估算;行对象不变则删行、排序后缓存仍有效。
|
|
555
661
|
*/
|
|
556
|
-
|
|
662
|
+
// 行高按父行测量,展开的子行会让偏移失真,开了子行就不走虚拟滚动
|
|
663
|
+
const virtualEnabled = computed(() => !!props.virtual && !props.children);
|
|
557
664
|
const virtualEstimate = computed(() => (isObject(props.virtual) && props.virtual.estimate) || 48);
|
|
558
665
|
const virtualOverscan = computed(() => (isObject(props.virtual) && props.virtual.overscan) || 600);
|
|
559
666
|
const scrollTop = ref(0);
|
|
@@ -586,6 +693,30 @@
|
|
|
586
693
|
return { rows, padTop: offsets[start] || 0, padBottom: Math.max(0, offsets[offsets.length - 1] - (offsets[end] || 0)) };
|
|
587
694
|
});
|
|
588
695
|
|
|
696
|
+
/** 桌面表格逐行渲染:开了子行时把树按展开状态摊平,记下层级、上级和是否同级最后一行(画树线用) */
|
|
697
|
+
const bodyRows = computed(() => {
|
|
698
|
+
if (!treeEnabled.value) {
|
|
699
|
+
return virtualSlice.value.rows.map(({ item, idx }: any) => ({ item, idx, level: 1, parent: noParent, key: rowKeyOf(item, idx), last: false, parentLast: false }));
|
|
700
|
+
}
|
|
701
|
+
const out: any[] = [];
|
|
702
|
+
props.lists.forEach((item: any, idx: number) => {
|
|
703
|
+
const key = rowKeyOf(item, idx);
|
|
704
|
+
out.push({ item, idx, level: 1, parent: noParent, key, last: false, parentLast: false });
|
|
705
|
+
if (item?.__folding) return;
|
|
706
|
+
const kids = childrenOf(item);
|
|
707
|
+
kids.forEach((child, cidx) => {
|
|
708
|
+
const last = cidx === kids.length - 1;
|
|
709
|
+
out.push({ item: child, idx: cidx, level: 2, parent: item, key: `${key}-${cidx}`, last, parentLast: false });
|
|
710
|
+
if (props.depth < 3 || child?.__folding) return;
|
|
711
|
+
const grand = childrenOf(child);
|
|
712
|
+
grand.forEach((g, gidx) => {
|
|
713
|
+
out.push({ item: g, idx: gidx, level: 3, parent: child, key: `${key}-${cidx}-${gidx}`, last: gidx === grand.length - 1, parentLast: last });
|
|
714
|
+
});
|
|
715
|
+
});
|
|
716
|
+
});
|
|
717
|
+
return out;
|
|
718
|
+
});
|
|
719
|
+
|
|
589
720
|
let scrollFrame = 0;
|
|
590
721
|
const onVirtualScroll = () => {
|
|
591
722
|
if (!virtualEnabled.value || scrollFrame) return;
|
|
@@ -706,3 +837,81 @@
|
|
|
706
837
|
return { ..._event };
|
|
707
838
|
};
|
|
708
839
|
</script>
|
|
840
|
+
<style lang="scss" scoped>
|
|
841
|
+
.table-tree-node {
|
|
842
|
+
flex-wrap: nowrap;
|
|
843
|
+
}
|
|
844
|
+
.table-tree-toggle,
|
|
845
|
+
.table-tree-toggle-pad {
|
|
846
|
+
flex: none;
|
|
847
|
+
position: relative;
|
|
848
|
+
width: 1em;
|
|
849
|
+
height: 1em;
|
|
850
|
+
margin-right: 0.25em;
|
|
851
|
+
}
|
|
852
|
+
.table-tree-toggle {
|
|
853
|
+
cursor: pointer;
|
|
854
|
+
&::after {
|
|
855
|
+
position: absolute;
|
|
856
|
+
content: '';
|
|
857
|
+
top: 50%;
|
|
858
|
+
left: 50%;
|
|
859
|
+
border-width: 0.35em 0 0.35em 0.35em;
|
|
860
|
+
border-style: solid;
|
|
861
|
+
border-color: transparent transparent transparent currentColor;
|
|
862
|
+
border-radius: 30%;
|
|
863
|
+
transform: translate(-30%, -50%) rotate(90deg);
|
|
864
|
+
transition: transform 0.25s;
|
|
865
|
+
}
|
|
866
|
+
&[data-fold='1']::after {
|
|
867
|
+
transform: translate(-30%, -50%) rotate(0deg);
|
|
868
|
+
}
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
// 树线画在内层,纵向裁到单元格高度,横向不裁,固定列的阴影照常显示
|
|
872
|
+
td[data-tree] {
|
|
873
|
+
clip-path: inset(0 -100vw);
|
|
874
|
+
&:not([col-fixed]) {
|
|
875
|
+
position: relative;
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
.table-tree-child {
|
|
879
|
+
position: relative;
|
|
880
|
+
padding-left: 1.75em;
|
|
881
|
+
&::before,
|
|
882
|
+
&::after {
|
|
883
|
+
position: absolute;
|
|
884
|
+
content: '';
|
|
885
|
+
left: 0.5em;
|
|
886
|
+
background: var(--co-line);
|
|
887
|
+
}
|
|
888
|
+
&::before {
|
|
889
|
+
width: 1px;
|
|
890
|
+
top: -100vh;
|
|
891
|
+
bottom: -100vh;
|
|
892
|
+
}
|
|
893
|
+
&[data-last='1']::before {
|
|
894
|
+
bottom: 50%;
|
|
895
|
+
}
|
|
896
|
+
&::after {
|
|
897
|
+
width: 0.85em;
|
|
898
|
+
height: 1px;
|
|
899
|
+
top: 50%;
|
|
900
|
+
}
|
|
901
|
+
}
|
|
902
|
+
// 第三层:外层只画上一层的竖线(上一层是最后一行就不画),内层画自己的
|
|
903
|
+
.table-tree-child[data-pass='1'] {
|
|
904
|
+
&::after {
|
|
905
|
+
display: none;
|
|
906
|
+
}
|
|
907
|
+
&[data-last='1']::before {
|
|
908
|
+
display: none;
|
|
909
|
+
}
|
|
910
|
+
}
|
|
911
|
+
.table-tree-item {
|
|
912
|
+
margin-left: 1.5em;
|
|
913
|
+
}
|
|
914
|
+
.table-tree-item-3 {
|
|
915
|
+
margin-left: 3em;
|
|
916
|
+
}
|
|
917
|
+
</style>
|