"use client"; import { useCallback, useEffect, useMemo, useState } from "react"; import { useParams } from "next/navigation"; import { AdminLayout } from "@/components/layout/admin-layout"; import { PageHeader } from "@/components/layout/page-header"; import { Card } from "@/components/ui/card"; import { ErrorBanner } from "@/components/ui/error-banner"; import { InputField } from "@/components/ui/input-field"; import { SelectField } from "@/components/ui/select-field"; import { PrimaryButton, SecondaryButton } from "@/components/ui/primary-button"; import { Skeleton } from "@/components/ui/skeleton"; import { useAuth } from "@/hooks/use-auth"; import { adminCreateBusinessCustomer, adminGetBusinessCustomerUsers, adminGetBusinessCustomers, adminGetMarkupConfigs, adminListTenantUsers, adminUpdateBusinessCustomer, adminUpdateMarkupConfig, } from "@/lib/frontend/api-client"; import { formatDateTime, formatPercent, formatUSD } from "@/lib/frontend/format"; import type { BusinessCustomerRecord, BusinessCustomerUserRecord, DataPageStatus, MarkupConfig, MarkupType, } from "@/lib/frontend/types"; function formatMarkupRule( row: Pick< MarkupConfig, "markup_type" | "markup_percent" | "markup_fixed_amount" >, ): string { if (row.markup_type === "fixed") { const amount = row.markup_fixed_amount ?? 0; return amount > 0 ? `固定 ${formatUSD(amount)}` : "未配置"; } return row.markup_percent > 0 ? formatPercent(row.markup_percent) : "0%"; } export default function TenantBusinessCustomersPage() { const params = useParams<{ customer_id: string }>(); const tenantId = decodeURIComponent(params.customer_id); const { token } = useAuth(); const [status, setStatus] = useState("loading"); const [rows, setRows] = useState([]); const [error, setError] = useState(null); /** 业务客户 → 当前加价摘要(打开/保存后刷新) */ const [markupByBcId, setMarkupByBcId] = useState>({}); const [createOpen, setCreateOpen] = useState(false); const [creating, setCreating] = useState(false); const [createError, setCreateError] = useState(null); const [createForm, setCreateForm] = useState({ business_customer_id: "", name: "", external_code: "", remark: "", }); const [editing, setEditing] = useState(null); const [savingEdit, setSavingEdit] = useState(false); const [editError, setEditError] = useState(null); const [editForm, setEditForm] = useState({ name: "", external_code: "", remark: "", status: "active" as "active" | "disabled", }); const [markupTarget, setMarkupTarget] = useState( null, ); const [markupType, setMarkupType] = useState("percent"); const [markupPercent, setMarkupPercent] = useState(""); const [markupFixedAmount, setMarkupFixedAmount] = useState(""); const [markupRemark, setMarkupRemark] = useState(""); const [savingMarkup, setSavingMarkup] = useState(false); const [loadingMarkup, setLoadingMarkup] = useState(false); const [markupError, setMarkupError] = useState(null); const [markupSuccess, setMarkupSuccess] = useState(null); const [usersTarget, setUsersTarget] = useState( null, ); const [usersStatus, setUsersStatus] = useState("empty"); const [users, setUsers] = useState([]); const [usersError, setUsersError] = useState(null); const [onlyWithUsers, setOnlyWithUsers] = useState(true); const [allUsersOpen, setAllUsersOpen] = useState(false); const [allUsersKeyword, setAllUsersKeyword] = useState(""); const [allUsersStatus, setAllUsersStatus] = useState("empty"); const [allUsers, setAllUsers] = useState([]); const [allUsersError, setAllUsersError] = useState(null); const load = useCallback(async () => { if (!token) return; setStatus("loading"); setError(null); const res = await adminGetBusinessCustomers("", token, tenantId); if (res.code !== 0) { setStatus("error"); setError(res.message); return; } setRows(res.data.list); setStatus(res.data.list.length ? "success" : "empty"); }, [tenantId, token]); useEffect(() => { void load(); }, [load]); const sortedRows = useMemo(() => { const filtered = onlyWithUsers ? rows.filter((r) => (r.user_count ?? 0) > 0) : rows; return [...filtered].sort((a, b) => { const uc = (b.user_count ?? 0) - (a.user_count ?? 0); if (uc !== 0) return uc; const ac = ( a.external_code || a.name || a.business_customer_id ).toLowerCase(); const bc = ( b.external_code || b.name || b.business_customer_id ).toLowerCase(); return ac.localeCompare(bc); }); }, [rows, onlyWithUsers]); const loadAllUsers = useCallback( async (keyword?: string) => { if (!token) return; setAllUsersStatus("loading"); setAllUsersError(null); const res = await adminListTenantUsers("", token, tenantId, keyword); if (res.code !== 0) { setAllUsersStatus("error"); setAllUsersError(res.message); return; } setAllUsers(res.data.list); setAllUsersStatus(res.data.list.length ? "success" : "empty"); }, [tenantId, token], ); const openEdit = (row: BusinessCustomerRecord) => { setEditing(row); setEditError(null); setEditForm({ name: row.name, external_code: row.external_code ?? "", remark: row.remark ?? "", status: row.status, }); }; const handleCreate = async () => { if (!token) return; setCreating(true); setCreateError(null); const res = await adminCreateBusinessCustomer("", token, tenantId, createForm); setCreating(false); if (res.code !== 0) { setCreateError(res.message); return; } setCreateOpen(false); setCreateForm({ business_customer_id: "", name: "", external_code: "", remark: "", }); await load(); }; const handleSaveEdit = async () => { if (!token || !editing) return; setSavingEdit(true); setEditError(null); const res = await adminUpdateBusinessCustomer( "", token, tenantId, editing.business_customer_id, editForm, ); setSavingEdit(false); if (res.code !== 0) { setEditError(res.message); return; } setRows((list) => list.map((item) => item.business_customer_id === res.data.business_customer_id ? res.data : item, ), ); setEditing(null); }; const openMarkup = async (row: BusinessCustomerRecord) => { setMarkupTarget(row); setMarkupType("percent"); setMarkupPercent(""); setMarkupFixedAmount(""); setMarkupRemark(""); setMarkupError(null); setMarkupSuccess(null); if (!token) { setMarkupError("未登录或登录已失效,请重新登录后再保存"); return; } setLoadingMarkup(true); const kw = row.external_code?.trim() || row.business_customer_id || row.name || ""; const res = await adminGetMarkupConfigs("", token, 1, 50, kw); setLoadingMarkup(false); if (res.code !== 0) { setMarkupError(`读取已有加价失败:${res.message}`); return; } const found = res.data.list.find( (c) => c.customer_id === tenantId && (c.business_customer_id ?? "") === row.business_customer_id, ); if (!found) return; setMarkupType(found.markup_type); setMarkupPercent(String(found.markup_percent ?? "")); setMarkupFixedAmount( found.markup_fixed_amount !== null && found.markup_fixed_amount !== undefined ? String(found.markup_fixed_amount) : "", ); setMarkupRemark(found.remark ?? ""); setMarkupByBcId((m) => ({ ...m, [row.business_customer_id]: formatMarkupRule(found), })); }; const handleSaveMarkup = async () => { if (!markupTarget) return; if (!token) { setMarkupError("未登录或登录已失效,请重新登录后再保存"); return; } setMarkupError(null); setMarkupSuccess(null); let percentValue = 0; let fixedValue: number | undefined; if (markupType === "percent") { const raw = markupPercent.trim(); if (!raw) { setMarkupError("请填写加价比例(0~30),空值不会保存"); return; } const n = Number(raw); if (Number.isNaN(n) || n < 0 || n > 30) { setMarkupError("加价比例须在 0~30% 之间"); return; } percentValue = n; } else { const raw = markupFixedAmount.trim(); if (!raw) { setMarkupError("请填写固定加价金额(USD),空值不会保存"); return; } const n = Number(raw); if (Number.isNaN(n) || n < 0) { setMarkupError("固定加价金额须 ≥ 0"); return; } fixedValue = n; } setSavingMarkup(true); const res = await adminUpdateMarkupConfig("", token, tenantId, { business_customer_id: markupTarget.business_customer_id, markup_type: markupType, markup_percent: markupType === "percent" ? percentValue : 0, markup_fixed_amount: markupType === "fixed" ? fixedValue : undefined, remark: markupRemark, }); setSavingMarkup(false); if (res.code !== 0) { setMarkupError(res.message || "保存失败,请稍后重试"); return; } const summary = formatMarkupRule(res.data); setMarkupByBcId((m) => ({ ...m, [markupTarget.business_customer_id]: summary, })); setMarkupSuccess(`已保存:${summary}`); }; return ( { setAllUsersOpen(true); void loadAllUsers(allUsersKeyword); }} > 全部登录账号 setCreateOpen(true)}> 新增业务客户 } /> {status === "loading" && (
{Array.from({ length: 4 }).map((_, i) => ( ))}
)} {status === "error" && ( void load()}>重试}> {error ?? "加载业务客户失败"} )} {status === "empty" && ( 当前租户还没有业务客户 )} {status === "success" && (
{sortedRows.map((row) => ( ))}
客户代码 客户名称 加价规则 登录账号数 状态 备注 更新时间 操作
{row.external_code || "—"} {row.name} {markupByBcId[row.business_customer_id] ?? "—"} {row.user_count ?? 0} {row.status === "active" ? "启用" : "停用"} {row.remark || "—"} {formatDateTime(row.updated_at)}
openEdit(row)}> 编辑 void openMarkup(row)} > 客户加价 { setUsersTarget(row); setUsersError(null); setUsers([]); setUsersStatus("loading"); if (!token) return; const res = await adminGetBusinessCustomerUsers( "", token, tenantId, row.business_customer_id, ); if (res.code !== 0) { setUsersStatus("error"); setUsersError(res.message); return; } setUsers(res.data.list); setUsersStatus( res.data.list.length ? "success" : "empty", ); }} > 登录账号
)} {createOpen && (
!creating && setCreateOpen(false)} >
e.stopPropagation()} >

