niuma-ui 1.0.0 → 1.0.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/CHANGELOG.md +50 -1
- package/NOTICE +2 -2
- package/package.json +13 -5
- package/src/__tests__/RsButton.spec.ts +47 -0
- package/src/__tests__/RsCard.spec.ts +160 -1
- package/src/__tests__/RsConfirmDialog.spec.ts +127 -0
- package/src/__tests__/RsContextMenu.spec.ts +12 -0
- package/src/__tests__/RsDateTimePicker.spec.ts +11 -0
- package/src/__tests__/RsDescriptions.spec.ts +50 -0
- package/src/__tests__/RsDialog.spec.ts +117 -0
- package/src/__tests__/RsDrawer.spec.ts +96 -3
- package/src/__tests__/RsDynamicTags.spec.ts +52 -0
- package/src/__tests__/RsForm.spec.ts +245 -1
- package/src/__tests__/RsInput.spec.ts +15 -1
- package/src/__tests__/RsMenu.spec.ts +406 -14
- package/src/__tests__/RsPagination.spec.ts +67 -2
- package/src/__tests__/RsSelect.spec.ts +42 -1
- package/src/__tests__/RsSwitch.spec.ts +44 -0
- package/src/__tests__/RsTable.a11y.spec.ts +83 -0
- package/src/__tests__/RsTable.dom-baseline.spec.ts +87 -0
- package/src/__tests__/RsTable.perf.spec.ts +23 -4
- package/src/__tests__/RsTable.spec.ts +63 -8
- package/src/__tests__/RsTable.ssr.spec.ts +71 -0
- package/src/__tests__/RsTable.summary.spec.ts +45 -0
- package/src/__tests__/RsTabs.spec.ts +102 -4
- package/src/__tests__/RsTerminal.spec.ts +1 -1
- package/src/__tests__/RsTimePicker.spec.ts +11 -0
- package/src/__tests__/RsUpload.spec.ts +62 -6
- package/src/__tests__/createRsDialog.spec.ts +145 -0
- package/src/__tests__/form-rules.spec.ts +76 -0
- package/src/__tests__/perf-budgets.ts +32 -0
- package/src/__tests__/rs-table-api.spec.ts +206 -0
- package/src/__tests__/rs-table-compat-matrix.spec.ts +108 -0
- package/src/__tests__/rs-table-echarts-adapter.spec.ts +43 -0
- package/src/__tests__/rs-table-grid-nav.spec.ts +63 -0
- package/src/__tests__/rs-table-isolation.spec.ts +158 -0
- package/src/__tests__/split-pane-utils.spec.ts +21 -0
- package/src/__tests__/table-chart-utils.spec.ts +88 -0
- package/src/__tests__/table-summary-utils.spec.ts +62 -0
- package/src/__tests__/table-utils.spec.ts +124 -0
- package/src/__tests__/theme.spec.ts +25 -0
- package/src/__tests__/useRsTableColumnResize.spec.ts +25 -0
- package/src/__tests__/useRsTableCore.perf.spec.ts +170 -0
- package/src/components/RsAlert.vue +212 -0
- package/src/components/RsButton.vue +145 -21
- package/src/components/RsCard.vue +200 -16
- package/src/components/RsConfirmDialog.vue +215 -28
- package/src/components/RsContextMenu.vue +51 -13
- package/src/components/RsDatePicker.vue +267 -34
- package/src/components/RsDateTimePicker.vue +19 -2
- package/src/components/RsDescriptions.vue +186 -0
- package/src/components/RsDescriptionsItem.vue +32 -0
- package/src/components/RsDialog.vue +363 -51
- package/src/components/RsDivider.vue +89 -0
- package/src/components/RsDrawer.vue +558 -33
- package/src/components/RsDropdown.vue +33 -2
- package/src/components/RsDynamicTags.vue +517 -0
- package/src/components/RsForm.vue +97 -8
- package/src/components/RsInput.vue +80 -82
- package/src/components/RsInputNumber.vue +64 -23
- package/src/components/RsLabel.vue +20 -5
- package/src/components/RsLoadingBar.vue +151 -0
- package/src/components/RsMenu.vue +73 -15
- package/src/components/RsMenuItems.vue +147 -9
- package/src/components/RsPagination.vue +93 -14
- package/src/components/RsRadio.vue +81 -0
- package/src/components/RsRadioItem.vue +156 -0
- package/src/components/RsSelect.vue +67 -58
- package/src/components/RsSplitPane.vue +69 -1
- package/src/components/RsSwitch.vue +179 -0
- package/src/components/RsTable.vue +653 -2555
- package/src/components/RsTabs.vue +415 -83
- package/src/components/RsTag.vue +191 -0
- package/src/components/RsTerminal.vue +3 -3
- package/src/components/RsTimePicker.vue +44 -5
- package/src/components/RsTimePickerColumns.vue +136 -77
- package/src/components/RsToaster.vue +58 -18
- package/src/components/RsTree.vue +39 -21
- package/src/components/RsUpload.vue +145 -12
- package/src/components/RsVNodeHost.vue +20 -0
- package/src/components/button-utils.ts +39 -0
- package/src/components/context-menu-utils.ts +6 -0
- package/src/components/date-picker-utils.ts +84 -4
- package/src/components/descriptions-utils.ts +24 -0
- package/src/components/dialog-utils.ts +147 -0
- package/src/components/dialog-window.ts +74 -9
- package/src/components/drawer-utils.ts +73 -0
- package/src/components/form-rules.ts +257 -0
- package/src/components/form-utils.ts +52 -2
- package/src/components/menu-utils.ts +11 -0
- package/src/components/overlay-utils.ts +3 -0
- package/src/components/radio-utils.ts +12 -0
- package/src/components/reka.ts +5 -0
- package/src/components/select-utils.ts +21 -2
- package/src/components/split-pane-utils.ts +65 -12
- package/src/components/table/RsTableBody.vue +217 -0
- package/src/components/table/RsTableBodyRow.vue +90 -1
- package/src/components/table/RsTableColGroup.vue +41 -0
- package/src/components/table/RsTableHeader.vue +158 -0
- package/src/components/table/RsTableSummaryRow.vue +52 -0
- package/src/components/table/rs-table-api.ts +99 -0
- package/src/components/table/rs-table-compat-matrix.ts +71 -0
- package/src/components/table/rs-table-echarts-adapter.ts +81 -0
- package/src/components/table/rs-table-feature-host.ts +178 -0
- package/src/components/table/rs-table-grid-nav.ts +122 -0
- package/src/components/table/rs-table-header-types.ts +47 -0
- package/src/components/table/rs-table-module-registry.ts +72 -0
- package/src/components/table/rs-table-props.ts +274 -0
- package/src/components/table/rs-table-view-context.ts +258 -0
- package/src/components/table/rs-table.css +84 -6
- package/src/components/table/table-chart-utils.ts +165 -0
- package/src/components/table/table-features.ts +284 -0
- package/src/components/table/table-summary-utils.ts +194 -0
- package/src/components/table-utils.ts +452 -2
- package/src/components/tabs-utils.ts +142 -2
- package/src/components/terminal-utils.ts +1 -1
- package/src/components/terminal-wheel.ts +1 -1
- package/src/components/time-picker-utils.ts +76 -3
- package/src/components/upload-utils.ts +122 -0
- package/src/components/virtual-list-utils.ts +15 -2
- package/src/composables/assembleRsTableApi.ts +79 -0
- package/src/composables/bindRsTableViewContext.ts +269 -0
- package/src/composables/createRsDialog.ts +357 -0
- package/src/composables/createRsTableEditEmitBridge.ts +38 -0
- package/src/composables/useRsLoadingBar.ts +36 -0
- package/src/composables/useRsTable.ts +134 -0
- package/src/composables/useRsTableA11y.ts +52 -0
- package/src/composables/useRsTableChartBridge.ts +79 -0
- package/src/composables/useRsTableColumnLayout.ts +320 -0
- package/src/composables/useRsTableColumnResize.ts +244 -0
- package/src/composables/useRsTableColumnVirtual.ts +180 -0
- package/src/composables/useRsTableColumns.ts +200 -0
- package/src/composables/useRsTableContextMenu.ts +257 -0
- package/src/composables/useRsTableCore.ts +256 -0
- package/src/composables/useRsTableEditActions.ts +606 -0
- package/src/composables/useRsTableEditLayer.ts +251 -0
- package/src/composables/useRsTableEngine.ts +357 -0
- package/src/composables/useRsTableGridKeyboard.ts +123 -0
- package/src/composables/useRsTableHeadless.ts +137 -0
- package/src/composables/useRsTableInteraction.ts +433 -0
- package/src/composables/useRsTableScrollHost.ts +75 -0
- package/src/composables/useRsTableScrollLayout.ts +255 -0
- package/src/composables/useRsTableSelectionSource.ts +89 -0
- package/src/composables/useRsTableShell.ts +416 -0
- package/src/composables/useRsTableShellChrome.ts +170 -0
- package/src/composables/useRsTableSummary.ts +113 -0
- package/src/composables/useRsTableViewProvide.ts +263 -0
- package/src/composables/useRsTableVirtual.ts +154 -0
- package/src/composables/useRsTabsNav.ts +4 -0
- package/src/composables/useTableEdit.ts +1 -1
- package/src/icons/lucide.ts +2 -2
- package/src/index.ts +309 -9
- package/src/locale/messages.ts +30 -0
- package/src/styles.css +305 -52
- package/src/theme/brand.example.css +21 -2
- package/src/theme/presets.ts +4 -0
- package/src/theme/types.ts +67 -0
- package/src/components/RsTableColgroup.vue +0 -17
- package/src/components/RsTableHeaderRow.vue +0 -30
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RsTable 滚动宿主生命周期 + 无限加载触底。
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { onActivated, onDeactivated, onMounted, ref, watch, type Ref } from 'vue'
|
|
6
|
+
import { isNearScrollBottom } from '../components/table-utils'
|
|
7
|
+
|
|
8
|
+
export interface UseRsTableScrollHostOptions {
|
|
9
|
+
scrollContainerRef: Ref<HTMLElement | null | undefined>
|
|
10
|
+
scheduleVirtualLayoutSync: () => void
|
|
11
|
+
bindScrollElement: (el: HTMLElement | null | undefined) => void
|
|
12
|
+
captureScrollFromDom: () => void
|
|
13
|
+
onScrollFrame: (element: HTMLElement) => void
|
|
14
|
+
hideCellTooltip: () => void
|
|
15
|
+
infinite: () => boolean
|
|
16
|
+
loading: () => boolean
|
|
17
|
+
loadingMore: () => boolean
|
|
18
|
+
hasMore: () => boolean
|
|
19
|
+
infiniteDistance: () => number
|
|
20
|
+
onLoadMore: () => void
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* 绑定滚动生命周期与无限加载。
|
|
25
|
+
*/
|
|
26
|
+
export function useRsTableScrollHost(options: UseRsTableScrollHostOptions) {
|
|
27
|
+
const loadMoreLocked = ref(false)
|
|
28
|
+
|
|
29
|
+
onMounted(() => {
|
|
30
|
+
options.scheduleVirtualLayoutSync()
|
|
31
|
+
options.bindScrollElement(options.scrollContainerRef.value)
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
onActivated(() => {
|
|
35
|
+
options.scheduleVirtualLayoutSync()
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
onDeactivated(() => {
|
|
39
|
+
options.captureScrollFromDom()
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
function onScroll(event: Event): void {
|
|
43
|
+
options.hideCellTooltip()
|
|
44
|
+
const element = event.target as HTMLElement
|
|
45
|
+
if (
|
|
46
|
+
options.infinite() &&
|
|
47
|
+
!options.loading() &&
|
|
48
|
+
!options.loadingMore() &&
|
|
49
|
+
options.hasMore() &&
|
|
50
|
+
!loadMoreLocked.value
|
|
51
|
+
) {
|
|
52
|
+
if (
|
|
53
|
+
isNearScrollBottom(
|
|
54
|
+
element.scrollTop,
|
|
55
|
+
element.scrollHeight,
|
|
56
|
+
element.clientHeight,
|
|
57
|
+
options.infiniteDistance(),
|
|
58
|
+
)
|
|
59
|
+
) {
|
|
60
|
+
loadMoreLocked.value = true
|
|
61
|
+
options.onLoadMore()
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
options.onScrollFrame(element)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
watch(
|
|
68
|
+
() => [options.loadingMore(), options.hasMore()] as const,
|
|
69
|
+
([loadingMore, hasMore]) => {
|
|
70
|
+
if (!loadingMore || !hasMore) loadMoreLocked.value = false
|
|
71
|
+
},
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
return { onScroll, loadMoreLocked }
|
|
75
|
+
}
|
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RsTable 滚动与视口布局:测量、节流同步、keep-alive / layoutActive 恢复。
|
|
3
|
+
*
|
|
4
|
+
* 职责边界:
|
|
5
|
+
* - 做:scrollTop/Left、视口宽高、写回 DOM、ResizeObserver、RAF 节流滚动
|
|
6
|
+
* - 不做:虚拟切片本身、无限加载业务判定(由调用方在 onScroll 钩子中处理)
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import {
|
|
10
|
+
computed,
|
|
11
|
+
nextTick,
|
|
12
|
+
onUnmounted,
|
|
13
|
+
ref,
|
|
14
|
+
watch,
|
|
15
|
+
type ComputedRef,
|
|
16
|
+
type Ref,
|
|
17
|
+
} from 'vue'
|
|
18
|
+
|
|
19
|
+
/** 统一读取 ComputedRef / getter */
|
|
20
|
+
function read<T>(source: ComputedRef<T> | (() => T)): T {
|
|
21
|
+
return typeof source === 'function' ? (source as () => T)() : source.value
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface UseRsTableScrollLayoutOptions {
|
|
25
|
+
/** 显式高度(number / px 字符串优先于测量值) */
|
|
26
|
+
height: ComputedRef<number | string | undefined> | (() => number | string | undefined)
|
|
27
|
+
/** 外层 keep-alive 可见性;false→快照,true→恢复 */
|
|
28
|
+
layoutActive: ComputedRef<boolean | undefined> | (() => boolean | undefined)
|
|
29
|
+
/** 视图切换标识;变化时重置滚动(跳过首次) */
|
|
30
|
+
viewKey: ComputedRef<string | number | undefined> | (() => string | number | undefined)
|
|
31
|
+
/** 无测量值时的回退视口高度 */
|
|
32
|
+
fallbackViewportHeight?: number
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* 表格滚动容器与视口状态。
|
|
37
|
+
*
|
|
38
|
+
* @returns 滚动状态、视口尺寸、恢复/重置 API、bindScrollElement / onScrollFrame
|
|
39
|
+
*/
|
|
40
|
+
export function useRsTableScrollLayout(options: UseRsTableScrollLayoutOptions) {
|
|
41
|
+
const scrollTop = ref(0)
|
|
42
|
+
const scrollLeft = ref(0)
|
|
43
|
+
const scrollContainerRef = ref<HTMLElement | null>(null)
|
|
44
|
+
const measuredViewportHeight = ref(0)
|
|
45
|
+
const measuredViewportWidth = ref(0)
|
|
46
|
+
|
|
47
|
+
let viewportResizeObserver: ResizeObserver | null = null
|
|
48
|
+
let preservedScrollTop = 0
|
|
49
|
+
let preservedScrollLeft = 0
|
|
50
|
+
let restoringScroll = false
|
|
51
|
+
let restoreGeneration = 0
|
|
52
|
+
let scrollRafId = 0
|
|
53
|
+
|
|
54
|
+
const fallback = options.fallbackViewportHeight ?? 320
|
|
55
|
+
|
|
56
|
+
/** 虚拟切片用视口高度:显式 height > 测量值 > DOM > fallback */
|
|
57
|
+
const viewportHeight = computed(() => {
|
|
58
|
+
const h = read(options.height)
|
|
59
|
+
if (typeof h === 'number' && h > 0) return h
|
|
60
|
+
if (typeof h === 'string') {
|
|
61
|
+
const parsed = Number.parseInt(h, 10)
|
|
62
|
+
if (Number.isFinite(parsed) && parsed > 0 && !h.includes('%')) return parsed
|
|
63
|
+
}
|
|
64
|
+
if (measuredViewportHeight.value > 0) return measuredViewportHeight.value
|
|
65
|
+
const el = scrollContainerRef.value
|
|
66
|
+
if (el && el.clientHeight > 0) return el.clientHeight
|
|
67
|
+
return fallback
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
function syncMeasuredViewportSize(): void {
|
|
71
|
+
const el = scrollContainerRef.value
|
|
72
|
+
if (!el) return
|
|
73
|
+
const nextH = el.clientHeight
|
|
74
|
+
if (nextH > 0 && nextH !== measuredViewportHeight.value) {
|
|
75
|
+
measuredViewportHeight.value = nextH
|
|
76
|
+
}
|
|
77
|
+
const nextW = el.clientWidth
|
|
78
|
+
if (nextW > 0 && nextW !== measuredViewportWidth.value) {
|
|
79
|
+
measuredViewportWidth.value = nextW
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* 失活前快照滚动。
|
|
85
|
+
* DOM 脱离文档后 scrollTop 常为 0——切勿用 0 覆盖 onScroll 已写入的位置。
|
|
86
|
+
*/
|
|
87
|
+
function captureScrollFromDom(): void {
|
|
88
|
+
const el = scrollContainerRef.value
|
|
89
|
+
if (el) {
|
|
90
|
+
if (el.scrollTop > 0) scrollTop.value = el.scrollTop
|
|
91
|
+
if (el.scrollLeft > 0) scrollLeft.value = el.scrollLeft
|
|
92
|
+
}
|
|
93
|
+
if (scrollTop.value > 0) preservedScrollTop = scrollTop.value
|
|
94
|
+
if (scrollLeft.value > 0) preservedScrollLeft = scrollLeft.value
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/** 将内部保存的滚动写回 DOM,并同步 reactive(驱动虚拟切片)。 */
|
|
98
|
+
function restoreScrollToDom(): void {
|
|
99
|
+
const el = scrollContainerRef.value
|
|
100
|
+
if (!el) return
|
|
101
|
+
syncMeasuredViewportSize()
|
|
102
|
+
const top = preservedScrollTop
|
|
103
|
+
const left = preservedScrollLeft
|
|
104
|
+
if (scrollTop.value !== top) scrollTop.value = top
|
|
105
|
+
if (scrollLeft.value !== left) scrollLeft.value = left
|
|
106
|
+
if (el.scrollTop !== top) el.scrollTop = top
|
|
107
|
+
if (el.scrollLeft !== left) el.scrollLeft = left
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* 布局稳定后多次写回滚动:keep-alive 切回首帧 clientHeight 常为 0,
|
|
112
|
+
* 需等 nextTick / rAF / 视口测稳后再设 scrollTop,否则会被浏览器钳成 0。
|
|
113
|
+
*/
|
|
114
|
+
function scheduleVirtualLayoutSync(): void {
|
|
115
|
+
restoreGeneration += 1
|
|
116
|
+
const gen = restoreGeneration
|
|
117
|
+
restoringScroll = true
|
|
118
|
+
const apply = (): void => {
|
|
119
|
+
if (gen !== restoreGeneration) return
|
|
120
|
+
restoreScrollToDom()
|
|
121
|
+
}
|
|
122
|
+
apply()
|
|
123
|
+
void nextTick(() => {
|
|
124
|
+
apply()
|
|
125
|
+
requestAnimationFrame(() => {
|
|
126
|
+
apply()
|
|
127
|
+
requestAnimationFrame(() => {
|
|
128
|
+
apply()
|
|
129
|
+
if (gen === restoreGeneration) restoringScroll = false
|
|
130
|
+
})
|
|
131
|
+
})
|
|
132
|
+
})
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function resetScrollPosition(): void {
|
|
136
|
+
preservedScrollTop = 0
|
|
137
|
+
preservedScrollLeft = 0
|
|
138
|
+
scrollTop.value = 0
|
|
139
|
+
scrollLeft.value = 0
|
|
140
|
+
const el = scrollContainerRef.value
|
|
141
|
+
if (el) {
|
|
142
|
+
el.scrollTop = 0
|
|
143
|
+
el.scrollLeft = 0
|
|
144
|
+
}
|
|
145
|
+
scheduleVirtualLayoutSync()
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function isRestoringScroll(): boolean {
|
|
149
|
+
return restoringScroll
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* RAF 节流写入滚动位置;恢复窗口内忽略伪 scrollTop=0。
|
|
154
|
+
* @returns true 表示本帧已调度或处于恢复中(调用方仍可先做无限加载判断)
|
|
155
|
+
*/
|
|
156
|
+
function onScrollFrame(element: HTMLElement): void {
|
|
157
|
+
if (scrollRafId) return
|
|
158
|
+
scrollRafId = requestAnimationFrame(() => {
|
|
159
|
+
if (restoringScroll) {
|
|
160
|
+
scrollRafId = 0
|
|
161
|
+
return
|
|
162
|
+
}
|
|
163
|
+
scrollTop.value = element.scrollTop
|
|
164
|
+
scrollLeft.value = element.scrollLeft
|
|
165
|
+
preservedScrollTop = element.scrollTop
|
|
166
|
+
preservedScrollLeft = element.scrollLeft
|
|
167
|
+
scrollRafId = 0
|
|
168
|
+
})
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* 绑定滚动容器 ResizeObserver:视口从 0→实高时补一次恢复写回。
|
|
173
|
+
*/
|
|
174
|
+
function bindScrollElement(el: HTMLElement | null | undefined): void {
|
|
175
|
+
viewportResizeObserver?.disconnect()
|
|
176
|
+
viewportResizeObserver = null
|
|
177
|
+
if (!el || typeof ResizeObserver === 'undefined') return
|
|
178
|
+
viewportResizeObserver = new ResizeObserver(() => {
|
|
179
|
+
const prevH = measuredViewportHeight.value
|
|
180
|
+
syncMeasuredViewportSize()
|
|
181
|
+
if (
|
|
182
|
+
prevH === 0 &&
|
|
183
|
+
measuredViewportHeight.value > 0 &&
|
|
184
|
+
(preservedScrollTop > 0 || preservedScrollLeft > 0)
|
|
185
|
+
) {
|
|
186
|
+
restoringScroll = true
|
|
187
|
+
restoreScrollToDom()
|
|
188
|
+
requestAnimationFrame(() => {
|
|
189
|
+
restoringScroll = false
|
|
190
|
+
})
|
|
191
|
+
}
|
|
192
|
+
})
|
|
193
|
+
viewportResizeObserver.observe(el)
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function disposeScrollLayout(): void {
|
|
197
|
+
viewportResizeObserver?.disconnect()
|
|
198
|
+
viewportResizeObserver = null
|
|
199
|
+
if (scrollRafId) {
|
|
200
|
+
cancelAnimationFrame(scrollRafId)
|
|
201
|
+
scrollRafId = 0
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
watch(
|
|
206
|
+
() => read(options.layoutActive),
|
|
207
|
+
(active, prev) => {
|
|
208
|
+
if (active === false && prev !== false) {
|
|
209
|
+
captureScrollFromDom()
|
|
210
|
+
}
|
|
211
|
+
},
|
|
212
|
+
{ flush: 'sync' },
|
|
213
|
+
)
|
|
214
|
+
|
|
215
|
+
watch(
|
|
216
|
+
() => read(options.layoutActive),
|
|
217
|
+
(active) => {
|
|
218
|
+
if (active) scheduleVirtualLayoutSync()
|
|
219
|
+
},
|
|
220
|
+
{ flush: 'post' },
|
|
221
|
+
)
|
|
222
|
+
|
|
223
|
+
watch(
|
|
224
|
+
() => read(options.viewKey),
|
|
225
|
+
(_next, prev) => {
|
|
226
|
+
if (prev === undefined) return
|
|
227
|
+
resetScrollPosition()
|
|
228
|
+
},
|
|
229
|
+
)
|
|
230
|
+
|
|
231
|
+
onUnmounted(() => {
|
|
232
|
+
disposeScrollLayout()
|
|
233
|
+
})
|
|
234
|
+
|
|
235
|
+
return {
|
|
236
|
+
scrollTop,
|
|
237
|
+
scrollLeft,
|
|
238
|
+
scrollContainerRef,
|
|
239
|
+
measuredViewportHeight,
|
|
240
|
+
measuredViewportWidth,
|
|
241
|
+
viewportHeight,
|
|
242
|
+
syncMeasuredViewportSize,
|
|
243
|
+
captureScrollFromDom,
|
|
244
|
+
restoreScrollToDom,
|
|
245
|
+
scheduleVirtualLayoutSync,
|
|
246
|
+
resetScrollPosition,
|
|
247
|
+
isRestoringScroll,
|
|
248
|
+
onScrollFrame,
|
|
249
|
+
bindScrollElement,
|
|
250
|
+
disposeScrollLayout,
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/** useRsTableScrollLayout 返回值类型 */
|
|
255
|
+
export type RsTableScrollLayoutApi = ReturnType<typeof useRsTableScrollLayout>
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 表格分析/图表数据源:viewRows + selectedRows → 稳定 sourceRows。
|
|
3
|
+
*
|
|
4
|
+
* 约定(便于柱状图/饼图等后期扩展):
|
|
5
|
+
* - 有选中行时 sourceMode='selected',图表跟随勾选
|
|
6
|
+
* - 无选中时回退 viewRows(筛选/排序后的业务行)
|
|
7
|
+
* - subscribe 仅在浅层引用变化时通知,不做 deep watch
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { computed, watch, type ComputedRef, type Ref } from 'vue'
|
|
11
|
+
import type { RsTableRowData } from '../components/table-utils'
|
|
12
|
+
|
|
13
|
+
export type RsTableAnalyticsSourceMode = 'selected' | 'view'
|
|
14
|
+
|
|
15
|
+
/** 分析快照:图表 / 指标卡 / 服务端请求共用 */
|
|
16
|
+
export interface RsTableAnalyticsSnapshot<T extends RsTableRowData = RsTableRowData> {
|
|
17
|
+
viewRows: readonly T[]
|
|
18
|
+
selectedRows: readonly T[]
|
|
19
|
+
sourceRows: readonly T[]
|
|
20
|
+
sourceMode: RsTableAnalyticsSourceMode
|
|
21
|
+
selectedCount: number
|
|
22
|
+
viewCount: number
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface UseRsTableSelectionSourceOptions<T extends RsTableRowData> {
|
|
26
|
+
viewRows: Ref<T[]> | ComputedRef<T[]>
|
|
27
|
+
selectedRows?: Ref<T[]> | ComputedRef<T[]>
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* 选中优先的分析数据源。
|
|
32
|
+
*
|
|
33
|
+
* @returns sourceRows / sourceMode / getSnapshot / subscribe
|
|
34
|
+
*/
|
|
35
|
+
export function useRsTableSelectionSource<T extends RsTableRowData>(
|
|
36
|
+
options: UseRsTableSelectionSourceOptions<T>,
|
|
37
|
+
) {
|
|
38
|
+
const viewRows = computed(() => options.viewRows.value ?? [])
|
|
39
|
+
const selectedRows = computed(() => options.selectedRows?.value ?? [])
|
|
40
|
+
|
|
41
|
+
const sourceMode = computed<RsTableAnalyticsSourceMode>(() =>
|
|
42
|
+
selectedRows.value.length > 0 ? 'selected' : 'view',
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
const sourceRows = computed(() =>
|
|
46
|
+
sourceMode.value === 'selected' ? selectedRows.value : viewRows.value,
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
function getSnapshot(): RsTableAnalyticsSnapshot<T> {
|
|
50
|
+
return {
|
|
51
|
+
viewRows: viewRows.value,
|
|
52
|
+
selectedRows: selectedRows.value,
|
|
53
|
+
sourceRows: sourceRows.value,
|
|
54
|
+
sourceMode: sourceMode.value,
|
|
55
|
+
selectedCount: selectedRows.value.length,
|
|
56
|
+
viewCount: viewRows.value.length,
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* 订阅快照变化。
|
|
62
|
+
* 依赖:viewRows 引用、selectedRows 引用、sourceMode。
|
|
63
|
+
* @returns teardown
|
|
64
|
+
*/
|
|
65
|
+
function subscribe(listener: (snap: RsTableAnalyticsSnapshot<T>) => void): () => void {
|
|
66
|
+
const stop = watch(
|
|
67
|
+
() => [viewRows.value, selectedRows.value, sourceMode.value] as const,
|
|
68
|
+
() => {
|
|
69
|
+
listener(getSnapshot())
|
|
70
|
+
},
|
|
71
|
+
{ flush: 'post' },
|
|
72
|
+
)
|
|
73
|
+
listener(getSnapshot())
|
|
74
|
+
return stop
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return {
|
|
78
|
+
viewRows,
|
|
79
|
+
selectedRows,
|
|
80
|
+
sourceRows,
|
|
81
|
+
sourceMode,
|
|
82
|
+
getSnapshot,
|
|
83
|
+
subscribe,
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export type RsTableSelectionSourceApi<T extends RsTableRowData> = ReturnType<
|
|
88
|
+
typeof useRsTableSelectionSource<T>
|
|
89
|
+
>
|