搜索框添加一键清除功能

This commit is contained in:
Eamon
2026-05-14 16:04:35 +08:00
parent 284b0d6afe
commit 616901acdd
12 changed files with 263 additions and 118 deletions

View File

@@ -1,6 +1,7 @@
"use client"
import { zodResolver } from "@hookform/resolvers/zod"
import { format } from "date-fns"
import Link from "next/link"
import { useRouter, useSearchParams } from "next/navigation"
import { Suspense, useCallback } from "react"
import { Controller, useForm } from "react-hook-form"
@@ -95,7 +96,7 @@ export default function BalancePage() {
className="w-40 flex-none"
>
<FieldLabel></FieldLabel>
<Input {...field} placeholder="请输入会员号" />
<Input {...field} placeholder="请输入会员号" clearable />
<FieldError>{fieldState.error?.message}</FieldError>
</Field>
)}
@@ -109,7 +110,7 @@ export default function BalancePage() {
className="w-40 flex-none"
>
<FieldLabel></FieldLabel>
<Input {...field} placeholder="请输入账单号" />
<Input {...field} placeholder="请输入账单号" clearable />
<FieldError>{fieldState.error?.message}</FieldError>
</Field>
)}
@@ -123,7 +124,7 @@ export default function BalancePage() {
className="w-40 flex-none"
>
<FieldLabel></FieldLabel>
<Input type="date" {...field} />
<Input type="date" {...field} clearable />
<FieldError>{fieldState.error?.message}</FieldError>
</Field>
)}
@@ -137,7 +138,7 @@ export default function BalancePage() {
className="w-40 flex-none"
>
<FieldLabel></FieldLabel>
<Input type="date" {...field} />
<Input type="date" {...field} clearable />
<FieldError>{fieldState.error?.message}</FieldError>
</Field>
)}
@@ -174,7 +175,20 @@ export default function BalancePage() {
},
{
header: "账单编号",
accessorFn: row => row.bill?.bill_no || "",
accessorKey: "bill?.bill_no",
cell: ({ row }) => {
const bill_no = row.original.bill?.bill_no
return (
<Link
href={`/billing?bill_no=${bill_no}`}
target="_blank"
rel="noopener noreferrer"
className="text-blue-600"
>
{bill_no}
</Link>
)
},
},
{
header: "管理员",

View File

@@ -120,7 +120,7 @@ export default function BatchPage() {
className="w-40 flex-none"
>
<FieldLabel></FieldLabel>
<Input {...field} placeholder="请输入提取编号" />
<Input {...field} placeholder="请输入提取编号" clearable />
<FieldError>{fieldState.error?.message}</FieldError>
</Field>
)}
@@ -134,7 +134,7 @@ export default function BatchPage() {
className="w-40 flex-none"
>
<FieldLabel></FieldLabel>
<Input {...field} placeholder="请输入套餐号" />
<Input {...field} placeholder="请输入套餐号" clearable />
<FieldError>{fieldState.error?.message}</FieldError>
</Field>
)}
@@ -148,7 +148,7 @@ export default function BatchPage() {
className="w-40 flex-none"
>
<FieldLabel></FieldLabel>
<Input {...field} placeholder="请输入会员号" />
<Input {...field} placeholder="请输入会员号" clearable />
<FieldError>{fieldState.error?.message}</FieldError>
</Field>
)}
@@ -163,7 +163,7 @@ export default function BatchPage() {
className="w-32 flex-none"
>
<FieldLabel></FieldLabel>
<Input {...field} placeholder="请输入省份" />
<Input {...field} placeholder="请输入省份" clearable />
<FieldError>{fieldState.error?.message}</FieldError>
</Field>
)}
@@ -178,7 +178,7 @@ export default function BatchPage() {
className="w-32 flex-none"
>
<FieldLabel></FieldLabel>
<Input {...field} placeholder="请输入城市" />
<Input {...field} placeholder="请输入城市" clearable />
<FieldError>{fieldState.error?.message}</FieldError>
</Field>
)}
@@ -215,7 +215,7 @@ export default function BatchPage() {
className="w-40 flex-none"
>
<FieldLabel></FieldLabel>
<Input type="date" {...field} />
<Input type="date" {...field} clearable />
<FieldError>{fieldState.error?.message}</FieldError>
</Field>
)}
@@ -230,7 +230,7 @@ export default function BatchPage() {
className="w-40 flex-none"
>
<FieldLabel></FieldLabel>
<Input type="date" {...field} />
<Input type="date" {...field} clearable />
<FieldError>{fieldState.error?.message}</FieldError>
</Field>
)}
@@ -277,17 +277,33 @@ export default function BatchPage() {
const resourceNo = row.original.resource?.resource_no
return (
<Link
href={`/channel?resource_no=${resourceNo}`}
href={`/resources?resource_no=${resourceNo}`}
target="_blank"
rel="noopener noreferrer"
className="text-blue-600 hover:underline cursor-pointer"
className="text-blue-600"
>
{resourceNo}
</Link>
)
},
},
{ header: "提取编号", accessorKey: "batch_no" },
{
header: "提取编号",
accessorKey: "batch_no",
cell: ({ row }) => {
const batch_no = row.original.batch_no
return (
<Link
href={`./channel?batch_no=${batch_no}`}
target="_blank"
rel="noopener noreferrer"
className="text-blue-600"
>
{batch_no}
</Link>
)
},
},
{ header: "省份", accessorKey: "prov" },
{ header: "城市", accessorKey: "city" },
{ header: "用户IP", accessorKey: "ip" },