新增业务客户

setCreateForm((prev) => ({ ...prev, business_customer_id: e.target.value, })) } /> setCreateForm((prev) => ({ ...prev, name: e.target.value })) } /> setCreateForm((prev) => ({ ...prev, external_code: e.target.value, })) } /> setCreateForm((prev) => ({ ...prev, remark: e.target.value })) } /> {createError ? {createError} : null}
setCreateOpen(false)}>取消 void handleCreate()}> 保存
)} {editing && (
!savingEdit && setEditing(null)} >
e.stopPropagation()} >

编辑业务客户

setEditForm((prev) => ({ ...prev, name: e.target.value })) } /> setEditForm((prev) => ({ ...prev, external_code: e.target.value, })) } /> setEditForm((prev) => ({ ...prev, status: e.target.value as "active" | "disabled", })) } /> setEditForm((prev) => ({ ...prev, remark: e.target.value })) } /> {editError ? {editError} : null}
setEditing(null)}>取消 void handleSaveEdit()}> 保存
)} {markupTarget && (
!savingMarkup && setMarkupTarget(null)} >
e.stopPropagation()} >

客户加价 ·{" "} {[markupTarget.external_code, markupTarget.name] .filter(Boolean) .join(" · ") || markupTarget.business_customer_id}

仅对本业务客户生效;未配置时询价按 0% 处理。

