diff --git a/src/actions/product.ts b/src/actions/product.ts new file mode 100644 index 0000000..8156848 --- /dev/null +++ b/src/actions/product.ts @@ -0,0 +1,21 @@ +"use server" + +import type { PageRecord } from "@/lib/api" +import type { Product } from "@/models/product" +import type { ProductSku } from "@/models/product_sku" +import { callByUser } from "./base" + +export async function getAllProduct() { + return callByUser("/api/admin/product/all") +} + +export async function getPageProductSku(params: { + page: number + size: number + product_id?: number +}) { + return callByUser>( + "/api/admin/product/sku/page", + params, + ) +} diff --git a/src/app/(root)/navigation.tsx b/src/app/(root)/navigation.tsx index c6963a6..1356ca8 100644 --- a/src/app/(root)/navigation.tsx +++ b/src/app/(root)/navigation.tsx @@ -13,6 +13,7 @@ import { type LucideIcon, Package, Shield, + ShoppingBag, Users, } from "lucide-react" import Link from "next/link" @@ -192,6 +193,7 @@ export default function Navigation() { {/* 运营 */} + diff --git a/src/app/(root)/product/page.tsx b/src/app/(root)/product/page.tsx new file mode 100644 index 0000000..9c08b4a --- /dev/null +++ b/src/app/(root)/product/page.tsx @@ -0,0 +1,160 @@ +"use client" + +import { format } from "date-fns" +import { EyeIcon, EyeOffIcon, PlusIcon, TrashIcon } from "lucide-react" +import { Suspense, useCallback, useEffect, useMemo, useState } from "react" +import { getAllProduct, getPageProductSku } from "@/actions/product" +import { DataTable, useDataTable } from "@/components/data-table" +import { Badge } from "@/components/ui/badge" +import { Button } from "@/components/ui/button" +import { + Tooltip, + TooltipContent, + TooltipTrigger, +} from "@/components/ui/tooltip" +import { cn } from "@/lib/utils" +import type { Product } from "@/models/product" + +export default function ProductPage() { + const [selected, setSelected] = useState(undefined) + + return ( +
+ + +
+ ) +} + +function Products(props: { + selected?: number + onSelect?: (id: number) => void +}) { + const [list, setList] = useState([]) + + const refresh = useCallback(async () => { + const resp = await getAllProduct() + if (resp.success) { + setList(resp.data) + console.log(resp.data) + } + }, []) + + const selected = useMemo(() => { + return list.find(item => item.id === props.selected) + }, [list, props.selected]) + + useEffect(() => { + refresh() + }, [refresh]) + + return ( +
+
+

产品列表

+
+ + + + + +

新建产品

+
+
+ + {!!selected && ( + + + + + +

{selected.status ? "禁用产品" : "启用产品"}

+
+
+ )} + + + + + + +

删除产品

+
+
+
+
+
    + {list.map(item => ( +
  • + +
  • + ))} +
+
+ ) +} + +function ProductSkus(props: { selected?: number }) { + const action = useCallback( + (page: number, size: number) => { + return getPageProductSku({ page, size, product_id: props.selected }) + }, + [props.selected], + ) + + const table = useDataTable(action) + + return ( +
+
+ +
+ + Number(row.price) * row.discount, + }, + { + header: "创建时间", + accessorFn: row => format(row.created_at, "yyyy-MM-dd HH:mm"), + }, + { + header: "更新时间", + accessorFn: row => format(row.updated_at, "yyyy-MM-dd HH:mm"), + }, + { header: "操作", cell: () =>
}, + ]} + /> +
+
+ ) +} diff --git a/src/app/(root)/security/page.tsx b/src/app/(root)/security/page.tsx deleted file mode 100644 index f42bf81..0000000 --- a/src/app/(root)/security/page.tsx +++ /dev/null @@ -1,5 +0,0 @@ -"use client" - -export default function SecurityPage() { - return
管理员页面待完善~
-} diff --git a/src/components/data-table/component.tsx b/src/components/data-table/component.tsx index 3074142..faea4f0 100644 --- a/src/components/data-table/component.tsx +++ b/src/components/data-table/component.tsx @@ -22,6 +22,7 @@ export type DataTableProps = { columns: ColumnDef[] pagination: PaginationProps classNames?: { + root?: string headRow?: string dataRow?: string } @@ -46,7 +47,7 @@ export function DataTable>( }) return ( -
+
{/* 数据表 */}
diff --git a/src/hooks/data.ts b/src/hooks/data.ts index b0c8b32..f0aa3ea 100644 --- a/src/hooks/data.ts +++ b/src/hooks/data.ts @@ -2,6 +2,7 @@ import { type Dispatch, type SetStateAction, useCallback, + useEffect, useState, } from "react" import { toast } from "sonner" @@ -49,3 +50,24 @@ export function useFetch( ], ) } + +type Action =

(...args: P) => Promise + +export function useAction(action: Action) { + const [status, setStatus] = useStatus() + const func = useCallback( + async (...args: Parameters) => { + try { + setStatus("load") + await action(...args) + setStatus("done") + } catch (e) { + setStatus("fail") + throw e + } + }, + [action, setStatus], + ) + + return [func, status] +} diff --git a/src/models/base/model.ts b/src/models/base/model.ts new file mode 100644 index 0000000..00e1a5c --- /dev/null +++ b/src/models/base/model.ts @@ -0,0 +1,5 @@ +export type Model = { + id: number + created_at: Date + updated_at: Date +} diff --git a/src/models/product.ts b/src/models/product.ts new file mode 100644 index 0000000..98eebce --- /dev/null +++ b/src/models/product.ts @@ -0,0 +1,12 @@ +import type { Model } from "./base/model" +import type { ProductSku } from "./product_sku" + +export type Product = Model & { + code: string + name: string + description?: string + sort: number + status: number + + skus?: ProductSku[] +} diff --git a/src/models/product_sku.ts b/src/models/product_sku.ts new file mode 100644 index 0000000..048ce3f --- /dev/null +++ b/src/models/product_sku.ts @@ -0,0 +1,12 @@ +import type { Model } from "./base/model" +import type { Product } from "./product" + +export type ProductSku = Model & { + product_id: number + code: string + name: string + price: string + discount: number + + product?: Product +}