Commit f27a8f3f authored by timel's avatar timel

feat: filter and order

parent 367ed2cb
......@@ -2,17 +2,19 @@ import { Database } from "sql.js";
import { CardData, CardMeta, CardText } from "@/api";
import { isNil } from "lodash-es";
import { constructCardMeta } from ".";
const TYPE_MONSTER = 0x1;
/** 过滤条件 */
export interface FtsConditions {
// 过滤条件
types?: number[]; // 卡片类型
levels?: number[]; // 星阶/刻度/link值
atk?: [number, number]; // 攻击力区间
def?: [number, number]; // 防御力区间
races?: number[]; // 种族
attributes?: number[]; // 属性
types: number[]; // 卡片类型
levels: number[]; // 星阶/刻度/link值
races: number[]; // 种族
attributes: number[]; // 属性
atk: { min: number | null; max: number | null }; // 攻击力区间
def: { min: number | null; max: number | null }; // 防御力区间
}
export interface FtsParams {
query: string; // 用于全文检索的query
......@@ -61,10 +63,16 @@ function getFtsCondtions(conditions: FtsConditions): string {
?.map((level) => `level = ${level}`)
.join(" OR ");
const atkCondition = atk
? `atk BETWEEN ${atk[0]} AND ${atk[1]} AND ${assertMonster}`
? `atk BETWEEN ${handleFinite(atk.min, "min")} AND ${handleFinite(
atk.max,
"max"
)} AND ${assertMonster}`
: undefined;
const defCondition = def
? `def BETWEEN ${def[0]} AND ${def[1]} AND ${assertMonster}`
? `def BETWEEN ${handleFinite(def.min, "min")} AND ${handleFinite(
def.max,
"max"
)} AND ${assertMonster}`
: undefined;
const raceCondition = races?.map((race) => `race = ${race}`).join(" OR ");
const attributeCondition = attributes
......@@ -85,3 +93,8 @@ function getFtsCondtions(conditions: FtsConditions): string {
return merged;
}
function handleFinite(value: number | null, type: "min" | "max"): number {
if (isNil(value)) return type === "min" ? -2 : 9999999;
return value;
}
This diff is collapsed.
......@@ -206,7 +206,14 @@ const DeckEditor: React.FC<{
const Search: React.FC = () => {
const { modal } = App.useApp();
const [searchWord, setSearchWord] = useState("");
const [searchConditions, setSearchConditions] = useState<FtsConditions>({});
const [searchConditions, setSearchConditions] = useState<FtsConditions>({
atk: { min: null, max: null },
def: { min: null, max: null },
levels: [],
races: [],
attributes: [],
types: [],
});
const [searchResult, setSearchResult] = useState<CardMeta[]>([]);
const sortRef = useRef<(a: CardMeta, b: CardMeta) => number>(
......@@ -218,34 +225,26 @@ const Search: React.FC = () => {
setSearchResult([...searchResult.sort(sortRef.current)]);
};
const genSort = (key: keyof CardMeta["data"], scale: 1 | -1 = 1) => {
return () =>
setSortRef(
(a: CardMeta, b: CardMeta) =>
((a.data?.[key] ?? 0) - (b.data?.[key] ?? 0)) * scale
);
};
const dropdownOptions: MenuProps["items"] = (
[
["从新到旧", () => setSortRef((a, b) => b.id - a.id)],
["从旧到新", () => setSortRef((a, b) => a.id - b.id)],
[
"攻击力从高到低",
() => setSortRef((a, b) => (b.data?.atk ?? 0) - (a.data?.atk ?? 0)),
],
[
"攻击力从低到高",
() => setSortRef((a, b) => (a.data?.atk ?? 0) - (b.data?.atk ?? 0)),
],
[
"守备力从高到低",
() => setSortRef((a, b) => (b.data?.def ?? 0) - (a.data?.def ?? 0)),
],
[
"守备力从低到高",
() => setSortRef((a, b) => (a.data?.def ?? 0) - (b.data?.def ?? 0)),
],
[
"星/阶/刻/Link从高到低",
() => setSortRef((a, b) => (a.data?.level ?? 0) - (b.data?.level ?? 0)),
],
[
"星/阶/刻/Link从低到高",
() => setSortRef((a, b) => (b.data?.level ?? 0) - (a.data?.level ?? 0)),
],
["攻击力从高到低", genSort("atk")],
["攻击力从低到高", genSort("atk", -1)],
["守备力从高到低", genSort("def")],
["守备力从低到高", genSort("def", -1)],
["星/阶/刻/Link从高到低", genSort("level")],
["星/阶/刻/Link从低到高", genSort("level", -1)],
["灵摆刻度从高到低", genSort("lscale")],
["灵摆刻度从低到高", genSort("lscale", -1)],
] as const
).map(([label, onClick], key) => ({ key, label, onClick }));
......@@ -268,6 +267,28 @@ const Search: React.FC = () => {
}
},
});
const showFilterModal = () => {
const { destroy } = modal.info({
width: 500,
centered: true,
title: null,
icon: null,
content: (
<Filter
conditions={searchConditions}
onConfirm={(newConditions) => {
setSearchConditions(newConditions);
destroy();
setTimeout(handleSearch, 50); // 先收起再搜索
}}
onCancel={() => destroy()}
/>
),
footer: null,
});
};
return (
<div className={styles.container} ref={dropRef}>
<div className={styles.title}>
......@@ -292,25 +313,7 @@ const Search: React.FC = () => {
block
type="text"
icon={<FilterOutlined />}
onClick={() => {
const { destroy } = modal.info({
width: 500,
centered: true,
title: null,
icon: null,
content: (
<Filter
conditions={searchConditions}
onConfirm={(newConditions) => {
setSearchConditions(newConditions);
destroy();
}}
onCancel={() => destroy()}
/>
),
footer: null,
});
}}
onClick={showFilterModal}
>
筛选
{/* TODO: 下面这个Badge应根据有无筛选规则而显示 */}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment