diff --git a/src/pages/options/routes/ScriptEditor/tabs/SettingsPane.test.tsx b/src/pages/options/routes/ScriptEditor/tabs/SettingsPane.test.tsx
index 63f2b6f06..51ab7e726 100644
--- a/src/pages/options/routes/ScriptEditor/tabs/SettingsPane.test.tsx
+++ b/src/pages/options/routes/ScriptEditor/tabs/SettingsPane.test.tsx
@@ -259,6 +259,47 @@ describe("SettingsPane 网站匹配/排除", () => {
fireEvent.click(screen.getByText(t("confirm"), { selector: "button" }));
expect(resetMatch).toHaveBeenCalledWith("u1", undefined);
});
+
+ it("重置匹配后应恢复脚本自带规则而非清空列表", async () => {
+ render();
+ await screen.findByText("*://user.com/*");
+ fireEvent.click(screen.getAllByText(t("reset"), { selector: "button" })[0]);
+ fireEvent.click(screen.getByText(t("confirm"), { selector: "button" }));
+ // 用户添加的 user.com 被清除,脚本自带的 script.com 应保留并标记为「脚本」
+ expect(screen.getByText("*://script.com/*")).toBeInTheDocument();
+ expect(screen.queryByText("*://user.com/*")).toBeNull();
+ expect(screen.getByText(t("editor:from_script"))).toBeInTheDocument();
+ });
+
+ it("重置排除后应恢复脚本自带规则而非清空列表", async () => {
+ fetchScript.mockResolvedValue({
+ ...sampleScript(),
+ metadata: { ...sampleScript().metadata, exclude: ["*://ads.script.com/*"] },
+ selfMetadata: { ...sampleScript().selfMetadata, exclude: ["*://ads.script.com/*", "*://exclude.com/*"] },
+ });
+ render();
+ await screen.findByText("*://exclude.com/*");
+ fireEvent.click(screen.getAllByText(t("reset"), { selector: "button" })[1]);
+ fireEvent.click(screen.getByText(t("confirm"), { selector: "button" }));
+ // 用户添加的 exclude.com 被清除,脚本自带的 ads.script.com 应保留
+ expect(screen.getByText("*://ads.script.com/*")).toBeInTheDocument();
+ expect(screen.queryByText("*://exclude.com/*")).toBeNull();
+ });
+
+ it("删除最后一项匹配后应回落脚本自带规则而非清空列表", async () => {
+ fetchScript.mockResolvedValue({
+ ...sampleScript(),
+ selfMetadata: { exclude: ["*://exclude.com/*"] },
+ });
+ render();
+ await screen.findByText("*://script.com/*");
+ fireEvent.click(screen.getByLabelText(`${t("delete")} *://script.com/*`));
+ fireEvent.click(screen.getByText(t("confirm"), { selector: "button" }));
+ // 后端 selfMetadataUpdate 收到空集即删除覆盖,生效列表回落 metadata.match,脚本自带规则应保留
+ expect(resetMatch).toHaveBeenCalledWith("u1", []);
+ expect(screen.getByText("*://script.com/*")).toBeInTheDocument();
+ expect(screen.getByText(t("editor:from_script"))).toBeInTheDocument();
+ });
});
describe("SettingsPane 授权管理(CORS)", () => {
diff --git a/src/pages/options/routes/ScriptEditor/tabs/SettingsPane.tsx b/src/pages/options/routes/ScriptEditor/tabs/SettingsPane.tsx
index 2b3ea1f7b..d2200b79e 100644
--- a/src/pages/options/routes/ScriptEditor/tabs/SettingsPane.tsx
+++ b/src/pages/options/routes/ScriptEditor/tabs/SettingsPane.tsx
@@ -234,14 +234,27 @@ function SettingsPaneContent({ uuid, data }: SettingsPaneProps & { data: Setting
// ===== 匹配 / 排除 =====
const setMatchList = (kind: "match" | "exclude", next: string[] | undefined) => {
+ // next 为 undefined(重置)或空数组(删除最后一项)时后端 selfMetadataUpdate 均删除用户覆盖,
+ // 生效列表回落到脚本自带的 metadata,前端乐观更新保持一致
+ const override = next?.length ? next : undefined;
+ const metaList = kind === "match" ? metaMatch : metaExclude;
if (kind === "match") {
void scriptClient.resetMatch(uuid, next);
- setMatches(next ?? []);
+ setMatches(override ?? metaList);
} else {
void scriptClient.resetExclude(uuid, next);
- setExcludes(next ?? []);
+ setExcludes(override ?? metaList);
}
- patchSelf({ [kind]: next ?? [] });
+ setScript((prev) => {
+ if (!prev) return prev;
+ const selfMetadata = { ...prev.selfMetadata };
+ if (override === undefined) {
+ delete selfMetadata[kind];
+ } else {
+ selfMetadata[kind] = override;
+ }
+ return { ...prev, selfMetadata };
+ });
};
const openBulkMatch = (kind: "match" | "exclude") => {
setBulkMatchValue("");