fix(web): 操作日志列头吸顶并移除重复列表工具条
Semi Table 增加 scroll.y(按容器高度测量),表头固定、仅表体滚动;移除「日志列表 / 已加载 N 条」重复工具条,刷新按钮移到页头,条数保留在底部分页;表格宽度统一跟随容器,修复宽屏右侧空白与 1024 留白。
This commit is contained in:
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}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in new issue
Block a user