feat(search): 筛选选项只列结果里有的值,季集合并成一个维度放最前

对照 MP useTorrentFilter:分面选项按实际结果生成,计数为 0 的不再列出(已选的值仍列出,可删除);
新增季集维度 seasonEpisode(MP season_episode 写法 `S01` / `S01-S02` / `S02 E14` / `S01 E25-E48`),
整季在前、单集在后、季号新的在前,在筛选条最前。旧的季 / 集维度保留(旧链接仍生效,以标签列出)。
搜索结果页与站点资源 Tab 共用这套口径。

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Cxwgnn2pbzQ7rXv3rq7fei
This commit is contained in:
rootandClaude Opus 5.5 committed 2026-10-06 15:39:32 +08:00
1 parent 18ee466664
commit f6d976a963
7 files changed
+181 -91

No files matched your search

+63 -28
View File
@@ -2,12 +2,14 @@ import { describe, expect, it } from "vitest";
import {
applyFacets,
audioBuckets,
compareSeasonEpisode,
type FacetInput,
formatFacetParam,
parseFacetParam,
parseRelease,
promotionBuckets,
releaseTypeBucket,
seasonEpisodeValue,
seedersBucket,
sizeBucket,
subtitleBuckets,
@@ -76,8 +78,9 @@ describe("release-facets", () => {
expect(counts(r.facets.videoCodec)).toEqual({ "H.265": 2, "H.264": 1, AV1: 1, other: 1 });
expect(counts(r.facets.releaseType)).toEqual({ disc: 1, remux: 1, encode: 1, webdl: 1, hdtv: 1 });
expect(r.facets.releaseType.map((o) => o.label)).toEqual(["原盘", "Remux", "蓝光压制", "WEB-DL", "HDTV"]);
expect(counts(r.facets.hdr)).toEqual({ DV: 1, HDR10: 2, "HDR10+": 0, SDR: 3 });
expect(counts(r.facets.promotion)).toEqual({ free: 2, "2x": 1, "50%": 0, none: 3 });
// 结果里没有的档位不列出(HDR10+、50%)
expect(counts(r.facets.hdr)).toEqual({ DV: 1, HDR10: 2, SDR: 3 });
expect(counts(r.facets.promotion)).toEqual({ free: 2, "2x": 1, none: 3 });
expect(r.facets.site).toEqual([
{ value: "a", label: "站 A", count: 3 },
{ value: "b", label: "站 B", count: 2 },
@@ -88,24 +91,25 @@ describe("release-facets", () => {
{ value: "FraMeSToR", label: "FraMeSToR", count: 1 },
]);
expect(r.facets.season).toEqual([]);
expect(r.facets.seasonEpisode).toEqual([]);
});
it("同维度 OR、不同维度 AND;计数按其它维度的已选条件", () => {
const r = run({ resolution: ["2160p", "1080p"], videoCodec: ["H.265"] });
expect(ids(r)).toEqual(["1", "2"]);
// 分辨率的计数只受编码条件影响:1080p 在 H.265 下没有结果 → 0
expect(counts(r.facets.resolution)).toEqual({ "2160p": 2, "1080p": 0, "720p": 0, other: 0 });
// 编码计数只受分辨率条件影响
expect(counts(r.facets.videoCodec)).toEqual({ "H.265": 2, "H.264": 1, AV1: 0, other: 0 });
// 其它维度按全部条件
expect(counts(r.facets.releaseType)).toMatchObject({ remux: 1, webdl: 1, encode: 0 });
// 分辨率的计数只受编码条件影响:1080p 在 H.265 下没有结果,但已选,仍列出(计数 0);720p / 其他不列
expect(counts(r.facets.resolution)).toEqual({ "2160p": 2, "1080p": 0 });
// 编码计数只受分辨率条件影响:在 2160p / 1080p 下没有 AV1 / 其他,不列
expect(counts(r.facets.videoCodec)).toEqual({ "H.265": 2, "H.264": 1 });
// 其它维度按全部条件:计数为 0 的档位不列
expect(counts(r.facets.releaseType)).toEqual({ remux: 1, webdl: 1 });
expect(ids(run({ site: ["b"], releaseType: ["disc", "webdl"] }))).toEqual(["2", "5"]);
expect(ids(run({ releaseGroup: ["flux"] }))).toEqual(["2", "3"]);
expect(ids(run({ hdr: ["SDR"] }))).toEqual(["3", "4", "5"]);
expect(ids(run({ promotion: ["2x"] }))).toEqual(["4"]);
});
it("H&R 三态分面:缺省按未知;未知档只在有资源时列出", () => {
it("H&R 三态分面:缺省按未知;只列有资源的档", () => {
const hrItems = [mk("y", { hr: true }), mk("n", { hr: false }), mk("n2", { hr: false }), mk("u")];
const r = applyFacets(hrItems, (x) => x, {}, { now: NOW });
expect(r.facets.hr).toEqual([
@@ -115,7 +119,7 @@ describe("release-facets", () => {
]);
expect(ids(applyFacets(hrItems, (x) => x, { hr: ["no", "unknown"] }, { now: NOW }))).toEqual(["n", "n2", "u"]);
const known = applyFacets([mk("y", { hr: true })], (x) => x, {}, { now: NOW });
expect(known.facets.hr.map((o) => o.value)).toEqual(["yes", "no"]);
expect(known.facets.hr.map((o) => o.value)).toEqual(["yes"]);
});
it("已选但没有结果的值仍列出(计数 0)", () => {
@@ -150,14 +154,56 @@ describe("release-facets", () => {
{ value: "2", label: "S02", count: 1 },
{ value: "3", label: "S03", count: 1 },
]);
expect(r.facets.episode).toEqual([
{ value: "pack", label: "整季", count: 1 },
{ value: "2", label: "E02", count: 0 },
{ value: "3", label: "E03", count: 0 },
]);
// 在 S02 下单集没有结果,不列
expect(r.facets.episode).toEqual([{ value: "pack", label: "整季", count: 1 }]);
expect(ids(applyFacets(tv, (x) => x, { season: ["1"], episode: ["3"] }, { now: NOW }))).toEqual(["s1e2"]);
});
it("季集(MP season_episode):一个维度,整季在前、单集在后,季号新的在前", () => {
const tv = [
mk("s1", { season: 1, episodes: null }),
mk("s1b", { season: 1, episodes: null }),
mk("s2", { season: 2, episodes: null }),
mk("s1-3", { season: 1, seasonEnd: 3, episodes: null }),
mk("s1e25", { season: 1, episodes: { start: 25, end: 48 } }),
mk("s2e14", { season: 2, episodes: { start: 14, end: 14 } }),
mk("s2e1", { season: 2, episodes: { start: 1, end: 1 } }),
mk("s2e14-15", { season: 2, episodes: { start: 14, end: 15 } }),
mk("movie"),
];
const r = applyFacets(tv, (x) => x, {}, { now: NOW });
expect(r.facets.seasonEpisode.map((o) => [o.value, o.count])).toEqual([
["S02", 1],
["S01", 2],
["S01-S03", 1],
["S02 E14-E15", 1],
["S02 E14", 1],
["S02 E01", 1],
["S01 E25-E48", 1],
]);
expect(r.facets.seasonEpisode[0]?.label).toBe("S02");
expect(ids(applyFacets(tv, (x) => x, { seasonEpisode: ["S01", "S02 E14"] }, { now: NOW }))).toEqual([
"s1",
"s1b",
"s2e14",
]);
// 计数按其它维度已选条件;在其它条件下为 0 的不列
const free = applyFacets(
tv.map((t) => (t.id === "s2" ? { ...t, promotion: { ...none, type: "free", downloadFactor: 0 } } : t)),
(x) => x,
{ promotion: ["free"] },
{ now: NOW },
);
expect(free.facets.seasonEpisode).toEqual([{ value: "S02", label: "S02", count: 1 }]);
expect(seasonEpisodeValue({ season: null, episodes: { start: 1, end: 2 } })).toBeNull();
expect(["S01 E02", "bogus", "S10", "S02 E10-E12"].sort(compareSeasonEpisode)).toEqual([
"S10",
"S02 E10-E12",
"S01 E02",
"bogus",
]);
});
it("促销与版本类型档位", () => {
expect(promotionBuckets({ type: "2x50%", downloadFactor: 0.5, uploadFactor: 2, expiresAt: null }, NOW)).toEqual([
"2x",
@@ -227,10 +273,7 @@ describe("release-facets", () => {
Atmos: 1,
TrueHD: 1,
"DTS-HD": 1,
DTS: 0,
DDP: 1,
DD: 0,
AAC: 0,
unknown: 1,
});
expect(r.facets.audioCodec.find((o) => o.value === "DDP")?.label).toBe("DD+");
@@ -246,16 +289,8 @@ describe("release-facets", () => {
["电影", 2],
["剧集", 1],
]);
expect(counts(r.facets.size)).toEqual({
lt1: 0,
"1-5": 1,
"5-10": 1,
"10-20": 0,
"20-50": 0,
"50+": 1,
unknown: 1,
});
expect(counts(r.facets.seeders)).toEqual({ "0": 1, "1-9": 0, "10-49": 1, "50+": 1, unknown: 1 });
expect(counts(r.facets.size)).toEqual({ "1-5": 1, "5-10": 1, "50+": 1, unknown: 1 });
expect(counts(r.facets.seeders)).toEqual({ "0": 1, "10-49": 1, "50+": 1, unknown: 1 });
});
it("新维度组合:同维度 OR、跨维度 AND,计数按其它维度已选条件", () => {