@pnt-team/pnt-component-frontend 0.0.132 → 0.0.133

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.
@@ -0,0 +1,404 @@
1
+ /**
2
+ * PntTable — 统一表格组件
3
+ *
4
+ * 封装 tdesign-react Table,落地产品规范 12 章 47 条规则:
5
+ * 排序三态循环、筛选草稿/生效分离、多选三态+翻页清空、分页越界校验、
6
+ * 请求顺序控制、7 种状态管理、蓝紫双主题、同字段 sort+headerFilter 互斥。
7
+ *
8
+ * 零业务侵入:业务项目通过 fetcher 注入数据源,不依赖组件库内部 API。
9
+ */
10
+
11
+ import { forwardRef, useCallback, useEffect, useImperativeHandle, useMemo, useState } from 'react';
12
+ import { Table } from 'tdesign-react';
13
+ import "./index.scss";
14
+ import { useTableCore, validateColumnConflicts } from "./useTableCore";
15
+
16
+ // ============================================================
17
+ // 常量
18
+ // ============================================================
19
+ import { jsx as _jsx } from "react/jsx-runtime";
20
+ import { jsxs as _jsxs } from "react/jsx-runtime";
21
+ import { Fragment as _Fragment } from "react/jsx-runtime";
22
+ const DEFAULT_PAGE_SIZE_OPTIONS = [10, 20, 50];
23
+
24
+ // ============================================================
25
+ // 工具:列定义转换
26
+ // ============================================================
27
+
28
+ function getDefaultAlign(type) {
29
+ switch (type) {
30
+ case 'number':
31
+ case 'money':
32
+ case 'percent':
33
+ return 'right';
34
+ default:
35
+ return 'left';
36
+ }
37
+ }
38
+ function mapPntColumns(pntCols) {
39
+ return pntCols.map(col => {
40
+ const mapped = {
41
+ colKey: col.key,
42
+ title: col.title,
43
+ width: col.width,
44
+ fixed: col.fixed,
45
+ align: col.align ?? getDefaultAlign(col.type),
46
+ ellipsis: col.ellipsis === false ? undefined : col.ellipsis ?? true
47
+ };
48
+
49
+ // 排序
50
+ if (col.sort) {
51
+ mapped.sorter = true;
52
+ }
53
+
54
+ // 筛选
55
+ if (col.headerFilter) {
56
+ if (col.headerFilter.mode === 'custom') {
57
+ // 自定义组件(例:DateRangePickerPanel)
58
+ mapped.filter = {
59
+ type: 'custom',
60
+ component: col.headerFilter.component,
61
+ ...(col.headerFilter.extra ?? {})
62
+ };
63
+ } else if (col.headerFilter.options?.length) {
64
+ // 静态选项单选/多选
65
+ mapped.filter = {
66
+ type: col.headerFilter.mode === 'multiple' ? 'multiple' : 'single',
67
+ list: col.headerFilter.options,
68
+ ...(col.headerFilter.extra ?? {})
69
+ };
70
+ }
71
+ }
72
+
73
+ // 自定义渲染
74
+ if (col.cell) {
75
+ mapped.cell = ({
76
+ row,
77
+ rowIndex
78
+ }) => col.cell(row, rowIndex);
79
+ }
80
+
81
+ // 表头说明(用 title 包裹 + CSS tooltip)
82
+ if (col.help && typeof mapped.title === 'string') {
83
+ mapped.title = /*#__PURE__*/_jsxs("span", {
84
+ className: "pnt-table-col-title",
85
+ children: [mapped.title, /*#__PURE__*/_jsx("span", {
86
+ className: "pnt-table-help-icon",
87
+ title: col.help,
88
+ children: "\u24D8"
89
+ })]
90
+ });
91
+ }
92
+ return mapped;
93
+ });
94
+ }
95
+
96
+ // ============================================================
97
+ // 主组件
98
+ // ============================================================
99
+
100
+ export const PntTable = /*#__PURE__*/forwardRef((props, ref) => {
101
+ const {
102
+ tableId,
103
+ theme = 'business',
104
+ rowKey,
105
+ columns,
106
+ dataMode,
107
+ fetcher,
108
+ data,
109
+ total: clientTotal,
110
+ selectable = false,
111
+ currentPageOnly = true,
112
+ pageable = true,
113
+ initialPage = 1,
114
+ pageSize: initialPageSize = 10,
115
+ pageSizeOptions = DEFAULT_PAGE_SIZE_OPTIONS,
116
+ empty,
117
+ emptyText = '暂无数据',
118
+ emptyWithFilterHint = '调整条件或重置查询',
119
+ bordered = false,
120
+ size = 'medium',
121
+ loading: controlledLoading,
122
+ bodyMaxHeight,
123
+ businessId,
124
+ env,
125
+ timezone,
126
+ onQueryChange,
127
+ onSelectionChange,
128
+ className,
129
+ style,
130
+ rowClassName
131
+ } = props;
132
+
133
+ // --- selectable 配置展开 ---
134
+ const selectConfig = useMemo(() => {
135
+ if (!selectable) return null;
136
+ if (typeof selectable === 'object') return selectable;
137
+ return {}; // 空对象表示启用多选但无额外配置
138
+ }, [selectable]);
139
+ const isSelectable = !!selectConfig;
140
+ const effectiveCurrentPageOnly = selectConfig?.currentPageOnly ?? currentPageOnly;
141
+ const controlledSelectedRowKeys = selectConfig?.selectedRowKeys;
142
+ const disabledRowKeysFn = selectConfig?.disabledRowKeysFn;
143
+
144
+ // --- 受控选中行 state ---
145
+ const [internalSelectedRowKeys, setInternalSelectedRowKeys] = useState(() => controlledSelectedRowKeys ?? []);
146
+ // 同步受控 prop 变化
147
+ useEffect(() => {
148
+ if (controlledSelectedRowKeys !== undefined) {
149
+ setInternalSelectedRowKeys(controlledSelectedRowKeys);
150
+ }
151
+ }, [controlledSelectedRowKeys]);
152
+
153
+ // --- core hook ---
154
+ const core = useTableCore({
155
+ tableId,
156
+ rowKey,
157
+ columns,
158
+ dataMode,
159
+ fetcher,
160
+ data,
161
+ total: clientTotal,
162
+ initialPage,
163
+ pageSize: initialPageSize,
164
+ pageSizeOptions,
165
+ businessId,
166
+ env,
167
+ timezone,
168
+ onQueryChange,
169
+ onSelectionChange,
170
+ selectable: isSelectable,
171
+ currentPageOnly: effectiveCurrentPageOnly
172
+ });
173
+
174
+ // --- imperative handle ---
175
+ useImperativeHandle(ref, () => ({
176
+ refresh: () => core.refresh(),
177
+ reset: () => {
178
+ core.resetFilters();
179
+ core.setPage(1);
180
+ },
181
+ getQuerySnapshot: () => ({
182
+ sort: core.sort,
183
+ filters: core.appliedFilters,
184
+ page: core.page,
185
+ pageSize: core.pageSize,
186
+ total: core.total
187
+ })
188
+ }));
189
+
190
+ // --- 开发阶段互斥校验 ---
191
+ const conflicts = useMemo(() => validateColumnConflicts(columns), [columns]);
192
+ useEffect(() => {
193
+ if (conflicts.length > 0) {
194
+ conflicts.forEach(c => console.error(c));
195
+ }
196
+ }, [conflicts]);
197
+
198
+ // --- 列定义转换 ---
199
+ const tdesignCols = useMemo(() => {
200
+ const mapped = mapPntColumns(columns);
201
+ // 多选模式:插入选择列
202
+ if (isSelectable) {
203
+ mapped.unshift({
204
+ colKey: 'pnt-row-select',
205
+ type: 'multiple',
206
+ width: 64,
207
+ checkProps: disabledRowKeysFn ? ({
208
+ row
209
+ }) => ({
210
+ disabled: disabledRowKeysFn(row)
211
+ }) : undefined
212
+ });
213
+ }
214
+ return mapped;
215
+ }, [columns, isSelectable, disabledRowKeysFn]);
216
+
217
+ // --- defaultSort 映射(TDesign 用 SortInfo:{ sortBy, descending }) ---
218
+ const defaultSortInfo = useMemo(() => {
219
+ if (!core.sort) return undefined;
220
+ return {
221
+ sortBy: core.sort.field,
222
+ descending: core.sort.direction === 'desc'
223
+ };
224
+ }, [core.sort]);
225
+
226
+ // --- 筛选 filterValue 映射 ---
227
+ const filterValue = useMemo(() => {
228
+ const fv = {};
229
+ Object.entries(core.appliedFilters).forEach(([k, v]) => {
230
+ fv[k] = v;
231
+ });
232
+ return Object.keys(fv).length > 0 ? fv : undefined;
233
+ }, [core.appliedFilters]);
234
+
235
+ // --- 排序三态处理(TDesign onSortChange 返回 TableSort) ---
236
+ const handleSortChange = useCallback(sort => {
237
+ // TDesign 可能返回 SortInfo 或 SortInfo[],取第一个
238
+ const info = Array.isArray(sort) ? sort[0] : sort;
239
+ if (!info) return;
240
+ const field = info.sortBy;
241
+ const currentDir = info.descending ? 'desc' : 'asc';
242
+ core.toggleSort(field);
243
+ void currentDir; // core.toggleSort 内部会自己处理三态
244
+ }, [core]);
245
+
246
+ // --- 筛选处理 ---
247
+ const handleFilterChange = useCallback(filterVal => {
248
+ core.applyFilters(filterVal);
249
+ }, [core]);
250
+
251
+ // --- 分页处理 ---
252
+ const pagination = useMemo(() => {
253
+ if (!pageable) return undefined;
254
+ return {
255
+ total: core.total,
256
+ current: core.page,
257
+ pageSize: core.pageSize,
258
+ pageSizeOptions,
259
+ showJumper: true,
260
+ showPageSize: true,
261
+ onChange: pageInfo => {
262
+ const {
263
+ current,
264
+ pageSize: size
265
+ } = pageInfo;
266
+ if (current !== core.page) core.setPage(current);
267
+ if (size !== core.pageSize) core.setPageSize(size);
268
+ }
269
+ };
270
+ }, [pageable, core.total, core.page, core.pageSize, pageSizeOptions, core.setPage, core.setPageSize]);
271
+
272
+ // --- 加载态 ---
273
+ const isLoading = typeof controlledLoading === 'boolean' ? controlledLoading : core.status === 'loading' || core.status === 'refreshing';
274
+
275
+ // --- 空态内容(不用 Empty 组件,直接 ReactNode) ---
276
+ const emptyContent = useMemo(() => {
277
+ if (core.status === 'error') {
278
+ return /*#__PURE__*/_jsxs("div", {
279
+ className: "pnt-table-empty",
280
+ children: [/*#__PURE__*/_jsx("div", {
281
+ className: "pnt-table-empty-icon",
282
+ children: "\u26A0\uFE0F"
283
+ }), /*#__PURE__*/_jsx("div", {
284
+ className: "pnt-table-empty-desc",
285
+ children: core.error || '加载失败'
286
+ })]
287
+ });
288
+ }
289
+ if (core.status === 'no-permission') {
290
+ return /*#__PURE__*/_jsxs("div", {
291
+ className: "pnt-table-empty",
292
+ children: [/*#__PURE__*/_jsx("div", {
293
+ className: "pnt-table-empty-icon",
294
+ children: "\uD83D\uDD12"
295
+ }), /*#__PURE__*/_jsx("div", {
296
+ className: "pnt-table-empty-desc",
297
+ children: "\u65E0\u6743\u9650\u67E5\u770B"
298
+ })]
299
+ });
300
+ }
301
+ if (core.status === 'empty-with-filter') {
302
+ return /*#__PURE__*/_jsxs("div", {
303
+ className: "pnt-table-empty",
304
+ children: [/*#__PURE__*/_jsx("div", {
305
+ className: "pnt-table-empty-icon",
306
+ children: "\uD83D\uDCED"
307
+ }), /*#__PURE__*/_jsx("div", {
308
+ className: "pnt-table-empty-desc",
309
+ children: emptyText
310
+ }), /*#__PURE__*/_jsx("div", {
311
+ className: "pnt-table-empty-hint",
312
+ children: emptyWithFilterHint
313
+ })]
314
+ });
315
+ }
316
+ if (core.status === 'empty') {
317
+ if (empty) return /*#__PURE__*/_jsx(_Fragment, {
318
+ children: empty
319
+ });
320
+ return /*#__PURE__*/_jsxs("div", {
321
+ className: "pnt-table-empty",
322
+ children: [/*#__PURE__*/_jsx("div", {
323
+ className: "pnt-table-empty-icon",
324
+ children: "\uD83D\uDCED"
325
+ }), /*#__PURE__*/_jsx("div", {
326
+ className: "pnt-table-empty-desc",
327
+ children: emptyText
328
+ })]
329
+ });
330
+ }
331
+ return undefined;
332
+ }, [core.status, core.error, empty, emptyText, emptyWithFilterHint]);
333
+
334
+ // --- 多选处理 ---
335
+ const handleSelectChange = useCallback(selectedRowKeys => {
336
+ if (!isSelectable) return;
337
+ const currentPageKeys = core.data.map(r => String(r[rowKey]));
338
+ const disabledKeys = disabledRowKeysFn ? core.data.filter(disabledRowKeysFn).map(r => String(r[rowKey])) : [];
339
+ const disabledSet = new Set(disabledKeys);
340
+
341
+ // 过滤掉禁用行的勾选
342
+ let keys = selectedRowKeys.map(String).filter(k => !disabledSet.has(k));
343
+ if (effectiveCurrentPageOnly) {
344
+ keys = keys.filter(k => currentPageKeys.includes(k));
345
+ }
346
+ if (controlledSelectedRowKeys === undefined) {
347
+ // 非受控:内部维护
348
+ setInternalSelectedRowKeys(keys);
349
+ }
350
+ const selectedRows = keys.map(k => core.data.find(r => String(r[rowKey]) === k) ?? {
351
+ [rowKey]: k
352
+ });
353
+ onSelectionChange?.(selectedRows, {
354
+ tableId,
355
+ pageKeys: currentPageKeys,
356
+ disabledKeys,
357
+ trigger: 'select'
358
+ });
359
+ }, [isSelectable, core.data, rowKey, disabledRowKeysFn, effectiveCurrentPageOnly, controlledSelectedRowKeys, tableId, onSelectionChange]);
360
+
361
+ // --- 滚动(类型宽松,TDesign 不同版本 TScroll 定义不同) ---
362
+ const scroll = useMemo(() => {
363
+ if (!bodyMaxHeight) return undefined;
364
+ return {
365
+ y: bodyMaxHeight
366
+ };
367
+ }, [bodyMaxHeight]);
368
+
369
+ // ============================================================
370
+ // 渲染
371
+ // ============================================================
372
+
373
+ return /*#__PURE__*/_jsx("div", {
374
+ "data-theme": theme,
375
+ className: `pnt-table ${className ?? ''}`,
376
+ style: style,
377
+ children: /*#__PURE__*/_jsx(Table, {
378
+ rowKey: rowKey,
379
+ data: core.data,
380
+ columns: tdesignCols,
381
+ loading: isLoading,
382
+ hover: true,
383
+ stripe: false,
384
+ bordered: bordered,
385
+ verticalAlign: "middle",
386
+ size: size,
387
+ defaultSort: defaultSortInfo,
388
+ filterValue: filterValue,
389
+ loadingProps: {
390
+ text: '加载中…'
391
+ },
392
+ pagination: pagination,
393
+ scroll: scroll,
394
+ empty: emptyContent,
395
+ selectedRowKeys: isSelectable ? controlledSelectedRowKeys ?? internalSelectedRowKeys : undefined,
396
+ onSortChange: handleSortChange,
397
+ onFilterChange: handleFilterChange,
398
+ onSelectChange: handleSelectChange,
399
+ rowClassName: rowClassName
400
+ })
401
+ });
402
+ });
403
+ PntTable.displayName = 'PntTable';
404
+ export default PntTable;
@@ -0,0 +1,315 @@
1
+ /**
2
+ * PntTable 蓝紫双主题样式
3
+ *
4
+ * CSS 变量映射:一套样式两类主题
5
+ * data-theme="business" → 蓝主题(业务侧,默认)
6
+ * data-theme="internal" → 紫主题(内部平台)
7
+ *
8
+ * 规范来源:管理端组件规范 TBL-02.01 / TBL-02.02
9
+ */
10
+
11
+ // ============================================================
12
+ // CSS 变量(默认蓝主题)
13
+ // ============================================================
14
+
15
+ .pnt-table {
16
+ // 主题品牌色
17
+ --pnt-table-brand: #3f8cff;
18
+ --pnt-table-brand-hover: #2875e3;
19
+ --pnt-table-brand-active: #1f65cc;
20
+ --pnt-table-brand-soft: #eaf3ff;
21
+
22
+ // 语义色(不随主题变化)
23
+ --pnt-table-danger: #d54941;
24
+ --pnt-table-danger-soft: #fdecea;
25
+ --pnt-table-success: #2ba471;
26
+ --pnt-table-success-soft: #e3f6ee;
27
+ --pnt-table-warning: #e37318;
28
+ --pnt-table-warning-soft: #fef3e8;
29
+
30
+ // 中性色
31
+ --pnt-table-bg: #ffffff;
32
+ --pnt-table-bg-header: #f8fafd;
33
+ --pnt-table-bg-hover: #f8fafd;
34
+ --pnt-table-bg-selected: var(--pnt-table-brand-soft);
35
+ --pnt-table-border: #e5e8ef;
36
+ --pnt-table-text-primary: #1d2129;
37
+ --pnt-table-text-secondary: #4e5969;
38
+ --pnt-table-text-tertiary: #86909c;
39
+ --pnt-table-text-placeholder: #bbbfc4;
40
+
41
+ // 尺寸(TBL-02.04 / TBL-02.05)
42
+ --pnt-table-header-height: 48px;
43
+ --pnt-table-row-min-height: 48px;
44
+ --pnt-table-cell-padding-v: 12px;
45
+ --pnt-table-cell-padding-h: 16px;
46
+ }
47
+
48
+ // 紫主题覆盖
49
+ .pnt-table[data-theme='internal'] {
50
+ --pnt-table-brand: #624beb;
51
+ --pnt-table-brand-hover: #503bd1;
52
+ --pnt-table-brand-active: #412eb5;
53
+ --pnt-table-brand-soft: #f0edff;
54
+ }
55
+
56
+ // ============================================================
57
+ // 表头(TBL-03.01 / TBL-03.02 / TBL-03.03)
58
+ // ============================================================
59
+
60
+ .pnt-table .t-table__header {
61
+ background-color: var(--pnt-table-bg-header);
62
+ font-weight: 600;
63
+ color: var(--pnt-table-text-primary);
64
+ font-size: 14px;
65
+ line-height: var(--pnt-table-header-height);
66
+ }
67
+
68
+ .pnt-table .t-table__header th {
69
+ height: var(--pnt-table-header-height);
70
+ padding: 0 var(--pnt-table-cell-padding-h);
71
+ border-bottom: 1px solid var(--pnt-table-border);
72
+ border-right: none;
73
+ background-color: var(--pnt-table-bg-header);
74
+ white-space: nowrap;
75
+ overflow: hidden;
76
+ text-overflow: ellipsis;
77
+ }
78
+
79
+ // 表头单元格(左中右对齐)
80
+ .pnt-table .t-table__header th.tdesign-table-td--left { text-align: left; }
81
+ .pnt-table .t-table__header th.tdesign-table-td--center,
82
+ .pnt-table .t-table__header th.tdesign-table-td--middle { text-align: center; }
83
+ .pnt-table .t-table__header th.tdesign-table-td--right { text-align: right; }
84
+
85
+ // ============================================================
86
+ // 表体(TBL-03.03 / TBL-03.05 / TBL-03.06)
87
+ // ============================================================
88
+
89
+ .pnt-table .t-table__body td {
90
+ padding: var(--pnt-table-cell-padding-v) var(--pnt-table-cell-padding-h);
91
+ min-height: var(--pnt-table-row-min-height);
92
+ border-bottom: 1px solid var(--pnt-table-border);
93
+ font-size: 14px;
94
+ color: var(--pnt-table-text-primary);
95
+ }
96
+
97
+ // hover 态(TBL-02.03 / TBL-02.08)
98
+ .pnt-table .t-table__body tr:hover > td {
99
+ background-color: var(--pnt-table-bg-hover);
100
+ }
101
+
102
+ // 选中态(TBL-02.09 / TBL-02.10)
103
+ .pnt-table .t-table__body tr.is-selected > td {
104
+ background-color: var(--pnt-table-bg-selected);
105
+ }
106
+ // 选中后 hover 仍保持选中背景
107
+ .pnt-table .t-table__body tr.is-selected:hover > td {
108
+ background-color: var(--pnt-table-bg-selected);
109
+ }
110
+
111
+ // 斑马纹:默认关闭(TBL-02.03 明确默认无)
112
+ // 业务需要时显式传 stripe 开启,PntTable 默认 false
113
+
114
+ // ============================================================
115
+ // 对齐(TBL-03.06)
116
+ // ============================================================
117
+
118
+ .pnt-table .tdesign-table-td--left,
119
+ .pnt-table .tdesign-table-td--start { text-align: left; }
120
+ .pnt-table .tdesign-table-td--center,
121
+ .pnt-table .tdesign-table-td--middle { text-align: center; }
122
+ .pnt-table .tdesign-table-td--right,
123
+ .pnt-table .tdesign-table-td--end { text-align: right; }
124
+
125
+ // ============================================================
126
+ // 列标题 + help icon
127
+ // ============================================================
128
+
129
+ .pnt-table-col-title {
130
+ display: inline-flex;
131
+ align-items: center;
132
+ gap: 4px;
133
+
134
+ .pnt-table-help-icon {
135
+ display: inline-flex;
136
+ width: 14px;
137
+ height: 14px;
138
+ border-radius: 50%;
139
+ background: var(--pnt-table-border);
140
+ color: var(--pnt-table-text-tertiary);
141
+ font-size: 10px;
142
+ line-height: 14px;
143
+ justify-content: center;
144
+ cursor: help;
145
+ flex-shrink: 0;
146
+
147
+ &:hover {
148
+ background: var(--pnt-table-brand);
149
+ color: #fff;
150
+ }
151
+ }
152
+ }
153
+
154
+ // ============================================================
155
+ // 空态(TBL-09.03 / TBL-09.04)
156
+ // ============================================================
157
+
158
+ .pnt-table .t-table__empty,
159
+ .pnt-table .t-empty {
160
+ padding: 48px 0;
161
+ color: var(--pnt-table-text-tertiary);
162
+ font-size: 14px;
163
+ }
164
+
165
+ .pnt-table-empty-with-filter {
166
+ display: flex;
167
+ flex-direction: column;
168
+ gap: 4px;
169
+
170
+ .pnt-table-empty-hint {
171
+ font-size: 12px;
172
+ color: var(--pnt-table-text-tertiary);
173
+ }
174
+ }
175
+
176
+ // ============================================================
177
+ // 分页(TBL-07.01 / TBL-07.06)
178
+ // ============================================================
179
+
180
+ .pnt-table .t-pagination {
181
+ padding: 16px 0;
182
+ justify-content: flex-end;
183
+ gap: 12px;
184
+ font-size: 14px;
185
+ color: var(--pnt-table-text-secondary);
186
+
187
+ .t-pagination__btn {
188
+ border: none;
189
+ border-radius: 4px;
190
+ padding: 0 8px;
191
+ height: 32px;
192
+
193
+ &:hover:not(.is-disabled) {
194
+ color: var(--pnt-table-brand);
195
+ }
196
+
197
+ &.is-active {
198
+ background: var(--pnt-table-brand);
199
+ color: #fff;
200
+ }
201
+ }
202
+
203
+ .t-pagination__total {
204
+ margin-right: auto;
205
+ }
206
+
207
+ .t-pagination__size,
208
+ .t-pagination__jump {
209
+ display: flex;
210
+ align-items: center;
211
+ gap: 8px;
212
+ }
213
+ }
214
+
215
+ // ============================================================
216
+ // 加载态(TBL-09.01 / TBL-09.02)
217
+ // ============================================================
218
+
219
+ .pnt-table .t-table__loading {
220
+ position: absolute;
221
+ inset: 0;
222
+ background: rgba(255, 255, 255, 0.6);
223
+ z-index: 1;
224
+
225
+ .t-table__loading-text {
226
+ color: var(--pnt-table-text-secondary);
227
+ font-size: 14px;
228
+ }
229
+ }
230
+
231
+ // 骨架(首次加载)
232
+ .pnt-table .t-table__loading--skeleton {
233
+ .t-table__body tr td {
234
+ height: var(--pnt-table-row-min-height);
235
+ }
236
+ }
237
+
238
+ // ============================================================
239
+ // 固定列(TBL-08.01 / TBL-08.02)
240
+ // ============================================================
241
+
242
+ .pnt-table .t-table__cell--fixed-left,
243
+ .pnt-table .t-table__cell--fixed-right {
244
+ background-color: var(--pnt-table-bg);
245
+ box-shadow: none;
246
+
247
+ // 固定列阴影边界
248
+ &::after {
249
+ content: '';
250
+ position: absolute;
251
+ top: 0;
252
+ bottom: 0;
253
+ width: 8px;
254
+ pointer-events: none;
255
+ }
256
+ }
257
+
258
+ .pnt-table .t-table__cell--fixed-left::after {
259
+ right: -8px;
260
+ box-shadow: inset -8px 0 4px -4px rgba(0, 0, 0, 0.06);
261
+ }
262
+
263
+ .pnt-table .t-table__cell--fixed-right::after {
264
+ left: -8px;
265
+ box-shadow: inset 8px 0 4px -4px rgba(0, 0, 0, 0.06);
266
+ }
267
+
268
+ // 固定列跟随 hover/选中背景
269
+ .pnt-table .t-table__body tr:hover > td.t-table__cell--fixed-left,
270
+ .pnt-table .t-table__body tr:hover > td.t-table__cell--fixed-right {
271
+ background-color: var(--pnt-table-bg-hover);
272
+ }
273
+
274
+ .pnt-table .t-table__body tr.is-selected > td.t-table__cell--fixed-left,
275
+ .pnt-table .t-table__body tr.is-selected > td.t-table__cell--fixed-right {
276
+ background-color: var(--pnt-table-bg-selected);
277
+ }
278
+
279
+ // ============================================================
280
+ // 多选列(TBL-06.01 / TBL-06.02)
281
+ // ============================================================
282
+
283
+ .pnt-table .t-table__select {
284
+ width: 44px;
285
+ text-align: center;
286
+
287
+ .t-checkbox {
288
+ cursor: pointer;
289
+ }
290
+ }
291
+
292
+ // ============================================================
293
+ // 滚动容器
294
+ // ============================================================
295
+
296
+ .pnt-table .t-table__body-wrapper::-webkit-scrollbar,
297
+ .pnt-table .t-table__header-wrapper::-webkit-scrollbar {
298
+ width: 8px;
299
+ height: 8px;
300
+ }
301
+
302
+ .pnt-table .t-table__body-wrapper::-webkit-scrollbar-thumb,
303
+ .pnt-table .t-table__header-wrapper::-webkit-scrollbar-thumb {
304
+ background: #c9cdd4;
305
+ border-radius: 4px;
306
+
307
+ &:hover {
308
+ background: var(--pnt-table-brand);
309
+ }
310
+ }
311
+
312
+ .pnt-table .t-table__body-wrapper::-webkit-scrollbar-track,
313
+ .pnt-table .t-table__header-wrapper::-webkit-scrollbar-track {
314
+ background: transparent;
315
+ }