diff --git a/.zed/settings.json b/.zed/settings.json index bd61d16..7f85537 100644 --- a/.zed/settings.json +++ b/.zed/settings.json @@ -5,18 +5,18 @@ "formatter": { "language_server": { "name": "biome" } }, "code_actions_on_format": { "source.fixAll.biome": true, - "source.organizeImports.biome": true, - }, + "source.organizeImports.biome": true + } }, "TSX": { "formatter": { "language_server": { "name": "biome" } }, "code_actions_on_format": { "source.fixAll.biome": true, - "source.organizeImports.biome": true, - }, + "source.organizeImports.biome": true + } }, "JSON": { - "formatter": { "language_server": { "name": "biome" } }, - }, - }, + "formatter": { "language_server": { "name": "biome" } } + } + } } diff --git a/biome.json b/biome.json index 74329f6..07d601f 100644 --- a/biome.json +++ b/biome.json @@ -18,6 +18,15 @@ "recommended": true, "style": { "useNodejsImportProtocol": "off" + }, + "a11y": { + "useButtonType": "off", + "noLabelWithoutControl": "off", + "noSvgWithoutTitle": "off", + "useSemanticElements": "off" + }, + "suspicious": { + "noArrayIndexKey": "off" } } }, diff --git a/postcss.config.mjs b/postcss.config.mjs index c7bcb4b..f4aa8a8 100644 --- a/postcss.config.mjs +++ b/postcss.config.mjs @@ -1,5 +1,5 @@ const config = { plugins: ["@tailwindcss/postcss"], -}; +} -export default config; +export default config diff --git a/src/actions/cust.ts b/src/actions/cust.ts index 3a0a031..b39fcc4 100644 --- a/src/actions/cust.ts +++ b/src/actions/cust.ts @@ -20,9 +20,9 @@ export async function updateCust(data: { } export async function createCust(data: { - password: string - username: string phone: string + password?: string + username?: string admin_id?: number discount_id?: number email?: string diff --git a/src/app/(root)/batch/page.tsx b/src/app/(root)/batch/page.tsx index 1d8c3ca..ffe789c 100644 --- a/src/app/(root)/batch/page.tsx +++ b/src/app/(root)/batch/page.tsx @@ -236,15 +236,7 @@ export default function BatchPage() { type="button" variant="outline" onClick={() => { - reset({ - user_phone: "", - batch_no: "", - prov: "", - city: "", - isp: "all", - created_at_start: "", - created_at_end: "", - }) + reset() setFilters({}) table.pagination.onPageChange(1) }} diff --git a/src/app/(root)/billing/page.tsx b/src/app/(root)/billing/page.tsx index 3f77907..df23de3 100644 --- a/src/app/(root)/billing/page.tsx +++ b/src/app/(root)/billing/page.tsx @@ -1,7 +1,7 @@ "use client" import { zodResolver } from "@hookform/resolvers/zod" import { format } from "date-fns" -import { CreditCard } from "lucide-react" +import { CreditCard, Wallet } from "lucide-react" import { Suspense, useEffect, useState } from "react" import { Controller, useForm } from "react-hook-form" import { toast } from "sonner" @@ -63,7 +63,6 @@ const filterSchema = z if (data.created_at_start && data.created_at_end) { const start = new Date(data.created_at_start) const end = new Date(data.created_at_end) - if (end < start) { ctx.addIssue({ code: z.ZodIssueCode.custom, @@ -152,7 +151,6 @@ export default function BillingPage() { return (
- {/* 筛选表单 */}
{ setSkuProductCode(value as ProductCode) - // 同步到表单值 field.onChange(value) }} > @@ -316,16 +313,7 @@ export default function BillingPage() { type="button" variant="outline" onClick={() => { - reset({ - bill_no: "", - inner_no: "", - created_at_start: "", - created_at_end: "", - phone: "", - resource_no: "", - product_code: ProductCode.All, - sku_code: "all", - }) + reset() setSkuProductCode(ProductCode.All) setFilters({}) table.pagination.onPageChange(1) @@ -351,7 +339,6 @@ export default function BillingPage() { return (
- {/* 类型展示 */}
{bill.type === 1 && (
@@ -372,8 +359,6 @@ export default function BillingPage() {
)}
- - {/* 账单详情 */}
{bill.info}
) @@ -421,9 +406,46 @@ export default function BillingPage() { ) }, }, - // { header: "套餐名称", accessorKey: "info" }, { header: "账单号", accessorKey: "bill_no" }, - { header: "订单号", accessorKey: "trade.inner_no" }, + { + header: "订单号", + accessorKey: "trade.inner_no", + cell: ({ row }) => { + const bill = row.original + + return ( +
+
+ {bill.trade?.acquirer === 1 && ( +
+ + 支付宝 +
+ )} + {bill.trade?.acquirer === 2 && ( +
+ + 微信 +
+ )} + {bill.trade?.acquirer === 3 && ( +
+ + 银联 +
+ )} + {!bill.trade?.acquirer && ( +
+ + 余额 +
+ )} +
+
{bill.trade?.inner_no}
+
+ ) + }, + }, { header: "创建时间", accessorKey: "created_at", diff --git a/src/app/(root)/channel/page.tsx b/src/app/(root)/channel/page.tsx index 3b038b1..845edf6 100644 --- a/src/app/(root)/channel/page.tsx +++ b/src/app/(root)/channel/page.tsx @@ -33,9 +33,6 @@ const filterSchema = z batch_no: z.string().optional(), user_phone: z.string().optional(), resource_no: z.string().optional(), - filter_prov: z.string().optional(), - filter_city: z.string().optional(), - filter_isp: z.string().optional(), proxy_host: z.string().optional(), proxy_port: z.string().optional(), node_ip: z.string().optional(), @@ -51,7 +48,7 @@ const filterSchema = z ctx.addIssue({ code: z.ZodIssueCode.custom, message: "结束时间不能早于开始时间", - path: ["created_at_end"], + path: ["expired_at_end"], }) } } @@ -59,7 +56,6 @@ const filterSchema = z type FilterSchema = z.infer -// 运营商映射 const ispMap: Record = { 1: "电信", 2: "联通", @@ -75,9 +71,6 @@ export default function ChannelPage() { batch_no: "", user_phone: "", resource_no: "", - filter_prov: "", - filter_city: "", - filter_isp: "all", proxy_port: "", proxy_host: "", node_ip: "", @@ -109,7 +102,6 @@ export default function ChannelPage() { return (
- {/* 筛选表单 */}
)} /> - )} /> - )} /> - )} /> - )} /> - )} /> - )} /> - { - reset({ - batch_no: "", - user_phone: "", - resource_no: "", - filter_prov: "", - filter_city: "", - filter_isp: "all", - proxy_host: "", - proxy_port: "", - node_ip: "", - expired_at_start: "", - expired_at_end: "", - }) + reset() setFilters({}) table.pagination.onPageChange(1) }} @@ -279,18 +252,6 @@ export default function ChannelPage() { { header: "自动配置", - accessorFn: row => { - const prov = row.filter_prov - const city = row.filter_city - const isp = row.filter_isp - const parts = [] - if (prov && prov !== "all") parts.push(prov) - if (city && city !== "all") parts.push(city) - if (isp && isp !== "all") { - parts.push(ispMap[Number(isp)] || isp) - } - return parts.length > 0 ? parts.join(" / ") : "不限" - }, cell: ({ row }) => { const prov = row.original.filter_prov const city = row.original.filter_city @@ -312,11 +273,9 @@ export default function ChannelPage() { header: "网关地址", accessorKey: "host", cell: ({ row }) => { - const ip = row.original.host - const port = row.original.port return ( - {ip}:{port}{" "} + {row.original.host}:{row.original.port} ) }, @@ -325,11 +284,9 @@ export default function ChannelPage() { header: "认证方式", cell: ({ row }) => { const channel = row.original - const hasWhitelist = channel.whitelists && channel.whitelists.trim() !== "" const hasAuth = channel.username && channel.password - return (
{hasWhitelist ? ( diff --git a/src/app/(root)/cust/balanceDialog.tsx b/src/app/(root)/cust/balanceDialog.tsx index 88b00bd..7a85218 100644 --- a/src/app/(root)/cust/balanceDialog.tsx +++ b/src/app/(root)/cust/balanceDialog.tsx @@ -67,9 +67,7 @@ export function BalanceDialog({ const onSubmit = async (data: BalanceFormValues) => { if (!currentUser) return - setIsLoading(true) - try { const result = await getBalance({ user_id: currentUser.id, @@ -85,9 +83,8 @@ export function BalanceDialog({ toast.error(result.message || "修改余额失败") } } catch (error) { - const message = - error instanceof Error ? error.message : "网络错误,请稍后重试" - toast.error(message) + const message = error instanceof Error ? error.message : error + toast.error(`网络错误,请稍后重试: ${message}`) } finally { setIsLoading(false) } @@ -155,7 +152,7 @@ export function BalanceDialog({ 取消 diff --git a/src/app/(root)/cust/create.tsx b/src/app/(root)/cust/create.tsx index bf84c45..9041831 100644 --- a/src/app/(root)/cust/create.tsx +++ b/src/app/(root)/cust/create.tsx @@ -35,9 +35,12 @@ import type { ProductDiscount } from "@/models/product_discount" // 表单验证规则 const addUserSchema = z .object({ - username: z.string().min(1, "账号不能为空"), - password: z.string().min(6, "密码至少6位"), - confirmPassword: z.string().min(1, "请确认密码"), + username: z.string().optional(), + password: z + .string() + .optional() + .refine(val => !val || val.length >= 6, { message: "密码至少6位" }), + confirmPassword: z.string().optional(), phone: z.string().regex(/^1[3-9]\d{9}$/, "请输入正确的手机号格式"), email: z .string() @@ -53,10 +56,18 @@ const addUserSchema = z contact_qq: z.string().optional(), contact_wechat: z.string().optional(), }) - .refine(data => data.password === data.confirmPassword, { - message: "两次输入的密码不一致", - path: ["confirmPassword"], - }) + .refine( + data => { + if (data.password) { + return data.password === data.confirmPassword + } + return true + }, + { + message: "两次输入的密码不一致", + path: ["confirmPassword"], + }, + ) export type AddUserFormValues = z.infer @@ -147,9 +158,9 @@ export function AddUserDialog({ const onSubmit = handleSubmit(async data => { const payload = { - username: data.username, - password: data?.password, phone: data.phone, + username: data?.username, + password: data?.password, email: data?.email || "", name: data?.name, admin_id: data.admin_id ? Number(data.admin_id) : undefined, @@ -228,7 +239,10 @@ export function AddUserDialog({ control={control} render={({ field, fieldState }) => ( - 用户密码 + + 用户密码 + (选填) + (fetchUsers) - console.log(table, "客户管理table") const onFilter = handleSubmit(data => { const result: FilterValues = {} @@ -106,10 +105,6 @@ export default function UserPage() { table.refresh() }, [table]) - const handleAddUserSuccess = () => { - refreshTable() - } - return (
@@ -222,14 +217,7 @@ export default function UserPage() { type="button" variant="outline" onClick={() => { - reset({ - account: "", - name: "", - identified: "all", - enabled: "all", - created_at_start: "", - created_at_end: "", - }) + reset() setFilters({}) table.pagination.onPageChange(1) }} @@ -386,7 +374,7 @@ export default function UserPage() { ( - 用户名 * + 用户名 {fieldState.error?.message} @@ -258,7 +258,7 @@ export function UpdateDialog({ {fieldState.error?.message} diff --git a/src/app/(root)/discount/create.tsx b/src/app/(root)/discount/create.tsx index 8e6e8fe..d4f45bd 100644 --- a/src/app/(root)/discount/create.tsx +++ b/src/app/(root)/discount/create.tsx @@ -92,7 +92,7 @@ export function CreateDiscount(props: { onSuccess?: () => void }) { render={({ field, fieldState }) => ( - 代码 + 折扣 ( - 代码 + 折扣 { - return getPageProductSku({ page, size, product_id: props.selected }) - }, + (page: number, size: number) => + getPageProductSku({ page, size, product_id: props.selected }), [props.selected], ) diff --git a/src/app/(root)/proxy/nodes/page.tsx b/src/app/(root)/proxy/nodes/page.tsx index 408bf37..042f051 100644 --- a/src/app/(root)/proxy/nodes/page.tsx +++ b/src/app/(root)/proxy/nodes/page.tsx @@ -1,21 +1,21 @@ -'use client' -import {useState, useEffect} from 'react' -import Link from 'next/link' +"use client" +import Link from "next/link" +import { useEffect, useState } from "react" // 定义节点数据接口 interface ProxyNode { - id: string; - ipAddress: string; + id: string + ipAddress: string location: { - country: string; - region: string; - }; - type: string; - status: 'online' | 'offline' | 'warning'; - responseTime: number; - lastCheckTime: string; - pool: string; - isStatic: boolean; + country: string + region: string + } + type: string + status: "online" | "offline" | "warning" + responseTime: number + lastCheckTime: string + pool: string + isStatic: boolean } export type ProxyNodesPageProps = {} @@ -24,101 +24,101 @@ export default function ProxyNodesPage(props: ProxyNodesPageProps) { const [loading, setLoading] = useState(true) const [nodes, setNodes] = useState([]) - const [searchTerm, setSearchTerm] = useState('') - const [filterStatus, setFilterStatus] = useState('all') - const [filterType, setFilterType] = useState('all') - const [filterPool, setFilterPool] = useState('all') + const [searchTerm, setSearchTerm] = useState("") + const [filterStatus, setFilterStatus] = useState("all") + const [filterType, setFilterType] = useState("all") + const [filterPool, setFilterPool] = useState("all") // 模拟数据加载 useEffect(() => { setTimeout(() => { setNodes([ { - id: 'ip-1', - ipAddress: '203.45.167.82', - location: {country: '美国', region: '纽约'}, - type: '数据中心', - status: 'online', + id: "ip-1", + ipAddress: "203.45.167.82", + location: { country: "美国", region: "纽约" }, + type: "数据中心", + status: "online", responseTime: 126, - lastCheckTime: '2024-05-10 15:30:22', - pool: '北美专用池', + lastCheckTime: "2024-05-10 15:30:22", + pool: "北美专用池", isStatic: true, }, { - id: 'ip-2', - ipAddress: '185.72.193.54', - location: {country: '德国', region: '法兰克福'}, - type: '住宅', - status: 'online', + id: "ip-2", + ipAddress: "185.72.193.54", + location: { country: "德国", region: "法兰克福" }, + type: "住宅", + status: "online", responseTime: 158, - lastCheckTime: '2024-05-10 15:28:45', - pool: '欧洲高速池', + lastCheckTime: "2024-05-10 15:28:45", + pool: "欧洲高速池", isStatic: false, }, { - id: 'ip-3', - ipAddress: '118.96.244.105', - location: {country: '新加坡', region: '中心区'}, - type: '移动', - status: 'warning', + id: "ip-3", + ipAddress: "118.96.244.105", + location: { country: "新加坡", region: "中心区" }, + type: "移动", + status: "warning", responseTime: 312, - lastCheckTime: '2024-05-10 15:25:12', - pool: '亚太地区池', + lastCheckTime: "2024-05-10 15:25:12", + pool: "亚太地区池", isStatic: false, }, { - id: 'ip-4', - ipAddress: '45.178.29.6', - location: {country: '加拿大', region: '多伦多'}, - type: '数据中心', - status: 'online', + id: "ip-4", + ipAddress: "45.178.29.6", + location: { country: "加拿大", region: "多伦多" }, + type: "数据中心", + status: "online", responseTime: 143, - lastCheckTime: '2024-05-10 15:23:08', - pool: '北美专用池', + lastCheckTime: "2024-05-10 15:23:08", + pool: "北美专用池", isStatic: false, }, { - id: 'ip-5', - ipAddress: '79.114.83.201', - location: {country: '英国', region: '伦敦'}, - type: '住宅', - status: 'offline', + id: "ip-5", + ipAddress: "79.114.83.201", + location: { country: "英国", region: "伦敦" }, + type: "住宅", + status: "offline", responseTime: 0, - lastCheckTime: '2024-05-10 15:18:33', - pool: '欧洲高速池', + lastCheckTime: "2024-05-10 15:18:33", + pool: "欧洲高速池", isStatic: false, }, { - id: 'ip-6', - ipAddress: '164.83.219.47', - location: {country: '日本', region: '东京'}, - type: '住宅', - status: 'online', + id: "ip-6", + ipAddress: "164.83.219.47", + location: { country: "日本", region: "东京" }, + type: "住宅", + status: "online", responseTime: 87, - lastCheckTime: '2024-05-10 15:15:21', - pool: '亚太地区池', + lastCheckTime: "2024-05-10 15:15:21", + pool: "亚太地区池", isStatic: true, }, { - id: 'ip-7', - ipAddress: '221.67.93.143', - location: {country: '中国', region: '上海'}, - type: '移动', - status: 'online', + id: "ip-7", + ipAddress: "221.67.93.143", + location: { country: "中国", region: "上海" }, + type: "移动", + status: "online", responseTime: 104, - lastCheckTime: '2024-05-10 15:10:46', - pool: '亚太地区池', + lastCheckTime: "2024-05-10 15:10:46", + pool: "亚太地区池", isStatic: false, }, { - id: 'ip-8', - ipAddress: '37.209.148.72', - location: {country: '法国', region: '巴黎'}, - type: '数据中心', - status: 'warning', + id: "ip-8", + ipAddress: "37.209.148.72", + location: { country: "法国", region: "巴黎" }, + type: "数据中心", + status: "warning", responseTime: 276, - lastCheckTime: '2024-05-10 15:05:19', - pool: '欧洲高速池', + lastCheckTime: "2024-05-10 15:05:19", + pool: "欧洲高速池", isStatic: false, }, ]) @@ -129,16 +129,16 @@ export default function ProxyNodesPage(props: ProxyNodesPageProps) { // 过滤节点数据 const filteredNodes = nodes.filter(node => { return ( - (searchTerm === '' || + (searchTerm === "" || node.ipAddress.includes(searchTerm) || node.location.country.includes(searchTerm) || node.pool.includes(searchTerm)) && - (filterStatus === 'all' || - (filterStatus === 'online' && node.status === 'online') || - (filterStatus === 'offline' && node.status === 'offline') || - (filterStatus === 'warning' && node.status === 'warning')) && - (filterType === 'all' || node.type === filterType) && - (filterPool === 'all' || node.pool === filterPool) + (filterStatus === "all" || + (filterStatus === "online" && node.status === "online") || + (filterStatus === "offline" && node.status === "offline") || + (filterStatus === "warning" && node.status === "warning")) && + (filterType === "all" || node.type === filterType) && + (filterPool === "all" || node.pool === filterPool) ) }) @@ -151,25 +151,43 @@ export default function ProxyNodesPage(props: ProxyNodesPageProps) {

节点列表

-
-

查看和管理所有代理IP资源,支持多维度筛选

+

+ 查看和管理所有代理IP资源,支持多维度筛选 +

{/* 统计信息区域 - 色块风格 */} @@ -178,15 +196,25 @@ export default function ProxyNodesPage(props: ProxyNodesPageProps) {
- + + strokeLinecap="round" + strokeLinejoin="round" + strokeWidth={2} + d="M21 12a9 9 0 01-9 9m9-9a9 9 0 00-9-9m9 9H3m9 9a9 9 0 01-9-9m9 9c1.657 0 3-4.03 3-9s-1.343-9-3-9m0 18c-1.657 0-3-4.03-3-9s1.343-9 3-9" + />

总IP数量

-
152,487
+
+ 152,487 +
@@ -195,18 +223,30 @@ export default function ProxyNodesPage(props: ProxyNodesPageProps) {
- + + strokeLinecap="round" + strokeLinejoin="round" + strokeWidth={2} + d="M5 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" + />

在线IP

- 91% + + 91% + +
+
+ 138,954
-
138,954
@@ -215,16 +255,26 @@ export default function ProxyNodesPage(props: ProxyNodesPageProps) {
- + + strokeLinecap="round" + strokeLinejoin="round" + strokeWidth={2} + d="M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10" + />

IP池分布

- 5个地区 + + 5个地区 +
12
@@ -235,16 +285,26 @@ export default function ProxyNodesPage(props: ProxyNodesPageProps) {
- + + strokeLinecap="round" + strokeLinejoin="round" + strokeWidth={2} + d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" + />

异常IP

- 需检查 + + 需检查 +
1,205
@@ -285,7 +345,7 @@ export default function ProxyNodesPage(props: ProxyNodesPageProps) { setFilterType(e.target.value)} + onChange={e => setFilterType(e.target.value)} > @@ -307,7 +367,7 @@ export default function ProxyNodesPage(props: ProxyNodesPageProps) { setFilterStatus(e.target.value)} + onChange={e => setFilterStatus(e.target.value)} > @@ -174,11 +185,13 @@ export default function ProxyPoolsPage(props: ProxyPoolsPageProps) {
- + )} /> - )} /> - )} /> - )} /> -
-