"use client" import * as React from "react" import { useState, useEffect } from "react" import { toast } from "sonner" import { Info } from "lucide-react" import { Input } from "@/components/ui/input" import { Label } from "@/components/ui/label" import { Button } from "@/components/ui/button" import { Spinner } from "@/components/ui/spinner" import { Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger } from "@/components/ui/dialog" import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip" import services, { type MerchantAPIKey, type CreateAPIKeyRequest, type UpdateAPIKeyRequest } from "@/lib/services" interface MerchantDialogProps { /** 模式:创建或更新 */ mode: 'create' | 'update' /** API Key*/ apiKey?: MerchantAPIKey /** 创建成功回调 */ onSuccess: (newKey: MerchantAPIKey) => void /** 更新成功回调 */ onUpdate?: (updatedKey: MerchantAPIKey) => void /** 触发按钮 */ trigger?: React.ReactNode /** 自定义创建函数 */ createAPIKey?: (data: CreateAPIKeyRequest) => Promise /** 更新函数 */ updateAPIKey?: (id: string, data: UpdateAPIKeyRequest) => Promise } /** * 集市中心应用对话框组件 * 负责创建和更新集市中心应用的表单和验证逻辑 */ export function MerchantDialog({ mode, apiKey, onSuccess, onUpdate, trigger, createAPIKey, updateAPIKey }: MerchantDialogProps) { const [open, setOpen] = useState(false) const [processing, setProcessing] = useState(false) const [mounted, setMounted] = useState(false) const [showNotifyTooltip, setShowNotifyTooltip] = useState(false) const [tooltipSide, setTooltipSide] = useState<'top' | 'bottom'>('bottom') const [formData, setFormData] = useState({ app_name: '', app_homepage_url: '', app_description: '', redirect_uri: '', notify_url: '', public_key: '', test_mode: false, }) useEffect(() => { setMounted(true) }, []) /** * 获取初始表单数据 */ const getInitialFormData = React.useCallback((): CreateAPIKeyRequest | UpdateAPIKeyRequest => { if (mode === 'update' && apiKey) { return { app_name: apiKey.app_name, app_homepage_url: apiKey.app_homepage_url, app_description: apiKey.app_description, redirect_uri: apiKey.redirect_uri, notify_url: apiKey.notify_url, public_key: apiKey.public_key, test_mode: apiKey.test_mode, } } return { app_name: '', app_homepage_url: '', app_description: '', redirect_uri: '', notify_url: '', test_mode: false, } }, [mode, apiKey]) /** * 初始化表单数据 */ useEffect(() => { setFormData(getInitialFormData()) }, [getInitialFormData]) const isValidUrl = (url: string): boolean => { if (!url || url.trim() === '') return false try { const urlObj = new URL(url.trim()) return urlObj.protocol === 'http:' || urlObj.protocol === 'https:' } catch { return false } } const resetForm = () => { setFormData(getInitialFormData()) } const validateForm = (): { valid: boolean; error?: string } => { /* 验证必填项 */ if (!formData.app_name || !formData.app_homepage_url || !formData.notify_url) { return { valid: false, error: '请填写所有必填项' } } /* 验证应用名称长度 */ if (formData.app_name.length > 20) { return { valid: false, error: '应用名称不能超过 20 个字符' } } /* 验证应用主页 URL */ if (!isValidUrl(formData.app_homepage_url)) { return { valid: false, error: '应用主页 URL 格式不正确' } } /* 验证回调 URI */ if (formData.redirect_uri && !isValidUrl(formData.redirect_uri)) { return { valid: false, error: '回调 URI 格式不正确' } } /* 验证通知 URL */ if (!isValidUrl(formData.notify_url)) { return { valid: false, error: '通知 URL 格式不正确' } } /* 验证应用描述长度 */ if (formData.app_description && formData.app_description.length > 100) { return { valid: false, error: '应用描述不能超过 100 个字符' } } return { valid: true } } const handleSubmit = async () => { const validation = validateForm() if (!validation.valid) { toast.error('表单验证失败', { description: validation.error }) return } try { setProcessing(true) if (mode === 'create') { const newKey = await (createAPIKey ? createAPIKey(formData as CreateAPIKeyRequest) : services.merchant.createAPIKey(formData as CreateAPIKeyRequest)) toast.success('创建成功', { description: '新应用已创建,请妥善保管您的 Client Secret' }) onSuccess(newKey) } else if (mode === 'update' && apiKey) { if (updateAPIKey) { await updateAPIKey(apiKey.id, formData as UpdateAPIKeyRequest) } else { await services.merchant.updateAPIKey(apiKey.id, formData as UpdateAPIKeyRequest) } toast.success('更新成功', { description: '应用信息已更新' }) /* 更新本地状态 */ const updatedKey = { ...apiKey, ...formData } onUpdate?.(updatedKey) } setOpen(false) } catch (error) { const errorMessage = (error as Error).message || `无法${mode === 'create' ? '创建' : '更新'}应用` toast.error(`${mode === 'create' ? '创建' : '更新'}失败`, { description: errorMessage }) throw error } finally { setProcessing(false) } } const handleOpenChange = (newOpen: boolean) => { if (newOpen && !processing) { resetForm() } setOpen(newOpen) } if (!mounted) { return null } return ( {trigger || ( )} {mode === 'create' ? '创建应用' : '更新应用信息'} {mode === 'create' ? '您可以创建一个集市中心应用来接入积分服务,请仔细填写以下信息' : '修改集市中心应用的基本信息和配置'}
setFormData({ ...formData, app_name: e.target.value })} disabled={processing} />
setFormData({ ...formData, app_description: e.target.value })} disabled={processing} />
setFormData({ ...formData, app_homepage_url: e.target.value })} disabled={processing} />

如果您只需要使用在线积分流转服务,建议直接填写 http://localhost:3000/ 即可

{ const value = e.target.value setFormData({ ...formData, notify_url: value }) if (value.trim()) { setShowNotifyTooltip(false) } }} onClick={() => { if (!formData.notify_url || !formData.notify_url.trim()) { setTooltipSide('top') setShowNotifyTooltip(true) } }} onBlur={() => setShowNotifyTooltip(false)} disabled={processing} />
setFormData({ ...formData, redirect_uri: e.target.value })} disabled={processing} />
setFormData({ ...formData, public_key: e.target.value })} disabled={processing} />
) }