diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..6b6ffcb --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,7 @@ +{ + "editor.defaultFormatter": "biomejs.biome", + "editor.codeActionsOnSave": { + "source.fixAll.biome": "explicit", + "source.organizeImports.biome": "explicit" + } +} diff --git a/bun.lock b/bun.lock index a29063a..4dcd127 100644 --- a/bun.lock +++ b/bun.lock @@ -12,6 +12,7 @@ "@radix-ui/react-select": "^2.2.6", "@radix-ui/react-separator": "^1.1.8", "@radix-ui/react-slot": "^1.2.4", + "@radix-ui/react-tabs": "^1.1.13", "@radix-ui/react-tooltip": "^1.2.8", "@tanstack/react-table": "^8.21.3", "class-variance-authority": "^0.7.1", @@ -193,6 +194,8 @@ "@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "https://registry.npmmirror.com/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="], + "@radix-ui/react-roving-focus": ["@radix-ui/react-roving-focus@1.1.11", "https://registry.npmmirror.com/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.11.tgz", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-collection": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-controllable-state": "1.2.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-7A6S9jSgm/S+7MdtNDSb+IU859vQqJ/QAtcYQcfFC6W8RS4IxIZDldLR0xqCFZ6DCyrQLjLPsxtTNch5jVA4lA=="], + "@radix-ui/react-scroll-area": ["@radix-ui/react-scroll-area@1.2.10", "https://registry.npmmirror.com/@radix-ui/react-scroll-area/-/react-scroll-area-1.2.10.tgz", { "dependencies": { "@radix-ui/number": "1.1.1", "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-tAXIa1g3sM5CGpVT0uIbUx/U3Gs5N8T52IICuCtObaos1S8fzsrPXG5WObkQN3S6NVl6wKgPhAIiBGbWnvc97A=="], "@radix-ui/react-select": ["@radix-ui/react-select@2.2.6", "https://registry.npmmirror.com/@radix-ui/react-select/-/react-select-2.2.6.tgz", { "dependencies": { "@radix-ui/number": "1.1.1", "@radix-ui/primitive": "1.1.3", "@radix-ui/react-collection": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-dismissable-layer": "1.1.11", "@radix-ui/react-focus-guards": "1.1.3", "@radix-ui/react-focus-scope": "1.1.7", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-popper": "1.2.8", "@radix-ui/react-portal": "1.1.9", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-slot": "1.2.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-controllable-state": "1.2.2", "@radix-ui/react-use-layout-effect": "1.1.1", "@radix-ui/react-use-previous": "1.1.1", "@radix-ui/react-visually-hidden": "1.2.3", "aria-hidden": "^1.2.4", "react-remove-scroll": "^2.6.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-I30RydO+bnn2PQztvo25tswPH+wFBjehVGtmagkU78yMdwTwVf12wnAOF+AeP8S2N8xD+5UPbGhkUfPyvT+mwQ=="], @@ -201,6 +204,8 @@ "@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.4", "https://registry.npmmirror.com/@radix-ui/react-slot/-/react-slot-1.2.4.tgz", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-Jl+bCv8HxKnlTLVrcDE8zTMJ09R9/ukw4qBs/oZClOfoQk/cOTbDn+NceXfV7j09YPVQUryJPHurafcSg6EVKA=="], + "@radix-ui/react-tabs": ["@radix-ui/react-tabs@1.1.13", "https://registry.npmmirror.com/@radix-ui/react-tabs/-/react-tabs-1.1.13.tgz", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-roving-focus": "1.1.11", "@radix-ui/react-use-controllable-state": "1.2.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-7xdcatg7/U+7+Udyoj2zodtI9H/IIopqo+YOIcZOq1nJwXWBZ9p8xiu5llXlekDbZkca79a/fozEYQXIA4sW6A=="], + "@radix-ui/react-tooltip": ["@radix-ui/react-tooltip@1.2.8", "https://registry.npmmirror.com/@radix-ui/react-tooltip/-/react-tooltip-1.2.8.tgz", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-dismissable-layer": "1.1.11", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-popper": "1.2.8", "@radix-ui/react-portal": "1.1.9", "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-slot": "1.2.3", "@radix-ui/react-use-controllable-state": "1.2.2", "@radix-ui/react-visually-hidden": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-tY7sVt1yL9ozIxvmbtN5qtmH2krXcBCfjEiCgKGLqunJHvgvZG2Pcl2oQ3kbcZARb1BGEHdkLzcYGO8ynVlieg=="], "@radix-ui/react-use-callback-ref": ["@radix-ui/react-use-callback-ref@1.1.1", "https://registry.npmmirror.com/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.1.tgz", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg=="], diff --git a/package.json b/package.json index 78a62a0..636e872 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,7 @@ "@radix-ui/react-select": "^2.2.6", "@radix-ui/react-separator": "^1.1.8", "@radix-ui/react-slot": "^1.2.4", + "@radix-ui/react-tabs": "^1.1.13", "@radix-ui/react-tooltip": "^1.2.8", "@tanstack/react-table": "^8.21.3", "class-variance-authority": "^0.7.1", diff --git a/src/actions/batch.ts b/src/actions/batch.ts new file mode 100644 index 0000000..f0c9808 --- /dev/null +++ b/src/actions/batch.ts @@ -0,0 +1,7 @@ +import type { PageRecord } from "@/lib/api" +import type { User } from "@/models/user" +import { callByUser } from "./base" + +export async function getPageBatch(params: { page: number; size: number }) { + return callByUser>("/api/admin/batch/page", params) +} diff --git a/src/actions/bill.ts b/src/actions/bill.ts new file mode 100644 index 0000000..78e4487 --- /dev/null +++ b/src/actions/bill.ts @@ -0,0 +1,7 @@ +import type { PageRecord } from "@/lib/api" +import type { User } from "@/models/user" +import { callByUser } from "./base" + +export async function getPageBill(params: { page: number; size: number }) { + return callByUser>("/api/admin/bill/page", params) +} diff --git a/src/actions/channel.ts b/src/actions/channel.ts new file mode 100644 index 0000000..913b89a --- /dev/null +++ b/src/actions/channel.ts @@ -0,0 +1,7 @@ +import type { PageRecord } from "@/lib/api" +import type { User } from "@/models/user" +import { callByUser } from "./base" + +export async function getPageChannel(params: { page: number; size: number }) { + return callByUser>("/api/admin/channel/page", params) +} diff --git a/src/actions/resources.ts b/src/actions/resources.ts new file mode 100644 index 0000000..4f68d68 --- /dev/null +++ b/src/actions/resources.ts @@ -0,0 +1,14 @@ +import type { PageRecord } from "@/lib/api" +import type { User } from "@/models/user" +import { callByUser } from "./base" + +export async function listResourceLong(params: { page: number; size: number }) { + return callByUser>("/api/admin/resource/long/page", params) +} + +export async function listResourceShort(params: { + page: number + size: number +}) { + return callByUser>("/api/admin/resource/short/page", params) +} diff --git a/src/actions/trade.ts b/src/actions/trade.ts new file mode 100644 index 0000000..5ab8ba9 --- /dev/null +++ b/src/actions/trade.ts @@ -0,0 +1,7 @@ +import type { PageRecord } from "@/lib/api" +import type { User } from "@/models/user" +import { callByUser } from "./base" + +export async function getPageTrade(params: { page: number; size: number }) { + return callByUser>("/api/admin/trade/page", params) +} diff --git a/src/actions/user.ts b/src/actions/user.ts index a27d0b9..fe30813 100644 --- a/src/actions/user.ts +++ b/src/actions/user.ts @@ -3,5 +3,5 @@ import type { User } from "@/models/user" import { callByUser } from "./base" export async function getPageUsers(params: { page: number; size: number }) { - return callByUser>("/api/user/page", params) + return callByUser>("/api/admin/user/page", params) } diff --git a/src/app/(root)/batch/page.tsx b/src/app/(root)/batch/page.tsx new file mode 100644 index 0000000..ff54a6e --- /dev/null +++ b/src/app/(root)/batch/page.tsx @@ -0,0 +1,29 @@ +"use client" +import { Suspense } from "react" +import { getPageBatch } from "@/actions/batch" +import { DataTable, useDataTable } from "@/components/data-table" +import type { User } from "@/models/user" + +export default function UserPage() { + const table = useDataTable((page, size) => getPageBatch({ page, size })) + console.log(table, "table") + + return ( + + + {...table} + columns={[ + { header: "ID", accessorKey: "id" }, + { header: "批次号", accessorKey: "batch_no" }, + { header: "城市", accessorKey: "city" }, + { header: "省份", accessorKey: "prov" }, + { header: "数量", accessorKey: "count" }, + { header: "提取IP", accessorKey: "ip" }, + { header: "运营商", accessorKey: "isp" }, + { header: "可用资源", accessorKey: "resource_id" }, + { header: "时间", accessorKey: "time" }, + ]} + /> + + ) +} diff --git a/src/app/(root)/billing/page.tsx b/src/app/(root)/billing/page.tsx new file mode 100644 index 0000000..99e8588 --- /dev/null +++ b/src/app/(root)/billing/page.tsx @@ -0,0 +1,28 @@ +"use client" +import { Suspense } from "react" +import { getPageBill } from "@/actions/bill" +import { DataTable, useDataTable } from "@/components/data-table" +import type { User } from "@/models/user" + +export default function UserPage() { + const table = useDataTable((page, size) => getPageBill({ page, size })) + console.log(table, "table") + + return ( + + + {...table} + columns={[ + { header: "ID", accessorKey: "id" }, + { header: "账单号", accessorKey: "bill_no" }, + { header: "信息", accessorKey: "info" }, + { header: "金额", accessorKey: "amount" }, + { header: "可用资源", accessorKey: "resource_id" }, + { header: "类型", accessorKey: "type" }, + { header: "创建时间", accessorKey: "created_at" }, + { header: "更新时间", accessorKey: "updated_at" }, + ]} + /> + + ) +} diff --git a/src/app/(root)/channel/page.tsx b/src/app/(root)/channel/page.tsx new file mode 100644 index 0000000..0cd1cb3 --- /dev/null +++ b/src/app/(root)/channel/page.tsx @@ -0,0 +1,37 @@ +"use client" +import { Suspense } from "react" +import { getPageChannel } from "@/actions/channel" +import { DataTable, useDataTable } from "@/components/data-table" +import type { User } from "@/models/user" + +export default function UserPage() { + const table = useDataTable((page, size) => + getPageChannel({ page, size }), + ) + console.log(table, "table") + + return ( + + + {...table} + columns={[ + { header: "ID", accessorKey: "id" }, + { header: "批次号", accessorKey: "batch_no" }, + { header: "边缘节点", accessorKey: "edge_ref" }, + { header: "省份", accessorKey: "filter_prov" }, + { header: "城市", accessorKey: "filter_city" }, + { header: "运营商", accessorKey: "filter_isp" }, + { header: "主机", accessorKey: "host" }, + { header: "端口", accessorKey: "port" }, + { header: "密码", accessorKey: "password" }, + { header: "代理号", accessorKey: "proxy_id" }, + { header: "可用资源", accessorKey: "resource_id" }, + { header: "用户名", accessorKey: "username" }, + { header: "创建时间", accessorKey: "created_at" }, + { header: "更新时间", accessorKey: "updated_at" }, + { header: "过期时间", accessorKey: "expired_at" }, + ]} + /> + + ) +} diff --git a/src/app/(root)/navigation.tsx b/src/app/(root)/navigation.tsx index f985c47..3e21735 100644 --- a/src/app/(root)/navigation.tsx +++ b/src/app/(root)/navigation.tsx @@ -188,17 +188,17 @@ export default function Navigation() { {/* 客户 */} - - + + {/* 运营 */} - - - + + + diff --git a/src/app/(root)/resources/page.tsx b/src/app/(root)/resources/page.tsx new file mode 100644 index 0000000..2f66396 --- /dev/null +++ b/src/app/(root)/resources/page.tsx @@ -0,0 +1,62 @@ +"use client" +import { Suspense } from "react" +import { listResourceLong, listResourceShort } from "@/actions/resources" +import { DataTable, useDataTable } from "@/components/data-table" +import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs" +import type { User } from "@/models/user" + +export default function UserPage() { + return ( +
+ + + + 短效套餐 + + + 长效套餐 + + + + + + + + + +
+ ) +} + +interface ResourceListProps { + resourceType: "long" | "short" +} + +function ResourceList({ resourceType }: ResourceListProps) { + const isLong = resourceType === "long" + const listFn = isLong ? listResourceLong : listResourceShort + const table = useDataTable((page, size) => listFn({ page, size })) + console.log(table, "table") + + return ( + + + {...table} + columns={[ + { header: "ID", accessorKey: "id" }, + { header: "套餐编号", accessorKey: "resource_no" }, + { header: "状态", accessorKey: "active" }, + { header: "类型", accessorKey: "type" }, + { header: "创建时间", accessorKey: "created_at" }, + { header: "更新时间", accessorKey: "updated_at" }, + ]} + /> + + ) +} diff --git a/src/app/(root)/trade/page.tsx b/src/app/(root)/trade/page.tsx new file mode 100644 index 0000000..ec5be74 --- /dev/null +++ b/src/app/(root)/trade/page.tsx @@ -0,0 +1,32 @@ +"use client" +import { Suspense } from "react" +import { getPageTrade } from "@/actions/trade" +import { DataTable, useDataTable } from "@/components/data-table" +import type { User } from "@/models/user" + +export default function UserPage() { + const table = useDataTable((page, size) => getPageTrade({ page, size })) + console.log(table, "table") + + return ( + + + {...table} + columns={[ + { header: "ID", accessorKey: "id" }, + { header: "套餐号", accessorKey: "inner_no" }, + { header: "支付方式", accessorKey: "method" }, + { header: "支付金额", accessorKey: "payment" }, + { header: "支付平台", accessorKey: "platform" }, + { header: "已退款", accessorKey: "refunded" }, + { header: "支付状态", accessorKey: "status" }, + { header: "购买套餐", accessorKey: "subject" }, + { header: "类型", accessorKey: "type" }, + { header: "创建时间", accessorKey: "created_at" }, + { header: "更新时间", accessorKey: "updated_at" }, + { header: "过期时间", accessorKey: "canceled_at" }, + ]} + /> + + ) +} diff --git a/src/app/(root)/user/page.tsx b/src/app/(root)/user/page.tsx index 21b9967..f6db39a 100644 --- a/src/app/(root)/user/page.tsx +++ b/src/app/(root)/user/page.tsx @@ -1,4 +1,5 @@ "use client" +import { Suspense } from "react" import { getPageUsers } from "@/actions/user" import { DataTable, useDataTable } from "@/components/data-table" import { Button } from "@/components/ui/button" @@ -6,32 +7,35 @@ import type { User } from "@/models/user" export default function UserPage() { const table = useDataTable((page, size) => getPageUsers({ page, size })) + return (
- - {...table} - columns={[ - { header: "账号", accessorKey: "username" }, - { header: "手机", accessorKey: "phone" }, - { header: "邮箱", accessorKey: "email" }, - { header: "姓名", accessorKey: "name" }, - { header: "余额", accessorKey: "balance" }, - { header: "认证状态", accessorKey: "id_type" }, - { header: "账号状态", accessorKey: "status" }, - { header: "联系方式", accessorKey: "contact_wechat" }, - { header: "管理员", accessorKey: "admin_id" }, - { header: "最后登录时间", accessorKey: "last_login" }, - { header: "创建时间", accessorKey: "created_at" }, - { - header: "操作", - cell: () => ( -
- -
- ), - }, - ]} - /> + + + {...table} + columns={[ + { header: "账号", accessorKey: "username" }, + { header: "手机", accessorKey: "phone" }, + { header: "邮箱", accessorKey: "email" }, + { header: "姓名", accessorKey: "name" }, + { header: "余额", accessorKey: "balance" }, + { header: "认证状态", accessorKey: "id_type" }, + { header: "账号状态", accessorKey: "status" }, + { header: "联系方式", accessorKey: "contact_wechat" }, + { header: "管理员", accessorKey: "admin_id" }, + { header: "最后登录时间", accessorKey: "last_login" }, + { header: "创建时间", accessorKey: "created_at" }, + { + header: "操作", + cell: () => ( +
+ +
+ ), + }, + ]} + /> +
) } diff --git a/src/components/ui/tabs.tsx b/src/components/ui/tabs.tsx new file mode 100644 index 0000000..497ba5e --- /dev/null +++ b/src/components/ui/tabs.tsx @@ -0,0 +1,66 @@ +"use client" + +import * as React from "react" +import * as TabsPrimitive from "@radix-ui/react-tabs" + +import { cn } from "@/lib/utils" + +function Tabs({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function TabsList({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function TabsTrigger({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function TabsContent({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +export { Tabs, TabsList, TabsTrigger, TabsContent }