|
|
@@ -1,27 +1,11 @@
|
|
|
"use client";
|
|
|
-
|
|
|
-import {
|
|
|
- Drawer,
|
|
|
- DrawerContent,
|
|
|
- DrawerHeader,
|
|
|
- DrawerBody,
|
|
|
-} from "@heroui/drawer";
|
|
|
-
|
|
|
import { Button } from "@heroui/button";
|
|
|
-
|
|
|
-import { useDisclosure } from "@heroui/use-disclosure";
|
|
|
-import FilterPrice from "./FilterPrice";
|
|
|
-import {
|
|
|
- AdjustmentsHorizontalIcon,
|
|
|
- XMarkIcon,
|
|
|
-} from "@heroicons/react/24/outline";
|
|
|
import { useEffect, useState, useRef } from "react";
|
|
|
+import FilterPrice from "./FilterPrice";
|
|
|
|
|
|
interface MobileFilterProps {
|
|
|
filterAttributes: any[];
|
|
|
-
|
|
|
filters: Record<string, string>;
|
|
|
-
|
|
|
onChange: (filters: Record<string, string>) => void;
|
|
|
}
|
|
|
export default function MobileFilter({
|
|
|
@@ -29,67 +13,38 @@ export default function MobileFilter({
|
|
|
filters,
|
|
|
onChange,
|
|
|
}: MobileFilterProps) {
|
|
|
- const { isOpen, onOpen, onOpenChange } = useDisclosure();
|
|
|
- // 记录上一次抽屉状态,用来捕获【关闭→打开】瞬间
|
|
|
- const prevIsOpenRef = useRef(isOpen);
|
|
|
-
|
|
|
/**
|
|
|
- * 弹窗里面临时选择
|
|
|
+ * 临时筛选状态
|
|
|
*/
|
|
|
const [tempFilters, setTempFilters] = useState<Record<string, string>>({});
|
|
|
-
|
|
|
const [expandedGroups, setExpandedGroups] = useState<Record<string, boolean>>(
|
|
|
{},
|
|
|
);
|
|
|
|
|
|
- /**
|
|
|
- * 打开弹窗同步父组件状态
|
|
|
- */
|
|
|
- useEffect(() => {
|
|
|
- // 仅从关闭切换到打开时执行
|
|
|
- if (!prevIsOpenRef.current && isOpen) {
|
|
|
- console.log("dadadadadadadadisopen");
|
|
|
- // 使用 queueMicrotask 把状态更新延后,避开同步setState警告
|
|
|
- queueMicrotask(() => {
|
|
|
- // 浅拷贝,断开引用
|
|
|
- setTempFilters({ ...filters });
|
|
|
- console.log("tempFilters---------------------", tempFilters);
|
|
|
-
|
|
|
- const expand: Record<string, boolean> = {};
|
|
|
- filterAttributes.forEach((attr) => {
|
|
|
- expand[attr.code] = true;
|
|
|
- });
|
|
|
- setExpandedGroups(expand);
|
|
|
- });
|
|
|
- }
|
|
|
- prevIsOpenRef.current = isOpen;
|
|
|
-
|
|
|
- // eslint-disable-next-line react-hooks/exhaustive-deps
|
|
|
- }, [isOpen]);
|
|
|
+ // 父组件filters更新时同步到本地临时筛选
|
|
|
useEffect(() => {
|
|
|
- console.log("【父组件传入筛选filters】", filters);
|
|
|
- console.log("【抽屉是否打开isOpen】", isOpen);
|
|
|
- }, [isOpen, filters]);
|
|
|
+ setTempFilters({ ...filters });
|
|
|
+ const expand: Record<string, boolean> = {};
|
|
|
+ filterAttributes.forEach((attr) => {
|
|
|
+ expand[attr.code] = true;
|
|
|
+ });
|
|
|
+ setExpandedGroups(expand);
|
|
|
+ }, [filters, filterAttributes]);
|
|
|
|
|
|
/**
|
|
|
- * 单选
|
|
|
+ * 单选切换
|
|
|
*/
|
|
|
const toggleOption = (code: string, id: string) => {
|
|
|
setTempFilters((prev) => {
|
|
|
const current = prev[code];
|
|
|
-
|
|
|
if (current === id) {
|
|
|
const next = { ...prev };
|
|
|
-
|
|
|
delete next[code];
|
|
|
- // 打印点击后最新的临时筛选
|
|
|
console.log("【点击选项后临时筛选tempFilters】", next);
|
|
|
return next;
|
|
|
}
|
|
|
-
|
|
|
return {
|
|
|
...prev,
|
|
|
-
|
|
|
[code]: id,
|
|
|
};
|
|
|
});
|
|
|
@@ -98,7 +53,6 @@ export default function MobileFilter({
|
|
|
const toggleGroupExpand = (code: string) => {
|
|
|
setExpandedGroups((prev) => ({
|
|
|
...prev,
|
|
|
-
|
|
|
[code]: !prev[code],
|
|
|
}));
|
|
|
};
|
|
|
@@ -108,130 +62,101 @@ export default function MobileFilter({
|
|
|
*/
|
|
|
const applyFilters = () => {
|
|
|
onChange(tempFilters);
|
|
|
-
|
|
|
- onOpenChange();
|
|
|
};
|
|
|
|
|
|
/**
|
|
|
- * 清空
|
|
|
+ * 清空筛选
|
|
|
*/
|
|
|
const clearAll = () => {
|
|
|
setTempFilters({});
|
|
|
-
|
|
|
onChange({});
|
|
|
};
|
|
|
|
|
|
const formatLabel = (str?: string) => {
|
|
|
return str ? str.charAt(0).toUpperCase() + str.slice(1).toLowerCase() : "";
|
|
|
};
|
|
|
- return (
|
|
|
- <>
|
|
|
- <div className="flex flex-wrap gap-3">
|
|
|
- <Button size="md" className="flex bg-neutral-100" onPress={onOpen}>
|
|
|
- <AdjustmentsHorizontalIcon className="h-6 w-8" />
|
|
|
|
|
|
- <span>Filter</span>
|
|
|
- </Button>
|
|
|
+ return (
|
|
|
+ <div className=" rounded-lg w-full max-w-[300px]">
|
|
|
+ {/* Header */}
|
|
|
+ <div className="flex justify-between items-center pb-4 border-b">
|
|
|
+ <h2 className="text-xl font-semibold">Filters:</h2>
|
|
|
+ <button onClick={clearAll} className="text-sm text-primary">Clear All</button>
|
|
|
</div>
|
|
|
|
|
|
- <Drawer
|
|
|
- isOpen={isOpen}
|
|
|
- placement="right"
|
|
|
- onOpenChange={onOpenChange}
|
|
|
- hideCloseButton
|
|
|
- >
|
|
|
- <DrawerContent className="h-full w-full max-w-full !rounded-none">
|
|
|
- <>
|
|
|
- <DrawerHeader className="flex justify-between px-6 py-4 border-b">
|
|
|
- <h2 className="text-xl font-semibold">Filters:</h2>
|
|
|
-
|
|
|
- <div className="flex gap-6">
|
|
|
- <button onClick={clearAll}>Clear All</button>
|
|
|
-
|
|
|
- <button onClick={() => onOpenChange()}>
|
|
|
- <XMarkIcon className="w-6 h-6" />
|
|
|
- </button>
|
|
|
- </div>
|
|
|
- </DrawerHeader>
|
|
|
-
|
|
|
- <DrawerBody className="px-6 overflow-y-auto">
|
|
|
- {filterAttributes.map((attr) => {
|
|
|
- const expand = expandedGroups[attr.node.code] ?? true;
|
|
|
-
|
|
|
- return (
|
|
|
- <div key={attr.node._id} className="py-4 border-b">
|
|
|
- <button
|
|
|
- className="flex justify-between w-full text-xl"
|
|
|
- onClick={() => toggleGroupExpand(attr.node.code)}
|
|
|
- >
|
|
|
- <span>{formatLabel(attr.node.adminName)}</span>
|
|
|
-
|
|
|
- <span>⌄</span>
|
|
|
- </button>
|
|
|
-
|
|
|
- {expand && (
|
|
|
- <div className="flex flex-col gap-3 mt-4">
|
|
|
- {attr.node.code === "price" ? (
|
|
|
- <FilterPrice
|
|
|
- priceAttr={attr.node}
|
|
|
- initialRange={{
|
|
|
- minPrice: tempFilters.minPrice
|
|
|
- ? Number(tempFilters.minPrice)
|
|
|
- : undefined,
|
|
|
- maxPrice: tempFilters.maxPrice
|
|
|
- ? Number(tempFilters.maxPrice)
|
|
|
- : undefined,
|
|
|
- }}
|
|
|
- onChangeComplete={(range) => {
|
|
|
- setTempFilters((prev) => ({
|
|
|
- ...prev,
|
|
|
- minPrice: String(range.minPrice),
|
|
|
- maxPrice: String(range.maxPrice),
|
|
|
- }));
|
|
|
- }}
|
|
|
- />
|
|
|
- ) : (
|
|
|
- (attr.node.options.edges || []).map((opt: any) => {
|
|
|
- const checked =
|
|
|
- tempFilters[attr.node.code] == opt.node._id;
|
|
|
- return (
|
|
|
- <label
|
|
|
- key={opt.node._id}
|
|
|
- className="flex gap-3 text-lg"
|
|
|
- >
|
|
|
- <input
|
|
|
- type="checkbox"
|
|
|
- checked={checked}
|
|
|
- onChange={() =>
|
|
|
- toggleOption(attr.node.code, opt.node._id)
|
|
|
- }
|
|
|
- />
|
|
|
-
|
|
|
- <span>{opt.node.adminName}</span>
|
|
|
- </label>
|
|
|
- );
|
|
|
- })
|
|
|
- )}
|
|
|
- </div>
|
|
|
- )}
|
|
|
- </div>
|
|
|
- );
|
|
|
- })}
|
|
|
- </DrawerBody>
|
|
|
-
|
|
|
- <div className="px-6 py-4 border-t">
|
|
|
- <Button
|
|
|
- color="primary"
|
|
|
- size="lg"
|
|
|
- className="w-full"
|
|
|
- onPress={applyFilters}
|
|
|
+ {/* 筛选内容区域 */}
|
|
|
+ <div className="py-4 max-h-[70vh] overflow-y-auto overflow-x-hidden">
|
|
|
+ {filterAttributes.map((attr) => {
|
|
|
+ const expand = expandedGroups[attr.node.code] ?? true;
|
|
|
+ return (
|
|
|
+ <div key={attr.node._id} className="py-4 border-b">
|
|
|
+ <button
|
|
|
+ className="flex justify-between w-full text-xl"
|
|
|
+ onClick={() => toggleGroupExpand(attr.node.code)}
|
|
|
>
|
|
|
- Apply Filter
|
|
|
- </Button>
|
|
|
+ <span>{formatLabel(attr.node.adminName)}</span>
|
|
|
+ <span>⌄</span>
|
|
|
+ </button>
|
|
|
+ {expand && (
|
|
|
+ <div className="flex flex-col gap-3 mt-4">
|
|
|
+ {attr.node.code === "price" ? (
|
|
|
+ <FilterPrice
|
|
|
+ priceAttr={attr.node}
|
|
|
+ initialRange={{
|
|
|
+ minPrice: tempFilters.price_from
|
|
|
+ ? Number(tempFilters.price_from)
|
|
|
+ : undefined,
|
|
|
+ maxPrice: tempFilters.price_to
|
|
|
+ ? Number(tempFilters.price_to)
|
|
|
+ : undefined,
|
|
|
+ }}
|
|
|
+ onChangeComplete={(range) => {
|
|
|
+ setTempFilters((prev) => ({
|
|
|
+ ...prev,
|
|
|
+ price_from: String(range.minPrice),
|
|
|
+ price_to: String(range.maxPrice),
|
|
|
+ }));
|
|
|
+ }}
|
|
|
+ />
|
|
|
+ ) : (
|
|
|
+ (attr.node.options.edges || []).map((opt: any) => {
|
|
|
+ const checked =
|
|
|
+ tempFilters[attr.node.code] == opt.node._id;
|
|
|
+ return opt.node.productCount > 0 && (
|
|
|
+ <label
|
|
|
+ key={opt.node._id}
|
|
|
+ className="flex gap-3 text-lg items-center"
|
|
|
+ >
|
|
|
+ <input
|
|
|
+ type="checkbox"
|
|
|
+ checked={checked}
|
|
|
+ onChange={() =>
|
|
|
+ toggleOption(attr.node.code, opt.node._id)
|
|
|
+ }
|
|
|
+ />
|
|
|
+ <span>{opt.node.adminName}</span>
|
|
|
+ </label>
|
|
|
+ );
|
|
|
+ })
|
|
|
+ )}
|
|
|
+ </div>
|
|
|
+ )}
|
|
|
</div>
|
|
|
- </>
|
|
|
- </DrawerContent>
|
|
|
- </Drawer>
|
|
|
- </>
|
|
|
+ );
|
|
|
+ })}
|
|
|
+ </div>
|
|
|
+
|
|
|
+ {/* 底部应用按钮 */}
|
|
|
+ <div className="">
|
|
|
+ <Button
|
|
|
+ color="primary"
|
|
|
+ size="lg"
|
|
|
+ className="w-full"
|
|
|
+ onPress={applyFilters}
|
|
|
+ >
|
|
|
+ Apply Filter
|
|
|
+ </Button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
);
|
|
|
}
|