feat: 支持主分组隐藏节点并保留排序

This commit is contained in:
2026-10-05 10:00:06 +08:00
parent b6aa2bf30d
commit 6ed0307ea9
8 changed files with 124 additions and 14 deletions
+53 -4
View File
@@ -42,6 +42,7 @@ type ProxyGroupEditorProps = {
id: string;
name: string;
memberSlugs: string[];
hiddenSlugs?: string[];
};
nodes: ProxyGroupNodeOption[];
};
@@ -90,11 +91,15 @@ function SortableNode({
index,
removable,
onRemove,
hidden,
onToggleHidden,
}: {
node: ProxyGroupNodeOption;
index: number;
removable: boolean;
onRemove: () => void;
hidden?: boolean;
onToggleHidden?: () => void;
}) {
const {
attributes,
@@ -135,6 +140,17 @@ function SortableNode({
<strong>{node.label}</strong>
<span className="proxy-sort-item__sub">{node.meta}</span>
</div>
{onToggleHidden && (
<label className="inline-flex items-center gap-2 text-sm whitespace-nowrap">
<input
type="checkbox"
aria-label={`主分组隐藏 ${node.label}`}
checked={hidden}
onChange={onToggleHidden}
/>
主分组隐藏
</label>
)}
{removable && (
<Button
variant="ghost"
@@ -170,15 +186,17 @@ export function ProxyGroupEditor({
const [selectedSlugs, setSelectedSlugs] = useState(() =>
normalizeSelectedSlugs(group?.memberSlugs ?? [], nodes, isMainGroup),
);
const [hiddenSlugs, setHiddenSlugs] = useState(group?.hiddenSlugs ?? []);
const [name, setName] = useState(group?.name ?? "");
const [saved, setSaved] = useState(() =>
JSON.stringify([
group?.name ?? "",
normalizeSelectedSlugs(group?.memberSlugs ?? [], nodes, isMainGroup),
group?.hiddenSlugs ?? [],
]),
);
const [feedback, setFeedback] = useState("");
const dirty = saved !== JSON.stringify([name, selectedSlugs]);
const dirty = saved !== JSON.stringify([name, selectedSlugs, hiddenSlugs]);
const selectedSlugSet = new Set(selectedSlugs);
const nodesBySlug = new Map(nodes.map((node) => [node.slug, node]));
const selectedNodes = selectedSlugs
@@ -251,7 +269,7 @@ export function ProxyGroupEditor({
await (
isMainGroup ? saveMainProxyGroupAction : saveProxyGroupAction
)(formData);
setSaved(JSON.stringify([name, selectedSlugs]));
setSaved(JSON.stringify([name, selectedSlugs, hiddenSlugs]));
setFeedback("已保存");
} catch {
setFeedback("保存失败,请重试。当前修改已保留。");
@@ -260,6 +278,22 @@ export function ProxyGroupEditor({
className="stack-md"
>
<EditorFields>
{isMainGroup && (
<>
<p className="muted">
隐藏仅影响主分组,节点及其他分组仍保留。新节点默认显示;全部隐藏时主分组回退为
DIRECT。
</p>
{hiddenSlugs.map((slug) => (
<input
key={slug}
type="hidden"
name="hidden_slugs"
value={slug}
/>
))}
</>
)}
{group && !isMainGroup ? (
<input type="hidden" name="id" value={group.id} />
) : null}
@@ -431,6 +465,19 @@ export function ProxyGroupEditor({
key={node.slug}
node={node}
index={index}
hidden={hiddenSlugs.includes(node.slug)}
onToggleHidden={
isMainGroup
? () => {
setHiddenSlugs((current) =>
current.includes(node.slug)
? current.filter((s) => s !== node.slug)
: [...current, node.slug],
);
setFeedback("");
}
: undefined
}
removable={!isMainGroup}
onRemove={() => removeNode(node.slug)}
/>
@@ -458,12 +505,14 @@ export function ProxyGroupEditor({
variant="ghost"
size="sm"
onClick={() => {
const [oldName, oldSlugs] = JSON.parse(saved) as [
const [oldName, oldSlugs, oldHidden] = JSON.parse(saved) as [
string,
string[],
string[],
];
setName(oldName);
setSelectedSlugs(oldSlugs);
setHiddenSlugs(oldHidden);
setFeedback("");
}}
>
@@ -471,7 +520,7 @@ export function ProxyGroupEditor({
</Button>
)}
<SubmitButton disabled={!dirty && (isEditing || isMainGroup)}>
{isMainGroup ? "保存顺序" : isEditing ? "保存分组" : "新建分组"}
{isMainGroup ? "保存主分组" : isEditing ? "保存分组" : "新建分组"}
</SubmitButton>
</div>
</EditorFields>