View File

@@ -81,7 +81,8 @@ type FilterSchema = z.infer<typeof filterSchema>
export default function BillingPage() {
const searchParams = useSearchParams()
const innerNo = searchParams.get("inner_no")
console.log(innerNo, "innerNo")
const billNo = searchParams.get("bill_no")
const resourceNo = searchParams.get("resource_no")
const [skuOptions, setSkuOptions] = useState<SkuOption[]>([])
const [loading, setLoading] = useState(true)
const [skuProductCode, setSkuProductCode] = useState<ProductCode>(
@@ -92,12 +93,12 @@ export default function BillingPage() {
const { control, handleSubmit, reset, getValues } = useForm<FilterSchema>({
resolver: zodResolver(filterSchema),
defaultValues: {
bill_no: "",
bill_no: billNo || "",
inner_no: innerNo || "",
created_at_start: "",
created_at_end: "",
phone: "",
resource_no: "",
resource_no: resourceNo || "",
sku_code: "all",
product_code: "",
},
@@ -133,10 +134,6 @@ export default function BillingPage() {
const loadData = (page: number, size: number) => {
const result: FilterValues = {}
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())
@@ -154,7 +151,6 @@ export default function BillingPage() {
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,
@@ -196,7 +192,7 @@ export default function BillingPage() {
className="w-40 flex-none"
>
<FieldLabel></FieldLabel>
<Input {...field} placeholder="请输入会员号" />
<Input {...field} placeholder="请输入会员号" clearable />
<FieldError>{fieldState.error?.message}</FieldError>
</Field>
)}
@@ -210,7 +206,7 @@ export default function BillingPage() {
className="w-40 flex-none"
>
<FieldLabel></FieldLabel>
<Input {...field} placeholder="请输入套餐号" />
<Input {...field} placeholder="请输入套餐号" clearable />
<FieldError>{fieldState.error?.message}</FieldError>
</Field>
)}
@@ -224,7 +220,7 @@ export default function BillingPage() {
className="w-40 flex-none"
>
<FieldLabel></FieldLabel>
<Input {...field} placeholder="请输入账单号" />
<Input {...field} placeholder="请输入账单号" clearable />
<FieldError>{fieldState.error?.message}</FieldError>
</Field>
)}
@@ -238,7 +234,7 @@ export default function BillingPage() {
className="w-40 flex-none"
>
<FieldLabel></FieldLabel>
<Input {...field} placeholder="请输入订单号" />
<Input {...field} placeholder="请输入订单号" clearable />
<FieldError>{fieldState.error?.message}</FieldError>
</Field>
)}
@@ -319,7 +315,7 @@ export default function BillingPage() {
className="w-40 flex-none"
>
<FieldLabel></FieldLabel>
<Input type="date" {...field} />
<Input type="date" {...field} clearable />
<FieldError>{fieldState.error?.message}</FieldError>
</Field>
)}
@@ -334,7 +330,7 @@ export default function BillingPage() {
className="w-40 flex-none"
>
<FieldLabel></FieldLabel>
<Input type="date" {...field} />
<Input type="date" {...field} clearable />
<FieldError>{fieldState.error?.message}</FieldError>
</Field>
)}
@@ -373,7 +369,7 @@ export default function BillingPage() {
href={`/resources?resource_no=${resource_no}`}
target="_blank"
rel="noopener noreferrer"
className="text-blue-600 hover:underline cursor-pointer"
className="text-blue-600"
>
{resource_no}
</Link>
@@ -468,7 +464,7 @@ export default function BillingPage() {
href={`./balance?bill_no=${billNo}`}
target="_blank"
rel="noopener noreferrer"
className="text-blue-600 hover:underline cursor-pointer"
className="text-blue-600"
>
{billNo}
</Link>
@@ -509,7 +505,16 @@ export default function BillingPage() {
</div>
)}
</div>
<div className="text-sm">{bill.trade?.inner_no}</div>
<div className="text-sm">
<Link
href={`/trade?inner_no=${bill.trade?.inner_no}`}
target="_blak"
rel="noopener noreferrer"
className="text-blue-600"
>
{bill.trade?.inner_no}
</Link>
</div>
</div>
)
},

