feat: 支持主分组隐藏节点并保留排序
This commit is contained in:
@@ -31,7 +31,8 @@ export async function saveSubscriptionSettingsAction(formData: FormData) {
|
||||
await saveSubscriptionSettings({
|
||||
template: String(formData.get("template") ?? ""),
|
||||
extraProxyGroups: String(formData.get("extra_proxy_groups") ?? ""),
|
||||
profileName: String(formData.get("profile_name") ?? "").trim() || "FeieProxy",
|
||||
profileName:
|
||||
String(formData.get("profile_name") ?? "").trim() || "FeieProxy",
|
||||
});
|
||||
|
||||
revalidatePath("/admin/subscriptions");
|
||||
@@ -55,7 +56,9 @@ export async function saveProxyGroupAction(formData: FormData) {
|
||||
type: "select",
|
||||
memberSlugs,
|
||||
} as const;
|
||||
const existingIndex = settings.proxyGroups.findIndex((group) => group.id === id);
|
||||
const existingIndex = settings.proxyGroups.findIndex(
|
||||
(group) => group.id === id,
|
||||
);
|
||||
const proxyGroups = [...settings.proxyGroups];
|
||||
|
||||
if (existingIndex === -1) {
|
||||
@@ -71,7 +74,10 @@ export async function saveProxyGroupAction(formData: FormData) {
|
||||
export async function saveMainProxyGroupAction(formData: FormData) {
|
||||
await requireAdminAccess();
|
||||
|
||||
await saveMainProxyGroupMemberSlugs(readUniqueMemberSlugs(formData));
|
||||
await saveMainProxyGroupMemberSlugs(
|
||||
readUniqueMemberSlugs(formData),
|
||||
formData.getAll("hidden_slugs").map(String),
|
||||
);
|
||||
revalidatePath("/admin/subscriptions");
|
||||
}
|
||||
|
||||
@@ -80,6 +86,8 @@ export async function deleteProxyGroupAction(formData: FormData) {
|
||||
|
||||
const settings = await getSubscriptionSettings();
|
||||
const id = String(formData.get("id") ?? "").trim();
|
||||
await saveProxyGroups(settings.proxyGroups.filter((group) => group.id !== id));
|
||||
await saveProxyGroups(
|
||||
settings.proxyGroups.filter((group) => group.id !== id),
|
||||
);
|
||||
revalidatePath("/admin/subscriptions");
|
||||
}
|
||||
|
||||
@@ -88,6 +88,7 @@ export default async function AdminSubscriptionsPage() {
|
||||
id: "main",
|
||||
name: settings.profileName,
|
||||
memberSlugs: settings.mainProxyGroupMemberSlugs,
|
||||
hiddenSlugs: settings.mainProxyGroupHiddenSlugs,
|
||||
}}
|
||||
nodes={proxyGroupNodes}
|
||||
/>
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user