diff --git a/src/actions/cust.ts b/src/actions/cust.ts index af2ba9b..a4b04fb 100644 --- a/src/actions/cust.ts +++ b/src/actions/cust.ts @@ -7,8 +7,14 @@ export async function getPageCusts(params: { page: number; size: number }) { } export async function updateCust(data: { id: number - phone: string - email: string + username?: string + email?: string + password?: string + admin_id?: number + discount_id?: number + status?: number + contact_qq?: string + contact_wechat?: string }) { return callByUser>("/api/admin/user/update", data) } diff --git a/src/app/(root)/cust/create.tsx b/src/app/(root)/cust/create.tsx index bdedf0d..bf84c45 100644 --- a/src/app/(root)/cust/create.tsx +++ b/src/app/(root)/cust/create.tsx @@ -148,14 +148,14 @@ export function AddUserDialog({ const onSubmit = handleSubmit(async data => { const payload = { username: data.username, - password: data.password, + password: data?.password, phone: data.phone, email: data?.email || "", name: data?.name, admin_id: data.admin_id ? Number(data.admin_id) : undefined, discount_id: data.discount_id ? Number(data.discount_id) : undefined, avatar: data?.avatar, - status: data.status ? parseInt(data.status) : 1, + status: data.status ? parseInt(data?.status) : 1, contact_qq: data?.contact_qq, contact_wechat: data?.contact_wechat, } @@ -233,7 +233,6 @@ export function AddUserDialog({ {...field} type="password" placeholder="请输入密码(至少6位)" - autoComplete="off" /> {fieldState.error?.message} @@ -249,7 +248,6 @@ export function AddUserDialog({ {...field} type="password" placeholder="请再次输入密码" - autoComplete="off" /> {fieldState.error?.message} diff --git a/src/app/(root)/cust/page.tsx b/src/app/(root)/cust/page.tsx index 316f089..e58c5f4 100644 --- a/src/app/(root)/cust/page.tsx +++ b/src/app/(root)/cust/page.tsx @@ -2,11 +2,10 @@ import { zodResolver } from "@hookform/resolvers/zod" import { format } from "date-fns" -import { Suspense, useCallback, useRef, useState } from "react" +import { Suspense, useCallback, useState } from "react" import { Controller, useForm } from "react-hook-form" -import { toast } from "sonner" import { z } from "zod" -import { getPageCusts, updateCust } from "@/actions/cust" +import { getPageCusts } from "@/actions/cust" import { DataTable, useDataTable } from "@/components/data-table" import { Badge } from "@/components/ui/badge" import { Button } from "@/components/ui/button" @@ -26,6 +25,7 @@ import { } from "@/components/ui/select" import type { Cust } from "@/models/cust" import { AddUserDialog } from "./create" +import { UpdateDialog } from "./update" type FilterValues = { account?: string @@ -64,14 +64,9 @@ type FormValues = z.infer export default function UserPage() { const [filters, setFilters] = useState({}) - const [editingRowId, setEditingRowId] = useState(null) - const [editPhone, setEditPhone] = useState("") - const [editEmail, setEditEmail] = useState("") - const [isSaving, setIsSaving] = useState(false) const [isAddDialogOpen, setIsAddDialogOpen] = useState(false) - - const editingRowRef = useRef(null) - + const [isEditDialogOpen, setIsEditDialogOpen] = useState(false) + const [currentEditUser, setCurrentEditUser] = useState(null) const { control, handleSubmit, reset } = useForm({ resolver: zodResolver(filterSchema), defaultValues: { @@ -90,6 +85,7 @@ export default function UserPage() { ) const table = useDataTable(fetchUsers) + console.log(table, "客户管理table") const onFilter = handleSubmit(data => { const result: FilterValues = {} @@ -107,58 +103,6 @@ export default function UserPage() { table.refresh() }, [table]) - const startEdit = (row: Cust) => { - setEditingRowId(row.id) - setEditPhone(row.phone || "") - setEditEmail(row.email || "") - editingRowRef.current = row - } - - const cancelEdit = () => { - setEditingRowId(null) - setEditPhone("") - setEditEmail("") - editingRowRef.current = null - } - - const saveEdit = async (row: Cust) => { - const phoneRegex = /^1[3-9]\d{9}$/ - if (editPhone && !phoneRegex.test(editPhone)) { - toast.error("请输入正确的手机号格式") - return false - } - - const emailRegex = /^[^\s@]+@([^\s@]+\.)+[^\s@]+$/ - if (editEmail && !emailRegex.test(editEmail)) { - toast.error("请输入正确的邮箱格式") - return false - } - - setIsSaving(true) - try { - const result = await updateCust({ - id: row.id, - phone: editPhone, - email: editEmail, - }) - if (result.success) { - toast.success("更新成功") - refreshTable() - cancelEdit() - return true - } else { - toast.error(result.message || "更新失败") - return false - } - } catch (error) { - toast.error("更新失败,请稍后重试") - console.error(error) - return false - } finally { - setIsSaving(false) - } - } - const handleAddUserSuccess = () => { refreshTable() } @@ -301,77 +245,8 @@ export default function UserPage() { columns={[ { header: "ID", accessorKey: "id" }, { header: "账号", accessorKey: "username" }, - { - header: "手机", - accessorKey: "phone", - cell: ({ row }) => { - const isEditing = editingRowId === row.original.id - if (isEditing) { - return ( - setEditPhone(e.target.value)} - onBlur={() => { - if (editingRowRef.current) { - saveEdit(editingRowRef.current) - } - }} - onKeyDown={e => { - if (e.key === "Enter") { - e.preventDefault() - if (editingRowRef.current) { - saveEdit(editingRowRef.current) - } - } - if (e.key === "Escape") { - e.preventDefault() - cancelEdit() - } - }} - placeholder="手机号" - className="w-32" - autoFocus - /> - ) - } - return row.original.phone || "-" - }, - }, - { - header: "邮箱", - accessorKey: "email", - cell: ({ row }) => { - const isEditing = editingRowId === row.original.id - if (isEditing) { - return ( - setEditEmail(e.target.value)} - onBlur={() => { - if (editingRowRef.current) { - saveEdit(editingRowRef.current) - } - }} - onKeyDown={e => { - if (e.key === "Enter") { - e.preventDefault() - if (editingRowRef.current) { - saveEdit(editingRowRef.current) - } - } - if (e.key === "Escape") { - e.preventDefault() - cancelEdit() - } - }} - placeholder="邮箱" - className="w-40" - /> - ) - } - return row.original.email || "-" - }, - }, + { header: "手机", accessorKey: "phone" }, + { header: "邮箱", accessorKey: "email" }, { header: "姓名", accessorKey: "name" }, { header: "客户来源", @@ -432,7 +307,22 @@ export default function UserPage() { accessorKey: "status", cell: ({ row }) => (row.original.status === 1 ? "正常" : "禁用"), }, - { header: "联系方式", accessorKey: "contact_wechat" }, + { + header: "联系方式", + cell: ({ row }) => { + const qq = row.original.contact_qq || "" + const wechat = row.original.contact_wechat || "" + const hasQQ = qq.trim() !== "" + const hasWechat = wechat.trim() !== "" + if (!hasQQ && !hasWechat) return null + return ( +
+ {hasWechat &&
微信:{wechat}
} + {hasQQ &&
QQ:{qq}
} +
+ ) + }, + }, { header: "客户经理", accessorKey: "admin.name" }, { header: "最后登录时间", @@ -461,30 +351,14 @@ export default function UserPage() { meta: { pin: "right" }, header: "操作", cell: ({ row }) => { - const isEditing = editingRowId === row.original.id - if (isEditing) { - return ( -
- - -
- ) - } return ( - ) @@ -499,6 +373,13 @@ export default function UserPage() { onOpenChange={setIsAddDialogOpen} onSuccess={handleAddUserSuccess} /> + + ) } diff --git a/src/app/(root)/cust/update.tsx b/src/app/(root)/cust/update.tsx new file mode 100644 index 0000000..7309abc --- /dev/null +++ b/src/app/(root)/cust/update.tsx @@ -0,0 +1,390 @@ +"use client" + +import { zodResolver } from "@hookform/resolvers/zod" +import { useEffect, useState } from "react" +import { Controller, useForm } from "react-hook-form" +import { toast } from "sonner" +import { z } from "zod" +import { getAllAdmin } from "@/actions/admin" +import { updateCust } from "@/actions/cust" +import { getAllProductDiscount } from "@/actions/product_discount" +import { Button } from "@/components/ui/button" +import { + Dialog, + DialogContent, + DialogHeader, + DialogTitle, +} from "@/components/ui/dialog" +import { + Field, + FieldError, + FieldGroup, + FieldLabel, +} from "@/components/ui/field" +import { Input } from "@/components/ui/input" +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select" +import type { Admin } from "@/models/admin" +import type { Cust } from "@/models/cust" +import type { ProductDiscount } from "@/models/product_discount" + +// 表单验证规则 +const editUserSchema = z + .object({ + id: z.number(), + username: z.string().min(2, "用户名至少2个字符"), + email: z.string().email("邮箱格式不正确").optional().or(z.literal("")), + password: z.string().optional(), + confirmPassword: z.string().optional(), + admin_id: z.string().optional(), + discount_id: z.string().optional(), + status: z.string().optional(), + contact_qq: z.string().optional(), + contact_wechat: z.string().optional(), + }) + .superRefine((data, ctx) => { + if (data.password && data.password.length < 6) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: "密码至少6位", + path: ["password"], + }) + } + if (data.password && data.password !== data.confirmPassword) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: "两次输入的密码不一致", + path: ["confirmPassword"], + }) + } + }) + +export type EditUserFormValues = z.infer + +interface EditUserDialogProps { + open: boolean + onOpenChange: (open: boolean) => void + currentUser: Cust | null + onSuccess: () => void +} + +export function UpdateDialog({ + open, + onOpenChange, + currentUser, + onSuccess, +}: EditUserDialogProps) { + const [admins, setAdmins] = useState([]) + const [discounts, setDiscounts] = useState([]) + const [isSubmitting, setIsSubmitting] = useState(false) + + // 表单 + const { control, handleSubmit, reset } = useForm({ + resolver: zodResolver(editUserSchema), + defaultValues: { + id: 0, + username: "", + email: "", + password: "", + confirmPassword: "", + admin_id: "", + discount_id: "", + status: "", + contact_qq: "", + contact_wechat: "", + }, + }) + + useEffect(() => { + const loadOptions = async () => { + try { + const [adminRes, discountRes] = await Promise.all([ + getAllAdmin(), + getAllProductDiscount(), + ]) + + if (adminRes.success) { + setAdmins(adminRes.data) + } else { + toast.error(adminRes.message || "获取管理员失败") + setAdmins([]) + } + + if (discountRes.success) { + setDiscounts(discountRes.data) + } else { + toast.error(discountRes.message || "获取折扣方案失败") + setDiscounts([]) + } + } catch (error) { + const message = error instanceof Error ? error.message : "加载数据失败" + toast.error(message) + } + } + + if (open) { + loadOptions() + } + }, [open]) + + useEffect(() => { + if (currentUser) { + reset({ + id: currentUser.id, + username: currentUser.username, + email: currentUser.email || "", + password: "", + confirmPassword: "", + admin_id: currentUser.admin_id ? String(currentUser.admin_id) : "", + discount_id: currentUser.discount_id + ? String(currentUser.discount_id) + : "", + status: + currentUser.status !== undefined ? String(currentUser.status) : "", + contact_qq: currentUser.contact_qq || "", + contact_wechat: currentUser.contact_wechat || "", + }) + } + }, [currentUser, reset]) + + const onSubmit = async (data: EditUserFormValues) => { + setIsSubmitting(true) + try { + const updateData: { + id: number + username?: string + email?: string + password?: string + admin_id?: number + discount_id?: number + status?: number + contact_qq?: string + contact_wechat?: string + } = { id: data.id } + + if (data.username && data.username.trim() !== "") { + updateData.username = data.username.trim() + } + if (data.email && data.email.trim() !== "") { + updateData.email = data.email.trim() + } + if (data.password && data.password.trim() !== "") { + updateData.password = data.password + } + if (data.admin_id && data.admin_id !== "") { + updateData.admin_id = Number(data.admin_id) + } + if (data.discount_id && data.discount_id !== "") { + updateData.discount_id = Number(data.discount_id) + } + if (data.status && data.status !== "") { + updateData.status = Number(data.status) + } + if (data.contact_qq !== undefined) { + updateData.contact_qq = data.contact_qq + } + if (data.contact_wechat !== undefined) { + updateData.contact_wechat = data.contact_wechat + } + const result = await updateCust(updateData) + if (result?.success) { + toast.success("修改成功") + onOpenChange(false) + onSuccess() + } else { + toast.error(result?.message || "修改失败") + } + } catch (error) { + console.error("修改用户失败:", error) + const message = + error instanceof Error ? error.message : "修改失败,请稍后重试" + toast.error(`修改失败:${message}`) + } finally { + setIsSubmitting(false) + } + } + + const handleOpenChange = (open: boolean) => { + if (!open) { + reset() + } + onOpenChange(open) + } + + return ( + + + + 修改用户 + + +
+
+ ( + + 用户名 * + + {fieldState.error?.message} + + )} + /> + + ( + + 邮箱 + + {fieldState.error?.message} + + )} + /> + + ( + + 用户密码 + + {fieldState.error?.message} + + )} + /> + + ( + + 确认密码 + + {fieldState.error?.message} + + )} + /> + + ( + + 账号状态 + + + )} + /> + + ( + + 客户经理 + + + )} + /> + + ( + + 折扣方案 + + + )} + /> + + ( + + QQ + + + )} + /> + + ( + + 微信 + + + )} + /> +
+ + + + + +
+
+
+ ) +} diff --git a/src/app/(root)/permissions/page.tsx b/src/app/(root)/permissions/page.tsx index 414ec87..f53b822 100644 --- a/src/app/(root)/permissions/page.tsx +++ b/src/app/(root)/permissions/page.tsx @@ -1,6 +1,5 @@ "use client" import { - flexRender, getCoreRowModel, getExpandedRowModel, type Row, diff --git a/src/models/cust.ts b/src/models/cust.ts index 3ef0446..0686ae1 100644 --- a/src/models/cust.ts +++ b/src/models/cust.ts @@ -5,7 +5,9 @@ export type Cust = { admin_id?: number phone: string admin?: Admin + password: string source: number + discount_id: string has_password: boolean username: string email: string