From 353e8a597b6abffab1669b1e398316e2aeef62be Mon Sep 17 00:00:00 2001 From: zihluwang Date: Thu, 2 Apr 2026 10:42:36 +0800 Subject: [PATCH] feat: add @tanstack/react-virtual for improved virtual scrolling and enhance modification codes with additional weapon data and filtering options --- package.json | 1 + pnpm-lock.yaml | 20 ++++ src/data/modification-codes.json | 83 ++++++++++++++- src/page/mod-codes/index.tsx | 171 +++++++++++++++++++++++-------- 4 files changed, 231 insertions(+), 44 deletions(-) diff --git a/package.json b/package.json index e8bf12b..af3899e 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ }, "dependencies": { "@tailwindcss/vite": "^4.2.2", + "@tanstack/react-virtual": "^3.13.23", "dayjs": "^1.11.20", "react": "^19.2.4", "react-dom": "^19.2.4", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8ba8497..41ba328 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -11,6 +11,9 @@ importers: '@tailwindcss/vite': specifier: ^4.2.2 version: 4.2.2(vite@8.0.1(@types/node@22.19.15)(esbuild@0.27.4)(jiti@2.6.1)) + '@tanstack/react-virtual': + specifier: ^3.13.23 + version: 3.13.23(react-dom@19.2.4(react@19.2.4))(react@19.2.4) dayjs: specifier: ^1.11.20 version: 1.11.20 @@ -439,6 +442,15 @@ packages: peerDependencies: vite: ^5.2.0 || ^6 || ^7 || ^8 + '@tanstack/react-virtual@3.13.23': + resolution: {integrity: sha512-XnMRnHQ23piOVj2bzJqHrRrLg4r+F86fuBcwteKfbIjJrtGxb4z7tIvPVAe4B+4UVwo9G4Giuz5fmapcrnZ0OQ==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + + '@tanstack/virtual-core@3.13.23': + resolution: {integrity: sha512-zSz2Z2HNyLjCplANTDyl3BcdQJc2k1+yyFoKhNRmCr7V7dY8o8q5m8uFTI1/Pg1kL+Hgrz6u3Xo6eFUB7l66cg==} + '@tybys/wasm-util@0.10.1': resolution: {integrity: sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==} @@ -961,6 +973,14 @@ snapshots: tailwindcss: 4.2.2 vite: 8.0.1(@types/node@22.19.15)(esbuild@0.27.4)(jiti@2.6.1) + '@tanstack/react-virtual@3.13.23(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + dependencies: + '@tanstack/virtual-core': 3.13.23 + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + + '@tanstack/virtual-core@3.13.23': {} + '@tybys/wasm-util@0.10.1': dependencies: tslib: 2.8.1 diff --git a/src/data/modification-codes.json b/src/data/modification-codes.json index ae02961..e6e5628 100644 --- a/src/data/modification-codes.json +++ b/src/data/modification-codes.json @@ -4,6 +4,87 @@ "modification-code": "6JJE3180BB9B3KKCCH41C", "mode": "烽火地带", "tags": ["精锐制式券", "突击步枪", "稳定"], - "note": "精锐火力券提供的 SCAR-H" + "note": "精锐火力券提供的 SCAR-H", + "price": 445295 + }, + { + "weapon": "M4A1突击步枪", + "modification-code": "6JAS9U80BB9B3KKCCH41C", + "mode": "烽火地带", + "tags": ["稳定", "突击步枪", "倍镜"], + "note": "带有三倍镜的稳定版 M4A1,全套价格左右。", + "price": 400000 + }, + { + "weapon": "M4A1突击步枪", + "modification-code": "6J9D8SO0BB9B3KKCCH41C", + "mode": "烽火地带", + "tags": ["突击步枪", "性价比"], + "note": "性价比改装稳定版 M4A1。", + "price": 242638 + }, + { + "weapon": "MK4冲锋枪", + "modification-code": "6IVR0N40BB9B3KKCCH41C", + "mode": "烽火地带", + "tags": ["超绝腰射", "三连发"], + "note": "超绝三连发腰射 MK4,近距离火力十足", + "price": 538686 + }, + { + "weapon": "MK4冲锋枪", + "modification-code": "6J7283O0BB9B3KKCCH41C", + "mode": "烽火地带", + "tags": ["超绝腰射", "全自动"], + "note": "超绝全自动腰射 MK4,见到人瞄准好之后左键按到死", + "price": 466836 + }, + { + "weapon": "M7战斗步枪", + "modification-code": "6IVH3HC0BB9B3KKCCH41C", + "mode": "烽火地带", + "tags": ["半改", "蓝管", "稳定"], + "note": "半改稳定 M7", + "price": 620597 + }, + { + "weapon": "M7战斗步枪", + "modification-code": "6J2QBQK0BB9B3KKCCH41C", + "mode": "烽火地带", + "tags": ["倍镜", "职业选手", "消音"], + "note": "需要较高控枪水平的满改 M7", + "price": 891030 + }, + { + "weapon": "M7战斗步枪", + "modification-code": "6J2QC080BB9B3KKCCH41C", + "mode": "烽火地带", + "tags": ["稳定"], + "note": "S8 赛季幻影版满改稳定 M7,51操控", + "price": 1039234 + }, + { + "weapon": "M7战斗步枪", + "modification-code": "6J2QC4O0BB9B3KKCCH41C", + "mode": "烽火地带", + "tags": ["稳定"], + "note": "S8 赛季隐袭版满改稳定 M7,49操控", + "price": 1064418 + }, + { + "weapon": "MK47突击步枪", + "modification-code": "6JAV6A80BB9B3KKCCH41C", + "mode": "烽火地带", + "tags": ["突袭", "大口径", "消音"], + "note": "S8 赛季余烬MK47,携带消音器", + "price": 862384 + }, + { + "weapon": "MK47突击步枪", + "modification-code": "6JAV6SS0BB9B3KKCCH41C", + "mode": "烽火地带", + "tags": ["突袭", "稳定"], + "note": "幻影余烬主宰者MK47", + "price": 864227 } ] diff --git a/src/page/mod-codes/index.tsx b/src/page/mod-codes/index.tsx index 8d2ed14..ead06fd 100644 --- a/src/page/mod-codes/index.tsx +++ b/src/page/mod-codes/index.tsx @@ -1,12 +1,30 @@ -import { useMemo, useState } from "react" +import { useMemo, useState, useEffect, useLayoutEffect, useRef } from "react" +import { useWindowVirtualizer } from "@tanstack/react-virtual" import rawModCodes from "@/data/modification-codes.json" +function useColumnCount() { + const getCount = () => { + if (window.innerWidth >= 1024) return 4 + if (window.innerWidth >= 768) return 3 + if (window.innerWidth >= 640) return 2 + return 1 + } + const [cols, setCols] = useState(getCount) + useEffect(() => { + const handler = () => setCols(getCount()) + window.addEventListener("resize", handler) + return () => window.removeEventListener("resize", handler) + }, []) + return cols +} + type ModCodeSource = { weapon: string "modification-code": string mode?: string tags?: string[] note?: string + price?: number } type ModCode = { @@ -16,6 +34,7 @@ type ModCode = { mode?: string tags: string[] note?: string + price?: number } const MOD_CODES: ModCode[] = (rawModCodes as ModCodeSource[]).map((item, index) => ({ @@ -25,11 +44,13 @@ const MOD_CODES: ModCode[] = (rawModCodes as ModCodeSource[]).map((item, index) mode: item.mode, tags: item.tags ?? [], note: item.note, + price: item.price, })) export default function ModCodes() { const [keyword, setKeyword] = useState("") const [activeTag, setActiveTag] = useState("全部") + const [activeWeapon, setActiveWeapon] = useState("全部") const [copiedId, setCopiedId] = useState(null) const [copyErrorId, setCopyErrorId] = useState(null) @@ -50,6 +71,12 @@ export default function ModCodes() { } } + const allWeapons = useMemo(() => { + const weapons = new Set() + MOD_CODES.forEach((item) => weapons.add(item.weapon)) + return ["全部", ...Array.from(weapons)] + }, []) + const allTags = useMemo(() => { const tags = new Set() MOD_CODES.forEach((item) => { @@ -61,6 +88,7 @@ export default function ModCodes() { const filtered = useMemo(() => { const q = keyword.trim().toLowerCase() return MOD_CODES.filter((item) => { + const matchWeapon = activeWeapon === "全部" || item.weapon === activeWeapon const matchTag = activeTag === "全部" || item.tags.includes(activeTag) const matchKeyword = q.length === 0 || @@ -68,18 +96,53 @@ export default function ModCodes() { item.code.toLowerCase().includes(q) || (item.mode?.toLowerCase().includes(q) ?? false) || item.tags.some((tag) => tag.toLowerCase().includes(q)) - return matchTag && matchKeyword + return matchWeapon && matchTag && matchKeyword }) - }, [activeTag, keyword]) + }, [activeWeapon, activeTag, keyword]) + + const colCount = useColumnCount() + + const rows = useMemo(() => { + const result: ModCode[][] = [] + for (let i = 0; i < filtered.length; i += colCount) { + result.push(filtered.slice(i, i + colCount)) + } + return result + }, [filtered, colCount]) + + const listRef = useRef(null) + const scrollMarginRef = useRef(0) + useLayoutEffect(() => { + scrollMarginRef.current = listRef.current?.offsetTop ?? 0 + }) + + const rowVirtualizer = useWindowVirtualizer({ + count: rows.length, + estimateSize: () => 220, + overscan: 3, + scrollMargin: scrollMarginRef.current, + }) return (

- 支持按 tag 与关键字筛选。关键字可匹配枪械名称、改枪码或 tag。 + 支持按武器、tag 与关键字筛选。关键字可匹配枪械名称、改枪码或 tag。

-
+
+
-
- {filtered.map((item) => ( -
-
-

{item.weapon}

- {item.mode ? ( - - {item.mode} - - ) : ( - ID: {item.id} - )} -
-
- {item.code} - -
- {copyErrorId === item.id ? ( -

复制失败,请手动选中复制。

- ) : null} - {item.note ?

{item.note}

: null} -
- {item.tags.map((tag) => ( - +
+ {rowVirtualizer.getVirtualItems().map((virtualRow) => ( +
+
+ {rows[virtualRow.index].map((item) => ( +
+
+

{item.weapon}

+ {item.mode ? ( + + {item.mode} + + ) : ( + ID: {item.id} + )} +
+
+ {item.code} + +
+ {copyErrorId === item.id ? ( +

复制失败,请手动选中复制。

+ ) : null} + {item.price ?

$ {item.price.toLocaleString()}

: null} + {item.note ?

{item.note}

: null} +
+ {item.tags.map((tag) => ( + + ))} +
+
))}
-
+
))}