feat(任务全景): 新增列显隐控制,可勾选去掉不想要的列

- 工具栏新增「列设置」按钮,弹出 checkbox 列表控制列显示/隐藏
- 勾选=显示,取消=去掉该列(如不需要宏观状态可直接取消勾选)
- 操作列(流转树)固定显示不可隐藏,提供「全部显示」重置
- 表格列数与网格跨度按实际显示列动态计算,原有排序/筛选/拖拽保留
This commit is contained in:
2026-08-28 11:21:22 +08:00
parent 7cfaf4b04b
commit ddb1b4364b

View File

@ -3,7 +3,7 @@ import { useState, useEffect, useMemo, useCallback } from "react";
import { useSearchParams } from "react-router-dom"; import { useSearchParams } from "react-router-dom";
import { import {
Search, Loader2, Package, ChevronDown, ChevronRight, Search, Loader2, Package, ChevronDown, ChevronRight,
Warehouse, GitBranch, X, ArrowUp, ArrowDown, ArrowUpDown, Filter, Warehouse, GitBranch, X, ArrowUp, ArrowDown, ArrowUpDown, Filter, Columns,
} from "lucide-react"; } from "lucide-react";
import { Tooltip, Popover, Checkbox, Input } from "antd"; import { Tooltip, Popover, Checkbox, Input } from "antd";
import api from "../../services/api"; import api from "../../services/api";
@ -185,8 +185,6 @@ export default function AdminTasksPage() {
}, },
]; ];
const TOTAL_SPAN = columns.reduce((s, c) => s + c.colSpan, 0);
// ---- 列顺序 / 行排序 / 列筛选状态 ---- // ---- 列顺序 / 行排序 / 列筛选状态 ----
const [columnOrder, setColumnOrder] = useState<string[]>(() => columns.map((c) => c.key)); const [columnOrder, setColumnOrder] = useState<string[]>(() => columns.map((c) => c.key));
const [sort, setSort] = useState<{ key: string; order: SortOrder } | null>(null); const [sort, setSort] = useState<{ key: string; order: SortOrder } | null>(null);
@ -194,6 +192,8 @@ export default function AdminTasksPage() {
const [enumFilters, setEnumFilters] = useState<Record<string, Set<string>>>({}); const [enumFilters, setEnumFilters] = useState<Record<string, Set<string>>>({});
const [dragCol, setDragCol] = useState<string | null>(null); const [dragCol, setDragCol] = useState<string | null>(null);
const [dragOverCol, setDragOverCol] = useState<string | null>(null); const [dragOverCol, setDragOverCol] = useState<string | null>(null);
// 🔧 列显隐:默认全部显示,勾选取消的列从表格中去掉(操作列始终显示)
const [hiddenColumns, setHiddenColumns] = useState<Set<string>>(new Set());
// 从看板跳转: ?sn=xxx → 自动搜索 + 自动展开流转树 // 从看板跳转: ?sn=xxx → 自动搜索 + 自动展开流转树
const autoSn = searchParams.get("sn") || ""; const autoSn = searchParams.get("sn") || "";
@ -308,6 +308,15 @@ export default function AdminTasksPage() {
}); });
} }
// ---- 列显隐:勾选=显示,取消=隐藏(操作列始终显示) ----
function toggleColumnVisible(key: string) {
setHiddenColumns((prev) => {
const next = new Set(prev);
if (next.has(key)) next.delete(key); else next.add(key);
return next;
});
}
// ---- 枚举列多选筛选 ---- // ---- 枚举列多选筛选 ----
function toggleEnumFilter(key: string, value: string) { function toggleEnumFilter(key: string, value: string) {
setEnumFilters((prev) => { setEnumFilters((prev) => {
@ -450,6 +459,29 @@ export default function AdminTasksPage() {
.map((key) => columns.find((c) => c.key === key)) .map((key) => columns.find((c) => c.key === key))
.filter((c): c is ColumnDef => !!c); .filter((c): c is ColumnDef => !!c);
// 🔧 实际显示的列(排除被隐藏的)+ 动态总跨度
const visibleColumns = orderedColumns.filter((c) => !hiddenColumns.has(c.key));
const visibleSpan = visibleColumns.reduce((s, c) => s + c.colSpan, 0);
// 列设置面板内容(列显隐)
function renderColumnPanel() {
const toggleable = columns.filter((c) => c.key !== "actions");
return (
<div className="max-h-80 w-48 overflow-auto p-2">
{toggleable.map((col) => (
<label key={col.key} className="flex cursor-pointer items-center gap-2 rounded px-1 py-1 text-xs text-gray-700 hover:bg-gray-50">
<Checkbox checked={!hiddenColumns.has(col.key)} onChange={() => toggleColumnVisible(col.key)} />
<span className="truncate">{col.label}</span>
</label>
))}
<div className="mt-1 flex items-center justify-between border-t border-gray-100 pt-1.5">
<button onClick={() => setHiddenColumns(new Set())} className="text-xs text-blue-600 hover:underline">全部显示</button>
<span className="text-[10px] text-gray-300">操作列固定</span>
</div>
</div>
);
}
// 列筛选面板内容 // 列筛选面板内容
function renderFilterPanel(col: ColumnDef) { function renderFilterPanel(col: ColumnDef) {
if (col.filterType === "text") { if (col.filterType === "text") {
@ -505,7 +537,12 @@ export default function AdminTasksPage() {
</button> </button>
))} ))}
</div> </div>
<div className="flex gap-1"> <div className="flex items-center gap-1">
<Popover trigger="click" placement="bottomRight" content={renderColumnPanel()}>
<button className="flex items-center gap-1 rounded px-2 py-1 text-[11px] text-gray-500 hover:bg-gray-100 hover:text-blue-600">
<Columns className="h-3 w-3" />列设置
</button>
</Popover>
<button onClick={() => setExpandedOrders(new Set(orderGroups.map(g => g.orderNo)))} <button onClick={() => setExpandedOrders(new Set(orderGroups.map(g => g.orderNo)))}
className="rounded px-2 py-1 text-[11px] text-blue-600 hover:bg-blue-50">全部展开</button> className="rounded px-2 py-1 text-[11px] text-blue-600 hover:bg-blue-50">全部展开</button>
<button onClick={() => setExpandedOrders(new Set())} <button onClick={() => setExpandedOrders(new Set())}
@ -646,9 +683,9 @@ export default function AdminTasksPage() {
{/* 订单展开内容 */} {/* 订单展开内容 */}
{isOpen && ( {isOpen && (
<div className="border-t border-gray-100"> <div className="border-t border-gray-100">
{/* 表头(可排序 / 可筛选 / 可拖拽换列) */} {/* 表头(可排序 / 可筛选 / 可拖拽换列 / 可显隐) */}
<div className="grid gap-2 bg-gray-50 px-5 py-2 text-xs font-medium text-gray-500" style={{ gridTemplateColumns: `repeat(${TOTAL_SPAN}, minmax(0, 1fr))` }}> <div className="grid gap-2 bg-gray-50 px-5 py-2 text-xs font-medium text-gray-500" style={{ gridTemplateColumns: `repeat(${visibleSpan}, minmax(0, 1fr))` }}>
{orderedColumns.map((col) => { {visibleColumns.map((col) => {
const sorted = sort?.key === col.key; const sorted = sort?.key === col.key;
const hasFilter = col.filterType === "text" const hasFilter = col.filterType === "text"
? !!(textFilters[col.key] || "").trim() ? !!(textFilters[col.key] || "").trim()
@ -694,8 +731,8 @@ export default function AdminTasksPage() {
{group.products.map((p) => { {group.products.map((p) => {
return ( return (
<div key={p.id}> <div key={p.id}>
<div className="grid gap-2 border-t border-gray-50 px-5 py-3 items-center text-sm hover:bg-gray-50/50" style={{ gridTemplateColumns: `repeat(${TOTAL_SPAN}, minmax(0, 1fr))` }}> <div className="grid gap-2 border-t border-gray-50 px-5 py-3 items-center text-sm hover:bg-gray-50/50" style={{ gridTemplateColumns: `repeat(${visibleSpan}, minmax(0, 1fr))` }}>
{orderedColumns.map((col) => ( {visibleColumns.map((col) => (
<div key={col.key} style={{ gridColumn: `span ${col.colSpan} / span ${col.colSpan}` }}> <div key={col.key} style={{ gridColumn: `span ${col.colSpan} / span ${col.colSpan}` }}>
{col.render(p)} {col.render(p)}
</div> </div>