"use client" import { useState, useEffect, useCallback } from "react" import { useRouter, useSearchParams } from "next/navigation" import { motion, AnimatePresence } from "motion/react" import { Key, Smartphone, Tablet, Monitor, RotateCw, Plus, Trash2, Copy, ExternalLink, CreditCard, Store, Loader2, Eye, EyeOff, Expand, Minimize, Sun, Moon } from "lucide-react" import { toast } from "sonner" import { Button } from "@/components/ui/button" import { Input } from "@/components/ui/input" import { Textarea } from "@/components/ui/textarea" import { Label } from "@/components/ui/label" import { Spinner } from "@/components/ui/spinner" import { Sheet, SheetContent, SheetHeader, SheetTitle, SheetDescription, SheetFooter } from "@/components/ui/sheet" import { ScrollArea, ScrollBar } from "@/components/ui/scroll-area" import { useMerchant } from "@/contexts/merchant-context" import { TransactionProvider, useTransaction } from "@/contexts/transaction-context" import { useUser } from "@/contexts/user-context" import { MerchantService, ConfigService, type PaymentLink, type GetMerchantOrderResponse, type MerchantAPIKey, type UserPayConfig } from "@/lib/services" import { PayingInfo } from "@/components/common/pay/paying/paying-info" import { PayingNow } from "@/components/common/pay/paying/paying-now" import { MerchantSelector } from "@/components/common/merchant/merchant-selector" import { LoadingPage } from "@/components/layout/loading" import { EmptyState } from "@/components/layout/empty" import { TransactionTableList } from "@/components/common/general/table-data" /** 设备预览配置 */ const DEVICE_CONFIG = { mobile: { width: 375, height: 812, borderRadius: 50, borderWidth: 14, scale: 0.6 }, tablet: { width: 768, height: 1024, borderRadius: 32, borderWidth: 18, scale: 0.45 }, desktop: { width: 1200, height: 750, borderRadius: 16, borderWidth: 8, scale: 0.35 } } as const export function MerchantOnline() { const router = useRouter() const searchParams = useSearchParams() const { apiKeys, loading: loadingKeys, loadAPIKeys } = useMerchant() useEffect(() => { loadAPIKeys() }, [loadAPIKeys]) const apiKeyId = searchParams.get("apiKeyId") const selectedKey = apiKeys.find(k => k.id.toString() === apiKeyId) || null if (loadingKeys) { return } if (apiKeys.length === 0) { return (

在线流转

) } return ( ) } interface MerchantOnlineContentProps { apiKeys: MerchantAPIKey[] } function MerchantOnlineContent({ apiKeys }: MerchantOnlineContentProps) { const router = useRouter() const searchParams = useSearchParams() const { transactions, loading: loadingTransactions, error: transactionsError, fetchTransactions, refresh } = useTransaction() /* 派生 selectedKey */ const apiKeyId = searchParams.get("apiKeyId") /* 处理集市应用切换 */ const handleMerchantSelect = useCallback((id: string) => { const params = new URLSearchParams(searchParams.toString()) params.set("apiKeyId", id.toString()) router.push(`${ window.location.pathname }?${ params.toString() }`) }, [searchParams, router]) /* 自动选择第一个集市应用 */ useEffect(() => { if (apiKeys.length > 0 && !apiKeyId) { handleMerchantSelect(apiKeys[0].id) } // eslint-disable-next-line react-hooks/exhaustive-deps }, [apiKeys.length, apiKeyId]) const selectedKey = apiKeys.find(k => k.id.toString() === apiKeyId) || null /* 集市状态 */ const [paymentLinks, setPaymentLinks] = useState([]) const [selectedLink, setSelectedLink] = useState(null) const [previewLink, setPreviewLink] = useState(null) const [isCreating, setIsCreating] = useState(false) const [isEditing, setIsEditing] = useState(false) const [loading, setLoading] = useState(false) /* 创建/编辑表单状态 */ const [productName, setProductName] = useState("") const [amount, setAmount] = useState("") const [remark, setRemark] = useState("") const [totalLimit, setTotalLimit] = useState("") const [userLimit, setUserLimit] = useState("") /* 设备预览状态 */ const [previewDevice, setPreviewDevice] = useState<'mobile' | 'tablet' | 'desktop'>('mobile') const [showToken, setShowToken] = useState(false) const [isFullscreen, setIsFullscreen] = useState(false) const [previewTheme, setPreviewTheme] = useState<'light' | 'dark'>('light') /* 获取应用列表 */ const fetchLinks = useCallback(async () => { if (!selectedKey) { setPaymentLinks([]) return } try { setLoading(true) const links = await MerchantService.listPaymentLinks(selectedKey.id) setPaymentLinks(links) /* 自动设置第一个应用为预览 */ if (links.length > 0) { setPreviewLink(prev => prev || links[0]) } } catch (error) { console.error(error) toast.error("获取应用列表失败") } finally { setLoading(false) } }, [selectedKey]) /* 当 selectedKey 变化时,重新获取应用列表和数据 */ useEffect(() => { if (selectedKey?.client_id) { fetchLinks() fetchTransactions({ page: 1, page_size: 20, types: ['online'], client_id: selectedKey.client_id }) setIsCreating(false) setSelectedLink(null) } else { setPaymentLinks([]) setIsCreating(false) setSelectedLink(null) } // eslint-disable-next-line react-hooks/exhaustive-deps }, [selectedKey?.client_id]) /* 处理创建 */ const handleCreate = async () => { if (!selectedKey) return if (!productName || !amount) { toast.error("请填写在线积分流转服务的名称和数量") return } if (isNaN(parseFloat(amount)) || parseFloat(amount) <= 0) { toast.error("积分数量必须设置为大于0的数值") return } // 验证限制字段 if (totalLimit && (isNaN(parseInt(totalLimit)) || parseInt(totalLimit) < 1)) { toast.error("总数量限制必须是大于0的整数") return } if (userLimit && (isNaN(parseInt(userLimit)) || parseInt(userLimit) < 1)) { toast.error("单用户限制必须是大于0的整数") return } try { setLoading(true) const newLink = await MerchantService.createPaymentLink(selectedKey.id, { product_name: productName, amount: parseFloat(amount), remark, ...(totalLimit && { total_limit: parseInt(totalLimit) }), ...(userLimit && { user_limit: parseInt(userLimit) }) }) toast.success("在线积分流转服务创建成功") /* 重置表单 */ setProductName("") setAmount("") setRemark("") setTotalLimit("") setUserLimit("") /* 更新UI */ fetchLinks() setIsCreating(false) setSelectedLink(newLink) } catch (error: unknown) { const message = error instanceof Error ? error.message : "在线积分流转服务创建失败" toast.error(message) } finally { setLoading(false) } } /* 处理删除 */ const handleDelete = async (link: PaymentLink) => { if (!selectedKey) return if (!confirm("确定要删除这个在线积分流转服务吗?该操作不可恢复。")) return try { setLoading(true) await MerchantService.deletePaymentLink(selectedKey.id, link.id) toast.success("在线积分流转服务已删除") if (selectedLink?.id === link.id) { setSelectedLink(null) } fetchLinks() } catch (error: unknown) { const message = error instanceof Error ? error.message : "在线积分流转服务删除失败" toast.error(message) } finally { setLoading(false) } } /* 处理编辑 */ const handleEdit = (link: PaymentLink) => { setProductName(link.product_name) setAmount(link.amount) setRemark(link.remark || "") setTotalLimit(link.total_limit?.toString() || "") setUserLimit(link.user_limit?.toString() || "") setIsEditing(true) } /* 处理更新 */ const handleUpdate = async () => { if (!selectedKey || !selectedLink) return if (!productName || !amount) { toast.error("请填写在线积分流转服务的名称和数量") return } if (isNaN(parseFloat(amount)) || parseFloat(amount) <= 0) { toast.error("积分数量必须设置为大于0的数值") return } // 验证限制字段 if (totalLimit && (isNaN(parseInt(totalLimit)) || parseInt(totalLimit) < 1)) { toast.error("总数量限制必须是大于0的整数") return } if (userLimit && (isNaN(parseInt(userLimit)) || parseInt(userLimit) < 1)) { toast.error("单用户限制必须是大于0的整数") return } try { setLoading(true) await MerchantService.updatePaymentLink(selectedKey.id, selectedLink.id, { product_name: productName, amount: parseFloat(amount), remark, ...(totalLimit && { total_limit: parseInt(totalLimit) }), ...(userLimit && { user_limit: parseInt(userLimit) }) }) toast.success("在线积分流转服务已更新") /* 更新UI */ fetchLinks() setIsEditing(false) setSelectedLink(null) } catch (error: unknown) { const message = error instanceof Error ? error.message : "在线积分流转服务更新失败" toast.error(message) } finally { setLoading(false) } } /* 处理复制链接 */ const handleCopyLink = (token: string) => { const url = `${ window.location.origin }/paying/online?token=${ token }` navigator.clipboard.writeText(url) toast.success("在线积分流转服务链接已复制") } /* 获取用户支付配置 */ const [userPayConfigs, setUserPayConfigs] = useState([]) const { user } = useUser() useEffect(() => { const loadConfigs = async () => { try { const configs = await ConfigService.getUserPayConfigs() setUserPayConfigs(configs) } catch (error) { console.error("Failed to load user pay configs:", error) } } loadConfigs() }, []) /* 预览订单信息 */ const currentUserConfig = userPayConfigs.find(c => user?.pay_level !== undefined && c.level === user.pay_level) const previewOrderInfo: GetMerchantOrderResponse = { merchant: { app_name: selectedKey?.app_name || "服务名称", redirect_uri: "", }, order: { id: '0', order_no: previewLink ? `LINK-${ previewLink.id }` : "PREVIEW", order_name: isCreating ? (productName || "服务名称") : (previewLink?.product_name || "服务名称"), payer_username: "user", payee_username: selectedKey?.app_name || "应用", amount: isCreating ? (amount || "0.00") : (previewLink?.amount || "0.00"), status: "pending", type: "payment", payment_type: "api", remark: isCreating ? remark : (previewLink?.remark || ""), client_id: "", return_url: "", notify_url: "", trade_time: null, created_at: new Date().toISOString(), updated_at: new Date().toISOString(), }, user_pay_config: currentUserConfig ? { ...currentUserConfig, fee_rate: String(currentUserConfig.fee_rate) } : { id: '0', level: user?.pay_level ?? 0, min_score: 0, max_score: null, daily_limit: null, fee_rate: "0.00", created_at: "", updated_at: "" } } return (

在线流转

{apiKeys.length > 0 && ( )}
{!selectedKey ? (
) : (

所有服务

{loading && paymentLinks.length === 0 && (
)} {paymentLinks.map((link) => ( ))}

活动数据

{isFullscreen ? '全屏预览' : '实时预览'}

{previewDevice === 'mobile' && (
)} {previewDevice === 'tablet' && (
)} {previewDevice === 'desktop' && (
)}
{ }} onCurrentStepChange={() => { }} onSelectedMethodChange={() => { }} onIsOpenChange={() => { }} onPayOrder={() => { }} forceMobile={previewDevice === 'mobile'} />
{ if (!open) { setIsCreating(false); setSelectedLink(null); } }}> {isCreating ? (
创建新服务 创建一个在线积分流转服务。
setProductName(e.target.value)} maxLength={50} />
setAmount(e.target.value)} />
setTotalLimit(e.target.value)} />

设置该链接总共可以支付的次数

setUserLimit(e.target.value)} />

设置每个用户最多可以支付的次数