修改客户认领和客户管理页面的查询和操作功能并且包含按钮权限 & 产品管理添加启动/禁用和最低价格字段

This commit is contained in:
Eamon
2026-04-07 17:29:42 +08:00
parent f6ae0a9463
commit ff645aaaca
14 changed files with 442 additions and 237 deletions

View File

@@ -270,17 +270,6 @@ export function AddUserDialog({
</div>
<div className="grid grid-cols-2 gap-4">
<Controller
name="name"
control={control}
render={({ field, fieldState }) => (
<Field data-invalid={fieldState.invalid}>
<FieldLabel></FieldLabel>
<Input {...field} placeholder="请输入真实姓名" />
<FieldError>{fieldState.error?.message}</FieldError>
</Field>
)}
/>
<Controller
name="email"
control={control}
@@ -292,9 +281,6 @@ export function AddUserDialog({
</Field>
)}
/>
</div>
<div className="grid grid-cols-2 gap-4">
<Controller
name="status"
control={control}

View File

@@ -0,0 +1,154 @@
"use client"
import { zodResolver } from "@hookform/resolvers/zod"
import { useState } from "react"
import { useForm } from "react-hook-form"
import { toast } from "sonner"
import { z } from "zod"
import { getDeduction } from "@/actions/cust"
import { Button } from "@/components/ui/button"
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog"
import { Field, FieldError, FieldLabel } from "@/components/ui/field"
import { Input } from "@/components/ui/input"
import type { Cust } from "@/models/cust"
const Schema = z.object({
deduction: z
.string()
.min(1, "请输入余额")
.refine(val => !Number.isNaN(Number(val)), "请输入有效的数字")
.refine(val => Number(val) >= 0, "余额不能为负数"),
})
type FormValues = z.infer<typeof Schema>
interface UpdateDeductionDialogProps {
open: boolean
onOpenChange: (open: boolean) => void
currentUser: Cust | null
onSuccess: () => void
}
export function DeductionDialog({
open,
onOpenChange,
currentUser,
onSuccess,
}: UpdateDeductionDialogProps) {
const [isLoading, setIsLoading] = useState(false)
const {
register,
handleSubmit,
reset,
setValue,
formState: { errors },
} = useForm<FormValues>({
resolver: zodResolver(Schema),
defaultValues: {
deduction: "",
},
})
const onSubmit = async (data: FormValues) => {
if (!currentUser) return
setIsLoading(true)
try {
const result = await getDeduction({
user_id: currentUser.id,
amount: data.deduction,
})
if (result.success) {
toast.success("扣款成功")
onOpenChange(false)
reset()
onSuccess()
} else {
toast.error(result.message || "扣款失败")
}
} catch (error) {
const message = error instanceof Error ? error.message : error
toast.error(`网络错误,请稍后重试: ${message}`)
} finally {
setIsLoading(false)
}
}
const handleOpenChange = (open: boolean) => {
if (!open) {
reset()
}
onOpenChange(open)
}
return (
<Dialog open={open} onOpenChange={handleOpenChange}>
<DialogContent className="max-w-2xl max-h-[90vh] overflow-y-auto">
<DialogHeader>
<DialogTitle></DialogTitle>
<DialogDescription>
{currentUser?.name || currentUser?.username}
</DialogDescription>
</DialogHeader>
<form onSubmit={handleSubmit(onSubmit)}>
<div className="grid gap-4 py-4">
<Field data-invalid={!!errors.deduction}>
<FieldLabel></FieldLabel>
<Input
type="number"
step="0.01"
min="0"
placeholder="请输入扣款金额"
{...register("deduction", {
setValueAs: value => {
if (!value) return ""
const num = Number(value)
if (Number.isNaN(num)) return value
return num.toFixed(2)
},
})}
onInput={(e: React.ChangeEvent<HTMLInputElement>) => {
let value = e.target.value
if (value.startsWith("-")) {
value = value.replace("-", "")
}
if (value.includes(".")) {
const parts = value.split(".")
if (parts[1] && parts[1].length > 2) {
value = `${parts[0]}.${parts[1].slice(0, 2)}`
}
}
setValue("deduction", value)
}}
/>
<FieldError>{errors.deduction?.message}</FieldError>
</Field>
</div>
<DialogFooter>
<Button
type="button"
variant="outline"
onClick={() => handleOpenChange(false)}
>
</Button>
<Button type="submit" disabled={isLoading}>
{isLoading ? "保存中" : "保存"}
</Button>
</DialogFooter>
</form>
</DialogContent>
</Dialog>
)
}

View File

@@ -1,11 +1,11 @@
"use client"
import { zodResolver } from "@hookform/resolvers/zod"
import { useEffect, useState } from "react"
import { useState } from "react"
import { useForm } from "react-hook-form"
import { toast } from "sonner"
import { z } from "zod"
import { getBalance } from "@/actions/cust"
import { getDeposit } from "@/actions/cust"
import { Button } from "@/components/ui/button"
import {
Dialog,
@@ -19,29 +19,29 @@ import { Field, FieldError, FieldLabel } from "@/components/ui/field"
import { Input } from "@/components/ui/input"
import type { Cust } from "@/models/cust"
const balanceSchema = z.object({
balance: z
const Schema = z.object({
deposit: z
.string()
.min(1, "请输入余额")
.refine(val => !Number.isNaN(Number(val)), "请输入有效的数字")
.refine(val => Number(val) >= 0, "余额不能为负数"),
})
type BalanceFormValues = z.infer<typeof balanceSchema>
type FormValues = z.infer<typeof Schema>
interface UpdateBalanceDialogProps {
interface UpdateDepositDialogProps {
open: boolean
onOpenChange: (open: boolean) => void
currentUser: Cust | null
onSuccess: () => void
}
export function BalanceDialog({
export function DepositDialog({
open,
onOpenChange,
currentUser,
onSuccess,
}: UpdateBalanceDialogProps) {
}: UpdateDepositDialogProps) {
const [isLoading, setIsLoading] = useState(false)
const {
@@ -50,37 +50,29 @@ export function BalanceDialog({
reset,
setValue,
formState: { errors },
} = useForm<BalanceFormValues>({
resolver: zodResolver(balanceSchema),
} = useForm<FormValues>({
resolver: zodResolver(Schema),
defaultValues: {
balance: "",
deposit: "",
},
})
useEffect(() => {
if (open && currentUser) {
const currentBalance = currentUser.balance?.toString() || "0"
const formattedBalance = Number(currentBalance).toFixed(2)
setValue("balance", formattedBalance)
}
}, [open, currentUser, setValue])
const onSubmit = async (data: BalanceFormValues) => {
const onSubmit = async (data: FormValues) => {
if (!currentUser) return
setIsLoading(true)
try {
const result = await getBalance({
const result = await getDeposit({
user_id: currentUser.id,
balance: data.balance,
amount: data.deposit,
})
if (result.success) {
toast.success("修改余额成功")
toast.success("充值成功")
onOpenChange(false)
reset()
onSuccess()
} else {
toast.error(result.message || "修改余额失败")
toast.error(result.message || "充值失败")
}
} catch (error) {
const message = error instanceof Error ? error.message : error
@@ -101,22 +93,22 @@ export function BalanceDialog({
<Dialog open={open} onOpenChange={handleOpenChange}>
<DialogContent className="max-w-2xl max-h-[90vh] overflow-y-auto">
<DialogHeader>
<DialogTitle></DialogTitle>
<DialogTitle></DialogTitle>
<DialogDescription>
{currentUser?.name || currentUser?.username}
{currentUser?.name || currentUser?.username}
</DialogDescription>
</DialogHeader>
<form onSubmit={handleSubmit(onSubmit)}>
<div className="grid gap-4 py-4">
<Field data-invalid={!!errors.balance}>
<FieldLabel></FieldLabel>
<Field data-invalid={!!errors.deposit}>
<FieldLabel></FieldLabel>
<Input
type="number"
step="0.01"
min="0"
placeholder="请输入额"
{...register("balance", {
placeholder="请输入充值金额"
{...register("deposit", {
setValueAs: value => {
if (!value) return ""
const num = Number(value)
@@ -136,10 +128,10 @@ export function BalanceDialog({
}
}
setValue("balance", value)
setValue("deposit", value)
}}
/>
<FieldError>{errors.balance?.message}</FieldError>
<FieldError>{errors.deposit?.message}</FieldError>
</Field>
</div>

View File

@@ -6,6 +6,7 @@ import { Suspense, useCallback, useState } from "react"
import { Controller, useForm } from "react-hook-form"
import { z } from "zod"
import { getPageCusts } from "@/actions/cust"
import { Auth } from "@/components/auth"
import { DataTable, useDataTable } from "@/components/data-table"
import { Badge } from "@/components/ui/badge"
import { Button } from "@/components/ui/button"
@@ -23,9 +24,16 @@ import {
SelectTrigger,
SelectValue,
} from "@/components/ui/select"
import {
ScopeUserWrite,
ScopeUserWriteBalance,
ScopeUserWriteBalanceDec,
ScopeUserWriteBalanceInc,
} from "@/lib/scopes"
import type { Cust } from "@/models/cust"
import { BalanceDialog } from "./balanceDialog"
import { AddUserDialog } from "./create"
import { DeductionDialog } from "./deduction"
import { DepositDialog } from "./deposit"
import { UpdateDialog } from "./update"
type FilterValues = {
@@ -68,8 +76,11 @@ export default function UserPage() {
const [isAddDialogOpen, setIsAddDialogOpen] = useState(false)
const [isEditDialogOpen, setIsEditDialogOpen] = useState(false)
const [currentEditUser, setCurrentEditUser] = useState<Cust | null>(null)
const [balanceDialog, setBalanceDialog] = useState(false)
const [balance, setBalance] = useState<Cust | null>(null)
const [depositDialog, setDepositDialog] = useState(false)
const [deposit, setDeposit] = useState<Cust | null>(null)
const [deductionDialog, setDeductionDialog] = useState(false)
const [deduction, setDeduction] = useState<Cust | null>(null)
const { control, handleSubmit, reset } = useForm<FormValues>({
resolver: zodResolver(filterSchema),
defaultValues: {
@@ -224,9 +235,11 @@ export default function UserPage() {
>
</Button>
<Button type="button" onClick={() => setIsAddDialogOpen(true)}>
</Button>
<Auth scope={ScopeUserWrite}>
<Button type="button" onClick={() => setIsAddDialogOpen(true)}>
</Button>
</Auth>
</FieldGroup>
</form>
@@ -289,7 +302,7 @@ export default function UserPage() {
accessorKey: "id_no",
cell: ({ row }) => {
const idNo = row.original.id_no
return idNo ? `${idNo.slice(0, 6)}****${idNo.slice(-4)}` : "-"
return idNo ? `${idNo.slice(0, 6)}****${idNo.slice(-4)}` : ""
},
},
{
@@ -323,12 +336,12 @@ export default function UserPage() {
new Date(row.original.last_login),
"yyyy-MM-dd HH:mm",
)
: "-",
: "",
},
{
header: "最后登录IP",
accessorKey: "last_login_ip",
cell: ({ row }) => row.original.last_login_ip || "-",
cell: ({ row }) => row.original.last_login_ip || "",
},
{
header: "创建时间",
@@ -343,25 +356,39 @@ export default function UserPage() {
cell: ({ row }) => {
return (
<div className="flex gap-2">
<Button
size="sm"
variant="outline"
onClick={() => {
setBalance(row.original)
setBalanceDialog(true)
}}
>
</Button>
<Button
size="sm"
onClick={() => {
setCurrentEditUser(row.original)
setIsEditDialogOpen(true)
}}
>
</Button>
<Auth scope={ScopeUserWriteBalanceInc}>
<Button
size="sm"
onClick={() => {
setDeposit(row.original)
setDepositDialog(true)
}}
>
</Button>
</Auth>
<Auth scope={ScopeUserWriteBalanceDec}>
<Button
size="sm"
onClick={() => {
setDeduction(row.original)
setDeductionDialog(true)
}}
>
</Button>
</Auth>
<Auth scope={ScopeUserWriteBalance}>
<Button
size="sm"
onClick={() => {
setCurrentEditUser(row.original)
setIsEditDialogOpen(true)
}}
>
</Button>
</Auth>
</div>
)
},
@@ -383,10 +410,16 @@ export default function UserPage() {
onSuccess={refreshTable}
/>
<BalanceDialog
open={balanceDialog}
onOpenChange={setBalanceDialog}
currentUser={balance}
<DepositDialog
open={depositDialog}
onOpenChange={setDepositDialog}
currentUser={deposit}
onSuccess={refreshTable}
/>
<DeductionDialog
open={deductionDialog}
onOpenChange={setDeductionDialog}
currentUser={deduction}
onSuccess={refreshTable}
/>
</div>