feat: 调整 Alpha 视图标签与筛选工具栏交互
Deploy production / deploy (push) Successful in 36s

This commit is contained in:
yuxuanhui
2026-09-10 14:55:23 +08:00
parent 6ce33eb572
commit 1e67f43012
9 changed files with 995 additions and 414 deletions
+190
View File
@@ -0,0 +1,190 @@
.alpha-views-header {
display: flex;
align-items: center;
flex-shrink: 0;
min-width: 0;
background: var(--semi-color-fill-0);
border-bottom: 1px solid var(--semi-color-border);
}
.alpha-view-tabs {
flex: 1;
min-width: 0;
}
.alpha-view-tabs > .semi-tabs-content {
display: none;
}
.alpha-view-tabs .semi-tabs-bar {
border-bottom: 0;
}
.alpha-view-label {
display: inline-flex;
align-items: center;
gap: 8px;
height: 24px;
}
.alpha-view-name {
max-width: 180px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.alpha-view-modified {
width: 6px;
height: 6px;
border-radius: 50%;
background: var(--semi-color-primary);
flex-shrink: 0;
}
.alpha-add-view {
flex-shrink: 0;
margin: 0 8px;
}
.alpha-toolbar {
gap: 8px 16px;
padding: 10px 12px;
border-bottom: 1px solid var(--semi-color-border);
}
.alpha-view-tools,
.alpha-table-actions {
gap: 4px;
}
.alpha-table-actions {
margin-left: auto;
}
.alpha-filter-count {
margin-left: 6px;
font-variant-numeric: tabular-nums;
}
.alpha-selection-actions {
padding: 8px 12px;
flex-shrink: 0;
background: var(--semi-color-primary-light-default);
border-bottom: 1px solid var(--semi-color-border);
}
.alpha-selection-actions .selection-text {
margin-left: 0;
margin-right: 8px;
}
.alpha-filter-panel {
display: flex;
flex-direction: column;
width: min(640px, calc(100vw - 32px));
max-height: min(700px, calc(100dvh - 180px));
color: var(--semi-color-text-0);
}
.alpha-filter-header {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: 16px;
padding: 20px 20px 16px;
flex-shrink: 0;
}
.alpha-filter-header p {
margin-top: 4px;
}
.alpha-filter-body {
min-height: 0;
overflow-y: auto;
overscroll-behavior: contain;
padding: 0 20px 20px;
}
.alpha-filter-fields {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 16px;
}
.alpha-filter-fields > label {
margin: 0;
gap: 6px;
}
.alpha-filter-fields .semi-select {
width: 100%;
}
.alpha-filter-search {
grid-column: 1 / -1;
}
.alpha-filter-panel .advanced-filters {
margin-top: 20px;
}
.alpha-filter-panel .advanced-filters > summary {
font-size: 13px;
color: var(--semi-color-text-1);
}
.alpha-filter-panel .advanced-filters .alpha-filter-fields {
padding-top: 16px;
}
.alpha-filter-panel .range-inputs {
min-width: 0;
}
.alpha-filter-panel .range-inputs .semi-input-wrapper {
min-width: 0;
}
.alpha-filter-footer {
display: flex;
justify-content: flex-end;
gap: 8px;
padding: 12px 20px;
border-top: 1px solid var(--semi-color-border);
flex-shrink: 0;
}
.alpha-filter-footer > :first-child {
margin-right: auto;
}
.alpha-sort-panel {
display: grid;
grid-template-columns: minmax(0, 1fr) 96px;
gap: 12px;
padding: 16px;
width: min(360px, calc(100vw - 32px));
}
.alpha-sort-panel > h2 {
grid-column: 1 / -1;
}
.alpha-sort-panel .semi-select {
width: 100%;
}
.alpha-table-meta {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 4px 16px;
}
.alpha-table-meta .alpha-last-sync {
margin: 0;
padding: 0;
}
.alpha-table-meta .count-pill {
margin: 0;
}
@media (max-width: 760px) {
.alpha-toolbar {
padding: 8px;
}
.alpha-table-actions {
margin-left: 0;
}
.alpha-view-name {
max-width: 120px;
}
.alpha-add-view {
margin: 0 4px;
}
.alpha-table-meta .alpha-last-sync {
display: none;
}
.alpha-filter-header {
padding: 16px;
}
.alpha-filter-body {
padding: 0 16px 16px;
}
.alpha-filter-fields {
gap: 12px;
}
}
@media (max-width: 420px) {
.alpha-filter-fields {
grid-template-columns: minmax(0, 1fr);
}
}
+454 -339
View File
@@ -11,12 +11,22 @@ import {
Pagination,
Select,
Table,
Tabs,
Tag,
TextArea,
Toast,
Typography,
} from "@douyinfe/semi-ui-19";
import {
IconFilter,
IconSetting,
IconSort,
IconSearch,
IconClose,
IconDownload,
IconUpload,
IconRefresh,
} from "@douyinfe/semi-icons";
import "./AlphaPage.css";
import type { ColumnProps } from "@douyinfe/semi-ui-19/lib/es/table/interface";
import {
api,
@@ -144,6 +154,8 @@ export function AlphaPage({
const [facets, setFacets] = useState<Facets>({});
const [draft, setDraft] = useState<Record<string, string>>({});
const [filters, setFilters] = useState<Record<string, string>>({});
const [filterOpen, setFilterOpen] = useState(false);
const [selectedViewId, setSelectedViewId] = useState<string | null>(null);
const [submission, setSubmission] = useState<Submission>("UNSUBMITTED");
const [syncingScope, setSyncingScope] = useState<Submission | null>(null);
const [page, setPage] = useState(1);
@@ -238,6 +250,8 @@ export function AlphaPage({
setDetailId(action.alpha_id);
onOverlay();
} else {
setSelectedViewId(null);
setFilterOpen(false);
const {
sort: nextSort,
direction: nextDirection,
@@ -296,15 +310,20 @@ export function AlphaPage({
}));
}
function applyFilters() {
setFilterOpen(false);
setFilters({ ...draft });
setPage(1);
}
function clearFilters() {
setFilterOpen(false);
setDraft({});
setFilters({});
setPage(1);
}
function changeSubmission(value: string) {
setSelectedViewId(null);
setFilterOpen(false);
setDirection("desc");
setSubmission(value as Submission);
setDraft({});
setFilters({});
@@ -610,6 +629,233 @@ export function AlphaPage({
const connected =
account?.connection_status === "connected" ||
account?.connection_status === "expired";
const filterPanel = (
<form
className="alpha-filter-panel"
aria-label="Alpha 筛选条件"
onSubmit={(e) => {
e.preventDefault();
applyFilters();
}}
>
<header className="alpha-filter-header">
<div>
<h2>筛选 Alpha</h2>
<p className="muted">设置搜索和筛选条件,查询后应用到当前视图。</p>
</div>
<Button
icon={<IconClose aria-hidden="true" />}
theme="borderless"
type="tertiary"
aria-label="关闭筛选"
onClick={() => setFilterOpen(false)}
/>
</header>
<div className="alpha-filter-body">
<div className="alpha-filter-fields">
<label className="alpha-filter-search">
搜索
<Input
prefix={<IconSearch aria-hidden="true" />}
aria-label="搜索 Alpha"
placeholder="搜索 ID、名称或表达式"
value={draft.q ?? ""}
onChange={(v) => updateDraft("q", v)}
showClear
/>
</label>
<label>
地区
<Select
aria-label="地区"
placeholder="全部地区"
showClear
value={draft.region || undefined}
optionList={options("region")}
onChange={(v) => updateDraft("region", v)}
/>
</label>
<label>
平台状态
<Select
aria-label="平台状态"
placeholder="全部平台状态"
showClear
value={draft.status || undefined}
optionList={options("status")}
onChange={(v) => updateDraft("status", v)}
/>
</label>
<label>
研究状态
<Select
aria-label="研究状态筛选"
placeholder="全部研究状态"
showClear
value={draft.research_state || undefined}
optionList={stateOptions}
onChange={(v) => updateDraft("research_state", v)}
/>
</label>
<label>
检查结果
<Select
aria-label="检查结果筛选"
placeholder="全部检查结果"
showClear
value={draft.check_type || undefined}
optionList={Object.entries(checkLabels).map(([value, label]) => ({
value,
label,
}))}
onChange={(v) => updateDraft("check_type", v)}
/>
</label>
<label>
自定义标签
<Select
aria-label="自定义标签筛选"
showClear
filter
value={draft.tag || undefined}
optionList={options("tags")}
placeholder="全部标签"
onChange={(v) => updateDraft("tag", v)}
/>
</label>
</div>
<details className="advanced-filters">
<summary>更多筛选</summary>
<div className="alpha-filter-fields">
<label>
研究来源
<Select
aria-label="研究来源筛选"
showClear
value={draft.source || undefined}
optionList={options("source").map((option) => ({
...option,
label: sourceLabel(option.value),
}))}
placeholder="全部来源"
onChange={(v) => updateDraft("source", v)}
/>
</label>
<label>
研究编号
<Input
aria-label="研究编号筛选"
value={draft.research_id || ""}
onChange={(v) => updateDraft("research_id", v)}
/>
</label>
{[
["universe", "Universe"],
["alpha_type", "Alpha 类型"],
["language", "语言"],
["stage", "IS / OS"],
].map(([key, label]) => (
<label key={key}>
{label}
<Select
showClear
aria-label={label}
value={draft[key] || undefined}
optionList={options(key)}
placeholder="全部"
onChange={(v) => updateDraft(key, v)}
/>
</label>
))}
<label>
平台隐藏状态
<Select
showClear
value={draft.hidden || undefined}
optionList={[
{ value: "false", label: "可见" },
{ value: "true", label: "隐藏" },
]}
placeholder="全部"
onChange={(v) => updateDraft("hidden", v)}
/>
</label>
<label>
中性化
<Input
aria-label="中性化筛选"
value={draft.neutralization || ""}
placeholder="例如 INDUSTRY"
onChange={(v) => updateDraft("neutralization", v)}
/>
</label>
<label>
创建时间起(UTC)
<Input
type="date"
value={draft.created_from?.slice(0, 10) || ""}
onChange={(v) =>
updateDraft("created_from", v ? `${v}T00:00:00Z` : "")
}
/>
</label>
<label>
创建时间止(UTC)
<Input
type="date"
value={draft.created_to?.slice(0, 10) || ""}
onChange={(v) =>
updateDraft("created_to", v ? `${v}T23:59:59.999999Z` : "")
}
/>
</label>
{Object.entries(metricLabels).map(([key, label]) => (
<label key={key}>
{label}
<div className="range-inputs">
<Input
type="number"
aria-label={`${label} 最小值`}
placeholder="最小值"
value={draft[`${key}_min`] || ""}
onChange={(v) => updateDraft(`${key}_min`, v)}
/>
<span>—</span>
<Input
type="number"
aria-label={`${label} 最大值`}
placeholder="最大值"
value={draft[`${key}_max`] || ""}
onChange={(v) => updateDraft(`${key}_max`, v)}
/>
</div>
</label>
))}
<Checkbox
checked={draft.favorite === "true"}
onChange={(e) =>
updateDraft("favorite", e.target.checked ? "true" : "")
}
>
只看收藏
</Checkbox>
</div>
<p className="muted">
数值筛选使用平台原始值,例如 Turnover 0.15 表示 15%。
检查结果仅汇总已同步的平台检查;预检通过不代表可以提交,活动资格可用自定义标签记录。
</p>
</details>
</div>
<footer className="alpha-filter-footer">
<Button type="tertiary" theme="borderless" onClick={clearFilters}>
重置
</Button>
<Button htmlType="submit" theme="solid">
查询
</Button>
</footer>
</form>
);
return (
<>
{!connected && (
@@ -627,223 +873,17 @@ export function AlphaPage({
)}
{error && <Banner type="danger" description={error} />}
<section className="library-panel" ref={tablePanel}>
<div className="alpha-tabs-header">
<Tabs
className="alpha-submission-tabs"
activeKey={submission}
onChange={changeSubmission}
tabList={[
{ itemKey: "UNSUBMITTED", tab: "待提交" },
{ itemKey: "SUBMITTED", tab: "已提交" },
]}
/>
<span className="alpha-last-sync">
最近数据更新{" "}
<span>
{formatTime(
typeof facets.last_sync === "string" ? facets.last_sync : null,
account?.timezone,
)}
</span>
</span>
</div>
<form
className="filter-panel"
onSubmit={(e) => {
e.preventDefault();
applyFilters();
}}
>
<div className="primary-filters">
<Input
aria-label="搜索 Alpha"
placeholder="搜索 ID、名称或表达式"
value={draft.q ?? ""}
onChange={(v) => updateDraft("q", v)}
showClear
/>
<Select
aria-label="地区"
placeholder="全部地区"
showClear
value={draft.region || undefined}
optionList={options("region")}
onChange={(v) => updateDraft("region", v)}
/>
<Select
aria-label="平台状态"
placeholder="全部平台状态"
showClear
value={draft.status || undefined}
optionList={options("status")}
onChange={(v) => updateDraft("status", v)}
/>
<Select
aria-label="研究状态筛选"
placeholder="全部研究状态"
showClear
value={draft.research_state || undefined}
optionList={stateOptions}
onChange={(v) => updateDraft("research_state", v)}
/>
<Select
aria-label="检查结果筛选"
placeholder="全部检查结果"
showClear
value={draft.check_type || undefined}
optionList={Object.entries(checkLabels).map(([value, label]) => ({
value,
label,
}))}
onChange={(v) => updateDraft("check_type", v)}
/>
<Select
aria-label="自定义标签筛选"
showClear
filter
value={draft.tag || undefined}
optionList={options("tags")}
placeholder="全部标签"
onChange={(v) => updateDraft("tag", v)}
/>
</div>
<details className="advanced-filters">
<summary>
更多筛选{" "}
{activeFilterCount ? `· ${activeFilterCount} 项已应用` : ""}
</summary>
<div className="advanced-grid">
<label>
研究来源
<Select
aria-label="研究来源筛选"
showClear
value={draft.source || undefined}
optionList={options("source").map((option) => ({
...option,
label: sourceLabel(option.value),
}))}
placeholder="全部来源"
onChange={(v) => updateDraft("source", v)}
/>
</label>
<label>
研究编号
<Input
aria-label="研究编号筛选"
value={draft.research_id || ""}
onChange={(v) => updateDraft("research_id", v)}
/>
</label>
{[
["universe", "Universe"],
["alpha_type", "Alpha 类型"],
["language", "语言"],
["stage", "IS / OS"],
].map(([key, label]) => (
<label key={key}>
{label}
<Select
showClear
aria-label={label}
value={draft[key] || undefined}
optionList={options(key)}
placeholder="全部"
onChange={(v) => updateDraft(key, v)}
/>
</label>
))}
<label>
平台隐藏状态
<Select
showClear
value={draft.hidden || undefined}
optionList={[
{ value: "false", label: "可见" },
{ value: "true", label: "隐藏" },
]}
placeholder="全部"
onChange={(v) => updateDraft("hidden", v)}
/>
</label>
<label>
中性化
<Input
aria-label="中性化筛选"
value={draft.neutralization || ""}
placeholder="例如 INDUSTRY"
onChange={(v) => updateDraft("neutralization", v)}
/>
</label>
<label>
创建时间起(UTC)
<Input
type="date"
value={draft.created_from?.slice(0, 10) || ""}
onChange={(v) =>
updateDraft("created_from", v ? `${v}T00:00:00Z` : "")
}
/>
</label>
<label>
创建时间止(UTC)
<Input
type="date"
value={draft.created_to?.slice(0, 10) || ""}
onChange={(v) =>
updateDraft("created_to", v ? `${v}T23:59:59.999999Z` : "")
}
/>
</label>
{Object.entries(metricLabels).map(([key, label]) => (
<label key={key}>
{label}
<div className="range-inputs">
<Input
type="number"
aria-label={`${label} 最小值`}
placeholder="最小值"
value={draft[`${key}_min`] || ""}
onChange={(v) => updateDraft(`${key}_min`, v)}
/>
<span>—</span>
<Input
type="number"
aria-label={`${label} 最大值`}
placeholder="最大值"
value={draft[`${key}_max`] || ""}
onChange={(v) => updateDraft(`${key}_max`, v)}
/>
</div>
</label>
))}
<Checkbox
checked={draft.favorite === "true"}
onChange={(e) =>
updateDraft("favorite", e.target.checked ? "true" : "")
}
>
只看收藏
</Checkbox>
</div>
<p className="muted">
数值筛选使用平台原始值,例如 Turnover 0.15 表示 15%。
检查结果仅汇总已同步的平台检查;预检通过不代表可以提交,活动资格可用自定义标签记录。
</p>
</details>
<div className="inline-actions filter-actions">
<Button htmlType="submit" theme="solid">
查询
</Button>
<Button type="tertiary" theme="borderless" onClick={clearFilters}>
重置
</Button>
</div>
</form>
<SavedViews
submission={submission}
selectedId={selectedViewId}
onSelect={setSelectedViewId}
onSubmissionChange={changeSubmission}
suspended={overlaySuspended || !active}
onOverlay={onOverlay}
filters={{ ...filters, submission, sort, direction, limit: pageSize }}
columns={visibleColumns}
onRestore={(view) => {
setFilterOpen(false);
const {
submission: savedSubmission,
sort: savedSort,
@@ -861,11 +901,9 @@ export function AlphaPage({
setDraft(values);
setPage(1);
setSelected([]);
if (
savedSubmission === "SUBMITTED" ||
savedSubmission === "UNSUBMITTED"
)
setSubmission(savedSubmission);
setSubmission(
savedSubmission === "SUBMITTED" ? "SUBMITTED" : "UNSUBMITTED",
);
setSort(String(savedSort || "date_created"));
setDirection(String(savedDirection || "desc"));
if (typeof limit === "number") setPageSize(limit);
@@ -879,104 +917,148 @@ export function AlphaPage({
localStorage.setItem("alpha-columns", JSON.stringify(next));
}}
/>
<div className="table-toolbar">
<div>
<strong>
{submission === "UNSUBMITTED" ? "待提交" : "已提交"} Alpha
</strong>
<span className="count-pill">{data.total.toLocaleString()}</span>
{selected.length > 0 && (
<span className="selection-text">
已选择 {selected.length} 条
</span>
)}
<div
className="table-toolbar alpha-toolbar"
aria-label="Alpha 表格工具栏"
>
<div className="inline-actions alpha-view-tools">
<Popover
trigger="custom"
position="bottomLeft"
visible={filterOpen && active && !overlaySuspended}
onClickOutSide={() => setFilterOpen(false)}
onEscKeyDown={() => setFilterOpen(false)}
content={filterPanel}
>
<Button
icon={<IconFilter aria-hidden="true" />}
type={filterOpen || activeFilterCount ? "primary" : "tertiary"}
theme={filterOpen || activeFilterCount ? "light" : "borderless"}
aria-label="筛选 Alpha"
onClick={() => {
setFilterOpen((open) => !open);
onOverlay();
}}
>
筛选
{activeFilterCount > 0 && (
<span className="alpha-filter-count">
{activeFilterCount}
</span>
)}
</Button>
</Popover>
<Popover
trigger="click"
position="bottomRight"
content={
<div className="columns-picker">
<strong>显示列</strong>
<CheckboxGroup
direction="vertical"
value={visibleColumns}
options={Object.entries(columnLabels).map(
([value, label]) => ({
value,
label,
disabled: value === "name",
}),
)}
onChange={(values) => {
const next = values.map(String);
setVisibleColumns(next);
localStorage.setItem(
"alpha-columns",
JSON.stringify(next),
);
}}
/>
</div>
}
>
<Button
icon={<IconSetting aria-hidden="true" />}
theme="borderless"
type="tertiary"
aria-label="显示列设置"
>
列设置
</Button>
</Popover>
<Popover
trigger="click"
position="bottomLeft"
content={
<div className="alpha-sort-panel">
<h2>排序</h2>
<Select
aria-label="排序字段"
size="small"
value={sort}
optionList={[
{ value: "date_created", label: "创建时间" },
{ value: "date_submitted", label: "提交时间" },
{ value: "synced_at", label: "同步时间" },
...Object.entries(metricLabels).map(([value, label]) => ({
value,
label,
})),
{ value: "name", label: "名称" },
]}
onChange={(v) => {
setSort(String(v));
setPage(1);
}}
/>
<Select
aria-label="排序方向"
size="small"
value={direction}
optionList={[
{ value: "desc", label: "降序" },
{ value: "asc", label: "升序" },
]}
onChange={(v) => {
setDirection(String(v));
setPage(1);
}}
/>
</div>
}
>
<Button
icon={<IconSort aria-hidden="true" />}
theme="borderless"
type="tertiary"
aria-label="排序设置"
>
排序
</Button>
</Popover>
</div>
<div className="inline-actions">
{selected.length > 0 && (
<>
<Button
type="tertiary"
size="small"
loading={busy === "self_correlation"}
onClick={() => void newTask("self_correlation", selected)}
>
检测自相关
</Button>
<Button
type="tertiary"
size="small"
onClick={() => {
setBulkVersions(
Object.fromEntries(
data.items
.filter((row) => selected.includes(row.id))
.map((row) => [row.id, row.research.version]),
),
);
setBulkOpen(true);
onOverlay();
}}
>
批量编辑
</Button>
<Button
type="tertiary"
size="small"
disabled={!connected}
loading={busy === "alpha_refresh"}
onClick={() => void newTask("alpha_refresh", selected)}
>
刷新选中
</Button>
</>
)}
<Select
aria-label="排序字段"
size="small"
value={sort}
optionList={[
{ value: "date_created", label: "创建时间" },
{ value: "date_submitted", label: "提交时间" },
{ value: "synced_at", label: "同步时间" },
...Object.entries(metricLabels).map(([value, label]) => ({
value,
label,
})),
{ value: "name", label: "名称" },
]}
onChange={(v) => {
setSort(String(v));
setPage(1);
}}
/>
<Select
aria-label="排序方向"
size="small"
value={direction}
optionList={[
{ value: "desc", label: "降序" },
{ value: "asc", label: "升序" },
]}
onChange={(v) => {
setDirection(String(v));
setPage(1);
}}
/>
<div className="inline-actions alpha-table-actions">
<a href={`/api/v1/alphas/export?${query}`}>
<Button type="tertiary" size="small">
<Button
icon={<IconDownload aria-hidden="true" />}
theme="borderless"
type="tertiary"
>
导出
</Button>
</a>
<Button
size="small"
icon={<IconUpload aria-hidden="true" />}
theme="borderless"
type="tertiary"
onClick={() => setImporting(true)}
onClick={() => {
setImporting(true);
onOverlay();
}}
>
导入 Alpha ID
</Button>
<Button
size="small"
icon={<IconRefresh aria-hidden="true" />}
theme="solid"
disabled={!connected}
onClick={() => {
@@ -1007,40 +1089,56 @@ export function AlphaPage({
全量同步已提交
</Button>
)}
<Popover
trigger="click"
position="bottomRight"
content={
<div className="columns-picker">
<strong>显示列</strong>
<CheckboxGroup
direction="vertical"
value={visibleColumns}
options={Object.entries(columnLabels).map(
([value, label]) => ({
value,
label,
disabled: value === "name",
}),
)}
onChange={(values) => {
const next = values.map(String);
setVisibleColumns(next);
localStorage.setItem(
"alpha-columns",
JSON.stringify(next),
);
}}
/>
</div>
}
>
<Button type="tertiary" aria-label="显示列设置" size="small">
列设置
</Button>
</Popover>
</div>
</div>
{selected.length > 0 && (
<div className="alpha-selection-actions inline-actions">
<span className="selection-text">已选择 {selected.length} 条</span>
<Button
type="tertiary"
size="small"
loading={busy === "self_correlation"}
onClick={() => void newTask("self_correlation", selected)}
>
检测自相关
</Button>
<Button
type="tertiary"
size="small"
onClick={() => {
setBulkVersions(
Object.fromEntries(
data.items
.filter((row) => selected.includes(row.id))
.map((row) => [row.id, row.research.version]),
),
);
setBulkOpen(true);
onOverlay();
}}
>
批量编辑
</Button>
<Button
type="tertiary"
size="small"
disabled={!connected}
loading={busy === "alpha_refresh"}
onClick={() => void newTask("alpha_refresh", selected)}
>
刷新选中
</Button>
<Button
size="small"
theme="borderless"
type="tertiary"
onClick={() => setSelected([])}
>
取消选择
</Button>
</div>
)}
<Table<Alpha>
columns={columns}
dataSource={data.items}
@@ -1075,7 +1173,24 @@ export function AlphaPage({
}
/>
<footer className="table-pagination" aria-label="Alpha 分页">
<span className="muted">共 {data.total.toLocaleString()} 条</span>
<div className="alpha-table-meta">
<span className="muted">
{submission === "UNSUBMITTED" ? "待提交" : "已提交"} Alpha{" "}
<span className="count-pill">{data.total.toLocaleString()}</span>{" "}
条
</span>
<span className="alpha-last-sync">
最近数据更新{" "}
<span>
{formatTime(
typeof facets.last_sync === "string"
? facets.last_sync
: null,
account?.timezone,
)}
</span>
</span>
</div>
<Pagination
size={compact ? "small" : "default"}
currentPage={page}