fix(web): 操作日志列头吸顶并移除重复列表工具条

Semi Table 增加 scroll.y(按容器高度测量),表头固定、仅表体滚动;移除「日志列表 / 已加载 N 条」重复工具条,刷新按钮移到页头,条数保留在底部分页;表格宽度统一跟随容器,修复宽屏右侧空白与 1024 留白。
This commit is contained in:
Stev_Wang committed 2026-11-04 16:55:54 +08:00
1 parent ab951894b9
commit dca498ec15
3 files changed
+35 -57

No files matched your search

@@ -5,7 +5,7 @@ import type { AuditLogItem } from '@lingxi/shared';
import { notifyError, notifySuccess, PageEmpty } from '@lingxi/ui';
import { IconCheck, IconCopy, IconRefresh } from '@lingxi/ui/icons';
import type { ReactNode } from 'react';
import { useEffect, useState } from 'react';
import { useEffect, useRef, useState } from 'react';
import { copyToClipboard } from './audit-clipboard';
import {
@@ -19,7 +19,6 @@ import {
export interface AuditLogTableProps {
items: readonly AuditLogItem[];
loading: boolean;
refreshing?: boolean;
isFetchingNextPage: boolean;
hasNextPage: boolean;
/** 追加分页失败时的提示(保留已有数据) */
@@ -27,7 +26,6 @@ export interface AuditLogTableProps {
onOpen: (logId: string) => void;
onLoadMore: () => void;
onRetryMore: () => void;
onRefresh: () => void;
onClearFilters: () => void;
}
@@ -92,17 +90,29 @@ function useNarrowTable(): boolean {
export function AuditLogTable({
items,
loading,
refreshing = false,
isFetchingNextPage,
hasNextPage,
errorMessage = null,
onOpen,
onLoadMore,
onRetryMore,
onRefresh,
onClearFilters,
}: AuditLogTableProps): ReactNode {
const narrow = useNarrowTable();
const wrapRef = useRef<HTMLDivElement>(null);
const [tableHeight, setTableHeight] = useState(360);
useEffect(() => {
const element = wrapRef.current;
if (element === null || typeof ResizeObserver === 'undefined') return undefined;
const observer = new ResizeObserver((entries) => {
const height = Math.floor(entries[0]?.contentRect.height ?? 0);
if (height > 0) setTableHeight(Math.max(160, height - 40));
});
observer.observe(element);
return () => {
observer.disconnect();
};
}, []);
const columns: ColumnProps<AuditLogItem>[] = [
{
title: '时间',
@@ -250,21 +260,7 @@ export function AuditLogTable({
return (
<section className="lx-audit-table-card" aria-label="操作日志列表">
<div className="lx-audit-table-card__head">
<b>日志列表</b>
<span className="lx-audit-table-card__count">{pagerText}</span>
<div className="lx-audit-table-card__tools">
<Button
theme="borderless"
icon={<IconRefresh size={16} />}
loading={refreshing}
aria-label="刷新日志"
onClick={onRefresh}
/>
</div>
</div>
<div className="lx-audit-table-wrap">
<div className="lx-audit-table-wrap" ref={wrapRef}>
<Table<AuditLogItem>
className="lx-audit-table"
columns={visibleColumns}
@@ -273,7 +269,7 @@ export function AuditLogTable({
pagination={false}
loading={loading}
size="middle"
scroll={{ x: narrow ? 760 : '100%' }}
scroll={{ x: '100%', y: tableHeight }}
empty={emptyState(onClearFilters)}
onRow={(record) =>
record === undefined
@@ -297,7 +293,7 @@ export function AuditLogTable({
<div className="lx-audit-pager">
<span>
游标分页 · 按操作时间倒序
{pagerText} · 游标分页 · 按操作时间倒序
{errorMessage === null ? null : (
<em className="lx-audit-pager__error"> · {errorMessage}</em>
)}
@@ -2,7 +2,7 @@
import { Button, Modal } from '@douyinfe/semi-ui-19';
import { Permission } from '@lingxi/shared';
import { ErrorState } from '@lingxi/ui';
import { IconAlert, IconDownload, IconLock } from '@lingxi/ui/icons';
import { IconAlert, IconDownload, IconLock, IconRefresh } from '@lingxi/ui/icons';
import type { ReactNode } from 'react';
import { useMemo, useState } from 'react';
import { useNavigate, useSearchParams } from 'react-router';
@@ -113,6 +113,17 @@ export function AuditLogsPage(): ReactNode {
<p>关键操作留痕 · 只读 · append-only</p>
</div>
<div className="lx-audit-page__actions">
<Button
theme="borderless"
icon={<IconRefresh size={16} />}
loading={logs.isFetching}
aria-label="刷新日志"
onClick={() => {
void logs.refetch();
}}
>
刷新
</Button>
{canExport ? (
<>
<span className="lx-audit-readonly">
@@ -181,7 +192,6 @@ export function AuditLogsPage(): ReactNode {
<AuditLogTable
items={items}
loading={logs.isPending}
refreshing={logs.isFetching && !logs.isFetchingNextPage}
isFetchingNextPage={logs.isFetchingNextPage}
hasNextPage={logs.hasNextPage}
errorMessage={moreError}
@@ -192,9 +202,6 @@ export function AuditLogsPage(): ReactNode {
onRetryMore={() => {
void logs.fetchNextPage();
}}
onRefresh={() => {
void logs.refetch();
}}
onClearFilters={resetFilters}
/>
)}
+5 -30
View File
@@ -202,47 +202,22 @@
box-shadow: var(--lx-shadow-panel);
}
.lx-audit-table-card__head {
display: flex;
flex: 0 0 auto;
align-items: center;
gap: 10px;
min-height: 48px;
padding: 0 16px;
border-bottom: 1px solid var(--lx-color-border);
}
.lx-audit-table-card__head b {
font-size: var(--lx-font-size-sm);
font-weight: 600;
}
.lx-audit-table-card__count {
color: var(--lx-color-text-tertiary);
font-size: var(--lx-font-size-xs);
font-variant-numeric: tabular-nums;
}
.lx-audit-table-card__tools {
margin-left: auto;
}
.lx-audit-table-wrap {
min-height: 0;
flex: 1;
overflow: auto;
}
.lx-audit-table .semi-table {
min-width: 1080px;
overflow: hidden;
}
.lx-audit-table .semi-table-thead > .semi-table-row > .semi-table-row-head {
position: sticky;
top: 0;
z-index: 3;
height: 40px;
background: var(--lx-color-bg-subtle);
color: var(--lx-color-text-tertiary);
font-size: var(--lx-font-size-xs);
font-weight: 500;
box-shadow: inset 0 -1px 0 var(--lx-color-border);
}
.lx-audit-table .semi-table-tbody > .semi-table-row > .semi-table-row-cell {