@vue-ui-kit/ant 2.4.3 → 2.4.5
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/cjs/index.js +3 -3
- package/dist/es/index.js +719 -610
- package/dist/index.d.ts +3096 -23
- package/package.json +1 -2
- package/src/declarations/antProxy.ts +21 -1
- package/src/index.ts +1 -0
- package/src/packages/components/PGrid.vue +90 -6
- package/src/packages/components/PromisePicker.vue +22 -14
- package/src/packages/components/RenderAntCell.tsx +1 -1
- package/src/packages/components/RenderDefaultSlots.tsx +6 -2
- package/src/packages/store/renderStore.tsx +1 -1
- package/src/packages/utils/autoViewportBox.ts +160 -0
- package/src/packages/utils/config.ts +4 -0
- package/dist/declarations/antProxy.d.ts +0 -373
- package/dist/declarations/type.d.ts +0 -1
- package/dist/packages/components/CollapseCard.vue.d.ts +0 -29
- package/dist/packages/components/PCanvasTable.vue.d.ts +0 -177
- package/dist/packages/components/PForm.vue.d.ts +0 -22
- package/dist/packages/components/PFormCol.vue.d.ts +0 -23
- package/dist/packages/components/PFormGroup.vue.d.ts +0 -38
- package/dist/packages/components/PGroupBlock.vue.d.ts +0 -16
- package/dist/packages/components/PromisePicker.vue.d.ts +0 -25
- package/dist/packages/components/RenderAntCell.d.ts +0 -28
- package/dist/packages/components/RenderAntItem.d.ts +0 -18
- package/dist/packages/components/RenderDefaultSlots.d.ts +0 -34
- package/dist/packages/components/RenderEditCell.d.ts +0 -25
- package/dist/packages/components/RenderItemSlots.d.ts +0 -25
- package/dist/packages/components/RenderTitleSlots.d.ts +0 -16
- package/dist/packages/hooks/useMessage.d.ts +0 -8
- package/dist/packages/renders/Icon.d.ts +0 -4
- package/dist/packages/renders/TableInput.vue.d.ts +0 -25
- package/dist/packages/store/renderStore.d.ts +0 -16
- package/dist/packages/utils/AFormatters.d.ts +0 -9
- package/dist/packages/utils/config.d.ts +0 -30
- package/dist/packages/utils/core.d.ts +0 -10
- package/dist/packages/utils/is.d.ts +0 -4
- package/dist/packages/utils/treeHelper.d.ts +0 -19
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vue-ui-kit/ant",
|
|
3
|
-
"version": "2.4.
|
|
3
|
+
"version": "2.4.5",
|
|
4
4
|
"description": "Vue3 UI Kit based on Ant Design",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/cjs/index.js",
|
|
@@ -77,7 +77,6 @@
|
|
|
77
77
|
"typescript-transform-paths": "^3.5.0",
|
|
78
78
|
"vite": "^8.0.8",
|
|
79
79
|
"vite-plugin-dts": "^4.5.4",
|
|
80
|
-
"vite-tsconfig-paths": "^5.1.4",
|
|
81
80
|
"vue": "^3.4.38"
|
|
82
81
|
}
|
|
83
82
|
}
|
|
@@ -164,7 +164,8 @@ export interface ColumnProps<D extends Recordable = Recordable> extends Omit<
|
|
|
164
164
|
export interface PButtonProps extends Omit<ButtonProps, 'type'> {
|
|
165
165
|
content?: string | (() => any);
|
|
166
166
|
icon?: string;
|
|
167
|
-
|
|
167
|
+
/** 与 `a-button` 一致,便于模板类型检查 */
|
|
168
|
+
type?: ButtonProps['type'];
|
|
168
169
|
}
|
|
169
170
|
|
|
170
171
|
export interface ToolbarButtonProps extends PButtonProps {
|
|
@@ -231,6 +232,16 @@ export type PGridProps<D extends Recordable = Recordable, F extends Recordable =
|
|
|
231
232
|
proxyConfig?: ProxyConfig<D, F>;
|
|
232
233
|
tableConfig?: TableProps<D>;
|
|
233
234
|
scrollMode?: 'outer' | 'inner';
|
|
235
|
+
/**
|
|
236
|
+
* 为 true 时根容器按视口从左上角铺满(与 v-rest 同源),用于外层未声明高度、靠内部撑开的布局。
|
|
237
|
+
* 偏移见 `autoBoxSizeOffset`。
|
|
238
|
+
*/
|
|
239
|
+
autoBoxSize?: boolean;
|
|
240
|
+
/**
|
|
241
|
+
* `autoBoxSize` 时距视口右侧/底部的预留像素;数字或 `"12px"` 同时作用于两侧;对象可分别指定 `right` / `bottom`。
|
|
242
|
+
* 未传时使用 `setUIKitConfig({ grid: { autoBoxSizeOffset } })` 的全局值。
|
|
243
|
+
*/
|
|
244
|
+
autoBoxSizeOffset?: number | string | { right?: number; bottom?: number };
|
|
234
245
|
// 重置后不自动提交
|
|
235
246
|
lazyReset?: boolean;
|
|
236
247
|
/**
|
|
@@ -245,6 +256,15 @@ export type PGridProps<D extends Recordable = Recordable, F extends Recordable =
|
|
|
245
256
|
* 虚拟列表 Y区计算高度
|
|
246
257
|
*/
|
|
247
258
|
renderY?: number;
|
|
259
|
+
/**
|
|
260
|
+
* 适应「外层高度由内容撑开」的布局(典型如弹窗 `PromisePicker`)。
|
|
261
|
+
*
|
|
262
|
+
* 开启后:
|
|
263
|
+
* - 根容器/表体宿主不再 `flex:1` 强行撑满(避免父无固定高度时塌陷到 0);
|
|
264
|
+
* - `a-table` 的纵向 `scroll.y` 作为「最多滚动到多少」的上限,由 `renderY` 显式指定;
|
|
265
|
+
* 若不传 `renderY`,使用基于视口的默认值。内容少时不会出现行区域下方的大片空白。
|
|
266
|
+
*/
|
|
267
|
+
fitContent?: boolean;
|
|
248
268
|
};
|
|
249
269
|
|
|
250
270
|
export interface RenderOptions {
|
package/src/index.ts
CHANGED
|
@@ -11,6 +11,12 @@
|
|
|
11
11
|
import { $confirm } from '@/hooks/useMessage';
|
|
12
12
|
import Icon from '@/renders/Icon';
|
|
13
13
|
import { getGridDefaults } from '@/utils/config';
|
|
14
|
+
import {
|
|
15
|
+
createAutoViewportBoxController,
|
|
16
|
+
parseAutoViewportBoxOffset,
|
|
17
|
+
type AutoViewportBoxController,
|
|
18
|
+
type AutoViewportBoxOffsetInput,
|
|
19
|
+
} from '@/utils/autoViewportBox';
|
|
14
20
|
import { cleanCol, defaultLabelCol } from '@/utils/core';
|
|
15
21
|
import { isGoodValue } from '@/utils/is';
|
|
16
22
|
import { eachTree } from '@/utils/treeHelper';
|
|
@@ -55,6 +61,8 @@
|
|
|
55
61
|
lazyReset: () => getGridDefaults().lazyReset ?? false,
|
|
56
62
|
fitHeight: () => getGridDefaults().fitHeight ?? 30,
|
|
57
63
|
striped: () => getGridDefaults().striped ?? false,
|
|
64
|
+
autoBoxSize: false,
|
|
65
|
+
fitContent: false,
|
|
58
66
|
});
|
|
59
67
|
|
|
60
68
|
const {
|
|
@@ -67,6 +75,16 @@
|
|
|
67
75
|
selectConfig,
|
|
68
76
|
scrollMode,
|
|
69
77
|
} = toRefs(props);
|
|
78
|
+
|
|
79
|
+
/** 局部 `autoBoxSizeOffset` 优先,否则用 `getGridDefaults().autoBoxSizeOffset`(`setUIKitConfig`) */
|
|
80
|
+
const resolvedAutoBoxSizeOffset = computed<AutoViewportBoxOffsetInput>(() => {
|
|
81
|
+
const local = props.autoBoxSizeOffset;
|
|
82
|
+
if (local !== undefined && local !== null) {
|
|
83
|
+
return local;
|
|
84
|
+
}
|
|
85
|
+
return getGridDefaults().autoBoxSizeOffset as AutoViewportBoxOffsetInput;
|
|
86
|
+
});
|
|
87
|
+
|
|
70
88
|
const loading = reactive({
|
|
71
89
|
table: false,
|
|
72
90
|
toolbar: false,
|
|
@@ -360,9 +378,9 @@
|
|
|
360
378
|
return lazy ? Promise.resolve() : debounceFetchData();
|
|
361
379
|
};
|
|
362
380
|
const defaultTableConfig = {
|
|
363
|
-
size: 'small',
|
|
381
|
+
size: 'small' as const,
|
|
364
382
|
sticky: true,
|
|
365
|
-
transformCellText: ({ text }) => {
|
|
383
|
+
transformCellText: ({ text }: { text: any }) => {
|
|
366
384
|
return isArray(text) && text.length === 0 ? '-' : text;
|
|
367
385
|
},
|
|
368
386
|
};
|
|
@@ -412,6 +430,42 @@
|
|
|
412
430
|
() => nextTick(() => resizeTable()),
|
|
413
431
|
{ flush: 'post' },
|
|
414
432
|
);
|
|
433
|
+
|
|
434
|
+
let autoViewportBoxCtrl: AutoViewportBoxController | null = null;
|
|
435
|
+
const clearAutoViewportBox = () => {
|
|
436
|
+
autoViewportBoxCtrl?.destroy();
|
|
437
|
+
autoViewportBoxCtrl = null;
|
|
438
|
+
};
|
|
439
|
+
const syncAutoViewportBox = () => {
|
|
440
|
+
clearAutoViewportBox();
|
|
441
|
+
if (!props.autoBoxSize || !boxEl.value) return;
|
|
442
|
+
autoViewportBoxCtrl = createAutoViewportBoxController(
|
|
443
|
+
() => boxEl.value ?? undefined,
|
|
444
|
+
() => parseAutoViewportBoxOffset(resolvedAutoBoxSizeOffset.value),
|
|
445
|
+
{
|
|
446
|
+
onLayout: () => {
|
|
447
|
+
nextTick(() => resizeTable());
|
|
448
|
+
},
|
|
449
|
+
},
|
|
450
|
+
);
|
|
451
|
+
autoViewportBoxCtrl.attach();
|
|
452
|
+
};
|
|
453
|
+
|
|
454
|
+
watch(
|
|
455
|
+
() => props.autoBoxSize,
|
|
456
|
+
(on) => {
|
|
457
|
+
if (!on) clearAutoViewportBox();
|
|
458
|
+
else nextTick(() => syncAutoViewportBox());
|
|
459
|
+
},
|
|
460
|
+
);
|
|
461
|
+
watch(
|
|
462
|
+
resolvedAutoBoxSizeOffset,
|
|
463
|
+
() => {
|
|
464
|
+
if (props.autoBoxSize) autoViewportBoxCtrl?.update();
|
|
465
|
+
},
|
|
466
|
+
{ deep: true },
|
|
467
|
+
);
|
|
468
|
+
|
|
415
469
|
let resizeRaf: number | null = null;
|
|
416
470
|
|
|
417
471
|
const resolveTableHeaderEl = (wrapper: HTMLElement): HTMLElement | null => {
|
|
@@ -431,6 +485,13 @@
|
|
|
431
485
|
renderHeight.value = props.renderY as number;
|
|
432
486
|
return;
|
|
433
487
|
}
|
|
488
|
+
// 内容撑开模式(弹窗等):scroll.y 作为上限,内容不足时 a-table-body 按自然高度展示。
|
|
489
|
+
if (props.fitContent) {
|
|
490
|
+
const vh = typeof window !== 'undefined' ? window.innerHeight : 800;
|
|
491
|
+
const reserve = props.fitHeight ?? 30;
|
|
492
|
+
renderHeight.value = Math.max(Math.floor(vh * 0.6) - reserve, 160);
|
|
493
|
+
return;
|
|
494
|
+
}
|
|
434
495
|
const wrapperH = tableWrapperEl.value.clientHeight;
|
|
435
496
|
const footerH = tableFooterEl.value?.offsetHeight ?? 0;
|
|
436
497
|
const tableHeaderEl = resolveTableHeaderEl(tableWrapperEl.value);
|
|
@@ -511,7 +572,10 @@
|
|
|
511
572
|
tableFooterResizeObserver.observe(tableFooterEl.value);
|
|
512
573
|
}
|
|
513
574
|
resetQueryFormData(props.manualFetch);
|
|
514
|
-
nextTick(() =>
|
|
575
|
+
nextTick(() => {
|
|
576
|
+
if (props.autoBoxSize) syncAutoViewportBox();
|
|
577
|
+
resizeTable();
|
|
578
|
+
});
|
|
515
579
|
});
|
|
516
580
|
const renderContent = (content: string | (() => any)) => {
|
|
517
581
|
if (isFunction(content)) {
|
|
@@ -535,6 +599,7 @@
|
|
|
535
599
|
...c,
|
|
536
600
|
}));
|
|
537
601
|
onBeforeUnmount(() => {
|
|
602
|
+
clearAutoViewportBox();
|
|
538
603
|
if (resizeRaf) cancelAnimationFrame(resizeRaf);
|
|
539
604
|
window.removeEventListener('resize', resizeTable);
|
|
540
605
|
observer.disconnect();
|
|
@@ -545,7 +610,18 @@
|
|
|
545
610
|
});
|
|
546
611
|
</script>
|
|
547
612
|
<template>
|
|
548
|
-
<div
|
|
613
|
+
<div
|
|
614
|
+
ref="boxEl"
|
|
615
|
+
:class="[
|
|
616
|
+
'p-wrapper flex flex-col gap-8px',
|
|
617
|
+
fitContent
|
|
618
|
+
? 'w-full h-auto'
|
|
619
|
+
: autoBoxSize
|
|
620
|
+
? 'overflow-y-auto min-h-0 min-w-0 flex-1 w-full'
|
|
621
|
+
: 'overflow-y-auto h-full',
|
|
622
|
+
]"
|
|
623
|
+
v-bind="attrs"
|
|
624
|
+
>
|
|
549
625
|
<div v-if="mode === 'bad'">请检查配置</div>
|
|
550
626
|
<template v-else>
|
|
551
627
|
<div
|
|
@@ -658,9 +734,17 @@
|
|
|
658
734
|
</div>
|
|
659
735
|
<div
|
|
660
736
|
ref="tableWrapperEl"
|
|
661
|
-
:class="
|
|
737
|
+
:class="
|
|
738
|
+
fitContent
|
|
739
|
+
? 'p-pane'
|
|
740
|
+
: `p-pane flex-1 h-0 min-h-0 flex flex-col p-${scrollMode ?? 'inner'}-scroll`
|
|
741
|
+
"
|
|
662
742
|
>
|
|
663
|
-
<div
|
|
743
|
+
<div
|
|
744
|
+
:class="
|
|
745
|
+
fitContent ? 'p-grid-table-body' : 'p-grid-table-body flex-1 min-h-0 overflow-hidden'
|
|
746
|
+
"
|
|
747
|
+
>
|
|
664
748
|
<a-table
|
|
665
749
|
:key="renderTableKey + '_table'"
|
|
666
750
|
:row-key="rowKey ?? 'id'"
|
|
@@ -4,15 +4,14 @@
|
|
|
4
4
|
name="PromisePicker"
|
|
5
5
|
generic="D extends Recordable = Recordable, F extends Recordable = Recordable"
|
|
6
6
|
>
|
|
7
|
-
import {
|
|
8
|
-
import { PGridInstance, PromisePickerProps } from '#/antProxy';
|
|
7
|
+
import { PGridInstance, PGridProps, PromisePickerProps } from '#/antProxy';
|
|
9
8
|
import PGrid from '@/components/PGrid.vue';
|
|
10
9
|
import { $warning } from '@/hooks/useMessage';
|
|
11
10
|
import { Button as AButton, Modal as AModal } from 'ant-design-vue';
|
|
11
|
+
import { computed, reactive, ref } from 'vue';
|
|
12
12
|
|
|
13
13
|
const gridEl = ref<PGridInstance<D>>();
|
|
14
14
|
|
|
15
|
-
const rendered = ref(false);
|
|
16
15
|
const props = withDefaults(defineProps<PromisePickerProps<D, F>>(), {
|
|
17
16
|
title: '数据选择',
|
|
18
17
|
width: '70%',
|
|
@@ -21,6 +20,24 @@
|
|
|
21
20
|
beforePick: () => Promise.resolve(),
|
|
22
21
|
});
|
|
23
22
|
const isMultiple = computed(() => props.gridSetting.selectConfig?.multiple);
|
|
23
|
+
/**
|
|
24
|
+
* 合并内置的 `fitContent: true`:
|
|
25
|
+
* - 弹窗容器由内容撑开,外层不写死高度;
|
|
26
|
+
* - 数据少时按内容自然高显示,避免出现大面积空白;
|
|
27
|
+
* - 数据多时由外层 `max-height` 限制总高度,表格内部 `scroll.y` 作为上限滚动。
|
|
28
|
+
* 用户在 `gridSetting` 中若显式传入 `fitContent`/`renderY` 可覆盖。
|
|
29
|
+
*/
|
|
30
|
+
const mergedGridSetting = computed<PGridProps<D, F>>(() => ({
|
|
31
|
+
fitContent: true,
|
|
32
|
+
...props.gridSetting,
|
|
33
|
+
}));
|
|
34
|
+
const pickerBodyStyle = {
|
|
35
|
+
display: 'flex',
|
|
36
|
+
flexDirection: 'column',
|
|
37
|
+
minHeight: 0,
|
|
38
|
+
maxHeight: 'min(80dvh, calc(100dvh - 200px))',
|
|
39
|
+
overflow: 'hidden',
|
|
40
|
+
} as const;
|
|
24
41
|
let resolvePromise: (
|
|
25
42
|
value: { row: D; field?: string } | PromiseLike<{ row: D; field?: string }>,
|
|
26
43
|
) => void;
|
|
@@ -61,27 +78,18 @@
|
|
|
61
78
|
});
|
|
62
79
|
}
|
|
63
80
|
};
|
|
64
|
-
onMounted(() => {});
|
|
65
81
|
defineExpose({
|
|
66
82
|
pick: () =>
|
|
67
83
|
new Promise<{ row: D; field?: string } | D[]>((resolve, reject) => {
|
|
68
84
|
resolvePromise = resolve;
|
|
69
85
|
rejectPromise = reject;
|
|
70
|
-
rendered.value = false;
|
|
71
86
|
visible.modal = true;
|
|
72
|
-
nextTick(() => {
|
|
73
|
-
rendered.value = true;
|
|
74
|
-
});
|
|
75
87
|
}),
|
|
76
88
|
pickMultiple: () =>
|
|
77
89
|
new Promise<{ row: D; field?: string } | D[]>((resolve, reject) => {
|
|
78
90
|
multipleResolver = resolve;
|
|
79
91
|
rejectPromise = reject;
|
|
80
|
-
rendered.value = false;
|
|
81
92
|
visible.modal = true;
|
|
82
|
-
nextTick(() => {
|
|
83
|
-
rendered.value = true;
|
|
84
|
-
});
|
|
85
93
|
}),
|
|
86
94
|
grid: gridEl,
|
|
87
95
|
hide: () => {
|
|
@@ -99,8 +107,8 @@
|
|
|
99
107
|
@cancel="handleCancel"
|
|
100
108
|
:body-style="bodyStyle"
|
|
101
109
|
>
|
|
102
|
-
<div :style="
|
|
103
|
-
<p-grid v-bind="
|
|
110
|
+
<div :style="pickerBodyStyle">
|
|
111
|
+
<p-grid v-bind="mergedGridSetting" ref="gridEl" @pick="selectRow" />
|
|
104
112
|
</div>
|
|
105
113
|
<template v-if="isMultiple" #footer>
|
|
106
114
|
<div class="w-full text-right p-2">
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { defineComponent } from 'vue';
|
|
2
|
-
import { ColumnProps } from '#/antProxy';
|
|
2
|
+
import type { ColumnProps } from '#/antProxy';
|
|
3
3
|
import { renderAntFormat } from '@/utils/AFormatters';
|
|
4
4
|
import RenderAntCell from '@/components/RenderAntCell';
|
|
5
5
|
|
|
@@ -7,7 +7,11 @@ export default defineComponent(
|
|
|
7
7
|
<D extends Recordable = Recordable>(props: {
|
|
8
8
|
tableData: D[];
|
|
9
9
|
row: D;
|
|
10
|
-
|
|
10
|
+
/**
|
|
11
|
+
* `PGrid` / `columns` 仍为 `ColumnProps<D>[]`;此处不能写 `ColumnProps<D>`:
|
|
12
|
+
* 模板里 `a-table` 的 `record` 无泛型上下文,会被推断为 `Record<string, any>`,与 `formatter` 里 `row: D` 逆变冲突(TS2322)。
|
|
13
|
+
*/
|
|
14
|
+
column: ColumnProps<any>;
|
|
11
15
|
rowIndex: number;
|
|
12
16
|
defaultHandler?: {
|
|
13
17
|
[key: string]: (...args: any[]) => any;
|
|
@@ -30,7 +30,7 @@ import { computed, ref, Ref } from 'vue';
|
|
|
30
30
|
|
|
31
31
|
interface BtnOptions extends Omit<ButtonProps, 'type'> {
|
|
32
32
|
content?: string;
|
|
33
|
-
type?:
|
|
33
|
+
type?: ButtonProps['type'];
|
|
34
34
|
getContent?: (p: RenderTableParams) => string;
|
|
35
35
|
dynamicClassName?: (p: RenderTableParams) => string;
|
|
36
36
|
dropdowns?: BtnOptions[];
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 按视口从元素左上角铺满宽高(与 storybook 中 v-rest / v-height / v-width 同源思路),
|
|
3
|
+
* 用于外层未声明高度、需由根节点自行占满可视区域的场景。
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export type AutoViewportBoxOffsetInput =
|
|
7
|
+
| number
|
|
8
|
+
| string
|
|
9
|
+
| { right?: number; bottom?: number }
|
|
10
|
+
| undefined;
|
|
11
|
+
|
|
12
|
+
export interface AutoViewportBoxOffset {
|
|
13
|
+
right: number;
|
|
14
|
+
bottom: number;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function parseSingle(value: unknown): number {
|
|
18
|
+
if (typeof value === 'number' && !Number.isNaN(value)) {
|
|
19
|
+
return value;
|
|
20
|
+
}
|
|
21
|
+
if (typeof value === 'string') {
|
|
22
|
+
const match = value.match(/^(-?\d+(?:\.\d+)?)(px)?$/i);
|
|
23
|
+
if (match) {
|
|
24
|
+
return Number.parseFloat(match[1]);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return 0;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** 解析与 v-rest 一致的偏移:数字/字符串同时作用于 right+bottom;对象可单独指定 */
|
|
31
|
+
export function parseAutoViewportBoxOffset(
|
|
32
|
+
value: AutoViewportBoxOffsetInput,
|
|
33
|
+
): AutoViewportBoxOffset {
|
|
34
|
+
if (value === undefined || value === null || value === '') {
|
|
35
|
+
return { right: 0, bottom: 0 };
|
|
36
|
+
}
|
|
37
|
+
if (typeof value === 'object' && !Array.isArray(value)) {
|
|
38
|
+
const o = value as { right?: number; bottom?: number };
|
|
39
|
+
return {
|
|
40
|
+
right: typeof o.right === 'number' ? o.right : 0,
|
|
41
|
+
bottom: typeof o.bottom === 'number' ? o.bottom : 0,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
const single = parseSingle(value);
|
|
45
|
+
return { right: single, bottom: single };
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function findResizeAncestor(start: Element | null): Element {
|
|
49
|
+
let el: Element | null = start?.parentElement ?? null;
|
|
50
|
+
while (el && el !== document.body) {
|
|
51
|
+
const overflow = window.getComputedStyle(el).overflow;
|
|
52
|
+
if (overflow === 'auto' || overflow === 'scroll' || overflow === 'hidden') {
|
|
53
|
+
return el;
|
|
54
|
+
}
|
|
55
|
+
el = el.parentElement;
|
|
56
|
+
}
|
|
57
|
+
return document.body;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* 宽/高先按「视口内从元素左上角到右/下边缘」计算。
|
|
62
|
+
*
|
|
63
|
+
* 宽度会再与**直接父元素**布局框取较小值,避免嵌套在侧栏 + flex 列等窄容器里仍按整屏宽度赋值,
|
|
64
|
+
* 从而撑出页面级横向滚动条。
|
|
65
|
+
*
|
|
66
|
+
* 高度不做 parent 截断:autoBoxSize 的语义就是「一直撑到视口底」。若对 parent 取 min,
|
|
67
|
+
* 遇到「父容器没有明确高度、靠子元素内容撑开」的常见布局(外层未写死高度,p-grid 根节点
|
|
68
|
+
* 自身决定可视高度),会形成循环依赖(子依赖父、父又靠子撑开),导致高度被截成极小值,
|
|
69
|
+
* 反映到 PGrid 上就是表体下方出现大面积空白。
|
|
70
|
+
*/
|
|
71
|
+
export function applyViewportRestSize(el: HTMLElement, offset: AutoViewportBoxOffset): void {
|
|
72
|
+
const rect = el.getBoundingClientRect();
|
|
73
|
+
const vw = window.innerWidth;
|
|
74
|
+
const vh = window.innerHeight;
|
|
75
|
+
|
|
76
|
+
let targetW = Math.max(0, vw - rect.left - offset.right);
|
|
77
|
+
const targetH = Math.max(0, vh - rect.top - offset.bottom);
|
|
78
|
+
|
|
79
|
+
const parent = el.parentElement;
|
|
80
|
+
if (parent) {
|
|
81
|
+
const pr = parent.getBoundingClientRect();
|
|
82
|
+
if (pr.width > 0) {
|
|
83
|
+
targetW = Math.min(targetW, pr.width);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
el.style.width = `${targetW}px`;
|
|
88
|
+
el.style.height = `${targetH}px`;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function clearViewportBoxSize(el: HTMLElement): void {
|
|
92
|
+
el.style.width = '';
|
|
93
|
+
el.style.height = '';
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export interface AutoViewportBoxControllerOptions {
|
|
97
|
+
/** 每次尺寸写回后回调(例如触发内部表格 resize) */
|
|
98
|
+
onLayout?: () => void;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export interface AutoViewportBoxController {
|
|
102
|
+
attach: () => void;
|
|
103
|
+
update: () => void;
|
|
104
|
+
destroy: () => void;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export function createAutoViewportBoxController(
|
|
108
|
+
getEl: () => HTMLElement | null | undefined,
|
|
109
|
+
getOffset: () => AutoViewportBoxOffset,
|
|
110
|
+
options?: AutoViewportBoxControllerOptions,
|
|
111
|
+
): AutoViewportBoxController {
|
|
112
|
+
let resizeObserver: ResizeObserver | null = null;
|
|
113
|
+
let onWinResize: (() => void) | null = null;
|
|
114
|
+
|
|
115
|
+
const disconnect = () => {
|
|
116
|
+
if (resizeObserver) {
|
|
117
|
+
resizeObserver.disconnect();
|
|
118
|
+
resizeObserver = null;
|
|
119
|
+
}
|
|
120
|
+
if (onWinResize) {
|
|
121
|
+
window.removeEventListener('resize', onWinResize);
|
|
122
|
+
onWinResize = null;
|
|
123
|
+
}
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
const update = () => {
|
|
127
|
+
const el = getEl();
|
|
128
|
+
if (!el) return;
|
|
129
|
+
applyViewportRestSize(el, getOffset());
|
|
130
|
+
options?.onLayout?.();
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
const attach = () => {
|
|
134
|
+
disconnect();
|
|
135
|
+
const el = getEl();
|
|
136
|
+
if (!el) return;
|
|
137
|
+
|
|
138
|
+
onWinResize = () => update();
|
|
139
|
+
window.addEventListener('resize', onWinResize);
|
|
140
|
+
|
|
141
|
+
const scrollAncestor = findResizeAncestor(el);
|
|
142
|
+
resizeObserver = new ResizeObserver(() => update());
|
|
143
|
+
resizeObserver.observe(scrollAncestor);
|
|
144
|
+
if (el.parentElement) {
|
|
145
|
+
resizeObserver.observe(el.parentElement);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
update();
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
return {
|
|
152
|
+
attach,
|
|
153
|
+
update,
|
|
154
|
+
destroy: () => {
|
|
155
|
+
disconnect();
|
|
156
|
+
const el = getEl();
|
|
157
|
+
if (el) clearViewportBoxSize(el);
|
|
158
|
+
},
|
|
159
|
+
};
|
|
160
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { PFormProps, PGridProps } from '#/antProxy';
|
|
2
|
+
import type { AutoViewportBoxOffsetInput } from '@/utils/autoViewportBox';
|
|
2
3
|
import { ConfigType } from 'e-virt-table';
|
|
3
4
|
import { clone } from 'xe-utils';
|
|
4
5
|
import type { VNode } from 'vue';
|
|
@@ -15,6 +16,8 @@ export interface UIKitConfig {
|
|
|
15
16
|
fitHeight?: number;
|
|
16
17
|
fitCanvasHeight?: number;
|
|
17
18
|
striped?: boolean;
|
|
19
|
+
/** `PGrid` 开启 `autoBoxSize` 且未传 `autoBoxSizeOffset` 时的视口边距,与 props 类型一致 */
|
|
20
|
+
autoBoxSizeOffset?: AutoViewportBoxOffsetInput;
|
|
18
21
|
};
|
|
19
22
|
canvasTable?: ConfigType;
|
|
20
23
|
/**
|
|
@@ -91,6 +94,7 @@ export function getGridDefaults(): Partial<PGridProps> {
|
|
|
91
94
|
lazyReset: currentConfig.grid?.lazyReset,
|
|
92
95
|
fitHeight: currentConfig.grid?.fitHeight,
|
|
93
96
|
striped: currentConfig.grid?.striped,
|
|
97
|
+
autoBoxSizeOffset: currentConfig.grid?.autoBoxSizeOffset,
|
|
94
98
|
};
|
|
95
99
|
}
|
|
96
100
|
|