View File

@@ -68,11 +68,12 @@ const ispMap: Record<number, string> = {
export default function ChannelPage() {
const searchParams = useSearchParams()
const resourceNo = searchParams.get("resource_no")
const batch_no = searchParams.get("batch_no")
const router = useRouter()
const { control, handleSubmit, reset, getValues } = useForm<FilterSchema>({
resolver: zodResolver(filterSchema),
defaultValues: {
batch_no: "",
batch_no: batch_no || "",
user_phone: "",
resource_no: resourceNo || "",
proxy_port: "",
@@ -119,7 +120,7 @@ export default function ChannelPage() {
className="w-40 flex-none"
>
<FieldLabel></FieldLabel>
<Input {...field} placeholder="请输入提取编号" />
<Input {...field} placeholder="请输入提取编号" clearable />
<FieldError>{fieldState.error?.message}</FieldError>
</Field>
)}
@@ -133,7 +134,7 @@ export default function ChannelPage() {
className="w-40 flex-none"
>
<FieldLabel></FieldLabel>
<Input {...field} placeholder="请输入会员号" />
<Input {...field} placeholder="请输入会员号" clearable />
<FieldError>{fieldState.error?.message}</FieldError>
</Field>
)}
@@ -147,7 +148,7 @@ export default function ChannelPage() {
className="w-40 flex-none"
>
<FieldLabel></FieldLabel>
<Input {...field} placeholder="请输入套餐号" />
<Input {...field} placeholder="请输入套餐号" clearable />
<FieldError>{fieldState.error?.message}</FieldError>
</Field>
)}
@@ -161,7 +162,7 @@ export default function ChannelPage() {
className="w-40 flex-none"
>
<FieldLabel>IP</FieldLabel>
<Input {...field} placeholder="请输入代理IP" />
<Input {...field} placeholder="请输入代理IP" clearable />
<FieldError>{fieldState.error?.message}</FieldError>
</Field>
)}
@@ -175,7 +176,7 @@ export default function ChannelPage() {
className="w-32 flex-none"
>
<FieldLabel></FieldLabel>
<Input {...field} placeholder="请输入代理端口" />
<Input {...field} placeholder="请输入代理端口" clearable />
<FieldError>{fieldState.error?.message}</FieldError>
</Field>
)}
@@ -189,7 +190,7 @@ export default function ChannelPage() {
className="w-40 flex-none"
>
<FieldLabel></FieldLabel>
<Input {...field} placeholder="请输入节点" />
<Input {...field} placeholder="请输入节点" clearable />
<FieldError>{fieldState.error?.message}</FieldError>
</Field>
)}
@@ -203,7 +204,7 @@ export default function ChannelPage() {
className="w-40 flex-none"
>
<FieldLabel></FieldLabel>
<Input type="date" {...field} />
<Input type="date" {...field} clearable />
<FieldError>{fieldState.error?.message}</FieldError>
</Field>
)}
@@ -217,7 +218,7 @@ export default function ChannelPage() {
className="w-40 flex-none"
>
<FieldLabel></FieldLabel>
<Input type="date" {...field} />
<Input type="date" {...field} clearable />
<FieldError>{fieldState.error?.message}</FieldError>
</Field>
)}
@@ -257,7 +258,23 @@ export default function ChannelPage() {
header: "会员号",
accessorFn: row => row.user?.phone || "-",
},
{ header: "套餐号", accessorKey: "resource.resource_no" },
{
header: "套餐号",
accessorKey: "resource.resource_no",
cell: ({ row }) => {
const resource_no = row.original.resource?.resource_no
return (
<Link
href={`./resources?resource_no=${resource_no}`}
target="_blank"
rel="noopener noreferrer"
className="text-blue-600"
>
{resource_no}
</Link>
)
},
},
{
header: "提取编号",
accessorKey: "batch_no",
@@ -268,7 +285,7 @@ export default function ChannelPage() {
href={`./batch?batch_no=${batchNo}`}
target="_blank"
rel="noopener noreferrer"
className="text-blue-600 hover:underline cursor-pointer"
className="text-blue-600"
>
{batchNo}
</Link>

View File

@@ -113,7 +113,7 @@ export default function UserQueryPage() {
render={({ field, fieldState }) => (
<Field data-invalid={fieldState.invalid} className="w-40 flex">
<FieldLabel></FieldLabel>
<Input {...field} placeholder="请输入手机号" />
<Input {...field} placeholder="请输入手机号" clearable />
<FieldError>{fieldState.error?.message}</FieldError>
</Field>
)}
@@ -125,18 +125,18 @@ export default function UserQueryPage() {
render={({ field, fieldState }) => (
<Field data-invalid={fieldState.invalid} className="w-40 flex">
<FieldLabel></FieldLabel>
<Input {...field} placeholder="请输入姓名" />
<Input {...field} placeholder="请输入姓名" clearable />
<FieldError>{fieldState.error?.message}</FieldError>
</Field>
)}
/>
<Auth scope={ScopeUserWrite}>
<AddUserDialog onSuccess={refreshTable} />
</Auth>
<Button type="submit"></Button>
<Button type="button" variant="outline" onClick={handleReset}>
</Button>
<Button type="submit"></Button>
<Auth scope={ScopeUserWrite}>
<AddUserDialog onSuccess={refreshTable} />
</Auth>
</div>
</form>

View File

@@ -214,6 +214,7 @@ export function AddUserDialog({ onSuccess }: AddUserDialogProps) {
{...field}
placeholder="请输入用户名"
autoComplete="off"
clearable
/>
<FieldError>{fieldState.error?.message}</FieldError>
</Field>
@@ -225,7 +226,7 @@ export function AddUserDialog({ onSuccess }: AddUserDialogProps) {
render={({ field, fieldState }) => (
<Field data-invalid={fieldState.invalid}>
<FieldLabel> *</FieldLabel>
<Input {...field} placeholder="请输入手机号" />
<Input {...field} placeholder="请输入手机号" clearable />
<FieldError>{fieldState.error?.message}</FieldError>
</Field>
)}
@@ -246,6 +247,7 @@ export function AddUserDialog({ onSuccess }: AddUserDialogProps) {
{...field}
type="password"
placeholder="请输入密码至少6位"
clearable
/>
<FieldError>{fieldState.error?.message}</FieldError>
</Field>
@@ -261,6 +263,7 @@ export function AddUserDialog({ onSuccess }: AddUserDialogProps) {
{...field}
type="password"
placeholder="请再次输入密码"
clearable
/>
<FieldError>{fieldState.error?.message}</FieldError>
</Field>
@@ -275,7 +278,7 @@ export function AddUserDialog({ onSuccess }: AddUserDialogProps) {
render={({ field, fieldState }) => (
<Field data-invalid={fieldState.invalid}>
<FieldLabel></FieldLabel>
<Input {...field} placeholder="请输入邮箱" />
<Input {...field} placeholder="请输入邮箱" clearable />
<FieldError>{fieldState.error?.message}</FieldError>
</Field>
)}
@@ -311,7 +314,7 @@ export function AddUserDialog({ onSuccess }: AddUserDialogProps) {
render={({ field, fieldState }) => (
<Field data-invalid={fieldState.invalid}>
<FieldLabel>QQ联系方式</FieldLabel>
<Input {...field} placeholder="请输入QQ联系方式" />
<Input {...field} placeholder="请输入QQ联系方式" clearable />
<FieldError>{fieldState.error?.message}</FieldError>
</Field>
)}
@@ -322,7 +325,11 @@ export function AddUserDialog({ onSuccess }: AddUserDialogProps) {
render={({ field, fieldState }) => (
<Field data-invalid={fieldState.invalid}>
<FieldLabel>/</FieldLabel>
<Input {...field} placeholder="请输入微信或联系方式" />
<Input
{...field}
placeholder="请输入微信或联系方式"
clearable
/>
<FieldError>{fieldState.error?.message}</FieldError>
</Field>
)}

View File

@@ -99,6 +99,8 @@ export default function CustPage() {
(page: number, size: number) => {
const result: FilterValues = {}
const filters = getValues()
console.log(filters, "filters")
if (filters.account?.trim()) result.account = filters.account.trim()
if (filters.name?.trim()) result.name = filters.name.trim()
if (filters.identified && filters.identified !== "all")
@@ -134,7 +136,11 @@ export default function CustPage() {
render={({ field, fieldState }) => (
<Field data-invalid={fieldState.invalid} className="w-80 flex">
<FieldLabel>//</FieldLabel>
<Input {...field} placeholder="请输入账号/手机号/邮箱" />
<Input
{...field}
placeholder="请输入账号/手机号/邮箱"
clearable
/>
<FieldError>{fieldState.error?.message}</FieldError>
</Field>
)}
@@ -146,7 +152,7 @@ export default function CustPage() {
render={({ field, fieldState }) => (
<Field data-invalid={fieldState.invalid} className="w-40 flex">
<FieldLabel></FieldLabel>
<Input {...field} placeholder="请输入姓名" />
<Input {...field} placeholder="请输入姓名" clearable />
<FieldError>{fieldState.error?.message}</FieldError>
</Field>
)}
@@ -200,7 +206,7 @@ export default function CustPage() {
render={({ field, fieldState }) => (
<Field data-invalid={fieldState.invalid} className="w-40 flex">
<FieldLabel></FieldLabel>
<Input type="date" {...field} />
<Input type="date" {...field} clearable />
<FieldError>{fieldState.error?.message}</FieldError>
</Field>
)}
@@ -212,14 +218,12 @@ export default function CustPage() {
render={({ field, fieldState }) => (
<Field data-invalid={fieldState.invalid} className="w-40 flex">
<FieldLabel></FieldLabel>
<Input type="date" {...field} />
<Input type="date" {...field} clearable />
<FieldError>{fieldState.error?.message}</FieldError>
</Field>
)}
/>
<Auth scope={ScopeUserWrite}>
<AddUserDialog onSuccess={refreshTable} />
</Auth>
<Button type="submit"></Button>
<Button
type="button"
variant="outline"
@@ -237,7 +241,9 @@ export default function CustPage() {
>
</Button>
<Button type="submit"></Button>
<Auth scope={ScopeUserWrite}>
<AddUserDialog onSuccess={refreshTable} />
</Auth>
</div>
</form>

View File

@@ -226,7 +226,7 @@ export function UpdateDialog({ user, onSuccess }: EditUserDialogProps) {
render={({ field, fieldState }) => (
<Field data-invalid={fieldState.invalid}>
<FieldLabel></FieldLabel>
<Input {...field} placeholder="请输入用户名" />
<Input {...field} placeholder="请输入用户名" clearable />
<FieldError>{fieldState.error?.message}</FieldError>
</Field>
)}
@@ -238,7 +238,7 @@ export function UpdateDialog({ user, onSuccess }: EditUserDialogProps) {
render={({ field, fieldState }) => (
<Field data-invalid={fieldState.invalid}>
<FieldLabel></FieldLabel>
<Input {...field} placeholder="请输入邮箱" />
<Input {...field} placeholder="请输入邮箱" clearable />
<FieldError>{fieldState.error?.message}</FieldError>
</Field>
)}
@@ -254,6 +254,7 @@ export function UpdateDialog({ user, onSuccess }: EditUserDialogProps) {
{...field}
type="password"
placeholder="选填,修改请输入新密码至少6位"
clearable
/>
<FieldError>{fieldState.error?.message}</FieldError>
</Field>
@@ -270,6 +271,7 @@ export function UpdateDialog({ user, onSuccess }: EditUserDialogProps) {
{...field}
type="password"
placeholder="请再次输入密码"
clearable
/>
<FieldError>{fieldState.error?.message}</FieldError>
</Field>
@@ -348,7 +350,7 @@ export function UpdateDialog({ user, onSuccess }: EditUserDialogProps) {
render={({ field }) => (
<Field>
<FieldLabel>QQ</FieldLabel>
<Input {...field} placeholder="QQ号" />
<Input {...field} placeholder="QQ号" clearable />
</Field>
)}
/>
@@ -359,7 +361,7 @@ export function UpdateDialog({ user, onSuccess }: EditUserDialogProps) {
render={({ field }) => (
<Field>
<FieldLabel></FieldLabel>
<Input {...field} placeholder="微信号" />
<Input {...field} placeholder="微信号" clearable />
</Field>
)}
/>

View File

@@ -2,7 +2,6 @@
import { zodResolver } from "@hookform/resolvers/zod"
import { format, isBefore, isSameDay } from "date-fns"
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 { Controller, useForm } from "react-hook-form"
@@ -17,6 +16,12 @@ import { DataTable, useDataTable } from "@/components/data-table"
import { Page } from "@/components/page"
import { Badge } from "@/components/ui/badge"
import { Button } from "@/components/ui/button"
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"
import {
Field,
FieldError,
@@ -249,10 +254,6 @@ function ResourceList({ resourceType }: ResourceListProps) {
const table = useDataTable<Resources>(fetchResources)
// const refreshTable = useCallback(() => {
// setFilters(prev => ({ ...prev }))
// }, [])
const handleStatusChange = useCallback(
async (resource: Resources, newStatusValue: string) => {
const newActive = newStatusValue === "0"
@@ -322,14 +323,34 @@ function ResourceList({ resourceType }: ResourceListProps) {
<div className="flex flex-col gap-1">
<div>{name}</div>
<div className="flex items-center gap-2">
<Link
href={`/batch?resource_no=${resourceNo}`}
target="_blank"
rel="noopener noreferrer"
className="text-xs text-gray-500"
<DropdownMenu>
<DropdownMenuTrigger asChild>
<span className="text-xs text-gray-500">{resourceNo}</span>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" className="w-40">
<DropdownMenuItem
onClick={() => {
router.push(`/billing?resource_no=${resourceNo}`)
}}
>
{resourceNo}
</Link>
</DropdownMenuItem>
<DropdownMenuItem
onClick={() => {
router.push(`/batch?resource_no=${resourceNo}`)
}}
>
</DropdownMenuItem>
<DropdownMenuItem
onClick={() => {
router.push(`/channel?resource_no=${resourceNo}`)
}}
>
IP管理
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
<ExpireBadge expireAt={expireAt} />
</div>
</div>
@@ -458,7 +479,7 @@ function ResourceList({ resourceType }: ResourceListProps) {
},
},
],
[isLong, updatingId, handleStatusChange, handleCheckipChange],
[isLong, updatingId, handleStatusChange, handleCheckipChange, router],
)
return (
@@ -474,7 +495,7 @@ function ResourceList({ resourceType }: ResourceListProps) {
className="w-40 flex-none"
>
<FieldLabel></FieldLabel>
<Input {...field} placeholder="请输入会员号" />
<Input {...field} placeholder="请输入会员号" clearable />
<FieldError>{fieldState.error?.message}</FieldError>
</Field>
)}
@@ -488,7 +509,7 @@ function ResourceList({ resourceType }: ResourceListProps) {
className="w-40 flex-none"
>
<FieldLabel></FieldLabel>
<Input {...field} placeholder="请输入套餐号" />
<Input {...field} placeholder="请输入套餐号" clearable />
<FieldError>{fieldState.error?.message}</FieldError>
</Field>
)}
@@ -562,7 +583,7 @@ function ResourceList({ resourceType }: ResourceListProps) {
className="w-40 flex-none"
>
<FieldLabel></FieldLabel>
<Input type="date" {...field} />
<Input type="date" {...field} clearable />
<FieldError>{fieldState.error?.message}</FieldError>
</Field>
)}
@@ -576,7 +597,7 @@ function ResourceList({ resourceType }: ResourceListProps) {
className="w-40 flex-none"
>
<FieldLabel></FieldLabel>
<Input type="date" {...field} />
<Input type="date" {...field} clearable />
<FieldError>{fieldState.error?.message}</FieldError>
</Field>
)}

View File

@@ -117,7 +117,7 @@ export default function TradePage() {
render={({ field, fieldState }) => (
<Field data-invalid={fieldState.invalid} className="w-40 flex">
<FieldLabel></FieldLabel>
<Input {...field} placeholder="请输入会员号" />
<Input {...field} placeholder="请输入会员号" clearable />
<FieldError>{fieldState.error?.message}</FieldError>
</Field>
)}
@@ -129,7 +129,7 @@ export default function TradePage() {
render={({ field, fieldState }) => (
<Field data-invalid={fieldState.invalid} className="w-40 flex">
<FieldLabel></FieldLabel>
<Input {...field} placeholder="请输入订单号" />
<Input {...field} placeholder="请输入订单号" clearable />
<FieldError>{fieldState.error?.message}</FieldError>
</Field>
)}
@@ -208,7 +208,7 @@ export default function TradePage() {
render={({ field, fieldState }) => (
<Field data-invalid={fieldState.invalid} className="w-40 flex">
<FieldLabel></FieldLabel>
<Input type="date" {...field} />
<Input type="date" {...field} clearable />
<FieldError>{fieldState.error?.message}</FieldError>
</Field>
)}
@@ -220,7 +220,7 @@ export default function TradePage() {
render={({ field, fieldState }) => (
<Field data-invalid={fieldState.invalid} className="w-40 flex">
<FieldLabel></FieldLabel>
<Input type="date" {...field} />
<Input type="date" {...field} clearable />
<FieldError>{fieldState.error?.message}</FieldError>
</Field>
)}
@@ -265,7 +265,7 @@ export default function TradePage() {
href={`/billing?inner_no=${innerNo}`}
target="_blank"
rel="noopener noreferrer"
className="text-blue-600 hover:underline cursor-pointer"
className="text-blue-600"
>
{innerNo}
</Link>

View File

@@ -65,7 +65,7 @@ export default function UserPage() {
render={({ field, fieldState }) => (
<Field data-invalid={fieldState.invalid} className="w-40 flex">
<FieldLabel></FieldLabel>
<Input {...field} placeholder="请输入手机号" />
<Input {...field} placeholder="请输入手机号" clearable />
<FieldError>{fieldState.error?.message}</FieldError>
</Field>
)}

View File

@@ -1,20 +1,77 @@
import type * as React from "react"
import { X } from "lucide-react"
import * as React from "react"
import { cn } from "@/lib/utils"
function Input({ className, type, ...props }: React.ComponentProps<"input">) {
interface InputProps extends React.ComponentProps<"input"> {
clearable?: boolean
onClear?: () => void
}
function Input({
className,
type,
clearable,
onClear,
value,
onChange,
...props
}: InputProps) {
const [showClear, setShowClear] = React.useState(false)
const inputRef = React.useRef<HTMLInputElement>(null)
const hasValue =
value !== undefined && value !== null && String(value).length > 0
// 监听输入框焦点状态
const handleFocus = () => setShowClear(true)
const handleBlur = () => setShowClear(false)
const handleClear = () => {
if (onClear) {
onClear()
} else if (onChange) {
// 触发 React 的 change 事件
const event = {
target: { value: "" },
currentTarget: { value: "" },
} as React.ChangeEvent<HTMLInputElement>
onChange(event)
}
// 清空后重新聚焦
inputRef.current?.focus()
}
return (
<div className="relative inline-block w-full">
<input
ref={inputRef}
type={type}
data-slot="input"
value={value}
onChange={onChange}
onFocus={handleFocus}
onBlur={handleBlur}
className={cn(
"file:text-foreground placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground dark:bg-input/30 border-input h-9 w-full min-w-0 rounded-md border bg-transparent px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
"focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]",
"aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
// 当有清空按钮时,右侧留出空间
clearable && hasValue && showClear && "pr-7",
className,
)}
{...props}
/>
{clearable && hasValue && showClear && (
<button
type="button"
onClick={handleClear}
className="absolute right-2 top-1/2 -translate-y-1/2 text-muted-foreground hover:text-foreground"
aria-label="清空"
>
<X className="h-4 w-4" />
</button>
)}
</div>
)
}