修复动静态ip标题跳转问题 & 添加admin页面
This commit is contained in:
@@ -156,13 +156,13 @@ export function Navbar() {
|
||||
expand={navbar}
|
||||
/>
|
||||
<NavItem
|
||||
href="/admin/channels"
|
||||
href="/admin/orders"
|
||||
icon={<ShoppingBag size={20} />}
|
||||
label="订单管理"
|
||||
expand={navbar}
|
||||
/>
|
||||
<NavItem
|
||||
href="/admin/channels"
|
||||
href="/admin/refundOrders"
|
||||
icon={<ClipboardList size={20} />}
|
||||
label="退货管理"
|
||||
expand={navbar}
|
||||
@@ -180,31 +180,31 @@ export function Navbar() {
|
||||
expand={navbar}
|
||||
/>
|
||||
<NavItem
|
||||
href="/admin/record"
|
||||
href="/admin/httpRecharge"
|
||||
icon={<Archive size={20} />}
|
||||
label="充值记录"
|
||||
expand={navbar}
|
||||
/>
|
||||
<NavItem
|
||||
href="/admin/resources_short"
|
||||
href="/admin/resources/short"
|
||||
icon={<Clock1 size={20} />}
|
||||
label="短效套餐"
|
||||
expand={navbar}
|
||||
/>
|
||||
<NavItem
|
||||
href="/admin/resources_long"
|
||||
href="/admin/resources/long"
|
||||
icon={<CalendarClock size={20} />}
|
||||
label="长效管理"
|
||||
expand={navbar}
|
||||
/>
|
||||
<NavItem
|
||||
href="/admin/channels"
|
||||
href="/admin/rosorder"
|
||||
icon={<Router size={20} />}
|
||||
label="软路由"
|
||||
expand={navbar}
|
||||
/>
|
||||
<NavItem
|
||||
href="/admin/channels"
|
||||
href="/admin/record"
|
||||
icon={<ClipboardClock size={20} />}
|
||||
label="使用记录"
|
||||
expand={navbar}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useState } from "react"
|
||||
import { EditInfoDialog } from "@/components/composites/dialogs/editInfoDialog"
|
||||
import { EditInfoDialog, type EditInfoValues } from "@/components/composites/dialogs/editInfoDialog"
|
||||
import DataTable from "@/components/data-table"
|
||||
import Page from "@/components/page"
|
||||
import { Button } from "@/components/ui/button"
|
||||
@@ -90,7 +90,7 @@ export default function DashboardPage() {
|
||||
yearConsumption: 2365,
|
||||
}
|
||||
const [editDialogOpen, setEditDialogOpen] = useState(false)
|
||||
const handleSaveInfo = (data: any) => {
|
||||
const handleSaveInfo = (data: EditInfoValues) => {
|
||||
console.log("保存的信息:", data)
|
||||
// 这里调 API 保存数据
|
||||
}
|
||||
@@ -300,11 +300,14 @@ export default function DashboardPage() {
|
||||
</div>
|
||||
|
||||
{/* 账号信息 */}
|
||||
<div className="md:col-start-4 md:row-start-1 md:row-span-2 bg-white rounded-lg p-4 flex flex-col">
|
||||
<div className="md:col-start-4 md:row-start-1 md:row-span-2 bg-white rounded-lg p-4 flex flex-col min-h-0">
|
||||
<h3 className="font-semibold text-lg shrink-0">账号信息</h3>
|
||||
<div className="mt-4 flex justify-between items-start gap-3 flex-auto">
|
||||
<div className="flex flex-col items-center gap-4 shrink-0">
|
||||
<span className="text-base font-medium">{accountInfo.phone}</span>
|
||||
<div className="mt-4 flex flex-col sm:flex-row justify-between items-start gap-4 flex-1 min-h-0">
|
||||
{/* 头像/二维码区域 */}
|
||||
<div className="flex flex-col items-center gap-3 shrink-0 w-full sm:w-auto">
|
||||
<span className="text-base font-medium truncate w-full text-center sm:text-left">
|
||||
{accountInfo.phone}
|
||||
</span>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
@@ -312,49 +315,51 @@ export default function DashboardPage() {
|
||||
>
|
||||
去实名
|
||||
</Button>
|
||||
<div className="w-20 h-20 border border-gray-200 rounded">
|
||||
<div className="w-20 h-20 border border-gray-200 rounded shrink-0 overflow-hidden">
|
||||
<img
|
||||
src="/qrcode-placeholder.png"
|
||||
alt="账号二维码"
|
||||
className="w-full h-full object-contain"
|
||||
className="w-full h-full object-cover"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-2.5 text-sm flex-1">
|
||||
<div className="flex items-start py-1 border-b border-gray-50 last:border-0">
|
||||
<span className="text-gray-400 w-20 shrink-0">密码</span>
|
||||
<span className="text-gray-700 font-mono tracking-wide">
|
||||
{/* 信息列表 */}
|
||||
<div className="flex flex-col gap-2 text-sm flex-1 min-w-0 w-full">
|
||||
<div className="flex items-start py-1 border-b border-gray-50 last:border-0 min-h-7">
|
||||
<span className="text-gray-400 w-14 shrink-0">密码</span>
|
||||
<span className="text-gray-700 font-mono tracking-wide truncate">
|
||||
********
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-start py-1 border-b border-gray-50 last:border-0">
|
||||
<span className="text-gray-400 w-20 shrink-0">QQ</span>
|
||||
<span className="text-gray-700">
|
||||
<div className="flex items-start py-1 border-b border-gray-50 last:border-0 min-h-7">
|
||||
<span className="text-gray-400 w-14 shrink-0">QQ</span>
|
||||
<span className="text-gray-700 truncate">
|
||||
{accountInfo.qq || "未绑定"}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-start py-1 border-b border-gray-50 last:border-0">
|
||||
<span className="text-gray-400 w-20 shrink-0">微信号</span>
|
||||
<span className="text-gray-700">
|
||||
<div className="flex items-start py-1 border-b border-gray-50 last:border-0 min-h-7">
|
||||
<span className="text-gray-400 w-14 shrink-0">微信号</span>
|
||||
<span className="text-gray-700 truncate">
|
||||
{accountInfo.wechat || "未绑定"}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-start py-1 border-b border-gray-50 last:border-0">
|
||||
<span className="text-gray-400 w-20 shrink-0">淘宝会员</span>
|
||||
<span className="text-gray-700">
|
||||
<div className="flex items-start py-1 border-b border-gray-50 last:border-0 min-h-7">
|
||||
<span className="text-gray-400 w-14 shrink-0">淘宝</span>
|
||||
<span className="text-gray-700 truncate">
|
||||
{accountInfo.taobao || "未绑定"}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-start py-1 border-b border-gray-50 last:border-0">
|
||||
<span className="text-gray-400 w-20 shrink-0">邮箱</span>
|
||||
<div className="flex items-start py-1 border-b border-gray-50 last:border-0 min-h-7">
|
||||
<span className="text-gray-400 w-14 shrink-0">邮箱</span>
|
||||
<span className="text-gray-700 truncate">
|
||||
{accountInfo.email || "未绑定"}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-5 flex justify-end shrink-0">
|
||||
|
||||
<div className="mt-4 flex justify-end shrink-0">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
|
||||
@@ -1,5 +1,29 @@
|
||||
import DataTable from "@/components/data-table"
|
||||
import Page from "@/components/page"
|
||||
|
||||
export default function FundsPage() {
|
||||
return <Page>资金明细</Page>
|
||||
return (
|
||||
<Page>
|
||||
<DataTable
|
||||
data={[]}
|
||||
status="done"
|
||||
columns={[
|
||||
{ accessorKey: "date", header: "交易时间" },
|
||||
{ accessorKey: "product", header: "用户" },
|
||||
{ accessorKey: "plan", header: "资金去向" },
|
||||
{
|
||||
accessorKey: "price",
|
||||
header: "金额",
|
||||
},
|
||||
{ accessorKey: "connections", header: "操作前约(元)" },
|
||||
{ accessorKey: "account", header: "备注" },
|
||||
]}
|
||||
pagination={{
|
||||
page: 0,
|
||||
size: 10,
|
||||
total: 0,
|
||||
}}
|
||||
/>
|
||||
</Page>
|
||||
)
|
||||
}
|
||||
|
||||
32
src/admin/httpRecharge/page.tsx
Normal file
32
src/admin/httpRecharge/page.tsx
Normal file
@@ -0,0 +1,32 @@
|
||||
import DataTable from "@/components/data-table"
|
||||
import Page from "@/components/page"
|
||||
|
||||
export default function HttpRechargePage() {
|
||||
return (
|
||||
<Page>
|
||||
<h3 className="font-semibold text-lg">HTTP 充值记录</h3>
|
||||
<DataTable
|
||||
data={[]}
|
||||
status="done"
|
||||
columns={[
|
||||
{
|
||||
accessorKey: "create_time",
|
||||
header: "交易时间",
|
||||
},
|
||||
{
|
||||
accessorKey: "ju_money",
|
||||
header: "充值H币",
|
||||
},
|
||||
{
|
||||
accessorKey: "pay_type",
|
||||
header: "充值渠道",
|
||||
},
|
||||
{
|
||||
accessorKey: "pay_money",
|
||||
header: "充值金额",
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</Page>
|
||||
)
|
||||
}
|
||||
401
src/admin/orders/mock.ts
Normal file
401
src/admin/orders/mock.ts
Normal file
@@ -0,0 +1,401 @@
|
||||
import type { OrderItem } from "@/lib/models/order"
|
||||
|
||||
export const MOCK_ORDERS: OrderItem[] = [
|
||||
{
|
||||
Id: 23,
|
||||
OrderNo: "JU202508150001",
|
||||
OrderType: 1,
|
||||
ProductId: 1,
|
||||
ProductName: "动静态IP",
|
||||
PackageName: "天卡",
|
||||
DayPrice: 3,
|
||||
ConnectCount: 1,
|
||||
AccountCount: 1,
|
||||
Accounts: "jx1234",
|
||||
OrderAmount: 3,
|
||||
CouponAmount: 0,
|
||||
PaymentAmount: 3,
|
||||
CreateTime: "2025-08-15 10:23:45",
|
||||
UpdateTime: "2025-08-15 10:24:01",
|
||||
},
|
||||
{
|
||||
Id: 22,
|
||||
OrderNo: "JU202508150002",
|
||||
OrderType: 1,
|
||||
ProductId: 1,
|
||||
ProductName: "动静态IP",
|
||||
PackageName: "周卡",
|
||||
DayPrice: 18,
|
||||
ConnectCount: 1,
|
||||
AccountCount: 1,
|
||||
Accounts: "jx1235",
|
||||
OrderAmount: 18,
|
||||
CouponAmount: 2,
|
||||
PaymentAmount: 16,
|
||||
CreateTime: "2025-08-15 09:12:33",
|
||||
UpdateTime: "2025-08-15 09:12:50",
|
||||
},
|
||||
{
|
||||
Id: 21,
|
||||
OrderNo: "JU202508140003",
|
||||
OrderType: 2,
|
||||
ProductId: 1,
|
||||
ProductName: "动静态IP",
|
||||
PackageName: "月卡",
|
||||
DayPrice: 68,
|
||||
ConnectCount: 2,
|
||||
AccountCount: 1,
|
||||
Accounts: "jx1236",
|
||||
OrderAmount: 68,
|
||||
CouponAmount: 0,
|
||||
PaymentAmount: 68,
|
||||
CreateTime: "2025-08-14 16:45:21",
|
||||
UpdateTime: "2025-08-14 16:45:39",
|
||||
},
|
||||
{
|
||||
Id: 20,
|
||||
OrderNo: "JU202508140004",
|
||||
OrderType: 3,
|
||||
ProductId: 2,
|
||||
ProductName: "新HTTP/S5",
|
||||
PackageName: "月卡(活动)",
|
||||
DayPrice: 88,
|
||||
ConnectCount: 5,
|
||||
AccountCount: 3,
|
||||
Accounts: "https-001,https-002,https-003",
|
||||
OrderAmount: 264,
|
||||
CouponAmount: 10,
|
||||
PaymentAmount: 254,
|
||||
CreateTime: "2025-08-14 14:02:17",
|
||||
UpdateTime: "2025-08-14 14:02:55",
|
||||
},
|
||||
{
|
||||
Id: 19,
|
||||
OrderNo: "JU202508140005",
|
||||
OrderType: 1,
|
||||
ProductId: 3,
|
||||
ProductName: "HTTP/S5",
|
||||
PackageName: "季卡",
|
||||
DayPrice: 168,
|
||||
ConnectCount: 1,
|
||||
AccountCount: 1,
|
||||
Accounts: "global-001",
|
||||
OrderAmount: 168,
|
||||
CouponAmount: 0,
|
||||
PaymentAmount: 168,
|
||||
CreateTime: "2025-08-14 11:30:08",
|
||||
UpdateTime: "2025-08-14 11:30:26",
|
||||
},
|
||||
{
|
||||
Id: 18,
|
||||
OrderNo: "JU202508130006",
|
||||
OrderType: 4,
|
||||
ProductId: 1,
|
||||
ProductName: "动静态IP",
|
||||
PackageName: "年卡",
|
||||
DayPrice: 688,
|
||||
ConnectCount: 10,
|
||||
AccountCount: 5,
|
||||
Accounts: "batch-jx-01,batch-jx-02,batch-jx-03,batch-jx-04,batch-jx-05",
|
||||
OrderAmount: 3440,
|
||||
CouponAmount: 100,
|
||||
PaymentAmount: 3340,
|
||||
CreateTime: "2025-08-13 20:15:44",
|
||||
UpdateTime: "2025-08-13 20:16:02",
|
||||
},
|
||||
{
|
||||
Id: 17,
|
||||
OrderNo: "JU202508130007",
|
||||
OrderType: 1,
|
||||
ProductId: 1,
|
||||
ProductName: "动静态IP",
|
||||
PackageName: "测试卡",
|
||||
DayPrice: 0.5,
|
||||
ConnectCount: 1,
|
||||
AccountCount: 1,
|
||||
Accounts: "jx1237",
|
||||
OrderAmount: 0.5,
|
||||
CouponAmount: 0,
|
||||
PaymentAmount: 0.5,
|
||||
CreateTime: "2025-08-13 18:03:12",
|
||||
UpdateTime: "2025-08-13 18:03:28",
|
||||
},
|
||||
{
|
||||
Id: 16,
|
||||
OrderNo: "JU202508120008",
|
||||
OrderType: 2,
|
||||
ProductId: 2,
|
||||
ProductName: "新HTTP/S5",
|
||||
PackageName: "双月卡(活动)",
|
||||
DayPrice: 128,
|
||||
ConnectCount: 3,
|
||||
AccountCount: 1,
|
||||
Accounts: "https-004",
|
||||
OrderAmount: 128,
|
||||
CouponAmount: 0,
|
||||
PaymentAmount: 128,
|
||||
CreateTime: "2025-08-12 15:22:37",
|
||||
UpdateTime: "2025-08-12 15:22:59",
|
||||
},
|
||||
{
|
||||
Id: 15,
|
||||
OrderNo: "JU202508120009",
|
||||
OrderType: 1,
|
||||
ProductId: 3,
|
||||
ProductName: "HTTP/S5",
|
||||
PackageName: "月卡",
|
||||
DayPrice: 98,
|
||||
ConnectCount: 1,
|
||||
AccountCount: 1,
|
||||
Accounts: "global-002",
|
||||
OrderAmount: 98,
|
||||
CouponAmount: 5,
|
||||
PaymentAmount: 93,
|
||||
CreateTime: "2025-08-12 13:40:55",
|
||||
UpdateTime: "2025-08-12 13:41:13",
|
||||
},
|
||||
{
|
||||
Id: 14,
|
||||
OrderNo: "JU202508110010",
|
||||
OrderType: 1,
|
||||
ProductId: 1,
|
||||
ProductName: "动静态IP",
|
||||
PackageName: "天卡",
|
||||
DayPrice: 3,
|
||||
ConnectCount: 1,
|
||||
AccountCount: 1,
|
||||
Accounts: "jx1238",
|
||||
OrderAmount: 3,
|
||||
CouponAmount: 0,
|
||||
PaymentAmount: 3,
|
||||
CreateTime: "2025-08-11 10:05:29",
|
||||
UpdateTime: "2025-08-11 10:05:44",
|
||||
},
|
||||
{
|
||||
Id: 13,
|
||||
OrderNo: "JU202508110011",
|
||||
OrderType: 3,
|
||||
ProductId: 1,
|
||||
ProductName: "动静态IP",
|
||||
PackageName: "周卡",
|
||||
DayPrice: 18,
|
||||
ConnectCount: 4,
|
||||
AccountCount: 4,
|
||||
Accounts: "batch-jx-06,batch-jx-07,batch-jx-08,batch-jx-09",
|
||||
OrderAmount: 72,
|
||||
CouponAmount: 0,
|
||||
PaymentAmount: 72,
|
||||
CreateTime: "2025-08-11 08:56:48",
|
||||
UpdateTime: "2025-08-11 08:57:06",
|
||||
},
|
||||
{
|
||||
Id: 12,
|
||||
OrderNo: "JU202508100012",
|
||||
OrderType: 2,
|
||||
ProductId: 1,
|
||||
ProductName: "动静态IP",
|
||||
PackageName: "季卡",
|
||||
DayPrice: 168,
|
||||
ConnectCount: 1,
|
||||
AccountCount: 1,
|
||||
Accounts: "jx1239",
|
||||
OrderAmount: 168,
|
||||
CouponAmount: 0,
|
||||
PaymentAmount: 168,
|
||||
CreateTime: "2025-08-10 19:33:20",
|
||||
UpdateTime: "2025-08-10 19:33:41",
|
||||
},
|
||||
{
|
||||
Id: 11,
|
||||
OrderNo: "JU202508090013",
|
||||
OrderType: 1,
|
||||
ProductId: 2,
|
||||
ProductName: "新HTTP/S5",
|
||||
PackageName: "年卡",
|
||||
DayPrice: 880,
|
||||
ConnectCount: 20,
|
||||
AccountCount: 2,
|
||||
Accounts: "https-005,https-006",
|
||||
OrderAmount: 1760,
|
||||
CouponAmount: 50,
|
||||
PaymentAmount: 1710,
|
||||
CreateTime: "2025-08-09 21:18:07",
|
||||
UpdateTime: "2025-08-09 21:18:30",
|
||||
},
|
||||
{
|
||||
Id: 10,
|
||||
OrderNo: "JU202508090014",
|
||||
OrderType: 1,
|
||||
ProductId: 3,
|
||||
ProductName: "HTTP/S5",
|
||||
PackageName: "测试卡",
|
||||
DayPrice: 1,
|
||||
ConnectCount: 1,
|
||||
AccountCount: 1,
|
||||
Accounts: "global-003",
|
||||
OrderAmount: 1,
|
||||
CouponAmount: 0,
|
||||
PaymentAmount: 1,
|
||||
CreateTime: "2025-08-09 17:47:52",
|
||||
UpdateTime: "2025-08-09 17:48:09",
|
||||
},
|
||||
{
|
||||
Id: 9,
|
||||
OrderNo: "JU202508080015",
|
||||
OrderType: 4,
|
||||
ProductId: 2,
|
||||
ProductName: "新HTTP/S5",
|
||||
PackageName: "月卡(活动)",
|
||||
DayPrice: 88,
|
||||
ConnectCount: 6,
|
||||
AccountCount: 6,
|
||||
Accounts: "https-b1,https-b2,https-b3,https-b4,https-b5,https-b6",
|
||||
OrderAmount: 528,
|
||||
CouponAmount: 20,
|
||||
PaymentAmount: 508,
|
||||
CreateTime: "2025-08-08 12:26:34",
|
||||
UpdateTime: "2025-08-08 12:26:58",
|
||||
},
|
||||
{
|
||||
Id: 8,
|
||||
OrderNo: "JU202508070016",
|
||||
OrderType: 1,
|
||||
ProductId: 1,
|
||||
ProductName: "动静态IP",
|
||||
PackageName: "月卡(活动)",
|
||||
DayPrice: 58,
|
||||
ConnectCount: 2,
|
||||
AccountCount: 1,
|
||||
Accounts: "jx1240",
|
||||
OrderAmount: 58,
|
||||
CouponAmount: 0,
|
||||
PaymentAmount: 58,
|
||||
CreateTime: "2025-08-07 09:11:26",
|
||||
UpdateTime: "2025-08-07 09:11:43",
|
||||
},
|
||||
{
|
||||
Id: 7,
|
||||
OrderNo: "JU202508060017",
|
||||
OrderType: 2,
|
||||
ProductId: 3,
|
||||
ProductName: "HTTP/S5",
|
||||
PackageName: "季卡",
|
||||
DayPrice: 268,
|
||||
ConnectCount: 1,
|
||||
AccountCount: 1,
|
||||
Accounts: "global-004",
|
||||
OrderAmount: 268,
|
||||
CouponAmount: 8,
|
||||
PaymentAmount: 260,
|
||||
CreateTime: "2025-08-06 22:05:11",
|
||||
UpdateTime: "2025-08-06 22:05:35",
|
||||
},
|
||||
{
|
||||
Id: 6,
|
||||
OrderNo: "JU202508050018",
|
||||
OrderType: 1,
|
||||
ProductId: 1,
|
||||
ProductName: "动静态IP",
|
||||
PackageName: "天卡",
|
||||
DayPrice: 3,
|
||||
ConnectCount: 1,
|
||||
AccountCount: 1,
|
||||
Accounts: "jx1241",
|
||||
OrderAmount: 3,
|
||||
CouponAmount: 0,
|
||||
PaymentAmount: 3,
|
||||
CreateTime: "2025-08-05 14:38:49",
|
||||
UpdateTime: "2025-08-05 14:39:05",
|
||||
},
|
||||
{
|
||||
Id: 5,
|
||||
OrderNo: "JU202508040019",
|
||||
OrderType: 3,
|
||||
ProductId: 3,
|
||||
ProductName: "HTTP/S5",
|
||||
PackageName: "月卡",
|
||||
DayPrice: 98,
|
||||
ConnectCount: 3,
|
||||
AccountCount: 3,
|
||||
Accounts: "global-b1,global-b2,global-b3",
|
||||
OrderAmount: 294,
|
||||
CouponAmount: 0,
|
||||
PaymentAmount: 294,
|
||||
CreateTime: "2025-08-04 10:52:18",
|
||||
UpdateTime: "2025-08-04 10:52:37",
|
||||
},
|
||||
{
|
||||
Id: 4,
|
||||
OrderNo: "JU202508030020",
|
||||
OrderType: 1,
|
||||
ProductId: 1,
|
||||
ProductName: "动静态IP",
|
||||
PackageName: "双月卡(活动)",
|
||||
DayPrice: 98,
|
||||
ConnectCount: 1,
|
||||
AccountCount: 1,
|
||||
Accounts: "jx1242",
|
||||
OrderAmount: 98,
|
||||
CouponAmount: 0,
|
||||
PaymentAmount: 98,
|
||||
CreateTime: "2025-08-03 16:07:23",
|
||||
UpdateTime: "2025-08-03 16:07:44",
|
||||
},
|
||||
{
|
||||
Id: 3,
|
||||
OrderNo: "JU202508020021",
|
||||
OrderType: 2,
|
||||
ProductId: 1,
|
||||
ProductName: "动静态IP",
|
||||
PackageName: "年卡",
|
||||
DayPrice: 688,
|
||||
ConnectCount: 1,
|
||||
AccountCount: 1,
|
||||
Accounts: "jx1243",
|
||||
OrderAmount: 688,
|
||||
CouponAmount: 30,
|
||||
PaymentAmount: 658,
|
||||
CreateTime: "2025-08-02 20:29:56",
|
||||
UpdateTime: "2025-08-02 20:30:14",
|
||||
},
|
||||
{
|
||||
Id: 2,
|
||||
OrderNo: "JU202508010022",
|
||||
OrderType: 1,
|
||||
ProductId: 2,
|
||||
ProductName: "新HTTP/S5",
|
||||
PackageName: "周卡",
|
||||
DayPrice: 25,
|
||||
ConnectCount: 2,
|
||||
AccountCount: 1,
|
||||
Accounts: "https-007",
|
||||
OrderAmount: 25,
|
||||
CouponAmount: 0,
|
||||
PaymentAmount: 25,
|
||||
CreateTime: "2025-08-01 11:44:02",
|
||||
UpdateTime: "2025-08-01 11:44:21",
|
||||
},
|
||||
{
|
||||
Id: 1,
|
||||
OrderNo: "JU202507310023",
|
||||
OrderType: 1,
|
||||
ProductId: 1,
|
||||
ProductName: "动静态IP",
|
||||
PackageName: "月卡",
|
||||
DayPrice: 68,
|
||||
ConnectCount: 1,
|
||||
AccountCount: 1,
|
||||
Accounts: "jx1244",
|
||||
OrderAmount: 68,
|
||||
CouponAmount: 0,
|
||||
PaymentAmount: 68,
|
||||
CreateTime: "2025-07-31 09:58:37",
|
||||
UpdateTime: "2025-07-31 09:58:55",
|
||||
},
|
||||
]
|
||||
|
||||
export const MOCK_PRODUCTS = [
|
||||
{ id: 1, name: "动静态IP" },
|
||||
{ id: 2, name: "新HTTP/S5" },
|
||||
{ id: 3, name: "HTTP/S5" },
|
||||
]
|
||||
290
src/admin/orders/page.tsx
Normal file
290
src/admin/orders/page.tsx
Normal file
@@ -0,0 +1,290 @@
|
||||
import type { ColumnDef } from "@tanstack/react-table"
|
||||
import { SearchIcon } from "lucide-react"
|
||||
import { useMemo, useState } from "react"
|
||||
import DataTable from "@/components/data-table"
|
||||
import Page from "@/components/page"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { Input } from "@/components/ui/input"
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/components/ui/select"
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipTrigger,
|
||||
} from "@/components/ui/tooltip"
|
||||
import {
|
||||
cutAccount,
|
||||
DEFAULT_FILTERS,
|
||||
formatOrderType,
|
||||
ORDER_TYPE_LABELS,
|
||||
type OrderFilters,
|
||||
type OrderItem,
|
||||
PACKAGE_OPTIONS,
|
||||
totalConnections,
|
||||
} from "@/lib/models/order"
|
||||
import { MOCK_ORDERS, MOCK_PRODUCTS } from "./mock"
|
||||
|
||||
function formatMoney(value: number): string {
|
||||
return value.toFixed(2)
|
||||
}
|
||||
|
||||
function formatDate(value: string): string {
|
||||
const date = new Date(value)
|
||||
if (Number.isNaN(date.getTime())) return value
|
||||
const year = date.getFullYear()
|
||||
const month = String(date.getMonth() + 1).padStart(2, "0")
|
||||
const day = String(date.getDate()).padStart(2, "0")
|
||||
return `${year}.${month}.${day}`
|
||||
}
|
||||
|
||||
function AccountCell({ accounts }: { accounts: string }) {
|
||||
const isCut = accounts.length > 15
|
||||
if (!isCut) return <span className="truncate">{accounts}</span>
|
||||
|
||||
return (
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<span className="cursor-default truncate">{cutAccount(accounts)}</span>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent className="max-w-80 break-all">{accounts}</TooltipContent>
|
||||
</Tooltip>
|
||||
)
|
||||
}
|
||||
|
||||
const columns: ColumnDef<OrderItem>[] = [
|
||||
{
|
||||
accessorKey: "CreateTime",
|
||||
header: "日期",
|
||||
cell: ({ row }) => formatDate(row.original.CreateTime),
|
||||
},
|
||||
{
|
||||
accessorKey: "OrderNo",
|
||||
header: "订单编号",
|
||||
},
|
||||
{
|
||||
accessorKey: "OrderType",
|
||||
header: "类型",
|
||||
cell: ({ row }) => formatOrderType(row.original.OrderType),
|
||||
},
|
||||
{
|
||||
accessorKey: "ProductName",
|
||||
header: "产品",
|
||||
},
|
||||
{
|
||||
accessorKey: "PackageName",
|
||||
header: "套餐",
|
||||
},
|
||||
{
|
||||
accessorKey: "DayPrice",
|
||||
header: "单价",
|
||||
cell: ({ row }) => formatMoney(row.original.DayPrice),
|
||||
},
|
||||
{
|
||||
accessorKey: "ConnectCount",
|
||||
header: "总连接数",
|
||||
cell: ({ row }) => totalConnections(row.original),
|
||||
},
|
||||
{
|
||||
accessorKey: "Accounts",
|
||||
header: "账号",
|
||||
cell: ({ row }) => <AccountCell accounts={row.original.Accounts} />,
|
||||
},
|
||||
{
|
||||
accessorKey: "OrderAmount",
|
||||
header: "订单金额",
|
||||
cell: ({ row }) => formatMoney(row.original.OrderAmount),
|
||||
},
|
||||
{
|
||||
accessorKey: "CouponAmount",
|
||||
header: "优惠券",
|
||||
cell: ({ row }) => formatMoney(row.original.CouponAmount),
|
||||
},
|
||||
{
|
||||
accessorKey: "PaymentAmount",
|
||||
header: "实付金额",
|
||||
cell: ({ row }) => formatMoney(row.original.PaymentAmount),
|
||||
},
|
||||
]
|
||||
|
||||
function matchOrders(list: typeof MOCK_ORDERS, filters: OrderFilters) {
|
||||
const keyword = filters.keyword.trim()
|
||||
|
||||
return list.filter(order => {
|
||||
if (
|
||||
filters.orderType !== "0" &&
|
||||
order.OrderType !== Number(filters.orderType)
|
||||
) {
|
||||
return false
|
||||
}
|
||||
if (
|
||||
filters.productId !== "0" &&
|
||||
order.ProductId !== Number(filters.productId)
|
||||
) {
|
||||
return false
|
||||
}
|
||||
if (
|
||||
filters.packageName !== "0" &&
|
||||
order.PackageName !== filters.packageName
|
||||
) {
|
||||
return false
|
||||
}
|
||||
if (
|
||||
keyword &&
|
||||
!order.OrderNo.includes(keyword) &&
|
||||
!order.Accounts.includes(keyword)
|
||||
) {
|
||||
return false
|
||||
}
|
||||
if (filters.bTime && order.CreateTime.slice(0, 10) < filters.bTime) {
|
||||
return false
|
||||
}
|
||||
if (filters.eTime && order.CreateTime.slice(0, 10) > filters.eTime) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
export default function OrdersPage() {
|
||||
const [filters, setFilters] = useState<OrderFilters>(DEFAULT_FILTERS)
|
||||
const [appliedFilters, setAppliedFilters] =
|
||||
useState<OrderFilters>(DEFAULT_FILTERS)
|
||||
const [page, setPage] = useState(1)
|
||||
const [pageSize, setPageSize] = useState(10)
|
||||
|
||||
const filtered = useMemo(
|
||||
() => matchOrders(MOCK_ORDERS, appliedFilters),
|
||||
[appliedFilters],
|
||||
)
|
||||
|
||||
const paged = useMemo(
|
||||
() => filtered.slice((page - 1) * pageSize, page * pageSize),
|
||||
[filtered, page, pageSize],
|
||||
)
|
||||
|
||||
const patch = (partial: Partial<OrderFilters>) =>
|
||||
setFilters({ ...filters, ...partial })
|
||||
|
||||
const handleSearch = () => {
|
||||
setAppliedFilters(filters)
|
||||
setPage(1)
|
||||
}
|
||||
|
||||
return (
|
||||
<Page>
|
||||
<div className="bg-white rounded-lg p-4 flex flex-wrap items-end gap-x-4 gap-y-3">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-sm text-gray-500 whitespace-nowrap">
|
||||
日期筛选:
|
||||
</span>
|
||||
<Input
|
||||
type="date"
|
||||
value={filters.bTime}
|
||||
onChange={e => patch({ bTime: e.target.value })}
|
||||
className="w-36"
|
||||
aria-label="开始时间"
|
||||
/>
|
||||
<span className="text-sm text-gray-400">至</span>
|
||||
<Input
|
||||
type="date"
|
||||
value={filters.eTime}
|
||||
onChange={e => patch({ eTime: e.target.value })}
|
||||
className="w-36"
|
||||
aria-label="结束时间"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Select
|
||||
value={filters.orderType}
|
||||
onValueChange={value => patch({ orderType: value })}
|
||||
>
|
||||
<SelectTrigger className="w-30">
|
||||
<SelectValue placeholder="全部类型" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="0">全部类型</SelectItem>
|
||||
{Object.entries(ORDER_TYPE_LABELS).map(([value, label]) => (
|
||||
<SelectItem key={value} value={value}>
|
||||
{label}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
|
||||
<Select
|
||||
value={filters.productId}
|
||||
onValueChange={value => patch({ productId: value })}
|
||||
>
|
||||
<SelectTrigger className="w-32">
|
||||
<SelectValue placeholder="全部产品" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="0">全部产品</SelectItem>
|
||||
{MOCK_PRODUCTS.map(product => (
|
||||
<SelectItem key={product.id} value={String(product.id)}>
|
||||
{product.name}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
|
||||
<Select
|
||||
value={filters.packageName}
|
||||
onValueChange={value => patch({ packageName: value })}
|
||||
>
|
||||
<SelectTrigger className="w-32">
|
||||
<SelectValue placeholder="全部套餐" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="0">全部套餐</SelectItem>
|
||||
{PACKAGE_OPTIONS.map(pkg => (
|
||||
<SelectItem key={pkg} value={pkg}>
|
||||
{pkg}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
<Input
|
||||
value={filters.keyword}
|
||||
onChange={e => patch({ keyword: e.target.value })}
|
||||
onKeyDown={e => {
|
||||
if (e.key === "Enter") handleSearch()
|
||||
}}
|
||||
placeholder="账号"
|
||||
className="w-40"
|
||||
/>
|
||||
<Button onClick={handleSearch}>
|
||||
<SearchIcon data-icon="inline-start" />
|
||||
查询
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="bg-white rounded-lg p-4 flex flex-col gap-4">
|
||||
<h3 className="font-semibold text-lg">订单管理</h3>
|
||||
<DataTable
|
||||
data={paged}
|
||||
status="done"
|
||||
columns={columns}
|
||||
pagination={{
|
||||
page: page - 1,
|
||||
size: pageSize,
|
||||
total: filtered.length,
|
||||
onPageChange: setPage,
|
||||
onSizeChange: size => {
|
||||
setPageSize(size)
|
||||
setPage(1)
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</Page>
|
||||
)
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
export default function LongPage() {
|
||||
return <div>LongPage</div>
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
export default function ShortPage() {
|
||||
return <div>ShortPage</div>
|
||||
}
|
||||
40
src/admin/record/mock.ts
Normal file
40
src/admin/record/mock.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
import type { HttpUsedLogItem, HttpUsedSummary } from "@/lib/models/record"
|
||||
|
||||
export const MOCK_USED_SUMMARY: HttpUsedSummary = {
|
||||
todayShortPackIpUsed: 3,
|
||||
shortPackIpUsed: 45,
|
||||
}
|
||||
|
||||
const PACK_IP_PREFIX = [
|
||||
{ packType: 21, prefix: "211.90.13" },
|
||||
{ packType: 22, prefix: "112.65.28" },
|
||||
]
|
||||
|
||||
function pad(value: number): string {
|
||||
return String(value).padStart(2, "0")
|
||||
}
|
||||
|
||||
function buildLogs(count: number): HttpUsedLogItem[] {
|
||||
const now = new Date()
|
||||
const logs: HttpUsedLogItem[] = []
|
||||
|
||||
for (let i = 0; i < count; i += 1) {
|
||||
const source = PACK_IP_PREFIX[i % PACK_IP_PREFIX.length]
|
||||
const time = new Date(now.getTime() - i * 37 * 60 * 1000)
|
||||
const createTime =
|
||||
`${time.getFullYear()}-${pad(time.getMonth() + 1)}-${pad(time.getDate())} ` +
|
||||
`${pad(time.getHours())}:${pad(time.getMinutes())}:${pad(time.getSeconds())}`
|
||||
|
||||
logs.push({
|
||||
packType: source.packType,
|
||||
userIp: `113.118.${(i % 200) + 10}.${(i % 250) + 3}`,
|
||||
ip: `${source.prefix}.${(i % 240) + 10}`,
|
||||
port: 6000 + (i % 300),
|
||||
createTime,
|
||||
})
|
||||
}
|
||||
|
||||
return logs
|
||||
}
|
||||
|
||||
export const MOCK_USED_LOGS = buildLogs(56)
|
||||
136
src/admin/record/page.tsx
Normal file
136
src/admin/record/page.tsx
Normal file
@@ -0,0 +1,136 @@
|
||||
import type { ColumnDef } from "@tanstack/react-table"
|
||||
import { useMemo, useState } from "react"
|
||||
import DataTable from "@/components/data-table"
|
||||
import Page from "@/components/page"
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/components/ui/select"
|
||||
import {
|
||||
formatPackType,
|
||||
HTTP_USE_PACK_TYPE_OPTIONS,
|
||||
type HttpUsedLogItem,
|
||||
} from "@/lib/models/record"
|
||||
import { MOCK_USED_LOGS, MOCK_USED_SUMMARY } from "./mock"
|
||||
|
||||
const columns: ColumnDef<HttpUsedLogItem>[] = [
|
||||
{
|
||||
accessorKey: "packType",
|
||||
header: "使用类型",
|
||||
cell: ({ row }) => formatPackType(row.original.packType),
|
||||
},
|
||||
{
|
||||
accessorKey: "userIp",
|
||||
header: "客户端IP",
|
||||
},
|
||||
{
|
||||
accessorKey: "ip",
|
||||
header: "使用IP",
|
||||
},
|
||||
{
|
||||
accessorKey: "port",
|
||||
header: "端口号",
|
||||
},
|
||||
{
|
||||
accessorKey: "createTime",
|
||||
header: "使用时间",
|
||||
},
|
||||
]
|
||||
|
||||
function StatCard(props: {
|
||||
label: string
|
||||
value: number
|
||||
accent: "amber" | "blue"
|
||||
}) {
|
||||
return (
|
||||
<div
|
||||
className={`rounded-lg p-4 flex flex-col gap-1 ${
|
||||
props.accent === "amber" ? "bg-amber-100" : "bg-blue-100"
|
||||
}`}
|
||||
>
|
||||
<span className="text-sm text-gray-600">{props.label}</span>
|
||||
<span className="text-2xl font-semibold">{props.value} 个</span>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default function RecordPage() {
|
||||
const [packType, setPackType] = useState(HTTP_USE_PACK_TYPE_OPTIONS[0])
|
||||
const [page, setPage] = useState(1)
|
||||
const [pageSize, setPageSize] = useState(10)
|
||||
|
||||
const filtered = useMemo(
|
||||
() => MOCK_USED_LOGS.filter(log => log.packType === packType),
|
||||
[packType],
|
||||
)
|
||||
|
||||
const paged = useMemo(
|
||||
() => filtered.slice((page - 1) * pageSize, page * pageSize),
|
||||
[filtered, page, pageSize],
|
||||
)
|
||||
|
||||
const handlePackTypeChange = (value: string) => {
|
||||
setPackType(Number(value))
|
||||
setPage(1)
|
||||
}
|
||||
|
||||
return (
|
||||
<Page>
|
||||
<div className="bg-white rounded-lg p-4 flex flex-col gap-4">
|
||||
<h3 className="font-semibold text-lg">包天包量使用概况</h3>
|
||||
<div className="grid grid-cols-4 sm:grid-cols-4 gap-4">
|
||||
<StatCard
|
||||
label="当日使用IP数量"
|
||||
value={MOCK_USED_SUMMARY.todayShortPackIpUsed}
|
||||
accent="amber"
|
||||
/>
|
||||
<StatCard
|
||||
label="累计使用IP数量"
|
||||
value={MOCK_USED_SUMMARY.shortPackIpUsed}
|
||||
accent="blue"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="bg-white rounded-lg p-4 flex flex-col gap-4">
|
||||
<div className="flex flex-col gap-4">
|
||||
<p className="text-sm text-orange-600">
|
||||
明细 最多允许查看72小时内记录(短效无限量套餐暂无记录)
|
||||
</p>
|
||||
|
||||
<Select value={String(packType)} onValueChange={handlePackTypeChange}>
|
||||
<SelectTrigger className="w-44">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{HTTP_USE_PACK_TYPE_OPTIONS.map(type => (
|
||||
<SelectItem key={type} value={String(type)}>
|
||||
{formatPackType(type)}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
<DataTable
|
||||
data={paged}
|
||||
status="done"
|
||||
columns={columns}
|
||||
pagination={{
|
||||
page,
|
||||
size: pageSize,
|
||||
total: filtered.length,
|
||||
onPageChange: setPage,
|
||||
onSizeChange: size => {
|
||||
setPageSize(size)
|
||||
setPage(1)
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</Page>
|
||||
)
|
||||
}
|
||||
401
src/admin/refundOrders/mock.ts
Normal file
401
src/admin/refundOrders/mock.ts
Normal file
@@ -0,0 +1,401 @@
|
||||
import type { OrderItem } from "@/lib/models/order"
|
||||
|
||||
export const MOCK_ORDERS: OrderItem[] = [
|
||||
{
|
||||
Id: 23,
|
||||
OrderNo: "JU202508150001",
|
||||
OrderType: 1,
|
||||
ProductId: 1,
|
||||
ProductName: "动静态IP",
|
||||
PackageName: "天卡",
|
||||
DayPrice: 3,
|
||||
ConnectCount: 1,
|
||||
AccountCount: 1,
|
||||
Accounts: "jx1234",
|
||||
OrderAmount: 3,
|
||||
CouponAmount: 0,
|
||||
PaymentAmount: 3,
|
||||
CreateTime: "2025-08-15 10:23:45",
|
||||
UpdateTime: "2025-08-15 10:24:01",
|
||||
},
|
||||
{
|
||||
Id: 22,
|
||||
OrderNo: "JU202508150002",
|
||||
OrderType: 1,
|
||||
ProductId: 1,
|
||||
ProductName: "动静态IP",
|
||||
PackageName: "周卡",
|
||||
DayPrice: 18,
|
||||
ConnectCount: 1,
|
||||
AccountCount: 1,
|
||||
Accounts: "jx1235",
|
||||
OrderAmount: 18,
|
||||
CouponAmount: 2,
|
||||
PaymentAmount: 16,
|
||||
CreateTime: "2025-08-15 09:12:33",
|
||||
UpdateTime: "2025-08-15 09:12:50",
|
||||
},
|
||||
{
|
||||
Id: 21,
|
||||
OrderNo: "JU202508140003",
|
||||
OrderType: 2,
|
||||
ProductId: 1,
|
||||
ProductName: "动静态IP",
|
||||
PackageName: "月卡",
|
||||
DayPrice: 68,
|
||||
ConnectCount: 2,
|
||||
AccountCount: 1,
|
||||
Accounts: "jx1236",
|
||||
OrderAmount: 68,
|
||||
CouponAmount: 0,
|
||||
PaymentAmount: 68,
|
||||
CreateTime: "2025-08-14 16:45:21",
|
||||
UpdateTime: "2025-08-14 16:45:39",
|
||||
},
|
||||
{
|
||||
Id: 20,
|
||||
OrderNo: "JU202508140004",
|
||||
OrderType: 3,
|
||||
ProductId: 2,
|
||||
ProductName: "新HTTP/S5",
|
||||
PackageName: "月卡(活动)",
|
||||
DayPrice: 88,
|
||||
ConnectCount: 5,
|
||||
AccountCount: 3,
|
||||
Accounts: "https-001,https-002,https-003",
|
||||
OrderAmount: 264,
|
||||
CouponAmount: 10,
|
||||
PaymentAmount: 254,
|
||||
CreateTime: "2025-08-14 14:02:17",
|
||||
UpdateTime: "2025-08-14 14:02:55",
|
||||
},
|
||||
{
|
||||
Id: 19,
|
||||
OrderNo: "JU202508140005",
|
||||
OrderType: 1,
|
||||
ProductId: 3,
|
||||
ProductName: "HTTP/S5",
|
||||
PackageName: "季卡",
|
||||
DayPrice: 168,
|
||||
ConnectCount: 1,
|
||||
AccountCount: 1,
|
||||
Accounts: "global-001",
|
||||
OrderAmount: 168,
|
||||
CouponAmount: 0,
|
||||
PaymentAmount: 168,
|
||||
CreateTime: "2025-08-14 11:30:08",
|
||||
UpdateTime: "2025-08-14 11:30:26",
|
||||
},
|
||||
{
|
||||
Id: 18,
|
||||
OrderNo: "JU202508130006",
|
||||
OrderType: 4,
|
||||
ProductId: 1,
|
||||
ProductName: "动静态IP",
|
||||
PackageName: "年卡",
|
||||
DayPrice: 688,
|
||||
ConnectCount: 10,
|
||||
AccountCount: 5,
|
||||
Accounts: "batch-jx-01,batch-jx-02,batch-jx-03,batch-jx-04,batch-jx-05",
|
||||
OrderAmount: 3440,
|
||||
CouponAmount: 100,
|
||||
PaymentAmount: 3340,
|
||||
CreateTime: "2025-08-13 20:15:44",
|
||||
UpdateTime: "2025-08-13 20:16:02",
|
||||
},
|
||||
{
|
||||
Id: 17,
|
||||
OrderNo: "JU202508130007",
|
||||
OrderType: 1,
|
||||
ProductId: 1,
|
||||
ProductName: "动静态IP",
|
||||
PackageName: "测试卡",
|
||||
DayPrice: 0.5,
|
||||
ConnectCount: 1,
|
||||
AccountCount: 1,
|
||||
Accounts: "jx1237",
|
||||
OrderAmount: 0.5,
|
||||
CouponAmount: 0,
|
||||
PaymentAmount: 0.5,
|
||||
CreateTime: "2025-08-13 18:03:12",
|
||||
UpdateTime: "2025-08-13 18:03:28",
|
||||
},
|
||||
{
|
||||
Id: 16,
|
||||
OrderNo: "JU202508120008",
|
||||
OrderType: 2,
|
||||
ProductId: 2,
|
||||
ProductName: "新HTTP/S5",
|
||||
PackageName: "双月卡(活动)",
|
||||
DayPrice: 128,
|
||||
ConnectCount: 3,
|
||||
AccountCount: 1,
|
||||
Accounts: "https-004",
|
||||
OrderAmount: 128,
|
||||
CouponAmount: 0,
|
||||
PaymentAmount: 128,
|
||||
CreateTime: "2025-08-12 15:22:37",
|
||||
UpdateTime: "2025-08-12 15:22:59",
|
||||
},
|
||||
{
|
||||
Id: 15,
|
||||
OrderNo: "JU202508120009",
|
||||
OrderType: 1,
|
||||
ProductId: 3,
|
||||
ProductName: "HTTP/S5",
|
||||
PackageName: "月卡",
|
||||
DayPrice: 98,
|
||||
ConnectCount: 1,
|
||||
AccountCount: 1,
|
||||
Accounts: "global-002",
|
||||
OrderAmount: 98,
|
||||
CouponAmount: 5,
|
||||
PaymentAmount: 93,
|
||||
CreateTime: "2025-08-12 13:40:55",
|
||||
UpdateTime: "2025-08-12 13:41:13",
|
||||
},
|
||||
{
|
||||
Id: 14,
|
||||
OrderNo: "JU202508110010",
|
||||
OrderType: 1,
|
||||
ProductId: 1,
|
||||
ProductName: "动静态IP",
|
||||
PackageName: "天卡",
|
||||
DayPrice: 3,
|
||||
ConnectCount: 1,
|
||||
AccountCount: 1,
|
||||
Accounts: "jx1238",
|
||||
OrderAmount: 3,
|
||||
CouponAmount: 0,
|
||||
PaymentAmount: 3,
|
||||
CreateTime: "2025-08-11 10:05:29",
|
||||
UpdateTime: "2025-08-11 10:05:44",
|
||||
},
|
||||
{
|
||||
Id: 13,
|
||||
OrderNo: "JU202508110011",
|
||||
OrderType: 3,
|
||||
ProductId: 1,
|
||||
ProductName: "动静态IP",
|
||||
PackageName: "周卡",
|
||||
DayPrice: 18,
|
||||
ConnectCount: 4,
|
||||
AccountCount: 4,
|
||||
Accounts: "batch-jx-06,batch-jx-07,batch-jx-08,batch-jx-09",
|
||||
OrderAmount: 72,
|
||||
CouponAmount: 0,
|
||||
PaymentAmount: 72,
|
||||
CreateTime: "2025-08-11 08:56:48",
|
||||
UpdateTime: "2025-08-11 08:57:06",
|
||||
},
|
||||
{
|
||||
Id: 12,
|
||||
OrderNo: "JU202508100012",
|
||||
OrderType: 2,
|
||||
ProductId: 1,
|
||||
ProductName: "动静态IP",
|
||||
PackageName: "季卡",
|
||||
DayPrice: 168,
|
||||
ConnectCount: 1,
|
||||
AccountCount: 1,
|
||||
Accounts: "jx1239",
|
||||
OrderAmount: 168,
|
||||
CouponAmount: 0,
|
||||
PaymentAmount: 168,
|
||||
CreateTime: "2025-08-10 19:33:20",
|
||||
UpdateTime: "2025-08-10 19:33:41",
|
||||
},
|
||||
{
|
||||
Id: 11,
|
||||
OrderNo: "JU202508090013",
|
||||
OrderType: 1,
|
||||
ProductId: 2,
|
||||
ProductName: "新HTTP/S5",
|
||||
PackageName: "年卡",
|
||||
DayPrice: 880,
|
||||
ConnectCount: 20,
|
||||
AccountCount: 2,
|
||||
Accounts: "https-005,https-006",
|
||||
OrderAmount: 1760,
|
||||
CouponAmount: 50,
|
||||
PaymentAmount: 1710,
|
||||
CreateTime: "2025-08-09 21:18:07",
|
||||
UpdateTime: "2025-08-09 21:18:30",
|
||||
},
|
||||
{
|
||||
Id: 10,
|
||||
OrderNo: "JU202508090014",
|
||||
OrderType: 1,
|
||||
ProductId: 3,
|
||||
ProductName: "HTTP/S5",
|
||||
PackageName: "测试卡",
|
||||
DayPrice: 1,
|
||||
ConnectCount: 1,
|
||||
AccountCount: 1,
|
||||
Accounts: "global-003",
|
||||
OrderAmount: 1,
|
||||
CouponAmount: 0,
|
||||
PaymentAmount: 1,
|
||||
CreateTime: "2025-08-09 17:47:52",
|
||||
UpdateTime: "2025-08-09 17:48:09",
|
||||
},
|
||||
{
|
||||
Id: 9,
|
||||
OrderNo: "JU202508080015",
|
||||
OrderType: 4,
|
||||
ProductId: 2,
|
||||
ProductName: "新HTTP/S5",
|
||||
PackageName: "月卡(活动)",
|
||||
DayPrice: 88,
|
||||
ConnectCount: 6,
|
||||
AccountCount: 6,
|
||||
Accounts: "https-b1,https-b2,https-b3,https-b4,https-b5,https-b6",
|
||||
OrderAmount: 528,
|
||||
CouponAmount: 20,
|
||||
PaymentAmount: 508,
|
||||
CreateTime: "2025-08-08 12:26:34",
|
||||
UpdateTime: "2025-08-08 12:26:58",
|
||||
},
|
||||
{
|
||||
Id: 8,
|
||||
OrderNo: "JU202508070016",
|
||||
OrderType: 1,
|
||||
ProductId: 1,
|
||||
ProductName: "动静态IP",
|
||||
PackageName: "月卡(活动)",
|
||||
DayPrice: 58,
|
||||
ConnectCount: 2,
|
||||
AccountCount: 1,
|
||||
Accounts: "jx1240",
|
||||
OrderAmount: 58,
|
||||
CouponAmount: 0,
|
||||
PaymentAmount: 58,
|
||||
CreateTime: "2025-08-07 09:11:26",
|
||||
UpdateTime: "2025-08-07 09:11:43",
|
||||
},
|
||||
{
|
||||
Id: 7,
|
||||
OrderNo: "JU202508060017",
|
||||
OrderType: 2,
|
||||
ProductId: 3,
|
||||
ProductName: "HTTP/S5",
|
||||
PackageName: "季卡",
|
||||
DayPrice: 268,
|
||||
ConnectCount: 1,
|
||||
AccountCount: 1,
|
||||
Accounts: "global-004",
|
||||
OrderAmount: 268,
|
||||
CouponAmount: 8,
|
||||
PaymentAmount: 260,
|
||||
CreateTime: "2025-08-06 22:05:11",
|
||||
UpdateTime: "2025-08-06 22:05:35",
|
||||
},
|
||||
{
|
||||
Id: 6,
|
||||
OrderNo: "JU202508050018",
|
||||
OrderType: 1,
|
||||
ProductId: 1,
|
||||
ProductName: "动静态IP",
|
||||
PackageName: "天卡",
|
||||
DayPrice: 3,
|
||||
ConnectCount: 1,
|
||||
AccountCount: 1,
|
||||
Accounts: "jx1241",
|
||||
OrderAmount: 3,
|
||||
CouponAmount: 0,
|
||||
PaymentAmount: 3,
|
||||
CreateTime: "2025-08-05 14:38:49",
|
||||
UpdateTime: "2025-08-05 14:39:05",
|
||||
},
|
||||
{
|
||||
Id: 5,
|
||||
OrderNo: "JU202508040019",
|
||||
OrderType: 3,
|
||||
ProductId: 3,
|
||||
ProductName: "HTTP/S5",
|
||||
PackageName: "月卡",
|
||||
DayPrice: 98,
|
||||
ConnectCount: 3,
|
||||
AccountCount: 3,
|
||||
Accounts: "global-b1,global-b2,global-b3",
|
||||
OrderAmount: 294,
|
||||
CouponAmount: 0,
|
||||
PaymentAmount: 294,
|
||||
CreateTime: "2025-08-04 10:52:18",
|
||||
UpdateTime: "2025-08-04 10:52:37",
|
||||
},
|
||||
{
|
||||
Id: 4,
|
||||
OrderNo: "JU202508030020",
|
||||
OrderType: 1,
|
||||
ProductId: 1,
|
||||
ProductName: "动静态IP",
|
||||
PackageName: "双月卡(活动)",
|
||||
DayPrice: 98,
|
||||
ConnectCount: 1,
|
||||
AccountCount: 1,
|
||||
Accounts: "jx1242",
|
||||
OrderAmount: 98,
|
||||
CouponAmount: 0,
|
||||
PaymentAmount: 98,
|
||||
CreateTime: "2025-08-03 16:07:23",
|
||||
UpdateTime: "2025-08-03 16:07:44",
|
||||
},
|
||||
{
|
||||
Id: 3,
|
||||
OrderNo: "JU202508020021",
|
||||
OrderType: 2,
|
||||
ProductId: 1,
|
||||
ProductName: "动静态IP",
|
||||
PackageName: "年卡",
|
||||
DayPrice: 688,
|
||||
ConnectCount: 1,
|
||||
AccountCount: 1,
|
||||
Accounts: "jx1243",
|
||||
OrderAmount: 688,
|
||||
CouponAmount: 30,
|
||||
PaymentAmount: 658,
|
||||
CreateTime: "2025-08-02 20:29:56",
|
||||
UpdateTime: "2025-08-02 20:30:14",
|
||||
},
|
||||
{
|
||||
Id: 2,
|
||||
OrderNo: "JU202508010022",
|
||||
OrderType: 1,
|
||||
ProductId: 2,
|
||||
ProductName: "新HTTP/S5",
|
||||
PackageName: "周卡",
|
||||
DayPrice: 25,
|
||||
ConnectCount: 2,
|
||||
AccountCount: 1,
|
||||
Accounts: "https-007",
|
||||
OrderAmount: 25,
|
||||
CouponAmount: 0,
|
||||
PaymentAmount: 25,
|
||||
CreateTime: "2025-08-01 11:44:02",
|
||||
UpdateTime: "2025-08-01 11:44:21",
|
||||
},
|
||||
{
|
||||
Id: 1,
|
||||
OrderNo: "JU202507310023",
|
||||
OrderType: 1,
|
||||
ProductId: 1,
|
||||
ProductName: "动静态IP",
|
||||
PackageName: "月卡",
|
||||
DayPrice: 68,
|
||||
ConnectCount: 1,
|
||||
AccountCount: 1,
|
||||
Accounts: "jx1244",
|
||||
OrderAmount: 68,
|
||||
CouponAmount: 0,
|
||||
PaymentAmount: 68,
|
||||
CreateTime: "2025-07-31 09:58:37",
|
||||
UpdateTime: "2025-07-31 09:58:55",
|
||||
},
|
||||
]
|
||||
|
||||
export const MOCK_PRODUCTS = [
|
||||
{ id: 1, name: "动静态IP" },
|
||||
{ id: 2, name: "新HTTP/S5" },
|
||||
{ id: 3, name: "HTTP/S5" },
|
||||
]
|
||||
272
src/admin/refundOrders/page.tsx
Normal file
272
src/admin/refundOrders/page.tsx
Normal file
@@ -0,0 +1,272 @@
|
||||
import type { ColumnDef } from "@tanstack/react-table"
|
||||
import { SearchIcon } from "lucide-react"
|
||||
import { useMemo, useState } from "react"
|
||||
import DataTable from "@/components/data-table"
|
||||
import Page from "@/components/page"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { Input } from "@/components/ui/input"
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/components/ui/select"
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipTrigger,
|
||||
} from "@/components/ui/tooltip"
|
||||
import {
|
||||
cutAccount,
|
||||
DEFAULT_FILTERS,
|
||||
formatOrderType,
|
||||
type OrderFilters,
|
||||
type OrderItem,
|
||||
PACKAGE_OPTIONS,
|
||||
totalConnections,
|
||||
} from "@/lib/models/order"
|
||||
import { MOCK_ORDERS, MOCK_PRODUCTS } from "./mock"
|
||||
|
||||
function formatMoney(value: number): string {
|
||||
return value.toFixed(2)
|
||||
}
|
||||
|
||||
function formatDate(value: string): string {
|
||||
const date = new Date(value)
|
||||
if (Number.isNaN(date.getTime())) return value
|
||||
const year = date.getFullYear()
|
||||
const month = String(date.getMonth() + 1).padStart(2, "0")
|
||||
const day = String(date.getDate()).padStart(2, "0")
|
||||
return `${year}.${month}.${day}`
|
||||
}
|
||||
|
||||
function AccountCell({ accounts }: { accounts: string }) {
|
||||
const isCut = accounts.length > 15
|
||||
if (!isCut) return <span className="truncate">{accounts}</span>
|
||||
|
||||
return (
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<span className="cursor-default truncate">{cutAccount(accounts)}</span>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent className="max-w-80 break-all">{accounts}</TooltipContent>
|
||||
</Tooltip>
|
||||
)
|
||||
}
|
||||
|
||||
const columns: ColumnDef<OrderItem>[] = [
|
||||
{
|
||||
accessorKey: "CreateTime",
|
||||
header: "日期",
|
||||
cell: ({ row }) => formatDate(row.original.CreateTime),
|
||||
},
|
||||
{
|
||||
accessorKey: "OrderNo",
|
||||
header: "订单编号",
|
||||
},
|
||||
{
|
||||
accessorKey: "OrderType",
|
||||
header: "类型",
|
||||
cell: ({ row }) => formatOrderType(row.original.OrderType),
|
||||
},
|
||||
{
|
||||
accessorKey: "ProductName",
|
||||
header: "产品",
|
||||
},
|
||||
{
|
||||
accessorKey: "PackageName",
|
||||
header: "套餐",
|
||||
},
|
||||
{
|
||||
accessorKey: "Accounts",
|
||||
header: "账号",
|
||||
cell: ({ row }) => <AccountCell accounts={row.original.Accounts} />,
|
||||
},
|
||||
{
|
||||
accessorKey: "ConnectCount",
|
||||
header: "连接数",
|
||||
cell: ({ row }) => totalConnections(row.original),
|
||||
},
|
||||
{
|
||||
accessorKey: "DayPrice",
|
||||
header: "退款时长",
|
||||
cell: ({ row }) => formatMoney(row.original.DayPrice),
|
||||
},
|
||||
{
|
||||
accessorKey: "DayPrice",
|
||||
header: "退款单价",
|
||||
cell: ({ row }) => formatMoney(row.original.DayPrice),
|
||||
},
|
||||
{
|
||||
accessorKey: "PaymentAmount",
|
||||
header: "实付金额",
|
||||
cell: ({ row }) => formatMoney(row.original.PaymentAmount),
|
||||
},
|
||||
{
|
||||
accessorKey: "CouponAmount",
|
||||
header: "退款金额",
|
||||
cell: ({ row }) => formatMoney(row.original.CouponAmount),
|
||||
},
|
||||
]
|
||||
|
||||
function matchOrders(list: typeof MOCK_ORDERS, filters: OrderFilters) {
|
||||
const keyword = filters.keyword.trim()
|
||||
|
||||
return list.filter(order => {
|
||||
if (
|
||||
filters.orderType !== "0" &&
|
||||
order.OrderType !== Number(filters.orderType)
|
||||
) {
|
||||
return false
|
||||
}
|
||||
if (
|
||||
filters.productId !== "0" &&
|
||||
order.ProductId !== Number(filters.productId)
|
||||
) {
|
||||
return false
|
||||
}
|
||||
if (
|
||||
filters.packageName !== "0" &&
|
||||
order.PackageName !== filters.packageName
|
||||
) {
|
||||
return false
|
||||
}
|
||||
if (
|
||||
keyword &&
|
||||
!order.OrderNo.includes(keyword) &&
|
||||
!order.Accounts.includes(keyword)
|
||||
) {
|
||||
return false
|
||||
}
|
||||
if (filters.bTime && order.CreateTime.slice(0, 10) < filters.bTime) {
|
||||
return false
|
||||
}
|
||||
if (filters.eTime && order.CreateTime.slice(0, 10) > filters.eTime) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
export default function RefundOrdersPage() {
|
||||
const [filters, setFilters] = useState<OrderFilters>(DEFAULT_FILTERS)
|
||||
const [appliedFilters, setAppliedFilters] =
|
||||
useState<OrderFilters>(DEFAULT_FILTERS)
|
||||
const [page, setPage] = useState(1)
|
||||
const [pageSize, setPageSize] = useState(10)
|
||||
|
||||
const filtered = useMemo(
|
||||
() => matchOrders(MOCK_ORDERS, appliedFilters),
|
||||
[appliedFilters],
|
||||
)
|
||||
|
||||
const paged = useMemo(
|
||||
() => filtered.slice((page - 1) * pageSize, page * pageSize),
|
||||
[filtered, page, pageSize],
|
||||
)
|
||||
|
||||
const patch = (partial: Partial<OrderFilters>) =>
|
||||
setFilters({ ...filters, ...partial })
|
||||
|
||||
const handleSearch = () => {
|
||||
setAppliedFilters(filters)
|
||||
setPage(1)
|
||||
}
|
||||
|
||||
return (
|
||||
<Page>
|
||||
<div className="bg-white rounded-lg p-4 flex flex-wrap items-end gap-x-4 gap-y-3">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-sm text-gray-500 whitespace-nowrap">
|
||||
日期筛选:
|
||||
</span>
|
||||
<Input
|
||||
type="date"
|
||||
value={filters.bTime}
|
||||
onChange={e => patch({ bTime: e.target.value })}
|
||||
className="w-36"
|
||||
aria-label="开始时间"
|
||||
/>
|
||||
<span className="text-sm text-gray-400">至</span>
|
||||
<Input
|
||||
type="date"
|
||||
value={filters.eTime}
|
||||
onChange={e => patch({ eTime: e.target.value })}
|
||||
className="w-36"
|
||||
aria-label="结束时间"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Select
|
||||
value={filters.productId}
|
||||
onValueChange={value => patch({ productId: value })}
|
||||
>
|
||||
<SelectTrigger className="w-32">
|
||||
<SelectValue placeholder="全部产品" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="0">全部产品</SelectItem>
|
||||
{MOCK_PRODUCTS.map(product => (
|
||||
<SelectItem key={product.id} value={String(product.id)}>
|
||||
{product.name}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
|
||||
<Select
|
||||
value={filters.packageName}
|
||||
onValueChange={value => patch({ packageName: value })}
|
||||
>
|
||||
<SelectTrigger className="w-32">
|
||||
<SelectValue placeholder="全部套餐" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="0">全部套餐</SelectItem>
|
||||
{PACKAGE_OPTIONS.map(pkg => (
|
||||
<SelectItem key={pkg} value={pkg}>
|
||||
{pkg}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
<Input
|
||||
value={filters.keyword}
|
||||
onChange={e => patch({ keyword: e.target.value })}
|
||||
onKeyDown={e => {
|
||||
if (e.key === "Enter") handleSearch()
|
||||
}}
|
||||
placeholder="账号"
|
||||
className="w-40"
|
||||
/>
|
||||
<Button onClick={handleSearch}>
|
||||
<SearchIcon data-icon="inline-start" />
|
||||
查询
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="bg-white rounded-lg p-4 flex flex-col gap-4">
|
||||
<h3 className="font-semibold text-lg">订单管理</h3>
|
||||
<DataTable
|
||||
data={paged}
|
||||
status="done"
|
||||
columns={columns}
|
||||
pagination={{
|
||||
page: page - 1,
|
||||
size: pageSize,
|
||||
total: filtered.length,
|
||||
onPageChange: setPage,
|
||||
onSizeChange: size => {
|
||||
setPageSize(size)
|
||||
setPage(1)
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</Page>
|
||||
)
|
||||
}
|
||||
5
src/admin/resources/long.tsx
Normal file
5
src/admin/resources/long.tsx
Normal file
@@ -0,0 +1,5 @@
|
||||
import Page from "@/components/page"
|
||||
|
||||
export default function LongPage() {
|
||||
return <Page>长效管理</Page>
|
||||
}
|
||||
5
src/admin/resources/short.tsx
Normal file
5
src/admin/resources/short.tsx
Normal file
@@ -0,0 +1,5 @@
|
||||
import Page from "@/components/page"
|
||||
|
||||
export default function ShortPage() {
|
||||
return <Page>短效套餐</Page>
|
||||
}
|
||||
5
src/admin/rosorder/page.tsx
Normal file
5
src/admin/rosorder/page.tsx
Normal file
@@ -0,0 +1,5 @@
|
||||
import Page from "@/components/page"
|
||||
|
||||
export default function RosorderPage() {
|
||||
return <Page>软路由页面</Page>
|
||||
}
|
||||
Reference in New Issue
Block a user