feat: 支持服务器主机名排序

- 服务器列表默认按主机名升序并支持表头切换

- 后端按白名单排序并区分分页缓存键
This commit is contained in:
Logan.Li committed 2026-07-26 23:21:10 +08:00
1 parent 6b5fc6a5c3
commit 09e6271bb4
5 files changed
+83 -7

No files matched your search

@@ -42,6 +42,7 @@ export function ServerManager() {
const [selectedServers, setSelectedServers] = useState<string[]>([]);
const [searchQuery, setSearchQuery] = useState("");
const [statusFilter, setStatusFilter] = useState<string>("all");
const [hostnameOrder, setHostnameOrder] = useState<"asc" | "desc">("asc");
const [cloudImportOpen, setCloudImportOpen] = useState(false);
const [excelImportOpen, setExcelImportOpen] = useState(false);
@@ -79,11 +80,16 @@ export function ServerManager() {
const [pageSize, setPageSize] = useState(10);
const queryParams = useMemo(() => {
const params: Record<string, unknown> = { page, page_size: pageSize };
const params: Record<string, unknown> = {
page,
page_size: pageSize,
sort: "hostname",
order: hostnameOrder,
};
if (searchQuery) params.keyword = searchQuery;
if (statusFilter && statusFilter !== "all") params.status = statusFilter;
return params;
}, [page, pageSize, searchQuery, statusFilter]);
}, [page, pageSize, searchQuery, statusFilter, hostnameOrder]);
const {
data,
@@ -130,6 +136,12 @@ export function ServerManager() {
setPage(1);
};
// 切换主机名排序后回到第一页,保持跨页顺序连续。 // Logan.Li
const handleHostnameSort = () => {
setHostnameOrder((current) => (current === "asc" ? "desc" : "asc"));
setPage(1);
};
const toggleSelectAll = () => {
if (selectedServers.length === servers.length) {
setSelectedServers([]);
@@ -370,8 +382,10 @@ export function ServerManager() {
}}
searchQuery={searchQuery}
statusFilter={statusFilter}
hostnameOrder={hostnameOrder}
onSearchChange={handleSearch}
onStatusFilterChange={handleStatusFilter}
onHostnameSort={handleHostnameSort}
selectedServers={selectedServers}
onToggleSelectAll={toggleSelectAll}
onToggleSelect={toggleSelect}
@@ -24,6 +24,8 @@ import {
RefreshCw,
ChevronLeft,
ChevronRight,
ArrowUp,
ArrowDown,
} from "lucide-react";
import { ContentPanel } from "@/components/layout/ContentPanel";
import { ServerTableRow } from "./ServerTableRow";
@@ -44,8 +46,10 @@ export interface ServerTableSectionProps {
/** 搜索/筛选 */
searchQuery: string;
statusFilter: string;
hostnameOrder: "asc" | "desc";
onSearchChange: (val: string) => void;
onStatusFilterChange: (val: string) => void;
onHostnameSort: () => void;
/** 选择 */
selectedServers: string[];
onToggleSelectAll: () => void;
@@ -72,8 +76,10 @@ export function ServerTableSection({
onPageSizeChange,
searchQuery,
statusFilter,
hostnameOrder,
onSearchChange,
onStatusFilterChange,
onHostnameSort,
selectedServers,
onToggleSelectAll,
onToggleSelect,
@@ -152,8 +158,23 @@ export function ServerTableSection({
onCheckedChange={onToggleSelectAll}
/>
</TableHead>
<TableHead className="text-[var(--dg-text-secondary)]">
{t("server.hostname")}
<TableHead
className="text-[var(--dg-text-secondary)]"
aria-sort={hostnameOrder === "asc" ? "ascending" : "descending"}
>
<button
type="button"
onClick={onHostnameSort}
className="inline-flex h-8 items-center gap-1 rounded px-1 hover:bg-[var(--dg-bg-hover)] hover:text-[var(--dg-text-primary)]"
title={`${t("server.hostname")} - ${t(`common.${hostnameOrder === "asc" ? "ascending" : "descending"}`)}`}
>
{t("server.hostname")}
{hostnameOrder === "asc" ? (
<ArrowUp className="h-3.5 w-3.5 dg-text-blue" />
) : (
<ArrowDown className="h-3.5 w-3.5 dg-text-blue" />
)}
</button>
</TableHead>
<TableHead className="text-[var(--dg-text-secondary)]">
{t("server.ipAddress")}
+3 -1
View File
@@ -18,6 +18,8 @@ type ListParams = {
page_size?: number;
keyword?: string;
status?: string;
sort?: 'hostname';
order?: 'asc' | 'desc';
};
type ServerListResult = {
@@ -65,4 +67,4 @@ export function useServerMutations() {
});
return { createMutation, updateMutation, deleteMutation, batchDeleteMutation, invalidate };
}
}