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,165 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 表格 → 图表系列纯函数(库无关)。
|
|
3
|
+
*
|
|
4
|
+
* 产出结构可直接喂给 ECharts / Chart.js 等:
|
|
5
|
+
* - pie / 普通 bar:points[{ name, value }]
|
|
6
|
+
* - 分类轴 bar:categories + values
|
|
7
|
+
*
|
|
8
|
+
* 不引入任何图表依赖,避免拖重内核。
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import type { RsTableRowData } from '../table-utils'
|
|
12
|
+
|
|
13
|
+
/** 图表意图类型(渲染层自行映射到具体库) */
|
|
14
|
+
export type RsTableChartKind = 'bar' | 'pie' | 'line'
|
|
15
|
+
|
|
16
|
+
/** 单点:饼图扇区 / 柱条 */
|
|
17
|
+
export interface RsTableChartPoint {
|
|
18
|
+
/** 分类名(展示) */
|
|
19
|
+
name: string
|
|
20
|
+
/** 聚合值 */
|
|
21
|
+
value: number
|
|
22
|
+
/** 原始分类 key(与 name 通常相同,供下钻) */
|
|
23
|
+
key: string
|
|
24
|
+
/** 该分类下的行数(便于 tooltip) */
|
|
25
|
+
count: number
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** 一组图表系列 */
|
|
29
|
+
export interface RsTableChartSeries {
|
|
30
|
+
id: string
|
|
31
|
+
kind: RsTableChartKind
|
|
32
|
+
points: RsTableChartPoint[]
|
|
33
|
+
/** 分类轴(与 points 顺序一致) */
|
|
34
|
+
categories: string[]
|
|
35
|
+
/** 数值轴(与 points 顺序一致) */
|
|
36
|
+
values: number[]
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export type RsTableChartValueAgg = 'count' | 'sum' | 'avg' | 'min' | 'max'
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* 图表系列定义。
|
|
43
|
+
* - categoryField:分组字段
|
|
44
|
+
* - valueField + valueAgg:度量;仅 count 时可省略 valueField
|
|
45
|
+
*/
|
|
46
|
+
export interface RsTableChartSeriesDef {
|
|
47
|
+
id: string
|
|
48
|
+
kind: RsTableChartKind
|
|
49
|
+
categoryField: string
|
|
50
|
+
valueField?: string
|
|
51
|
+
valueAgg?: RsTableChartValueAgg
|
|
52
|
+
/** 最多保留前 N 类,其余合并为 otherLabel;0/不传表示不截断 */
|
|
53
|
+
topN?: number
|
|
54
|
+
otherLabel?: string
|
|
55
|
+
/** 空分类展示名 */
|
|
56
|
+
emptyCategoryLabel?: string
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function readField(row: RsTableRowData, field: string): unknown {
|
|
60
|
+
return (row as Record<string, unknown>)[field]
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function toNumber(value: unknown): number | null {
|
|
64
|
+
if (typeof value === 'number' && Number.isFinite(value)) return value
|
|
65
|
+
if (typeof value === 'string' && value.trim() !== '') {
|
|
66
|
+
const n = Number(value)
|
|
67
|
+
return Number.isFinite(n) ? n : null
|
|
68
|
+
}
|
|
69
|
+
return null
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function categoryKey(raw: unknown, emptyLabel: string): string {
|
|
73
|
+
if (raw == null || raw === '') return emptyLabel
|
|
74
|
+
return String(raw)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
interface Bucket {
|
|
78
|
+
key: string
|
|
79
|
+
name: string
|
|
80
|
+
count: number
|
|
81
|
+
nums: number[]
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function aggregateBucket(bucket: Bucket, agg: RsTableChartValueAgg): number {
|
|
85
|
+
if (agg === 'count') return bucket.count
|
|
86
|
+
if (!bucket.nums.length) return 0
|
|
87
|
+
if (agg === 'sum') return bucket.nums.reduce((a, b) => a + b, 0)
|
|
88
|
+
if (agg === 'avg') return bucket.nums.reduce((a, b) => a + b, 0) / bucket.nums.length
|
|
89
|
+
if (agg === 'min') return Math.min(...bucket.nums)
|
|
90
|
+
if (agg === 'max') return Math.max(...bucket.nums)
|
|
91
|
+
return 0
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* 按分类字段对行做聚合,生成库无关图表系列。
|
|
96
|
+
*
|
|
97
|
+
* @param rows 通常为 SelectionSource.sourceRows(选中优先)
|
|
98
|
+
* @param def 系列定义
|
|
99
|
+
*/
|
|
100
|
+
export function buildTableChartSeries<T extends RsTableRowData>(
|
|
101
|
+
rows: readonly T[],
|
|
102
|
+
def: RsTableChartSeriesDef,
|
|
103
|
+
): RsTableChartSeries {
|
|
104
|
+
const emptyLabel = def.emptyCategoryLabel ?? '(空)'
|
|
105
|
+
const agg = def.valueAgg ?? (def.valueField ? 'sum' : 'count')
|
|
106
|
+
const map = new Map<string, Bucket>()
|
|
107
|
+
|
|
108
|
+
for (let i = 0; i < rows.length; i += 1) {
|
|
109
|
+
const row = rows[i] as T
|
|
110
|
+
const key = categoryKey(readField(row, def.categoryField), emptyLabel)
|
|
111
|
+
let bucket = map.get(key)
|
|
112
|
+
if (!bucket) {
|
|
113
|
+
bucket = { key, name: key, count: 0, nums: [] }
|
|
114
|
+
map.set(key, bucket)
|
|
115
|
+
}
|
|
116
|
+
bucket.count += 1
|
|
117
|
+
if (def.valueField && agg !== 'count') {
|
|
118
|
+
const n = toNumber(readField(row, def.valueField))
|
|
119
|
+
if (n != null) bucket.nums.push(n)
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
let points: RsTableChartPoint[] = [...map.values()].map((bucket) => ({
|
|
124
|
+
key: bucket.key,
|
|
125
|
+
name: bucket.name,
|
|
126
|
+
value: aggregateBucket(bucket, agg),
|
|
127
|
+
count: bucket.count,
|
|
128
|
+
}))
|
|
129
|
+
|
|
130
|
+
// 按 value 降序,便于 topN / 饼图可读性
|
|
131
|
+
points.sort((a, b) => b.value - a.value || a.name.localeCompare(b.name))
|
|
132
|
+
|
|
133
|
+
const topN = def.topN ?? 0
|
|
134
|
+
if (topN > 0 && points.length > topN) {
|
|
135
|
+
const head = points.slice(0, topN)
|
|
136
|
+
const rest = points.slice(topN)
|
|
137
|
+
const otherValue = rest.reduce((s, p) => s + p.value, 0)
|
|
138
|
+
const otherCount = rest.reduce((s, p) => s + p.count, 0)
|
|
139
|
+
head.push({
|
|
140
|
+
key: '__other__',
|
|
141
|
+
name: def.otherLabel ?? '其他',
|
|
142
|
+
value: otherValue,
|
|
143
|
+
count: otherCount,
|
|
144
|
+
})
|
|
145
|
+
points = head
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
return {
|
|
149
|
+
id: def.id,
|
|
150
|
+
kind: def.kind,
|
|
151
|
+
points,
|
|
152
|
+
categories: points.map((p) => p.name),
|
|
153
|
+
values: points.map((p) => p.value),
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* 批量构建多组系列(同一 sourceRows,多套分组/度量)。
|
|
159
|
+
*/
|
|
160
|
+
export function buildTableChartSeriesList<T extends RsTableRowData>(
|
|
161
|
+
rows: readonly T[],
|
|
162
|
+
defs: readonly RsTableChartSeriesDef[],
|
|
163
|
+
): RsTableChartSeries[] {
|
|
164
|
+
return defs.map((def) => buildTableChartSeries(rows, def))
|
|
165
|
+
}
|
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RsTable 特性插件协议。
|
|
3
|
+
*
|
|
4
|
+
* 目标:新能力以 feature 注册,避免继续向 RsTable.vue 堆分支。
|
|
5
|
+
*
|
|
6
|
+
* 已迁出 composable 的内置能力(行为真源):
|
|
7
|
+
* - virtual → useRsTableVirtual + useRsTableColumnVirtual + useRsTableScrollLayout
|
|
8
|
+
* - contextMenu → useRsTableContextMenu
|
|
9
|
+
* - summary → useRsTableSummary
|
|
10
|
+
* - sort/selection/filter/tree → useRsTableEngine / useRsTableColumns / useRsTableInteraction
|
|
11
|
+
* - edit → useTableEdit + useRsTableEditActions
|
|
12
|
+
* - analytics / chart → useRsTableSelectionSource + useRsTableChartBridge(表外)
|
|
13
|
+
*
|
|
14
|
+
* Feature.setup 用于生命周期登记与扩展点;重逻辑不在此重复执行。
|
|
15
|
+
*
|
|
16
|
+
* 视图分层(世界级演进):
|
|
17
|
+
* - Core / Shell / EditLayer = 行为真源
|
|
18
|
+
* - RsTableColGroup / Header / Body / BodyRow = 纯视图,不持业务状态
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import type { RsContextMenuItem } from '../context-menu-utils'
|
|
22
|
+
import type { RsTableRowData } from '../table-utils'
|
|
23
|
+
import type { RsTableAnalyticsSnapshot } from '../../composables/useRsTableSelectionSource'
|
|
24
|
+
import type {
|
|
25
|
+
RsTableOverlayContribution,
|
|
26
|
+
RsTableToolbarItem,
|
|
27
|
+
} from './rs-table-feature-host'
|
|
28
|
+
import {
|
|
29
|
+
buildTableChartSeriesList,
|
|
30
|
+
type RsTableChartSeries,
|
|
31
|
+
type RsTableChartSeriesDef,
|
|
32
|
+
} from './table-chart-utils'
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* 表格特性插件上下文(只读查询 + 订阅 + 有限贡献点)。
|
|
36
|
+
* Feature 不得直接操作表格 DOM;图表/指标通过 snapshot / subscribe 接入。
|
|
37
|
+
*/
|
|
38
|
+
export interface RsTableFeatureContext<T extends RsTableRowData = RsTableRowData> {
|
|
39
|
+
/** 当前筛选/排序后的业务行(Engine.viewRows) */
|
|
40
|
+
getViewRows: () => T[]
|
|
41
|
+
/** 当前选中行 */
|
|
42
|
+
getSelectedRows: () => T[]
|
|
43
|
+
/**
|
|
44
|
+
* 分析快照:含 sourceRows(选中优先)与 sourceMode。
|
|
45
|
+
* 柱状图/饼图等应消费 sourceRows,而不是直接绑原始 data。
|
|
46
|
+
*/
|
|
47
|
+
getAnalyticsSnapshot: () => RsTableAnalyticsSnapshot<T>
|
|
48
|
+
/**
|
|
49
|
+
* 订阅分析快照变化(浅层:view/selected 引用或 sourceMode)。
|
|
50
|
+
* @returns teardown
|
|
51
|
+
*/
|
|
52
|
+
subscribeAnalytics: (listener: (snap: RsTableAnalyticsSnapshot<T>) => void) => () => void
|
|
53
|
+
/**
|
|
54
|
+
* 向右键菜单贡献项(setup 时调用)。
|
|
55
|
+
* - 传入数组:静态追加
|
|
56
|
+
* - 传入函数:每次打开菜单时动态贡献(可 push 到 items)
|
|
57
|
+
*/
|
|
58
|
+
contributeContextMenuItems?: (
|
|
59
|
+
contributor:
|
|
60
|
+
| RsContextMenuItem[]
|
|
61
|
+
| ((
|
|
62
|
+
items: RsContextMenuItem[],
|
|
63
|
+
menuCtx: { row: T | null; selectedRows: T[] },
|
|
64
|
+
) => void),
|
|
65
|
+
) => void
|
|
66
|
+
/**
|
|
67
|
+
* 向工具条贡献项(实例级;多表互不共享贡献袋)。
|
|
68
|
+
*/
|
|
69
|
+
contributeToolbarItems?: (
|
|
70
|
+
contributor: RsTableToolbarItem[] | ((items: RsTableToolbarItem[]) => void),
|
|
71
|
+
) => void
|
|
72
|
+
/**
|
|
73
|
+
* 向 overlay 旁路贡献挂载描述(图表壳等,不直接改表格 DOM)。
|
|
74
|
+
*/
|
|
75
|
+
contributeOverlay?: (
|
|
76
|
+
contributor:
|
|
77
|
+
| RsTableOverlayContribution[]
|
|
78
|
+
| ((items: RsTableOverlayContribution[]) => void),
|
|
79
|
+
) => void
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* 企业级表格特性插件接口。
|
|
84
|
+
*
|
|
85
|
+
* @property id 稳定标识,与 props 开关 / 遥测对齐
|
|
86
|
+
* @property setup 挂载时调用;可返回 teardown
|
|
87
|
+
*/
|
|
88
|
+
export interface RsTableFeature<T extends RsTableRowData = RsTableRowData> {
|
|
89
|
+
id: string
|
|
90
|
+
setup(ctx: RsTableFeatureContext<T>): void | (() => void)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* 内置特性 id(与 RsTable props 开关映射)。
|
|
95
|
+
* analytics 不进内置 props 开关:由业务通过 ChartBridge / Feature 外挂。
|
|
96
|
+
*/
|
|
97
|
+
export type RsTableBuiltinFeatureId =
|
|
98
|
+
| 'selection'
|
|
99
|
+
| 'sort'
|
|
100
|
+
| 'filter'
|
|
101
|
+
| 'virtual'
|
|
102
|
+
| 'edit'
|
|
103
|
+
| 'tree'
|
|
104
|
+
| 'contextMenu'
|
|
105
|
+
| 'summary'
|
|
106
|
+
|
|
107
|
+
/** 已有独立 composable 承载的内置 id(feature 仅作登记) */
|
|
108
|
+
export const RS_TABLE_FEATURE_COMPOSABLE_MAP: Record<
|
|
109
|
+
RsTableBuiltinFeatureId,
|
|
110
|
+
string | null
|
|
111
|
+
> = {
|
|
112
|
+
selection: 'useRsTableEngine',
|
|
113
|
+
sort: 'useRsTableEngine',
|
|
114
|
+
filter: 'useRsTableEngine',
|
|
115
|
+
virtual: 'useRsTableVirtual+useRsTableColumnVirtual+useRsTableScrollLayout',
|
|
116
|
+
edit: 'useTableEdit+useRsTableEditActions',
|
|
117
|
+
tree: 'useRsTableEngine+useRsTableInteraction',
|
|
118
|
+
contextMenu: 'useRsTableContextMenu',
|
|
119
|
+
summary: 'useRsTableSummary',
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/** 列布局/拖拽非 builtin feature id,但属内核横切能力 */
|
|
123
|
+
export const RS_TABLE_COLUMN_SHELL = {
|
|
124
|
+
layout: 'useRsTableColumnLayout',
|
|
125
|
+
resize: 'useRsTableColumnResize',
|
|
126
|
+
} as const
|
|
127
|
+
|
|
128
|
+
/** 表外分析/图表扩展入口(不进 RsTable 内核渲染) */
|
|
129
|
+
export const RS_TABLE_ANALYTICS_SHELL = {
|
|
130
|
+
selectionSource: 'useRsTableSelectionSource',
|
|
131
|
+
chartBridge: 'useRsTableChartBridge',
|
|
132
|
+
chartUtils: 'table-chart-utils',
|
|
133
|
+
} as const
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* 按 props 开关解析应启用的内置特性 id 列表。
|
|
137
|
+
*/
|
|
138
|
+
export function resolveBuiltinTableFeatures(flags: {
|
|
139
|
+
selectable?: boolean
|
|
140
|
+
sortableColumns?: boolean
|
|
141
|
+
filterable?: boolean
|
|
142
|
+
virtual?: boolean | 'auto'
|
|
143
|
+
editable?: boolean
|
|
144
|
+
tree?: boolean
|
|
145
|
+
contextMenu?: boolean
|
|
146
|
+
summary?: boolean
|
|
147
|
+
}): RsTableBuiltinFeatureId[] {
|
|
148
|
+
const ids: RsTableBuiltinFeatureId[] = []
|
|
149
|
+
if (flags.selectable) ids.push('selection')
|
|
150
|
+
if (flags.sortableColumns !== false) ids.push('sort')
|
|
151
|
+
if (flags.filterable) ids.push('filter')
|
|
152
|
+
if (flags.virtual !== false) ids.push('virtual')
|
|
153
|
+
if (flags.editable) ids.push('edit')
|
|
154
|
+
if (flags.tree) ids.push('tree')
|
|
155
|
+
if (flags.contextMenu !== false) ids.push('contextMenu')
|
|
156
|
+
if (flags.summary) ids.push('summary')
|
|
157
|
+
return ids
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* 依次 setup 特性列表,并返回统一 teardown(逆序 dispose)。
|
|
162
|
+
*/
|
|
163
|
+
export function setupTableFeatures<T extends RsTableRowData>(
|
|
164
|
+
features: RsTableFeature<T>[],
|
|
165
|
+
ctx: RsTableFeatureContext<T>,
|
|
166
|
+
): () => void {
|
|
167
|
+
const teardowns: Array<() => void> = []
|
|
168
|
+
for (const feature of features) {
|
|
169
|
+
const dispose = feature.setup(ctx)
|
|
170
|
+
if (typeof dispose === 'function') teardowns.push(dispose)
|
|
171
|
+
}
|
|
172
|
+
return () => {
|
|
173
|
+
for (const dispose of teardowns.reverse()) dispose()
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* 创建已绑定 composable 真源的内置 feature。
|
|
179
|
+
*/
|
|
180
|
+
export function createBuiltinTableFeatures<T extends RsTableRowData>(
|
|
181
|
+
ids: RsTableBuiltinFeatureId[],
|
|
182
|
+
): RsTableFeature<T>[] {
|
|
183
|
+
return ids.map((id) => ({
|
|
184
|
+
id,
|
|
185
|
+
setup(ctx) {
|
|
186
|
+
if (import.meta.env.DEV && id === 'summary') {
|
|
187
|
+
void ctx.getAnalyticsSnapshot()
|
|
188
|
+
}
|
|
189
|
+
},
|
|
190
|
+
}))
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* 创建分析/图表旁路 feature:setup 时订阅 snapshot,交由业务 listener。
|
|
195
|
+
* 用于把「选中 → 图表」挂到统一生命周期,而不改 RsTable 模板。
|
|
196
|
+
*/
|
|
197
|
+
export function createAnalyticsTableFeature<T extends RsTableRowData>(options: {
|
|
198
|
+
id?: string
|
|
199
|
+
onSnapshot: (snap: RsTableAnalyticsSnapshot<T>) => void
|
|
200
|
+
}): RsTableFeature<T> {
|
|
201
|
+
return {
|
|
202
|
+
id: options.id ?? 'analytics',
|
|
203
|
+
setup(ctx) {
|
|
204
|
+
return ctx.subscribeAnalytics(options.onSnapshot)
|
|
205
|
+
},
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* 创建右键菜单贡献 feature(静态项或动态函数)。
|
|
211
|
+
*/
|
|
212
|
+
export function createContextMenuTableFeature<T extends RsTableRowData>(options: {
|
|
213
|
+
id?: string
|
|
214
|
+
items:
|
|
215
|
+
| RsContextMenuItem[]
|
|
216
|
+
| ((
|
|
217
|
+
items: RsContextMenuItem[],
|
|
218
|
+
menuCtx: { row: T | null; selectedRows: T[] },
|
|
219
|
+
) => void)
|
|
220
|
+
}): RsTableFeature<T> {
|
|
221
|
+
return {
|
|
222
|
+
id: options.id ?? 'contextMenuExtra',
|
|
223
|
+
setup(ctx) {
|
|
224
|
+
ctx.contributeContextMenuItems?.(options.items)
|
|
225
|
+
},
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/** 创建工具条贡献 feature */
|
|
230
|
+
export function createToolbarTableFeature(options: {
|
|
231
|
+
id?: string
|
|
232
|
+
items: RsTableToolbarItem[] | ((items: RsTableToolbarItem[]) => void)
|
|
233
|
+
}): RsTableFeature {
|
|
234
|
+
return {
|
|
235
|
+
id: options.id ?? 'toolbarExtra',
|
|
236
|
+
setup(ctx) {
|
|
237
|
+
ctx.contributeToolbarItems?.(options.items)
|
|
238
|
+
},
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/** 创建 overlay 贡献 feature(图表旁路挂载点等) */
|
|
243
|
+
export function createOverlayTableFeature(options: {
|
|
244
|
+
id?: string
|
|
245
|
+
items:
|
|
246
|
+
| RsTableOverlayContribution[]
|
|
247
|
+
| ((items: RsTableOverlayContribution[]) => void)
|
|
248
|
+
}): RsTableFeature {
|
|
249
|
+
return {
|
|
250
|
+
id: options.id ?? 'overlayExtra',
|
|
251
|
+
setup(ctx) {
|
|
252
|
+
ctx.contributeOverlay?.(options.items)
|
|
253
|
+
},
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* 官方图表 Feature:订阅 analytics → 库无关 series,并登记 overlay 挂载点。
|
|
259
|
+
* 不捆绑 ECharts/Chart.js;业务用 onSeries 映射 option。
|
|
260
|
+
*/
|
|
261
|
+
export function createChartSeriesTableFeature<T extends RsTableRowData>(options: {
|
|
262
|
+
id?: string
|
|
263
|
+
seriesDefs: RsTableChartSeriesDef[] | (() => RsTableChartSeriesDef[])
|
|
264
|
+
onSeries: (
|
|
265
|
+
series: RsTableChartSeries[],
|
|
266
|
+
snap: RsTableAnalyticsSnapshot<T>,
|
|
267
|
+
) => void
|
|
268
|
+
/** overlay 登记;默认 key=chart / slot=chart */
|
|
269
|
+
overlay?: RsTableOverlayContribution
|
|
270
|
+
}): RsTableFeature<T> {
|
|
271
|
+
return {
|
|
272
|
+
id: options.id ?? 'chartSeries',
|
|
273
|
+
setup(ctx) {
|
|
274
|
+
ctx.contributeOverlay?.([
|
|
275
|
+
options.overlay ?? { key: 'chart', slot: 'chart', meta: { kind: 'series' } },
|
|
276
|
+
])
|
|
277
|
+
return ctx.subscribeAnalytics((snap) => {
|
|
278
|
+
const defs =
|
|
279
|
+
typeof options.seriesDefs === 'function' ? options.seriesDefs() : options.seriesDefs
|
|
280
|
+
options.onSeries(buildTableChartSeriesList(snap.sourceRows, defs), snap)
|
|
281
|
+
})
|
|
282
|
+
},
|
|
283
|
+
}
|
|
284
|
+
}
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RsTable 列汇总纯函数:聚合算法、footer 单元格构建、兼容性警告。
|
|
3
|
+
*
|
|
4
|
+
* 不依赖 Vue;由 RsTable 在 computed 中调用。
|
|
5
|
+
* 聚合范围约定为「当前 viewRows」(筛选/排序后的业务行),不含 group/expand 伪行。
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { RsTableColumn, RsTableRowData } from '../table-utils'
|
|
9
|
+
import { resolveColumnRawValue } from './table-edit-utils'
|
|
10
|
+
|
|
11
|
+
/** 列汇总类型:内置数值聚合 + custom reduce */
|
|
12
|
+
export type RsTableSummaryType = 'sum' | 'avg' | 'count' | 'min' | 'max' | 'custom'
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* 列级汇总配置(挂在 `RsTableColumn.summary`)。
|
|
16
|
+
*
|
|
17
|
+
* - sum/avg/min/max:从行字段取可解析数字;无可解析值时单元格为空
|
|
18
|
+
* - count:行数(不读字段)
|
|
19
|
+
* - custom:必须提供 `reduce`
|
|
20
|
+
*/
|
|
21
|
+
export interface RsTableColumnSummary<T extends RsTableRowData = RsTableRowData> {
|
|
22
|
+
type: RsTableSummaryType
|
|
23
|
+
/** 展示格式化;未传时用默认 String(value) */
|
|
24
|
+
formatter?: (value: number | string, rows: T[]) => string
|
|
25
|
+
/** type=custom 时必填;返回值再经 formatter(若有) */
|
|
26
|
+
reduce?: (rows: T[]) => string | number
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* 汇总数据来源:
|
|
31
|
+
* - client:本地对 viewRows 聚合
|
|
32
|
+
* - server:直接展示业务传入的 summaryData,不做本地 reduce
|
|
33
|
+
*/
|
|
34
|
+
export type RsTableSummaryMode = 'client' | 'server'
|
|
35
|
+
|
|
36
|
+
/** 服务端汇总:列 key → 展示值(null/undefined 显示为空) */
|
|
37
|
+
export type RsTableSummaryData = Record<string, string | number | null | undefined>
|
|
38
|
+
|
|
39
|
+
/** 单列汇总单元格结果(供 footer 行渲染) */
|
|
40
|
+
export interface RsTableSummaryCell {
|
|
41
|
+
key: string
|
|
42
|
+
/** 原始聚合值;无数据时为 null */
|
|
43
|
+
value: string | number | null
|
|
44
|
+
/** 已格式化的展示文案 */
|
|
45
|
+
text: string
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** 将未知值解析为有限数字;失败返回 null(跳过该行) */
|
|
49
|
+
function toNumber(value: unknown): number | null {
|
|
50
|
+
if (typeof value === 'number' && Number.isFinite(value)) return value
|
|
51
|
+
if (typeof value === 'string' && value.trim() !== '') {
|
|
52
|
+
const n = Number(value)
|
|
53
|
+
return Number.isFinite(n) ? n : null
|
|
54
|
+
}
|
|
55
|
+
return null
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* 收集列上可解析的数值序列。
|
|
60
|
+
* 取值路径与编辑/复制一致:resolveColumnRawValue(dataIndex / key)。
|
|
61
|
+
*/
|
|
62
|
+
function collectNumericValues<T extends RsTableRowData>(
|
|
63
|
+
rows: readonly T[],
|
|
64
|
+
column: RsTableColumn<T>,
|
|
65
|
+
): number[] {
|
|
66
|
+
const nums: number[] = []
|
|
67
|
+
for (let i = 0; i < rows.length; i += 1) {
|
|
68
|
+
const raw = resolveColumnRawValue(rows[i] as T, column, i)
|
|
69
|
+
const n = toNumber(raw)
|
|
70
|
+
if (n != null) nums.push(n)
|
|
71
|
+
}
|
|
72
|
+
return nums
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* 对单列做汇总。列未配置 `summary` 时返回 null(由调用方填空单元格)。
|
|
77
|
+
*
|
|
78
|
+
* @param column 含可选 summary 的列定义
|
|
79
|
+
* @param rows 参与聚合的业务行(通常为 Engine.viewRows)
|
|
80
|
+
*/
|
|
81
|
+
export function aggregateColumnSummary<T extends RsTableRowData>(
|
|
82
|
+
column: RsTableColumn<T> & { summary?: RsTableColumnSummary<T> },
|
|
83
|
+
rows: readonly T[],
|
|
84
|
+
): RsTableSummaryCell | null {
|
|
85
|
+
const config = column.summary
|
|
86
|
+
if (!config) return null
|
|
87
|
+
|
|
88
|
+
let value: string | number | null = null
|
|
89
|
+
|
|
90
|
+
if (config.type === 'custom') {
|
|
91
|
+
value = config.reduce ? config.reduce(rows as T[]) : null
|
|
92
|
+
} else if (config.type === 'count') {
|
|
93
|
+
value = rows.length
|
|
94
|
+
} else {
|
|
95
|
+
const nums = collectNumericValues(rows, column)
|
|
96
|
+
if (nums.length === 0) {
|
|
97
|
+
value = null
|
|
98
|
+
} else if (config.type === 'sum') {
|
|
99
|
+
value = nums.reduce((a, b) => a + b, 0)
|
|
100
|
+
} else if (config.type === 'avg') {
|
|
101
|
+
value = nums.reduce((a, b) => a + b, 0) / nums.length
|
|
102
|
+
} else if (config.type === 'min') {
|
|
103
|
+
value = Math.min(...nums)
|
|
104
|
+
} else if (config.type === 'max') {
|
|
105
|
+
value = Math.max(...nums)
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const text =
|
|
110
|
+
value == null
|
|
111
|
+
? ''
|
|
112
|
+
: config.formatter
|
|
113
|
+
? config.formatter(value, rows as T[])
|
|
114
|
+
: String(value)
|
|
115
|
+
|
|
116
|
+
return { key: column.key, value, text }
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* 计算整行 footer 汇总单元格(与 displayColumns 一一对应)。
|
|
121
|
+
*
|
|
122
|
+
* - client:按 columns.summary 对 rows 聚合;无 summary 的列 text 为空
|
|
123
|
+
* - server:优先读 summaryData[key];仍可用列上 formatter;忽略本地 sum/avg 等
|
|
124
|
+
*
|
|
125
|
+
* @returns 与 columns 等长的单元格数组
|
|
126
|
+
*/
|
|
127
|
+
export function buildTableSummaryCells<T extends RsTableRowData>(options: {
|
|
128
|
+
columns: Array<RsTableColumn<T> & { summary?: RsTableColumnSummary<T> }>
|
|
129
|
+
rows: readonly T[]
|
|
130
|
+
mode: RsTableSummaryMode
|
|
131
|
+
summaryData?: RsTableSummaryData | null
|
|
132
|
+
}): RsTableSummaryCell[] {
|
|
133
|
+
const { columns, rows, mode, summaryData } = options
|
|
134
|
+
if (mode === 'server') {
|
|
135
|
+
return columns.map((column) => {
|
|
136
|
+
if (!column.summary && summaryData?.[column.key] === undefined) {
|
|
137
|
+
return { key: column.key, value: null, text: '' }
|
|
138
|
+
}
|
|
139
|
+
const raw = summaryData?.[column.key]
|
|
140
|
+
const value = raw == null ? null : raw
|
|
141
|
+
const text =
|
|
142
|
+
value == null
|
|
143
|
+
? ''
|
|
144
|
+
: column.summary?.formatter
|
|
145
|
+
? column.summary.formatter(value as string | number, rows as T[])
|
|
146
|
+
: String(value)
|
|
147
|
+
return { key: column.key, value, text }
|
|
148
|
+
})
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
return columns.map((column) => {
|
|
152
|
+
const cell = aggregateColumnSummary(column, rows)
|
|
153
|
+
return cell ?? { key: column.key, value: null, text: '' }
|
|
154
|
+
})
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* 是否存在任何可展示的汇总配置。
|
|
159
|
+
*
|
|
160
|
+
* - client:任一列带 summary
|
|
161
|
+
* - server:summaryData 非空对象
|
|
162
|
+
*
|
|
163
|
+
* 用于决定是否启用 summary feature / 渲染内置汇总行。
|
|
164
|
+
*/
|
|
165
|
+
export function hasTableSummaryConfig<T extends RsTableRowData>(options: {
|
|
166
|
+
columns: Array<RsTableColumn<T> & { summary?: RsTableColumnSummary<T> }>
|
|
167
|
+
mode: RsTableSummaryMode
|
|
168
|
+
summaryData?: RsTableSummaryData | null
|
|
169
|
+
}): boolean {
|
|
170
|
+
if (options.mode === 'server') {
|
|
171
|
+
return Boolean(options.summaryData && Object.keys(options.summaryData).length > 0)
|
|
172
|
+
}
|
|
173
|
+
return options.columns.some((column) => Boolean(column.summary))
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* summary 与互斥/易误解能力的兼容矩阵检查(仅 DEV 警告)。
|
|
178
|
+
*
|
|
179
|
+
* - tree:合计基于已展开的 viewRows,不含折叠子节点
|
|
180
|
+
* - detail expandable:合计不含 expand 明细槽内容
|
|
181
|
+
*/
|
|
182
|
+
export function warnSummaryCompatibility(flags: {
|
|
183
|
+
summaryEnabled: boolean
|
|
184
|
+
tree: boolean
|
|
185
|
+
detailExpandable: boolean
|
|
186
|
+
}): void {
|
|
187
|
+
if (!flags.summaryEnabled) return
|
|
188
|
+
if (flags.tree) {
|
|
189
|
+
console.warn('[RsTable] summary 与 treeConfig 同开时,合计基于展平行 viewRows,不含折叠子节点')
|
|
190
|
+
}
|
|
191
|
+
if (flags.detailExpandable) {
|
|
192
|
+
console.warn('[RsTable] summary 与 detail expandable 同开时,合计不含 expand 明细槽内容')
|
|
193
|
+
}
|
|
194
|
+
}
|