"use client"; import { Suspense, useCallback, useEffect, useRef, useState } from "react"; import { useSearchParams } from "next/navigation"; import { AdminLayout } from "@/components/layout/admin-layout"; import { PageHeader } from "@/components/layout/page-header"; import { Skeleton } from "@/components/ui/skeleton"; import { ErrorBanner } from "@/components/ui/error-banner"; import { SecondaryButton } from "@/components/ui/primary-button"; import { PrimaryButton } from "@/components/ui/primary-button"; import { InputField } from "@/components/ui/input-field"; import { SelectField } from "@/components/ui/select-field"; import { Card } from "@/components/ui/card"; import { adminGetMarkupConfigs, adminUpdateMarkupConfig, } from "@/lib/frontend/api-client"; import { formatDateTime, formatPercent, formatUSD } from "@/lib/frontend/format"; import { useAuth } from "@/hooks/use-auth"; import type { DataPageStatus, MarkupConfig, MarkupType } from "@/lib/frontend/types"; const PAGE_SIZE = 10; function formatMarkupSummary(row: MarkupConfig): 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%"; } /** 业务客户展示:始终「客户代码 · 客户名称」(不展示表 UUID) */ function formatBusinessCustomerLabel(row: MarkupConfig): string { const code = (row.business_customer_code ?? "").trim(); const name = (row.business_customer_name ?? "").trim(); if (code && name) { return `${code} · ${name}`; } if (code) return code; if (name) return name; return row.business_customer_id || "—"; } function AdminMarkupPageContent() { const { token, user } = useAuth(); const searchParams = useSearchParams(); const [status, setStatus] = useState("loading"); const [rows, setRows] = useState([]); const [total, setTotal] = useState(0); const [page, setPage] = useState(1); const [keywordInput, setKeywordInput] = useState( () => searchParams.get("keyword") ?? "", ); const [keyword, setKeyword] = useState( () => searchParams.get("keyword") ?? "", ); const loadSeqRef = useRef(0); const [editing, setEditing] = useState(null); const [markupType, setMarkupType] = useState("percent"); const [percent, setPercent] = useState(""); const [fixedAmount, setFixedAmount] = useState(""); const [remark, setRemark] = useState(""); const [saving, setSaving] = useState(false); const [fieldError, setFieldError] = useState(); const load = useCallback( async (p: number, kw: string) => { if (!token) return; const seq = ++loadSeqRef.current; setStatus("loading"); const res = await adminGetMarkupConfigs("", token, p, PAGE_SIZE, kw); if (seq !== loadSeqRef.current) return; if (res.code !== 0) { setStatus("error"); return; } setRows(res.data.list); setTotal(res.data.total); setStatus(res.data.total === 0 ? "empty" : "success"); }, [token], ); useEffect(() => { const t = window.setTimeout(() => { const next = keywordInput.trim(); setKeyword((prev) => { if (prev === next) return prev; setPage(1); return next; }); }, 300); return () => window.clearTimeout(t); }, [keywordInput]); useEffect(() => { void load(page, keyword); }, [load, page, keyword]); const openEdit = (row: MarkupConfig) => { setEditing(row); setMarkupType(row.markup_type); setPercent(String(row.markup_percent)); setFixedAmount( row.markup_fixed_amount !== null ? String(row.markup_fixed_amount) : "", ); setRemark(row.remark ?? ""); setFieldError(undefined); }; const handleSave = async () => { if (!editing || !token || !user) return; if (markupType === "percent") { const n = Number(percent); if (Number.isNaN(n) || n < 0 || n > 30) { setFieldError("加价比例须在 0~30% 之间"); return; } } else { const n = Number(fixedAmount); if (Number.isNaN(n) || n < 0) { setFieldError("固定加价金额须 ≥ 0"); return; } } setSaving(true); const res = await adminUpdateMarkupConfig("", token, editing.customer_id, { markup_type: markupType, markup_percent: markupType === "percent" ? Number(percent) : undefined, markup_fixed_amount: markupType === "fixed" ? Number(fixedAmount) : undefined, business_customer_id: editing.business_customer_id ?? undefined, remark, }); setSaving(false); if (res.code !== 0) { setFieldError(res.message); return; } setRows((list) => list.map((r) => r.customer_id === res.data.customer_id && (r.business_customer_id ?? null) === (res.data.business_customer_id ?? null) ? { ...res.data, // 为何这样改:旧 PUT 响应可能无 name/code,保留原展示字段避免变成 UUID business_customer_code: res.data.business_customer_code ?? r.business_customer_code, business_customer_name: res.data.business_customer_name ?? r.business_customer_name, } : r, ), ); setEditing(null); }; const totalPages = Math.max(1, Math.ceil(total / PAGE_SIZE)); return ( setKeywordInput(e.target.value)} /> } /> {status === "loading" && (
{Array.from({ length: 5 }).map((_, i) => ( ))}
)} {status === "error" && ( void load(page, keyword)}> 重试 } > 加载加价配置失败 )} {status === "empty" && ( {keyword ? `未找到匹配「${keyword}」的租户或业务客户。可试:客户代码 SP012、名称 STAR、登录账号 STARPOST(数字 0 勿输成字母 O)` : "暂无业务客户,请先在「租户管理」同步或新增客户"} )} {status === "success" && (
{rows.map((row) => ( ))}
租户 ID 业务客户 加价方式 加价规则 操作人 备注 更新时间 操作
{row.customer_id}
{formatBusinessCustomerLabel(row)}
{row.markup_type === "fixed" ? "固定金额" : "百分比"} {formatMarkupSummary(row)} {row.operator_id || "—"} {row.remark || "—"} {row.operator_id ? formatDateTime(row.updated_at) : "—"} openEdit(row)}> 编辑
共 {total} 条,第 {page} / {totalPages} 页
setPage((p) => Math.max(1, p - 1))} > 上一页 = totalPages} onClick={() => setPage((p) => p + 1)} > 下一页
)} {editing && (
!saving && setEditing(null)} >
e.stopPropagation()} >

编辑加价 · {editing.customer_id} {` / ${formatBusinessCustomerLabel(editing)}`}

百分比仅对运费加价(上限 30%,步长 0.01%);固定金额为每档报价叠加固定金额。

setMarkupType(e.target.value as MarkupType) } /> {markupType === "percent" ? ( setPercent(e.target.value)} /> ) : ( setFixedAmount(e.target.value)} /> )} setRemark(e.target.value)} />
setEditing(null)}> 取消 void handleSave()}> 保存
)}
); } export default function AdminMarkupPage() { return ( } > ); }