{loadingMarkup ? ( ) : null} { setMarkupType(e.target.value as MarkupType); setMarkupError(null); setMarkupSuccess(null); }} /> {markupType === "percent" ? ( { setMarkupPercent(e.target.value); setMarkupError(null); setMarkupSuccess(null); }} /> ) : ( { setMarkupFixedAmount(e.target.value); setMarkupError(null); setMarkupSuccess(null); }} /> )} setMarkupRemark(e.target.value)} /> 保存后按客户组织 ID 匹配加价。百分比上限 30%;固定金额为每档报价叠加。空值不会保存,也不会覆盖已有规则。 {markupError ? {markupError} : null} {markupSuccess ? ( {markupSuccess} ) : null}
setMarkupTarget(null)} > {markupSuccess ? "关闭" : "取消"} void handleSaveMarkup()} > 保存
)} {usersTarget && (
setUsersTarget(null)} >
e.stopPropagation()} >

登录账号 ·{" "} {[usersTarget.external_code, usersTarget.name] .filter(Boolean) .join(" · ") || usersTarget.business_customer_id}

来自 ccnew PC/Web 登录账号(LR_Base_User),用于询价识别加价客户。

{usersStatus === "loading" ? : null} {usersError ? {usersError} : null} {usersStatus === "empty" ? (

暂无同步账号

) : null} {usersStatus === "success" ? ( {users.map((u) => ( ))}
账号 姓名 状态
{u.account} {u.display_name || "—"} {u.status === "active" ? "启用" : "停用"}
) : null}
setUsersTarget(null)}>关闭
)} {allUsersOpen && (
setAllUsersOpen(false)} >
e.stopPropagation()} >

全部登录账号

对应 ccnew「用户管理」中的客户账号;询价靠账号识别所属客户代码再套加价。

setAllUsersKeyword(e.target.value)} />
void loadAllUsers(allUsersKeyword)}> 查询
{allUsersStatus === "loading" ? ( ) : null} {allUsersError ? {allUsersError} : null} {allUsersStatus === "empty" ? (

暂无账号

) : null} {allUsersStatus === "success" ? ( {allUsers.map((u) => ( ))}
账号 姓名 客户代码 客户名称 状态
{u.account} {u.display_name || "—"} {u.business_customer_code || "—"} {u.business_customer_name || "—"} {u.status === "active" ? "启用" : "停用"}
) : null}
setAllUsersOpen(false)}>关闭
)}
); }