时间显示到秒 & 表格之间的跳转页面

This commit is contained in:
Eamon
2026-05-13 14:57:27 +08:00
parent bc29a025b0
commit 284b0d6afe
25 changed files with 442 additions and 240 deletions

View File

@@ -2,7 +2,16 @@ import type { PageRecord } from "@/lib/api"
import type { User } from "@/models/user" import type { User } from "@/models/user"
import { callByUser } from "./base" import { callByUser } from "./base"
export async function getPageCusts(params: { page: number; size: number }) { export async function getPageCusts(params: {
page: number
size: number
account?: string
name?: string
identified?: boolean
enabled?: boolean
created_at_start?: Date
created_at_end?: Date
}) {
return callByUser<PageRecord<User>>("/api/admin/user/page", params) return callByUser<PageRecord<User>>("/api/admin/user/page", params)
} }
export async function updateCust(data: { export async function updateCust(data: {

View File

@@ -6,7 +6,6 @@ import {
ChevronsRight, ChevronsRight,
CircleDollarSign, CircleDollarSign,
ClipboardList, ClipboardList,
ComputerIcon,
ContactRound, ContactRound,
DollarSign, DollarSign,
DoorClosedIcon, DoorClosedIcon,
@@ -24,7 +23,7 @@ import {
} from "lucide-react" } from "lucide-react"
import Link from "next/link" import Link from "next/link"
import { usePathname } from "next/navigation" import { usePathname } from "next/navigation"
import { createContext, type ReactNode, Suspense, useContext, useState } from "react" import { createContext, type ReactNode, useContext, useState } from "react"
import { twJoin } from "tailwind-merge" import { twJoin } from "tailwind-merge"
import { Auth } from "@/components/auth" import { Auth } from "@/components/auth"
import { Button } from "@/components/ui/button" import { Button } from "@/components/ui/button"
@@ -307,7 +306,7 @@ export default function Navigation() {
)} )}
> >
{/*Logo 区域 */} {/*Logo 区域 */}
<Suspense><Logo collapsed={collapsed} /></Suspense> <Logo collapsed={collapsed} />
{/* Navigation Menu */} {/* Navigation Menu */}
<ScrollArea className="flex-1 py-3 overflow-hidden"> <ScrollArea className="flex-1 py-3 overflow-hidden">

View File

@@ -1,10 +1,15 @@
"use client"
import { ComputerIcon } from "lucide-react" import { ComputerIcon } from "lucide-react"
import { useEffect, useState } from "react"
import { getNodeEnv } from "@/actions/env" import { getNodeEnv } from "@/actions/env"
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils"
export default async function Logo(props: { collapsed: boolean }) { export default function Logo(props: { collapsed: boolean }) {
const env = await getNodeEnv() const [env, setEnv] = useState<string>("")
useEffect(() => {
getNodeEnv().then(setEnv)
}, [])
return ( return (
<div <div
className={cn( className={cn(

View File

@@ -18,11 +18,9 @@ import type { Admin } from "@/models/admin"
export default function Appbar(props: { admin: Admin }) { export default function Appbar(props: { admin: Admin }) {
const router = useRouter() const router = useRouter()
const [showDropdown, setShowDropdown] = useState(false) const [showDropdown, setShowDropdown] = useState(false)
const [showNotifications, setShowNotifications] = useState(false)
const pathname = usePathname() const pathname = usePathname()
const dropdownRef = useRef<HTMLDivElement>(null) const dropdownRef = useRef<HTMLDivElement>(null)
const notificationRef = useRef<HTMLDivElement>(null)
// 处理点击外部关闭下拉菜单 // 处理点击外部关闭下拉菜单
useEffect(() => { useEffect(() => {
@@ -33,12 +31,6 @@ export default function Appbar(props: { admin: Admin }) {
) { ) {
setShowDropdown(false) setShowDropdown(false)
} }
if (
notificationRef.current &&
!notificationRef.current.contains(event.target as Node)
) {
setShowNotifications(false)
}
} }
document.addEventListener("mousedown", handleClickOutside) document.addEventListener("mousedown", handleClickOutside)
@@ -51,7 +43,7 @@ export default function Appbar(props: { admin: Admin }) {
const filteredPaths = paths.filter(path => !hiddenSegments.includes(path)) const filteredPaths = paths.filter(path => !hiddenSegments.includes(path))
const breadcrumbs = [ const breadcrumbs = [
{ path: "/", label: "首页" }, { path: "/", label: "首页" },
...filteredPaths.map((path, index) => { ...filteredPaths.map(path => {
const originalIndex = paths.findIndex(p => p === path) const originalIndex = paths.findIndex(p => p === path)
const url = `/${paths.slice(0, originalIndex + 1).join("/")}` const url = `/${paths.slice(0, originalIndex + 1).join("/")}`
const label = getBreadcrumbLabel(path) const label = getBreadcrumbLabel(path)

View File

@@ -1,19 +1,15 @@
"use client" "use client"
import { zodResolver } from "@hookform/resolvers/zod" import { zodResolver } from "@hookform/resolvers/zod"
import { format } from "date-fns" import { format } from "date-fns"
import { Suspense, useCallback, useState } from "react" import { useRouter, useSearchParams } from "next/navigation"
import { Suspense, useCallback } from "react"
import { Controller, useForm } from "react-hook-form" import { Controller, useForm } from "react-hook-form"
import z from "zod" import z from "zod"
import { getPageBalance } from "@/actions/balance" import { getPageBalance } from "@/actions/balance"
import { DataTable, useDataTable } from "@/components/data-table" import { DataTable, useDataTable } from "@/components/data-table"
import { Page } from "@/components/page" import { Page } from "@/components/page"
import { Button } from "@/components/ui/button" import { Button } from "@/components/ui/button"
import { import { Field, FieldError, FieldLabel } from "@/components/ui/field"
Field,
FieldError,
FieldGroup,
FieldLabel,
} from "@/components/ui/field"
import { Input } from "@/components/ui/input" import { Input } from "@/components/ui/input"
import type { Balance } from "@/models/balance" import type { Balance } from "@/models/balance"
@@ -51,12 +47,14 @@ const filterSchema = z
type FormValues = z.infer<typeof filterSchema> type FormValues = z.infer<typeof filterSchema>
export default function BalancePage() { export default function BalancePage() {
const [filters, setFilters] = useState<FilterValues>({}) const searchParams = useSearchParams()
const { control, handleSubmit, reset } = useForm<FormValues>({ const billNo = searchParams.get("bill_no")
const router = useRouter()
const { control, handleSubmit, reset, getValues } = useForm<FormValues>({
resolver: zodResolver(filterSchema), resolver: zodResolver(filterSchema),
defaultValues: { defaultValues: {
phone: "", phone: "",
bill_no: "", bill_no: billNo || "",
admin_id: "", admin_id: "",
created_at_start: "", created_at_start: "",
created_at_end: "", created_at_end: "",
@@ -64,21 +62,23 @@ export default function BalancePage() {
}) })
const fetchUsers = useCallback( const fetchUsers = useCallback(
(page: number, size: number) => getPageBalance({ page, size, ...filters }), (page: number, size: number) => {
[filters], const result: FilterValues = {}
const filters = getValues()
if (filters.phone?.trim()) result.user_phone = filters.phone.trim()
if (filters.bill_no?.trim()) result.bill_no = filters.bill_no.trim()
if (filters.created_at_start)
result.created_at_start = new Date(filters.created_at_start)
if (filters.created_at_end)
result.created_at_end = new Date(filters.created_at_end)
return getPageBalance({ page, size, ...result })
},
[getValues],
) )
const table = useDataTable<Balance>(fetchUsers) const table = useDataTable<Balance>(fetchUsers)
const onFilter = handleSubmit(data => { const onFilter = handleSubmit(() => {
const result: FilterValues = {}
if (data.phone?.trim()) result.user_phone = data.phone.trim()
if (data.bill_no?.trim()) result.bill_no = data.bill_no.trim()
if (data.created_at_start)
result.created_at_start = new Date(data.created_at_start)
if (data.created_at_end)
result.created_at_end = new Date(data.created_at_end)
setFilters(result)
table.pagination.onPageChange(1) table.pagination.onPageChange(1)
}) })
@@ -148,8 +148,14 @@ export default function BalancePage() {
type="button" type="button"
variant="outline" variant="outline"
onClick={() => { onClick={() => {
reset() reset({
setFilters({}) phone: "",
bill_no: "",
admin_id: "",
created_at_start: "",
created_at_end: "",
})
router.replace("./balance")
table.pagination.onPageChange(1) table.pagination.onPageChange(1)
}} }}
> >
@@ -218,7 +224,10 @@ export default function BalancePage() {
header: "创建时间", header: "创建时间",
accessorKey: "created_at", accessorKey: "created_at",
cell: ({ row }) => cell: ({ row }) =>
format(new Date(row.original.created_at), "yyyy-MM-dd HH:mm"), format(
new Date(row.original.created_at),
"yyyy-MM-dd HH:mm:ss",
),
}, },
]} ]}
/> />

View File

@@ -1,7 +1,9 @@
"use client" "use client"
import { zodResolver } from "@hookform/resolvers/zod" import { zodResolver } from "@hookform/resolvers/zod"
import { format } from "date-fns" import { format } from "date-fns"
import { Suspense, useState } from "react" import Link from "next/link"
import { useRouter, useSearchParams } from "next/navigation"
import { Suspense } from "react"
import { Controller, useForm } from "react-hook-form" import { Controller, useForm } from "react-hook-form"
import { z } from "zod" import { z } from "zod"
import { getPageBatch } from "@/actions/batch" import { getPageBatch } from "@/actions/batch"
@@ -64,14 +66,17 @@ const filterSchema = z
type FilterSchema = z.infer<typeof filterSchema> type FilterSchema = z.infer<typeof filterSchema>
export default function BatchPage() { export default function BatchPage() {
const [filters, setFilters] = useState<APIFilterParams>({}) const searchParams = useSearchParams()
const resourceNo = searchParams.get("resource_no")
const { control, handleSubmit, reset } = useForm<FilterSchema>({ const batchNo = searchParams.get("batch_no")
const router = useRouter()
const { control, handleSubmit, reset, getValues } = useForm<FilterSchema>({
resolver: zodResolver(filterSchema), resolver: zodResolver(filterSchema),
defaultValues: { defaultValues: {
user_phone: "", user_phone: "",
batch_no: "", batch_no: batchNo || "",
prov: "", prov: "",
resource_no: resourceNo || "",
city: "", city: "",
isp: "all", isp: "all",
created_at_start: "", created_at_start: "",
@@ -79,24 +84,25 @@ export default function BatchPage() {
}, },
}) })
const table = useDataTable<Batch>((page, size) => const table = useDataTable<Batch>((page, size) => {
getPageBatch({ page, size, ...filters }),
)
const onFilter = handleSubmit(data => {
const result: APIFilterParams = {} const result: APIFilterParams = {}
if (data.user_phone?.trim()) result.user_phone = data.user_phone.trim() const filters = getValues()
if (data.batch_no?.trim()) result.batch_no = data.batch_no.trim() if (filters.user_phone?.trim())
if (data.resource_no?.trim()) result.resource_no = data.resource_no.trim() result.user_phone = filters.user_phone.trim()
if (data.prov?.trim()) result.prov = data.prov.trim() if (filters.batch_no?.trim()) result.batch_no = filters.batch_no.trim()
if (data.city?.trim()) result.city = data.city.trim() if (filters.resource_no?.trim())
if (data.isp && data.isp !== "all") result.isp = data.isp result.resource_no = filters.resource_no.trim()
if (data.created_at_start) if (filters.prov?.trim()) result.prov = filters.prov.trim()
result.created_at_start = new Date(data.created_at_start) if (filters.city?.trim()) result.city = filters.city.trim()
if (data.created_at_end) if (filters.isp && filters.isp !== "all") result.isp = filters.isp
result.created_at_end = new Date(data.created_at_end) if (filters.created_at_start)
result.created_at_start = new Date(filters.created_at_start)
if (filters.created_at_end)
result.created_at_end = new Date(filters.created_at_end)
return getPageBatch({ page, size, ...result })
})
setFilters(result) const onFilter = handleSubmit(() => {
table.pagination.onPageChange(1) table.pagination.onPageChange(1)
}) })
@@ -237,8 +243,17 @@ export default function BatchPage() {
type="button" type="button"
variant="outline" variant="outline"
onClick={() => { onClick={() => {
reset() reset({
setFilters({}) user_phone: "",
batch_no: "",
prov: "",
resource_no: "",
city: "",
isp: "all",
created_at_start: "",
created_at_end: "",
})
router.replace("./batch")
table.pagination.onPageChange(1) table.pagination.onPageChange(1)
}} }}
> >
@@ -255,7 +270,23 @@ export default function BatchPage() {
header: "会员号", header: "会员号",
accessorFn: row => row.user?.phone || "", accessorFn: row => row.user?.phone || "",
}, },
{ header: "套餐号", accessorKey: "resource.resource_no" }, {
header: "套餐号",
accessorKey: "resource.resource_no",
cell: ({ row }) => {
const resourceNo = row.original.resource?.resource_no
return (
<Link
href={`/channel?resource_no=${resourceNo}`}
target="_blank"
rel="noopener noreferrer"
className="text-blue-600 hover:underline cursor-pointer"
>
{resourceNo}
</Link>
)
},
},
{ header: "提取编号", accessorKey: "batch_no" }, { header: "提取编号", accessorKey: "batch_no" },
{ header: "省份", accessorKey: "prov" }, { header: "省份", accessorKey: "prov" },
{ header: "城市", accessorKey: "city" }, { header: "城市", accessorKey: "city" },
@@ -266,7 +297,7 @@ export default function BatchPage() {
header: "提取时间", header: "提取时间",
accessorKey: "time", accessorKey: "time",
cell: ({ row }) => cell: ({ row }) =>
format(new Date(row.original.time), "yyyy-MM-dd HH:mm"), format(new Date(row.original.time), "yyyy-MM-dd HH:mm:ss"),
}, },
]} ]}
/> />

View File

@@ -2,6 +2,8 @@
import { zodResolver } from "@hookform/resolvers/zod" import { zodResolver } from "@hookform/resolvers/zod"
import { format } from "date-fns" import { format } from "date-fns"
import { CreditCard, Wallet } from "lucide-react" import { CreditCard, Wallet } from "lucide-react"
import Link from "next/link"
import { useRouter, useSearchParams } from "next/navigation"
import { Suspense, useEffect, useState } from "react" import { Suspense, useEffect, useState } from "react"
import { Controller, useForm } from "react-hook-form" import { Controller, useForm } from "react-hook-form"
import { toast } from "sonner" import { toast } from "sonner"
@@ -77,17 +79,21 @@ const filterSchema = z
type FilterSchema = z.infer<typeof filterSchema> type FilterSchema = z.infer<typeof filterSchema>
export default function BillingPage() { export default function BillingPage() {
const [filters, setFilters] = useState<FilterValues>({}) const searchParams = useSearchParams()
const innerNo = searchParams.get("inner_no")
console.log(innerNo, "innerNo")
const [skuOptions, setSkuOptions] = useState<SkuOption[]>([]) const [skuOptions, setSkuOptions] = useState<SkuOption[]>([])
const [loading, setLoading] = useState(true) const [loading, setLoading] = useState(true)
const [skuProductCode, setSkuProductCode] = useState<ProductCode>( const [skuProductCode, setSkuProductCode] = useState<ProductCode>(
ProductCode.All, ProductCode.All,
) )
const { control, handleSubmit, reset } = useForm<FilterSchema>({ const router = useRouter()
const { control, handleSubmit, reset, getValues } = useForm<FilterSchema>({
resolver: zodResolver(filterSchema), resolver: zodResolver(filterSchema),
defaultValues: { defaultValues: {
bill_no: "", bill_no: "",
inner_no: "", inner_no: innerNo || "",
created_at_start: "", created_at_start: "",
created_at_end: "", created_at_end: "",
phone: "", phone: "",
@@ -125,31 +131,58 @@ export default function BillingPage() {
}) })
}, [skuProductCode]) }, [skuProductCode])
const table = useDataTable<Billing>((page, size) => const loadData = (page: number, size: number) => {
getPageBill({ page, size, ...filters }),
)
const onFilter = handleSubmit(data => {
const result: FilterValues = {} const result: FilterValues = {}
if (data.phone?.trim()) result.user_phone = data.phone.trim()
if (data.inner_no?.trim()) result.trade_inner_no = data.inner_no.trim()
if (data.bill_no?.trim()) result.bill_no = data.bill_no.trim()
if (data.resource_no?.trim()) result.resource_no = data.resource_no.trim()
if (data.product_code && data.product_code !== ProductCode.All) {
result.product_code = data.product_code
}
if (data.sku_code && data.sku_code !== "all") {
result.sku_code = data.sku_code
}
if (data.created_at_start)
result.created_at_start = new Date(data.created_at_start)
if (data.created_at_end)
result.created_at_end = new Date(data.created_at_end)
setFilters(result) if (innerNo) {
result.trade_inner_no = innerNo
} else {
const filters = getValues()
if (filters.phone?.trim()) result.user_phone = filters.phone.trim()
if (filters.inner_no?.trim())
result.trade_inner_no = filters.inner_no.trim()
if (filters.bill_no?.trim()) result.bill_no = filters.bill_no.trim()
if (filters.resource_no?.trim())
result.resource_no = filters.resource_no.trim()
if (filters.product_code && filters.product_code !== ProductCode.All) {
result.product_code = filters.product_code
}
if (filters.sku_code && filters.sku_code !== "all") {
result.sku_code = filters.sku_code
}
if (filters.created_at_start)
result.created_at_start = new Date(filters.created_at_start)
if (filters.created_at_end)
result.created_at_end = new Date(filters.created_at_end)
}
return getPageBill({
page,
size,
...result,
})
}
const clearFilter = () => {
router.replace("/billing")
reset({
bill_no: "",
inner_no: "",
created_at_start: "",
created_at_end: "",
phone: "",
resource_no: "",
sku_code: "all",
product_code: "",
})
setSkuProductCode(ProductCode.All)
table.pagination.onPageChange(1)
}
const table = useDataTable<Billing>(loadData)
const onFilter = handleSubmit(() => {
table.pagination.onPageChange(1) table.pagination.onPageChange(1)
}) })
return ( return (
<Page> <Page>
<form onSubmit={onFilter} className="bg-card p-4 rounded-lg"> <form onSubmit={onFilter} className="bg-card p-4 rounded-lg">
@@ -310,16 +343,7 @@ export default function BillingPage() {
<FieldGroup className="flex-row justify-start mt-4 gap-2"> <FieldGroup className="flex-row justify-start mt-4 gap-2">
<Button type="submit"></Button> <Button type="submit"></Button>
<Button <Button type="button" variant="outline" onClick={clearFilter}>
type="button"
variant="outline"
onClick={() => {
reset()
setSkuProductCode(ProductCode.All)
setFilters({})
table.pagination.onPageChange(1)
}}
>
</Button> </Button>
</FieldGroup> </FieldGroup>
@@ -333,9 +357,31 @@ export default function BillingPage() {
header: "创建时间", header: "创建时间",
accessorKey: "created_at", accessorKey: "created_at",
cell: ({ row }) => cell: ({ row }) =>
format(new Date(row.original.created_at), "yyyy-MM-dd HH:mm"), format(
new Date(row.original.created_at),
"yyyy-MM-dd HH:mm:ss",
),
},
{
header: "套餐号",
accessorKey: "resource.resource_no",
cell: ({ row }) => {
const resource_no = row.original.resource?.resource_no
return resource_no ? (
<Link
href={`/resources?resource_no=${resource_no}`}
target="_blank"
rel="noopener noreferrer"
className="text-blue-600 hover:underline cursor-pointer"
>
{resource_no}
</Link>
) : (
<span></span>
)
},
}, },
{ header: "套餐号", accessorKey: "resource.resource_no" },
{ header: "会员号", accessorFn: row => row.user?.phone || "" }, { header: "会员号", accessorFn: row => row.user?.phone || "" },
{ {
header: "账单详情", header: "账单详情",
@@ -412,7 +458,23 @@ export default function BillingPage() {
) )
}, },
}, },
{ header: "账单号", accessorKey: "bill_no" }, {
header: "账单号",
accessorKey: "bill_no",
cell: ({ row }) => {
const billNo = row.original.bill_no
return (
<Link
href={`./balance?bill_no=${billNo}`}
target="_blank"
rel="noopener noreferrer"
className="text-blue-600 hover:underline cursor-pointer"
>
{billNo}
</Link>
)
},
},
{ {
header: "订单号", header: "订单号",
accessorKey: "trade.inner_no", accessorKey: "trade.inner_no",

View File

@@ -1,7 +1,9 @@
"use client" "use client"
import { zodResolver } from "@hookform/resolvers/zod" import { zodResolver } from "@hookform/resolvers/zod"
import { format } from "date-fns" import { format } from "date-fns"
import { Suspense, useState } from "react" import Link from "next/link"
import { useRouter, useSearchParams } from "next/navigation"
import { Suspense } from "react"
import { Controller, useForm } from "react-hook-form" import { Controller, useForm } from "react-hook-form"
import { z } from "zod" import { z } from "zod"
import { getPageChannel } from "@/actions/channel" import { getPageChannel } from "@/actions/channel"
@@ -64,14 +66,15 @@ const ispMap: Record<number, string> = {
} }
export default function ChannelPage() { export default function ChannelPage() {
const [filters, setFilters] = useState<FilterValues>({}) const searchParams = useSearchParams()
const resourceNo = searchParams.get("resource_no")
const { control, handleSubmit, reset } = useForm<FilterSchema>({ const router = useRouter()
const { control, handleSubmit, reset, getValues } = useForm<FilterSchema>({
resolver: zodResolver(filterSchema), resolver: zodResolver(filterSchema),
defaultValues: { defaultValues: {
batch_no: "", batch_no: "",
user_phone: "", user_phone: "",
resource_no: "", resource_no: resourceNo || "",
proxy_port: "", proxy_port: "",
proxy_host: "", proxy_host: "",
node_ip: "", node_ip: "",
@@ -80,24 +83,26 @@ export default function ChannelPage() {
}, },
}) })
const table = useDataTable<Channel>((page, size) => const table = useDataTable<Channel>((page, size) => {
getPageChannel({ page, size, ...filters }),
)
const onFilter = handleSubmit(data => {
const result: FilterValues = {} const result: FilterValues = {}
if (data.batch_no?.trim()) result.batch_no = data.batch_no.trim() const filters = getValues()
if (data.user_phone?.trim()) result.user_phone = data.user_phone.trim() if (filters.batch_no?.trim()) result.batch_no = filters.batch_no.trim()
if (data.resource_no?.trim()) result.resource_no = data.resource_no.trim() if (filters.user_phone?.trim())
if (data.proxy_host?.trim()) result.proxy_host = data.proxy_host.trim() result.user_phone = filters.user_phone.trim()
if (data.proxy_port?.trim()) if (filters.resource_no?.trim())
result.proxy_port = Number(data.proxy_port.trim()) result.resource_no = filters.resource_no.trim()
if (data.node_ip?.trim()) result.node_ip = data.node_ip.trim() if (filters.proxy_host?.trim())
if (data.expired_at_start) result.proxy_host = filters.proxy_host.trim()
result.expired_at_start = new Date(data.expired_at_start) if (filters.proxy_port) result.proxy_port = Number(filters.proxy_port)
if (data.expired_at_end) if (filters.node_ip?.trim()) result.node_ip = filters.node_ip.trim()
result.expired_at_end = new Date(data.expired_at_end) if (filters.expired_at_start)
setFilters(result) result.expired_at_start = new Date(filters.expired_at_start)
if (filters.expired_at_end)
result.expired_at_end = new Date(filters.expired_at_end)
return getPageChannel({ page, size, ...result })
})
const onFilter = handleSubmit(() => {
table.pagination.onPageChange(1) table.pagination.onPageChange(1)
}) })
@@ -225,8 +230,17 @@ export default function ChannelPage() {
type="button" type="button"
variant="outline" variant="outline"
onClick={() => { onClick={() => {
reset() reset({
setFilters({}) batch_no: "",
user_phone: "",
resource_no: "",
proxy_port: "",
proxy_host: "",
node_ip: "",
expired_at_start: "",
expired_at_end: "",
})
router.replace("./channel")
table.pagination.onPageChange(1) table.pagination.onPageChange(1)
}} }}
> >
@@ -244,7 +258,23 @@ export default function ChannelPage() {
accessorFn: row => row.user?.phone || "-", accessorFn: row => row.user?.phone || "-",
}, },
{ header: "套餐号", accessorKey: "resource.resource_no" }, { header: "套餐号", accessorKey: "resource.resource_no" },
{ header: "提取编号", accessorKey: "batch_no" }, {
header: "提取编号",
accessorKey: "batch_no",
cell: ({ row }) => {
const batchNo = row.original.batch_no
return (
<Link
href={`./batch?batch_no=${batchNo}`}
target="_blank"
rel="noopener noreferrer"
className="text-blue-600 hover:underline cursor-pointer"
>
{batchNo}
</Link>
)
},
},
{ {
header: "节点", header: "节点",
accessorFn: row => row.ip || row.edge_ref || row.edge_id, accessorFn: row => row.ip || row.edge_ref || row.edge_id,
@@ -318,7 +348,10 @@ export default function ChannelPage() {
header: "过期时间", header: "过期时间",
accessorKey: "expired_at", accessorKey: "expired_at",
cell: ({ row }) => cell: ({ row }) =>
format(new Date(row.original.expired_at), "yyyy-MM-dd HH:mm"), format(
new Date(row.original.expired_at),
"yyyy-MM-dd HH:mm:ss",
),
}, },
]} ]}
/> />

View File

@@ -213,7 +213,10 @@ export default function BalancePage() {
header: "创建时间", header: "创建时间",
accessorKey: "created_at", accessorKey: "created_at",
cell: ({ row }) => cell: ({ row }) =>
format(new Date(row.original.created_at), "yyyy-MM-dd HH:mm"), format(
new Date(row.original.created_at),
"yyyy-MM-dd HH:mm:ss",
),
}, },
]} ]}
/> />

View File

@@ -261,7 +261,7 @@ export default function BatchPage() {
header: "提取时间", header: "提取时间",
accessorKey: "time", accessorKey: "time",
cell: ({ row }) => cell: ({ row }) =>
format(new Date(row.original.time), "yyyy-MM-dd HH:mm"), format(new Date(row.original.time), "yyyy-MM-dd HH:mm:ss"),
}, },
]} ]}
/> />

View File

@@ -335,7 +335,10 @@ export default function BillingPage() {
header: "创建时间", header: "创建时间",
accessorKey: "created_at", accessorKey: "created_at",
cell: ({ row }) => cell: ({ row }) =>
format(new Date(row.original.created_at), "yyyy-MM-dd HH:mm"), format(
new Date(row.original.created_at),
"yyyy-MM-dd HH:mm:ss",
),
}, },
{ header: "套餐号", accessorKey: "resource.resource_no" }, { header: "套餐号", accessorKey: "resource.resource_no" },
{ {

View File

@@ -319,7 +319,10 @@ export default function ChannelPage() {
header: "过期时间", header: "过期时间",
accessorKey: "expired_at", accessorKey: "expired_at",
cell: ({ row }) => cell: ({ row }) =>
format(new Date(row.original.expired_at), "yyyy-MM-dd HH:mm"), format(
new Date(row.original.expired_at),
"yyyy-MM-dd HH:mm:ss",
),
}, },
]} ]}
/> />

View File

@@ -99,7 +99,7 @@ export default function CouponPage() {
header: "发放时间", header: "发放时间",
accessorKey: "issued_at", accessorKey: "issued_at",
// cell: ({ row }) => // cell: ({ row }) =>
// format(new Date(row.original.issued_at), "yyyy-MM-dd HH:mm"), // format(new Date(row.original.issued_at), "yyyy-MM-dd HH:mm:ss"),
}, },
{ {
header: "备注", header: "备注",

View File

@@ -150,7 +150,10 @@ export default function UserQueryPage() {
header: "创建时间", header: "创建时间",
accessorKey: "created_at", accessorKey: "created_at",
cell: ({ row }) => cell: ({ row }) =>
format(new Date(row.original.created_at), "yyyy-MM-dd HH:mm"), format(
new Date(row.original.created_at),
"yyyy-MM-dd HH:mm:ss",
),
}, },
{ {
header: "客户来源", header: "客户来源",
@@ -215,7 +218,7 @@ export default function UserQueryPage() {
row.original.last_login row.original.last_login
? format( ? format(
new Date(row.original.last_login), new Date(row.original.last_login),
"yyyy-MM-dd HH:mm", "yyyy-MM-dd HH:mm:ss",
) )
: "", : "",
}, },

View File

@@ -158,7 +158,7 @@ function ExpireBadge({ expireAt }: { expireAt: Date | null | undefined }) {
// 格式化日期 // 格式化日期
function formatDateTime(date: Date | null | undefined) { function formatDateTime(date: Date | null | undefined) {
if (!date) return "-" if (!date) return "-"
return format(date, "yyyy-MM-dd HH:mm") return format(date, "yyyy-MM-dd HH:mm:ss")
} }
// 计算今日使用量 // 计算今日使用量

View File

@@ -252,7 +252,10 @@ export default function TradePage() {
header: "创建时间", header: "创建时间",
accessorKey: "created_at", accessorKey: "created_at",
cell: ({ row }) => cell: ({ row }) =>
format(new Date(row.original.created_at), "yyyy-MM-dd HH:mm"), format(
new Date(row.original.created_at),
"yyyy-MM-dd HH:mm:ss",
),
}, },
{ {
header: "订单号", header: "订单号",

View File

@@ -84,11 +84,14 @@ export default function CouponPage() {
if (coupon.expire_type === 2 && coupon.expire_in) { if (coupon.expire_type === 2 && coupon.expire_in) {
const expireDate = new Date(coupon.created_at) const expireDate = new Date(coupon.created_at)
expireDate.setDate(expireDate.getDate() + coupon.expire_in) expireDate.setDate(expireDate.getDate() + coupon.expire_in)
return format(expireDate, "yyyy-MM-dd HH:mm") return format(expireDate, "yyyy-MM-dd HH:mm:ss")
} }
if (coupon.expire_type === 1 && coupon.expire_at) { if (coupon.expire_type === 1 && coupon.expire_at) {
return format(new Date(coupon.expire_at), "yyyy-MM-dd HH:mm") return format(
new Date(coupon.expire_at),
"yyyy-MM-dd HH:mm:ss",
)
} }
return <span></span> return <span></span>
}, },
@@ -97,7 +100,10 @@ export default function CouponPage() {
header: "创建时间", header: "创建时间",
accessorKey: "created_at", accessorKey: "created_at",
cell: ({ row }) => cell: ({ row }) =>
format(new Date(row.original.created_at), "yyyy-MM-dd HH:mm"), format(
new Date(row.original.created_at),
"yyyy-MM-dd HH:mm:ss",
),
}, },
{ {
id: "action", id: "action",

View File

@@ -308,7 +308,7 @@ export function ReleaseCoupon(props: {
{user.last_login {user.last_login
? format( ? format(
new Date(user.last_login), new Date(user.last_login),
"yyyy-MM-dd HH:mm", "yyyy-MM-dd HH:mm:ss",
) )
: "-"} : "-"}
</span> </span>

View File

@@ -3,7 +3,7 @@
import { zodResolver } from "@hookform/resolvers/zod" import { zodResolver } from "@hookform/resolvers/zod"
import { format } from "date-fns" import { format } from "date-fns"
import { useRouter } from "next/navigation" import { useRouter } from "next/navigation"
import { Suspense, useCallback, useState } from "react" import { Suspense, useCallback } from "react"
import { Controller, useForm } from "react-hook-form" import { Controller, useForm } from "react-hook-form"
import { z } from "zod" import { z } from "zod"
import { getPageCusts } from "@/actions/cust" import { getPageCusts } from "@/actions/cust"
@@ -82,9 +82,8 @@ const filterSchema = z
type FormValues = z.infer<typeof filterSchema> type FormValues = z.infer<typeof filterSchema>
export default function CustPage() { export default function CustPage() {
const [filters, setFilters] = useState<FilterValues>({})
const router = useRouter() const router = useRouter()
const { control, handleSubmit, reset } = useForm<FormValues>({ const { control, handleSubmit, reset, getValues } = useForm<FormValues>({
resolver: zodResolver(filterSchema), resolver: zodResolver(filterSchema),
defaultValues: { defaultValues: {
account: "", account: "",
@@ -97,21 +96,27 @@ export default function CustPage() {
}) })
const fetchUsers = useCallback( const fetchUsers = useCallback(
(page: number, size: number) => getPageCusts({ page, size, ...filters }), (page: number, size: number) => {
[filters], const result: FilterValues = {}
const filters = getValues()
if (filters.account?.trim()) result.account = filters.account.trim()
if (filters.name?.trim()) result.name = filters.name.trim()
if (filters.identified && filters.identified !== "all")
result.identified = filters.identified === "1"
if (filters.enabled && filters.enabled !== "all")
result.enabled = filters.enabled === "1"
if (filters.created_at_start)
result.created_at_start = new Date(filters.created_at_start)
if (filters.created_at_end)
result.created_at_end = new Date(filters.created_at_end)
return getPageCusts({ page, size, ...result })
},
[getValues],
) )
const table = useDataTable<User>(fetchUsers) const table = useDataTable<User>(fetchUsers)
const onFilter = handleSubmit(data => { const onFilter = handleSubmit(() => {
const result: FilterValues = {}
if (data.account?.trim()) result.account = data.account.trim()
if (data.name?.trim()) result.name = data.name.trim()
if (data.identified && data.identified !== "all")
result.identified = data.identified === "1"
if (data.enabled && data.enabled !== "all")
result.enabled = data.enabled === "1"
setFilters(result)
table.pagination.onPageChange(1) table.pagination.onPageChange(1)
}) })
@@ -219,8 +224,14 @@ export default function CustPage() {
type="button" type="button"
variant="outline" variant="outline"
onClick={() => { onClick={() => {
reset() reset({
setFilters({}) account: "",
name: "",
identified: "all",
enabled: "all",
created_at_start: "",
created_at_end: "",
})
table.pagination.onPageChange(1) table.pagination.onPageChange(1)
}} }}
> >
@@ -240,7 +251,10 @@ export default function CustPage() {
header: "创建时间", header: "创建时间",
accessorKey: "created_at", accessorKey: "created_at",
cell: ({ row }) => cell: ({ row }) =>
format(new Date(row.original.created_at), "yyyy-MM-dd HH:mm"), format(
new Date(row.original.created_at),
"yyyy-MM-dd HH:mm:ss",
),
}, },
// { header: "邮箱", accessorKey: "email" }, // { header: "邮箱", accessorKey: "email" },
{ {
@@ -311,7 +325,7 @@ export default function CustPage() {
row.original.last_login row.original.last_login
? format( ? format(
new Date(row.original.last_login), new Date(row.original.last_login),
"yyyy-MM-dd HH:mm", "yyyy-MM-dd HH:mm:ss",
) )
: "", : "",
}, },

View File

@@ -49,13 +49,19 @@ export default function DiscountPage() {
header: "创建时间", header: "创建时间",
accessorKey: "created_at", accessorKey: "created_at",
cell: ({ row }) => cell: ({ row }) =>
format(new Date(row.original.created_at), "yyyy-MM-dd HH:mm"), format(
new Date(row.original.created_at),
"yyyy-MM-dd HH:mm:ss",
),
}, },
{ {
header: "更新时间", header: "更新时间",
accessorKey: "updated_at", accessorKey: "updated_at",
cell: ({ row }) => cell: ({ row }) =>
format(new Date(row.original.updated_at), "yyyy-MM-dd HH:mm"), format(
new Date(row.original.updated_at),
"yyyy-MM-dd HH:mm:ss",
),
}, },
{ {
id: "action", id: "action",

View File

@@ -105,7 +105,10 @@ export default function GatewayPage() {
header: "创建时间", header: "创建时间",
accessorKey: "created_at", accessorKey: "created_at",
cell: ({ row }) => cell: ({ row }) =>
format(new Date(row.original.created_at), "yyyy-MM-dd HH:mm"), format(
new Date(row.original.created_at),
"yyyy-MM-dd HH:mm:ss",
),
}, },
{ {
id: "action", id: "action",

View File

@@ -144,11 +144,11 @@ function ProductSkus(props: {
{ header: "最低购买数量", accessorKey: "count_min" }, { header: "最低购买数量", accessorKey: "count_min" },
{ {
header: "创建时间", header: "创建时间",
accessorFn: row => format(row.created_at, "yyyy-MM-dd HH:mm"), accessorFn: row => format(row.created_at, "yyyy-MM-dd HH:mm:ss"),
}, },
{ {
header: "更新时间", header: "更新时间",
accessorFn: row => format(row.updated_at, "yyyy-MM-dd HH:mm"), accessorFn: row => format(row.updated_at, "yyyy-MM-dd HH:mm:ss"),
}, },
{ {
id: "action", id: "action",

View File

@@ -2,6 +2,8 @@
import { zodResolver } from "@hookform/resolvers/zod" import { zodResolver } from "@hookform/resolvers/zod"
import { format, isBefore, isSameDay } from "date-fns" import { format, isBefore, isSameDay } from "date-fns"
import { Box, Loader2, Timer } from "lucide-react" import { Box, Loader2, Timer } from "lucide-react"
import Link from "next/link"
import { useRouter, useSearchParams } from "next/navigation"
import { Suspense, useCallback, useMemo, useState } from "react" import { Suspense, useCallback, useMemo, useState } from "react"
import { Controller, useForm } from "react-hook-form" import { Controller, useForm } from "react-hook-form"
import { toast } from "sonner" import { toast } from "sonner"
@@ -156,8 +158,8 @@ function ExpireBadge({ expireAt }: { expireAt: Date | null | undefined }) {
// 格式化日期 // 格式化日期
function formatDateTime(date: Date | null | undefined) { function formatDateTime(date: Date | null | undefined) {
if (!date) return "-" if (!date) return ""
return format(date, "yyyy-MM-dd HH:mm") return format(date, "yyyy-MM-dd HH:mm:ss")
} }
// 计算今日使用量 // 计算今日使用量
@@ -200,15 +202,17 @@ interface ResourceListProps {
} }
function ResourceList({ resourceType }: ResourceListProps) { function ResourceList({ resourceType }: ResourceListProps) {
const searchParams = useSearchParams()
const resourceNo = searchParams.get("resource_no")
const isLong = resourceType === "long" const isLong = resourceType === "long"
const listFn = isLong ? listResourceLong : listResourceShort const listFn = isLong ? listResourceLong : listResourceShort
const [filters, setFilters] = useState<FilterParams>({})
const [updatingId, setUpdatingId] = useState<number | null>(null) const [updatingId, setUpdatingId] = useState<number | null>(null)
const { control, handleSubmit, reset } = useForm<FormValues>({ const router = useRouter()
const { control, handleSubmit, reset, getValues } = useForm<FormValues>({
resolver: zodResolver(filterSchema), resolver: zodResolver(filterSchema),
defaultValues: { defaultValues: {
user_phone: "", user_phone: "",
resource_no: "", resource_no: resourceNo || "",
status: "all", status: "all",
type: "all", type: "all",
created_at_start: "", created_at_start: "",
@@ -219,16 +223,35 @@ function ResourceList({ resourceType }: ResourceListProps) {
const fetchResources = useCallback( const fetchResources = useCallback(
(page: number, size: number) => { (page: number, size: number) => {
return listFn({ page, size, ...filters }) const result: FilterParams = {}
const filters = getValues()
if (filters.user_phone?.trim())
result.user_phone = filters.user_phone.trim()
if (filters.resource_no?.trim())
result.resource_no = filters.resource_no.trim()
if (filters.status && filters.status !== "all") {
result.active = filters.status === "0"
}
if (filters.type && filters.type !== "all") {
result.mode = Number(filters.type)
}
if (filters.expired && filters.expired !== "all") {
result.expired = filters.expired === "1"
}
if (filters.created_at_start)
result.created_at_start = new Date(filters.created_at_start)
if (filters.created_at_end)
result.created_at_end = new Date(filters.created_at_end)
return listFn({ page, size, ...result })
}, },
[listFn, filters], [listFn, getValues],
) )
const table = useDataTable<Resources>(fetchResources) const table = useDataTable<Resources>(fetchResources)
const refreshTable = useCallback(() => { // const refreshTable = useCallback(() => {
setFilters(prev => ({ ...prev })) // setFilters(prev => ({ ...prev }))
}, []) // }, [])
const handleStatusChange = useCallback( const handleStatusChange = useCallback(
async (resource: Resources, newStatusValue: string) => { async (resource: Resources, newStatusValue: string) => {
@@ -243,7 +266,7 @@ function ResourceList({ resourceType }: ResourceListProps) {
toast.success("更新成功", { toast.success("更新成功", {
description: `资源状态已更新为${newActive ? "启用" : "禁用"}`, description: `资源状态已更新为${newActive ? "启用" : "禁用"}`,
}) })
refreshTable() table.refresh()
} catch (error) { } catch (error) {
console.error("更新状态失败:", error) console.error("更新状态失败:", error)
toast.error("更新失败", { toast.error("更新失败", {
@@ -253,7 +276,7 @@ function ResourceList({ resourceType }: ResourceListProps) {
setUpdatingId(null) setUpdatingId(null)
} }
}, },
[refreshTable], [table],
) )
const handleCheckipChange = useCallback( const handleCheckipChange = useCallback(
async (resource: Resources) => { async (resource: Resources) => {
@@ -267,7 +290,7 @@ function ResourceList({ resourceType }: ResourceListProps) {
toast.success("更新成功", { toast.success("更新成功", {
description: `IP检查已${newCheckip ? "启用IP检查" : "停用IP检查"}`, description: `IP检查已${newCheckip ? "启用IP检查" : "停用IP检查"}`,
}) })
refreshTable() table.refresh()
} catch (error) { } catch (error) {
console.error("更新IP检查状态失败:", error) console.error("更新IP检查状态失败:", error)
toast.error("更新失败", { toast.error("更新失败", {
@@ -277,27 +300,9 @@ function ResourceList({ resourceType }: ResourceListProps) {
setUpdatingId(null) setUpdatingId(null)
} }
}, },
[refreshTable], [table],
) )
const onFilter = handleSubmit(data => { const onFilter = handleSubmit(() => {
const result: FilterParams = {}
if (data.user_phone?.trim()) result.user_phone = data.user_phone.trim()
if (data.resource_no?.trim()) result.resource_no = data.resource_no.trim()
if (data.status && data.status !== "all") {
result.active = data.status === "0"
}
if (data.type && data.type !== "all") {
result.mode = Number(data.type)
}
if (data.expired && data.expired !== "all") {
result.expired = data.expired === "1"
}
if (data.created_at_start)
result.created_at_start = new Date(data.created_at_start)
if (data.created_at_end)
result.created_at_end = new Date(data.created_at_end)
setFilters(result)
table.pagination.onPageChange(1) table.pagination.onPageChange(1)
}) })
@@ -317,7 +322,14 @@ function ResourceList({ resourceType }: ResourceListProps) {
<div className="flex flex-col gap-1"> <div className="flex flex-col gap-1">
<div>{name}</div> <div>{name}</div>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<span className="text-xs text-gray-500">{resourceNo}</span> <Link
href={`/batch?resource_no=${resourceNo}`}
target="_blank"
rel="noopener noreferrer"
className="text-xs text-gray-500"
>
{resourceNo}
</Link>
<ExpireBadge expireAt={expireAt} /> <ExpireBadge expireAt={expireAt} />
</div> </div>
</div> </div>
@@ -576,8 +588,16 @@ function ResourceList({ resourceType }: ResourceListProps) {
type="button" type="button"
variant="outline" variant="outline"
onClick={() => { onClick={() => {
reset() router.replace("./resources")
setFilters({}) reset({
user_phone: "",
resource_no: "",
status: "all",
type: "all",
created_at_start: "",
created_at_end: "",
expired: "all",
})
table.pagination.onPageChange(1) table.pagination.onPageChange(1)
}} }}
> >

View File

@@ -2,6 +2,7 @@
import { zodResolver } from "@hookform/resolvers/zod" import { zodResolver } from "@hookform/resolvers/zod"
import { format } from "date-fns" import { format } from "date-fns"
import { CheckCircle, Clock, XCircle } from "lucide-react" import { CheckCircle, Clock, XCircle } from "lucide-react"
import Link from "next/link"
import { Suspense, useCallback, useState } from "react" import { Suspense, useCallback, useState } from "react"
import { Controller, useForm } from "react-hook-form" import { Controller, useForm } from "react-hook-form"
import { z } from "zod" import { z } from "zod"
@@ -9,12 +10,7 @@ import { getPageTrade } from "@/actions/trade"
import { DataTable, useDataTable } from "@/components/data-table" import { DataTable, useDataTable } from "@/components/data-table"
import { Page } from "@/components/page" import { Page } from "@/components/page"
import { Button } from "@/components/ui/button" import { Button } from "@/components/ui/button"
import { import { Field, FieldError, FieldLabel } from "@/components/ui/field"
Field,
FieldError,
FieldGroup,
FieldLabel,
} from "@/components/ui/field"
import { Input } from "@/components/ui/input" import { Input } from "@/components/ui/input"
import { import {
Select, Select,
@@ -69,7 +65,6 @@ type FilterSchema = z.infer<typeof filterSchema>
export default function TradePage() { export default function TradePage() {
const [filters, setFilters] = useState<FilterValues>({}) const [filters, setFilters] = useState<FilterValues>({})
const { control, handleSubmit, reset } = useForm<FilterSchema>({ const { control, handleSubmit, reset } = useForm<FilterSchema>({
resolver: zodResolver(filterSchema), resolver: zodResolver(filterSchema),
defaultValues: { defaultValues: {
@@ -120,10 +115,7 @@ export default function TradePage() {
name="user_phone" name="user_phone"
control={control} control={control}
render={({ field, fieldState }) => ( render={({ field, fieldState }) => (
<Field <Field data-invalid={fieldState.invalid} className="w-40 flex">
data-invalid={fieldState.invalid}
className="w-40 flex"
>
<FieldLabel></FieldLabel> <FieldLabel></FieldLabel>
<Input {...field} placeholder="请输入会员号" /> <Input {...field} placeholder="请输入会员号" />
<FieldError>{fieldState.error?.message}</FieldError> <FieldError>{fieldState.error?.message}</FieldError>
@@ -135,10 +127,7 @@ export default function TradePage() {
name="inner_no" name="inner_no"
control={control} control={control}
render={({ field, fieldState }) => ( render={({ field, fieldState }) => (
<Field <Field data-invalid={fieldState.invalid} className="w-40 flex">
data-invalid={fieldState.invalid}
className="w-40 flex"
>
<FieldLabel></FieldLabel> <FieldLabel></FieldLabel>
<Input {...field} placeholder="请输入订单号" /> <Input {...field} placeholder="请输入订单号" />
<FieldError>{fieldState.error?.message}</FieldError> <FieldError>{fieldState.error?.message}</FieldError>
@@ -217,10 +206,7 @@ export default function TradePage() {
name="created_at_start" name="created_at_start"
control={control} control={control}
render={({ field, fieldState }) => ( render={({ field, fieldState }) => (
<Field <Field data-invalid={fieldState.invalid} className="w-40 flex">
data-invalid={fieldState.invalid}
className="w-40 flex"
>
<FieldLabel></FieldLabel> <FieldLabel></FieldLabel>
<Input type="date" {...field} /> <Input type="date" {...field} />
<FieldError>{fieldState.error?.message}</FieldError> <FieldError>{fieldState.error?.message}</FieldError>
@@ -232,10 +218,7 @@ export default function TradePage() {
name="created_at_end" name="created_at_end"
control={control} control={control}
render={({ field, fieldState }) => ( render={({ field, fieldState }) => (
<Field <Field data-invalid={fieldState.invalid} className="w-40 flex">
data-invalid={fieldState.invalid}
className="w-40 flex"
>
<FieldLabel></FieldLabel> <FieldLabel></FieldLabel>
<Input type="date" {...field} /> <Input type="date" {...field} />
<FieldError>{fieldState.error?.message}</FieldError> <FieldError>{fieldState.error?.message}</FieldError>
@@ -266,10 +249,29 @@ export default function TradePage() {
header: "创建时间", header: "创建时间",
accessorKey: "created_at", accessorKey: "created_at",
cell: ({ row }) => cell: ({ row }) =>
format(new Date(row.original.created_at), "yyyy-MM-dd HH:mm"), format(
new Date(row.original.created_at),
"yyyy-MM-dd HH:mm:ss",
),
}, },
{ header: "会员号", accessorFn: row => row.user?.phone || "" }, { header: "会员号", accessorFn: row => row.user?.phone || "" },
{ header: "订单号", accessorKey: "inner_no" }, {
header: "订单号",
accessorKey: "inner_no",
cell: ({ row }) => {
const innerNo = row.original.inner_no
return (
<Link
href={`/billing?inner_no=${innerNo}`}
target="_blank"
rel="noopener noreferrer"
className="text-blue-600 hover:underline cursor-pointer"
>
{innerNo}
</Link>
)
},
},
{ header: "购买套餐", accessorKey: "subject" }, { header: "购买套餐", accessorKey: "subject" },
{ {
header: "支付金额", header: "支付金额",

View File

@@ -10,11 +10,7 @@ import { DataTable, useDataTable } from "@/components/data-table"
import { Page } from "@/components/page" import { Page } from "@/components/page"
import { Badge } from "@/components/ui/badge" import { Badge } from "@/components/ui/badge"
import { Button } from "@/components/ui/button" import { Button } from "@/components/ui/button"
import { import { Field, FieldError, FieldLabel } from "@/components/ui/field"
Field,
FieldError,
FieldLabel,
} from "@/components/ui/field"
import { Input } from "@/components/ui/input" import { Input } from "@/components/ui/input"
import { useFetch } from "@/hooks/data" import { useFetch } from "@/hooks/data"
import { ScopeUserWriteBind } from "@/lib/scopes" import { ScopeUserWriteBind } from "@/lib/scopes"
@@ -67,10 +63,7 @@ export default function UserPage() {
name="phone" name="phone"
control={control} control={control}
render={({ field, fieldState }) => ( render={({ field, fieldState }) => (
<Field <Field data-invalid={fieldState.invalid} className="w-40 flex">
data-invalid={fieldState.invalid}
className="w-40 flex"
>
<FieldLabel></FieldLabel> <FieldLabel></FieldLabel>
<Input {...field} placeholder="请输入手机号" /> <Input {...field} placeholder="请输入手机号" />
<FieldError>{fieldState.error?.message}</FieldError> <FieldError>{fieldState.error?.message}</FieldError>
@@ -161,7 +154,7 @@ export default function UserPage() {
row.original.last_login row.original.last_login
? format( ? format(
new Date(row.original.last_login), new Date(row.original.last_login),
"yyyy-MM-dd HH:mm", "yyyy-MM-dd HH:mm:ss",
) )
: "", : "",
}, },
@@ -174,7 +167,10 @@ export default function UserPage() {
header: "创建时间", header: "创建时间",
accessorKey: "created_at", accessorKey: "created_at",
cell: ({ row }) => cell: ({ row }) =>
format(new Date(row.original.created_at), "yyyy-MM-dd HH:mm"), format(
new Date(row.original.created_at),
"yyyy-MM-dd HH:mm:ss",
),
}, },
{ {
id: "action", id: "action",