diff --git a/README.md b/README.md index 73a0338..3c775f6 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,8 @@ npm run dev ```bash VITE_API_PROXY_TARGET=http://127.0.0.1:8080 npm run dev + +VITE_API_PROXY_TARGET=https://hao858.com/ npm run dev ``` ## 构建 diff --git a/src/App.tsx b/src/App.tsx index 4126ba1..226c064 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -10,7 +10,6 @@ import ConfigProvider from "antd/es/config-provider"; import Empty from "antd/es/empty"; import Input from "antd/es/input"; import Pagination from "antd/es/pagination"; -import Select from "antd/es/select"; import Spin from "antd/es/spin"; import Tag from "antd/es/tag"; import Tooltip from "antd/es/tooltip"; @@ -53,25 +52,36 @@ const initialOptions: ListingPublishOptions = { rank_options: [], insurance_options: [], level_options: [], + region_options: [], skin_groups: [], }; -const fallbackLevels = ["4级", "5级", "6级", "7级"]; -const fallbackInsurance = ["1*2", "2*2", "2*3", "3*3"]; -const fallbackLogins = ["QQ", "微信", "Steam", "不限"]; -const fallbackRanks = ["青铜", "白银", "黄金", "铂金", "钻石", "黑鹰", "巅峰"]; -const fallbackSkins = ["信条", "坠星者", "处刑者", "影锋", "暗星", "电锯", "蚀金玫瑰", "维什戴尔"]; +/** 数值区间预设(主站 HomeFilters 同款,非 publish_options) */ +interface RangeOption { + label: string; + min: string; + max: string; +} -const coinRanges = [ +const coinRanges: RangeOption[] = [ { label: "15-50", min: "15", max: "50" }, { label: "50-100", min: "50", max: "100" }, { label: "100-150", min: "100", max: "150" }, { label: "150-200", min: "150", max: "200" }, { label: "200-250", min: "200", max: "250" }, + { label: "250-300", min: "250", max: "300" }, { label: "300以上", min: "300", max: "" }, ]; -const priceRanges = [ +const ratioRanges: RangeOption[] = [ + { label: "≤30", min: "", max: "30" }, + { label: "30-35", min: "30", max: "35" }, + { label: "35-40", min: "35", max: "40" }, + { label: "40-45", min: "40", max: "45" }, + { label: "45+", min: "45", max: "" }, +]; + +const priceRanges: RangeOption[] = [ { label: "0-50", min: "0", max: "50" }, { label: "50-100", min: "50", max: "100" }, { label: "100-150", min: "100", max: "150" }, @@ -79,49 +89,85 @@ const priceRanges = [ { label: "200以上", min: "200", max: "" }, ]; +const depositRanges: RangeOption[] = [ + { label: "50-100", min: "50", max: "100" }, + { label: "100-150", min: "100", max: "150" }, + { label: "150-200", min: "150", max: "200" }, + { label: "200以上", min: "200", max: "" }, +]; + +const totalRanges: RangeOption[] = [ + { label: "0-500", min: "0", max: "500" }, + { label: "500-1000", min: "500", max: "1000" }, + { label: "1000-2000", min: "1000", max: "2000" }, + { label: "2000-5000", min: "2000", max: "5000" }, + { label: "5000+", min: "5000", max: "" }, +]; + +const fireLevelRanges: RangeOption[] = [ + { label: "38-50", min: "38", max: "50" }, + { label: "50-60", min: "50", max: "60" }, +]; + const sortOptions = [ { key: "recommended", label: "默认排序" }, { key: "priceDesc", label: "价格排序" }, { key: "ratioDesc", label: "比例排序" }, { key: "coinDesc", label: "哈夫币排序" }, { key: "ratioAsc", label: "绝密KD排序" }, - { key: "coinAsc", label: "AWM排序" }, + { key: "awmDesc", label: "AWM子弹排序" }, { key: "priceAsc", label: "六头排序" }, { key: "loadDesc", label: "六甲排序" }, ]; +type RangeMinKey = "minCoin" | "minRatio" | "minPrice" | "minDeposit" | "minTotal" | "minFireLevel"; +type RangeMaxKey = "maxCoin" | "maxRatio" | "maxPrice" | "maxDeposit" | "maxTotal" | "maxFireLevel"; +type ListFilterKey = "stamina" | "load" | "insurance" | "skinNames" | "loginMethods" | "ranks" | "regions"; + interface Filters { keyword: string; minCoin: string; maxCoin: string; + minRatio: string; + maxRatio: string; minPrice: string; maxPrice: string; - minKd: string; - maxKd: string; + minDeposit: string; + maxDeposit: string; + minTotal: string; + maxTotal: string; + minFireLevel: string; + maxFireLevel: string; stamina: string[]; load: string[]; insurance: string[]; skinNames: string[]; loginMethods: string[]; ranks: string[]; - zones: string[]; + regions: string[]; } const emptyFilters: Filters = { keyword: "", minCoin: "", maxCoin: "", + minRatio: "", + maxRatio: "", minPrice: "", maxPrice: "", - minKd: "", - maxKd: "", + minDeposit: "", + maxDeposit: "", + minTotal: "", + maxTotal: "", + minFireLevel: "", + maxFireLevel: "", stamina: [], load: [], insurance: [], skinNames: [], loginMethods: [], ranks: [], - zones: [], + regions: [], }; export default function App() { @@ -141,9 +187,6 @@ export default function App() { Input: { controlHeight: 38, }, - Select: { - controlHeight: 30, - }, }, }} > @@ -168,46 +211,40 @@ function Showcase() { const [copiedCode, setCopiedCode] = useState(""); const [copiedInfoCode, setCopiedInfoCode] = useState(""); - const levelOptions = useMemo( - () => (publishOptions.level_options.length ? publishOptions.level_options : fallbackLevels), - [publishOptions.level_options] + // 枚举类筛选项全部来自 /api/mobile-home-config 的 publish_options + const levelOptions = publishOptions.level_options; + const insuranceOptions = publishOptions.insurance_options; + const loginOptions = publishOptions.login_method_options; + const rankOptions = publishOptions.rank_options; + const regionOptions = publishOptions.region_options; + /** 基础筛选:刀皮 / 红皮 */ + const basicSkinSections = useMemo( + () => + resolveSkinSections(publishOptions.skin_groups, [ + { key: "melee", title: "刀皮" }, + { key: "operatorRed", title: "红皮" }, + ]), + [publishOptions.skin_groups] ); - const insuranceOptions = useMemo( - () => (publishOptions.insurance_options.length ? publishOptions.insurance_options : fallbackInsurance), - [publishOptions.insurance_options] + /** 高级筛选:枪皮 */ + const advancedSkinSections = useMemo( + () => resolveSkinSections(publishOptions.skin_groups, [{ key: "weapon", title: "枪皮" }]), + [publishOptions.skin_groups] ); - const loginOptions = useMemo( - () => (publishOptions.login_method_options.length ? publishOptions.login_method_options : fallbackLogins), - [publishOptions.login_method_options] - ); - const rankOptions = useMemo( - () => (publishOptions.rank_options.length ? publishOptions.rank_options : fallbackRanks), - [publishOptions.rank_options] - ); - const zoneOptions = useMemo(() => publishOptions.server_options, [publishOptions.server_options]); - const skinSections = useMemo(() => { - const groups = publishOptions.skin_groups.filter((group) => - ["melee", "operatorGold", "operatorRed", "weapon"].includes(group.key) - ); - if (groups.length) { - return groups.map((group) => ({ - key: group.key, - title: groupTitle(group.key, group.title), - options: group.options, - })); - } - return [{ key: "skin", title: "稀有皮肤", options: fallbackSkins }]; - }, [publishOptions.skin_groups]); const noticeText = announcements[0]?.trim() || ""; const activeFilterCount = useMemo(() => countActiveFilters(filters), [filters]); const appliedFilterCount = useMemo(() => countActiveFilters(appliedFilters), [appliedFilters]); + const advancedActiveCount = useMemo(() => countAdvancedFilters(filters), [filters]); const displayListings = useMemo(() => { const items = [...listings]; if (activeSort === "ratioAsc") return items.sort((a, b) => ratioValue(a) - ratioValue(b)); if (activeSort === "ratioDesc") return items.sort((a, b) => ratioValue(b) - ratioValue(a)); - if (activeSort === "coinAsc") return items.sort((a, b) => a.haf_coin_amount - b.haf_coin_amount); + // 与后端 awmDesc 一致:按 AW子弹数量降序(当前页兜底) + if (activeSort === "awmDesc") { + return items.sort((a, b) => getResourceQuantity(b, "awmAmmo") - getResourceQuantity(a, "awmAmmo")); + } if (activeSort === "loadDesc") { return items.sort((a, b) => getResourceQuantity(b, "armor6") - getResourceQuantity(a, "armor6")); } @@ -258,7 +295,7 @@ function Showcase() { setFilters((current) => ({ ...current, [key]: value })); } - function toggleFilterList(key: "stamina" | "load" | "insurance" | "skinNames", value: string) { + function toggleFilterList(key: ListFilterKey, value: string) { setFilters((current) => { const nextList = current[key].includes(value) ? current[key].filter((item) => item !== value) : [...current[key], value]; return { ...current, [key]: nextList }; @@ -281,7 +318,7 @@ function Showcase() { setAdvancedOpen(false); } - function selectRange(minKey: "minCoin" | "minPrice", maxKey: "maxCoin" | "maxPrice", min: string, max: string) { + function selectRange(minKey: RangeMinKey, maxKey: RangeMaxKey, min: string, max: string) { setFilters((current) => { if (current[minKey] === min && current[maxKey] === max) { return { ...current, [minKey]: "", [maxKey]: "" }; @@ -290,6 +327,10 @@ function Showcase() { }); } + function isRangeActive(minKey: RangeMinKey, maxKey: RangeMaxKey, item: RangeOption) { + return filters[minKey] === item.min && filters[maxKey] === item.max; + } + function setSort(sortKey: string) { setActiveSort(sortKey); setPage(1); @@ -411,7 +452,7 @@ function Showcase() { {appliedFilterCount > 0 && !filterOpen ? (
- {buildFilterSummary(appliedFilters, insuranceLabel).map((item) => ( + {buildFilterSummary(appliedFilters).map((item) => ( {item} @@ -420,26 +461,27 @@ function Showcase() { ) : null}
+ {/* 基础筛选:哈夫币 / 比例 / 保险 / 体力 / 负重 / 登录 / 刀皮 / 红皮 */}
filters.minCoin === item.min && filters.maxCoin === item.max} + active={(item) => isRangeActive("minCoin", "maxCoin", item)} label="哈夫币" options={coinRanges} render={(item) => item.label} onSelect={(item) => selectRange("minCoin", "maxCoin", item.min, item.max)} /> filters.minPrice === item.min && filters.maxPrice === item.max} - label="租金" - options={priceRanges} + active={(item) => isRangeActive("minRatio", "maxRatio", item)} + label="比例" + options={ratioRanges} render={(item) => item.label} - onSelect={(item) => selectRange("minPrice", "maxPrice", item.min, item.max)} + onSelect={(item) => selectRange("minRatio", "maxRatio", item.min, item.max)} /> filters.insurance.includes(item)} label="保险" options={insuranceOptions} - render={insuranceLabel} + render={(item) => item} onSelect={(item) => toggleFilterList("insurance", item)} /> item} onSelect={(item) => toggleFilterList("load", item)} /> -
- IP地址 - ({ label: item, value: item }))} - placeholder="不限" - value={filters.loginMethods} - onChange={(value) => updateFilter("loginMethods", value)} - /> -
-
- 段位 - updateFilter("minCoin", event.target.value)} - /> - - updateFilter("maxCoin", event.target.value)} - /> -
- - -
- {skinSections.map((section) => ( + filters.loginMethods.includes(item)} + label="登录方式" + options={loginOptions} + render={(item) => item} + onSelect={(item) => toggleFilterList("loginMethods", item)} + /> + {basicSkinSections.map((section) => ( filters.skinNames.includes(item)} label={section.title} - options={section.options.slice(0, 18)} + options={section.options} render={(item) => item} onSelect={(item) => toggleFilterList("skinNames", item)} /> ))}
-
+
+
+ + {/* 高级筛选:租金 / 押金 / 合计 / 等级 / 段位 / IP / 枪皮 */} +
+
+ isRangeActive("minPrice", "maxPrice", item)} + label="租金" + options={priceRanges} + render={(item) => item.label} + onSelect={(item) => selectRange("minPrice", "maxPrice", item.min, item.max)} + /> + isRangeActive("minDeposit", "maxDeposit", item)} + label="押金" + options={depositRanges} + render={(item) => item.label} + onSelect={(item) => selectRange("minDeposit", "maxDeposit", item.min, item.max)} + /> + isRangeActive("minTotal", "maxTotal", item)} + label="合计金额" + options={totalRanges} + render={(item) => item.label} + onSelect={(item) => selectRange("minTotal", "maxTotal", item.min, item.max)} + /> + isRangeActive("minFireLevel", "maxFireLevel", item)} + label="等级" + options={fireLevelRanges} + render={(item) => item.label} + onSelect={(item) => selectRange("minFireLevel", "maxFireLevel", item.min, item.max)} + /> + filters.ranks.includes(item)} + label="段位" + options={rankOptions} + render={(item) => item} + onSelect={(item) => toggleFilterList("ranks", item)} + /> + filters.regions.includes(item)} + label="IP地区" + options={regionOptions} + render={(item) => item} + onSelect={(item) => toggleFilterList("regions", item)} + /> + {advancedSkinSections.map((section) => ( + filters.skinNames.includes(item)} + label={section.title} + options={section.options} + render={(item) => item} + onSelect={(item) => toggleFilterList("skinNames", item)} + /> + ))} +
+
+ +