diff --git a/license_ui/src/App.module.css b/license_ui/src/App.module.css
index e4154db..cb2671c 100644
--- a/license_ui/src/App.module.css
+++ b/license_ui/src/App.module.css
@@ -6,7 +6,8 @@
gap: var(--mantine-space-lg);
}
.form-column {
- flex-grow: 1;
+ flex-shrink: 0;
+ flex-grow: 0;
}
.product-column {
flex-grow: 5;
diff --git a/license_ui/src/App.tsx b/license_ui/src/App.tsx
index a9f142d..ef8653d 100644
--- a/license_ui/src/App.tsx
+++ b/license_ui/src/App.tsx
@@ -13,6 +13,7 @@ function App() {
align="stretch"
justify="flex-start"
gap="lg"
+ w={450}
className={classes["form-column"]}
>
diff --git a/license_ui/src/components/LicenseCode.tsx b/license_ui/src/components/LicenseCode.tsx
index b19138d..169cd72 100644
--- a/license_ui/src/components/LicenseCode.tsx
+++ b/license_ui/src/components/LicenseCode.tsx
@@ -11,7 +11,7 @@ export function LicenseCode() {
授权码
-
+
diff --git a/license_ui/src/components/ProductList.module.css b/license_ui/src/components/ProductList.module.css
index befc017..a6e9e0c 100644
--- a/license_ui/src/components/ProductList.module.css
+++ b/license_ui/src/components/ProductList.module.css
@@ -7,3 +7,10 @@
.title-search {
flex-grow: 1;
}
+.product-list-container {
+ align-content: flex-start;
+ flex-grow: 1;
+ overflow-x: hidden;
+ overflow-y: auto;
+ min-height: 0;
+}
diff --git a/license_ui/src/components/ProductList.tsx b/license_ui/src/components/ProductList.tsx
index 4f5c176..08209a0 100644
--- a/license_ui/src/components/ProductList.tsx
+++ b/license_ui/src/components/ProductList.tsx
@@ -1,22 +1,100 @@
-import { Flex, Paper, TextInput, Title } from "@mantine/core";
-import { IconSearch } from "@tabler/icons-react";
+import { useProducts } from "@/hooks/use-products";
+import { useProductsStore } from "@/hooks/use-products-store";
+import { ActionIcon, Box, Flex, Paper, Text, TextInput, Title, Tooltip } from "@mantine/core";
+import { IconDeselect, IconListCheck, IconSearch, IconX } from "@tabler/icons-react";
+import { isEmpty, not } from "ramda";
+import { useCallback, useMemo, useState } from "react";
import classes from "./ProductList.module.css";
export function ProductList() {
+ const [keyword, setKeyword] = useState("");
+ const [selected, products, selectAll, unselectAll] = useProductsStore((state) => [
+ state.selctedProducts,
+ state.products,
+ state.selectAll,
+ state.unselectAll,
+ ]);
+ const clearSearchKeyword = useCallback(() => setKeyword(""), []);
+ useProducts(keyword);
+
return (
-
+
-
- 产品列表
+
+ 产品列表{not(isEmpty(selected)) && `(已选择 ${selected.length} 个产品)`}
+
+
+
+
+
+
+
+
+
+
setKeyword(event.currentTarget.value)}
leftSection={}
+ rightSection={
+
+
+
+ }
/>
+
+ {products.map((product) => (
+
+ ))}
+
);
}
+
+interface ProductItemProps {
+ id: string;
+ name: string;
+}
+
+function ProductItem(props: ProductItemProps) {
+ const [selectedProducts, append, remove] = useProductsStore((state) => [
+ state.selctedProducts,
+ state.append,
+ state.remove,
+ ]);
+ const selected = useMemo(() => selectedProducts.includes(props.id), [props.id, selectedProducts]);
+
+ const handleSelectAction = useCallback(() => {
+ if (selected) {
+ remove(props.id);
+ } else {
+ append(props.id);
+ }
+ }, [props.id, selected, append, remove]);
+
+ return (
+
+
+ {props.name}
+
+
+ );
+}
diff --git a/license_ui/src/hooks/use-products-store.ts b/license_ui/src/hooks/use-products-store.ts
new file mode 100644
index 0000000..35ef1bc
--- /dev/null
+++ b/license_ui/src/hooks/use-products-store.ts
@@ -0,0 +1,30 @@
+import { concat, pluck, uniq } from "ramda";
+import { create } from "zustand";
+
+interface Product {
+ id: string;
+ name: string;
+}
+
+interface ProductsStore {
+ products: Product[];
+ selctedProducts: string[];
+ append: (code: string) => void;
+ remove: (code: string) => void;
+ unselectAll: () => void;
+ selectAll: () => void;
+}
+
+export const useProductsStore = create((set, get) => ({
+ products: [],
+ selctedProducts: [],
+ append: (code: string) => set((state) => ({ selctedProducts: [...state.selctedProducts, code] })),
+ remove: (code: string) =>
+ set((state) => ({ selctedProducts: state.selctedProducts.filter((item) => item !== code) })),
+ unselectAll: () => set({ selctedProducts: [] }),
+ selectAll: () => {
+ const products = pluck("id", get().products);
+ const selectedProducts = uniq(concat(get().selctedProducts, products));
+ set({ selctedProducts: selectedProducts });
+ },
+}));
diff --git a/license_ui/src/hooks/use-products.ts b/license_ui/src/hooks/use-products.ts
index da6f394..6844ca1 100644
--- a/license_ui/src/hooks/use-products.ts
+++ b/license_ui/src/hooks/use-products.ts
@@ -1,24 +1,16 @@
-import { useEffect, useState } from "react";
+import { useEffect } from "react";
+import { useProductsStore } from "./use-products-store";
type ProductSearchKeyword = string | undefined | null;
-interface Product {
- id: string;
- name: string;
-}
-
-export function useProducts(keyword: ProductSearchKeyword): Product[] {
- const [products, setProducts] = useState([]);
-
+export function useProducts(keyword: ProductSearchKeyword) {
useEffect(() => {
async function fetchProducts() {
const response = await fetch(`/api/products?keyword=${keyword}`);
const data = await response.json();
- setProducts(data);
+ useProductsStore.setState((state) => (state.products = data));
}
fetchProducts();
}, [keyword]);
-
- return products;
}