客户管理添加修改金额操作
This commit is contained in:
@@ -34,3 +34,7 @@ export async function createCust(data: {
|
|||||||
}) {
|
}) {
|
||||||
return callByUser<PageRecord<Cust>>("/api/admin/user/create", data)
|
return callByUser<PageRecord<Cust>>("/api/admin/user/create", data)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function getBalance(params: { user_id: number; balance: string }) {
|
||||||
|
return callByUser<PageRecord<Cust>>("/api/admin/user/update/balance", params)
|
||||||
|
}
|
||||||
|
|||||||
@@ -421,7 +421,7 @@ export default function BillingPage() {
|
|||||||
)
|
)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{ header: "套餐名称", accessorKey: "info" },
|
// { header: "套餐名称", accessorKey: "info" },
|
||||||
{ header: "账单号", accessorKey: "bill_no" },
|
{ header: "账单号", accessorKey: "bill_no" },
|
||||||
{ header: "订单号", accessorKey: "trade.inner_no" },
|
{ header: "订单号", accessorKey: "trade.inner_no" },
|
||||||
{
|
{
|
||||||
|
|||||||
165
src/app/(root)/cust/balanceDialog.tsx
Normal file
165
src/app/(root)/cust/balanceDialog.tsx
Normal file
@@ -0,0 +1,165 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import { zodResolver } from "@hookform/resolvers/zod"
|
||||||
|
import { useEffect, useState } from "react"
|
||||||
|
import { useForm } from "react-hook-form"
|
||||||
|
import { toast } from "sonner"
|
||||||
|
import { z } from "zod"
|
||||||
|
import { getBalance } 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 balanceSchema = z.object({
|
||||||
|
balance: z
|
||||||
|
.string()
|
||||||
|
.min(1, "请输入余额")
|
||||||
|
.refine(val => !Number.isNaN(Number(val)), "请输入有效的数字")
|
||||||
|
.refine(val => Number(val) >= 0, "余额不能为负数"),
|
||||||
|
})
|
||||||
|
|
||||||
|
type BalanceFormValues = z.infer<typeof balanceSchema>
|
||||||
|
|
||||||
|
interface UpdateBalanceDialogProps {
|
||||||
|
open: boolean
|
||||||
|
onOpenChange: (open: boolean) => void
|
||||||
|
currentUser: Cust | null
|
||||||
|
onSuccess: () => void
|
||||||
|
}
|
||||||
|
|
||||||
|
export function BalanceDialog({
|
||||||
|
open,
|
||||||
|
onOpenChange,
|
||||||
|
currentUser,
|
||||||
|
onSuccess,
|
||||||
|
}: UpdateBalanceDialogProps) {
|
||||||
|
const [isLoading, setIsLoading] = useState(false)
|
||||||
|
|
||||||
|
const {
|
||||||
|
register,
|
||||||
|
handleSubmit,
|
||||||
|
reset,
|
||||||
|
setValue,
|
||||||
|
formState: { errors },
|
||||||
|
} = useForm<BalanceFormValues>({
|
||||||
|
resolver: zodResolver(balanceSchema),
|
||||||
|
defaultValues: {
|
||||||
|
balance: "",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
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) => {
|
||||||
|
if (!currentUser) return
|
||||||
|
|
||||||
|
setIsLoading(true)
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result = await getBalance({
|
||||||
|
user_id: currentUser.id,
|
||||||
|
balance: data.balance,
|
||||||
|
})
|
||||||
|
|
||||||
|
if (result.success) {
|
||||||
|
toast.success("修改余额成功")
|
||||||
|
onOpenChange(false)
|
||||||
|
reset()
|
||||||
|
onSuccess()
|
||||||
|
} else {
|
||||||
|
toast.error(result.message || "修改余额失败")
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
const message =
|
||||||
|
error instanceof Error ? error.message : "网络错误,请稍后重试"
|
||||||
|
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.balance}>
|
||||||
|
<FieldLabel>余额(元)</FieldLabel>
|
||||||
|
<Input
|
||||||
|
type="number"
|
||||||
|
step="0.01"
|
||||||
|
min="0"
|
||||||
|
placeholder="请输入余额"
|
||||||
|
{...register("balance", {
|
||||||
|
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("balance", value)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<FieldError>{errors.balance?.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>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -24,6 +24,7 @@ import {
|
|||||||
SelectValue,
|
SelectValue,
|
||||||
} from "@/components/ui/select"
|
} from "@/components/ui/select"
|
||||||
import type { Cust } from "@/models/cust"
|
import type { Cust } from "@/models/cust"
|
||||||
|
import { BalanceDialog } from "./balanceDialog"
|
||||||
import { AddUserDialog } from "./create"
|
import { AddUserDialog } from "./create"
|
||||||
import { UpdateDialog } from "./update"
|
import { UpdateDialog } from "./update"
|
||||||
|
|
||||||
@@ -67,6 +68,8 @@ export default function UserPage() {
|
|||||||
const [isAddDialogOpen, setIsAddDialogOpen] = useState(false)
|
const [isAddDialogOpen, setIsAddDialogOpen] = useState(false)
|
||||||
const [isEditDialogOpen, setIsEditDialogOpen] = useState(false)
|
const [isEditDialogOpen, setIsEditDialogOpen] = useState(false)
|
||||||
const [currentEditUser, setCurrentEditUser] = useState<Cust | null>(null)
|
const [currentEditUser, setCurrentEditUser] = useState<Cust | null>(null)
|
||||||
|
const [balanceDialog, setBalanceDialog] = useState(false)
|
||||||
|
const [balance, setBalance] = useState<Cust | null>(null)
|
||||||
const { control, handleSubmit, reset } = useForm<FormValues>({
|
const { control, handleSubmit, reset } = useForm<FormValues>({
|
||||||
resolver: zodResolver(filterSchema),
|
resolver: zodResolver(filterSchema),
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
@@ -258,7 +261,7 @@ export default function UserPage() {
|
|||||||
2: "代理商注册",
|
2: "代理商注册",
|
||||||
3: "代理商添加",
|
3: "代理商添加",
|
||||||
}
|
}
|
||||||
return sourceMap[row.original.source] ?? "未知"
|
return sourceMap[row.original.source] ?? "官网注册"
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -352,15 +355,27 @@ export default function UserPage() {
|
|||||||
header: "操作",
|
header: "操作",
|
||||||
cell: ({ row }) => {
|
cell: ({ row }) => {
|
||||||
return (
|
return (
|
||||||
<Button
|
<div className="flex gap-2">
|
||||||
size="sm"
|
<Button
|
||||||
onClick={() => {
|
size="sm"
|
||||||
setCurrentEditUser(row.original)
|
variant="outline"
|
||||||
setIsEditDialogOpen(true)
|
onClick={() => {
|
||||||
}}
|
setBalance(row.original)
|
||||||
>
|
setBalanceDialog(true)
|
||||||
修改
|
}}
|
||||||
</Button>
|
>
|
||||||
|
修改余额
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
size="sm"
|
||||||
|
onClick={() => {
|
||||||
|
setCurrentEditUser(row.original)
|
||||||
|
setIsEditDialogOpen(true)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
修改
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -380,6 +395,13 @@ export default function UserPage() {
|
|||||||
currentUser={currentEditUser}
|
currentUser={currentEditUser}
|
||||||
onSuccess={refreshTable}
|
onSuccess={refreshTable}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<BalanceDialog
|
||||||
|
open={balanceDialog}
|
||||||
|
onOpenChange={setBalanceDialog}
|
||||||
|
currentUser={balance}
|
||||||
|
onSuccess={refreshTable}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user