'use client'; import { useState, useEffect } from 'react'; import { useRouter } from 'next/navigation'; import Image from 'next/image'; const DEFAULT_LOGIN_LOGO = process.env.NEXT_PUBLIC_DEFAULT_LOGIN_LOGO_URL || ''; type PublicSystemSettings = { loginLogoUrl?: string | null; siteName?: string | null; }; export default function ActivatePage() { const router = useRouter(); const freeTrialActivationEnabled = process.env.NEXT_PUBLIC_ENABLE_FREE_TRIAL_ACTIVATION === 'true'; const [initialAdmin, setInitialAdmin] = useState<{ email: string; username: string; password: string | null; mustChangePassword: boolean; passwordSource?: string | null; } | null>(null); const [licenseKey, setLicenseKey] = useState(''); const [loading, setLoading] = useState(false); const [checking, setChecking] = useState(true); const [error, setError] = useState(''); const [activated, setActivated] = useState(false); const [showFreeTrialModal, setShowFreeTrialModal] = useState(false); const [customerName, setCustomerName] = useState(''); const [contactInfo, setContactInfo] = useState(''); const [loginLogoUrl, setLoginLogoUrl] = useState(DEFAULT_LOGIN_LOGO); const [siteName, setSiteName] = useState('FLNXI素材库'); const [logoError, setLogoError] = useState(false); useEffect(() => { const loadSettings = async () => { try { const response = await fetch('/api/system-settings'); if (response.ok) { const settings = await response.json() as PublicSystemSettings; setLoginLogoUrl(settings.loginLogoUrl || DEFAULT_LOGIN_LOGO); setSiteName(settings.siteName || 'FLNXI素材库'); setLogoError(false); } } catch (error) { console.error('加载系统设置失败:', error); } }; loadSettings(); }, []); useEffect(() => { if (!loginLogoUrl) return; if (loginLogoUrl.startsWith('/')) { setLogoError(false); return; } try { const parsed = new URL(loginLogoUrl); const host = parsed.hostname.toLowerCase(); const isLocal = host === 'localhost' || host === '127.0.0.1'; if (isLocal && typeof window !== 'undefined') { const currentHost = window.location.hostname.toLowerCase(); if (currentHost !== host) { console.warn( `[Activate] Logo 地址 ${loginLogoUrl} 指向本地服务,但当前访问域名为 ${currentHost},自动降级为占位符` ); setLogoError(true); } } } catch (error) { console.warn('[Activate] Logo 地址解析失败,自动降级为占位符:', loginLogoUrl, error); setLogoError(true); } }, [loginLogoUrl]); // 页面加载时检查数据库中是否已有许可证 useEffect(() => { const checkLicense = async () => { try { const res = await fetch('/api/license/check'); const data = await res.json(); if (data.activated) { // 数据库中已有许可证,cookie 已自动恢复,重定向到首页 router.push('/'); return; } } catch (error) { console.error('检查许可证状态失败:', error); } finally { setChecking(false); } }; checkLicense(); }, [router]); // 格式化许可证密钥输入(自动添加横杠) const formatLicenseKey = (value: string) => { const cleaned = value.toUpperCase().replace(/[^A-Z0-9]/g, ''); const parts = []; for (let i = 0; i < cleaned.length && i < 16; i += 4) { parts.push(cleaned.slice(i, i + 4)); } return parts.join('-'); }; const handleKeyChange = (e: React.ChangeEvent) => { setLicenseKey(formatLicenseKey(e.target.value)); setError(''); }; // 激活许可证 const handleActivate = async () => { if (!licenseKey || licenseKey.length !== 19) { setError('请输入完整的许可证密钥'); return; } setLoading(true); setError(''); try { const res = await fetch('/api/license/activate', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ licenseKey }), }); const data = await res.json(); if (data.success) { setInitialAdmin(data.initialAdmin || null); setActivated(true); } else { setError(data.error || '激活失败,请检查许可证密钥'); } } catch { setError('网络错误,请检查网络连接'); } finally { setLoading(false); } }; // 打开临时授权申请弹窗 const openFreeTrialModal = () => { if (!freeTrialActivationEnabled) { setError('当前部署未开放临时授权申请,请使用许可证密钥或联系系统交付人员'); return; } setCustomerName(''); setContactInfo(''); setShowFreeTrialModal(true); }; // 临时授权申请 const handleFreeTrial = async () => { if (!freeTrialActivationEnabled) { setError('当前部署未开放临时授权申请,请使用许可证密钥或联系系统交付人员'); setShowFreeTrialModal(false); return; } const trimmedName = customerName.trim(); const trimmedContact = contactInfo.trim(); if (!trimmedName) { setError('请输入客户名称'); return; } if (!trimmedContact) { setError('请输入联系方式'); return; } setLoading(true); setError(''); setShowFreeTrialModal(false); try { const res = await fetch('/api/license/activate-free', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ customerName: trimmedName, contactInfo: trimmedContact }), }); let data; try { data = await res.json(); } catch (jsonError) { // 如果响应不是JSON(可能是HTML重定向),显示详细错误 const text = await res.text(); console.error('激活响应不是JSON:', text.substring(0, 200)); setError(`服务器返回了无效响应(${res.status}),请检查网络连接`); return; } if (data.success) { setInitialAdmin(data.initialAdmin || null); setActivated(true); } else { // 处理各种错误消息格式 let errorMsg = data.error || '激活失败,请检查输入信息'; if (errorMsg === 'Required' || errorMsg.includes('required')) { errorMsg = '请填写完整的客户信息(客户名称和联系方式都是必填项)'; } setError(errorMsg); } } catch (error) { console.error('激活请求失败:', error); setError('网络错误,请检查网络连接'); } finally { setLoading(false); } }; // 检查中或已激活,显示加载状态 if (checking) { return (

正在检查许可证状态...

); } // 激活成功页面 if (activated) { return (

激活成功

系统已准备就绪

{initialAdmin ? ( <>

初始管理员账号

邮箱: {initialAdmin.email}
{initialAdmin.password ? (
密码: {initialAdmin.password}
) : initialAdmin.passwordSource === 'environment' ? (

初始密码已由部署配置提供,系统不会在页面或日志中回显明文。

) : (

管理员账号已存在,请使用现有管理员密码登录。

)}

{initialAdmin.password ? '初始密码只在当前页面展示一次,首次登录后将强制修改密码。' : '首次登录后将强制修改密码。'}

) : (

系统已激活,请使用现有管理员账号登录。

)}
); } return (
{/* Logo */}
{loginLogoUrl && !logoError ? ( {siteName} setLogoError(true)} /> ) : (
{siteName.charAt(0) || 'F'}
)}

{siteName}

请输入交付给您的许可证密钥

{/* 许可证输入 */}
{/* 错误提示 */} {error && (
{error}
)} {/* 激活按钮 */} {freeTrialActivationEnabled && ( <> {/* 分隔线 */}
或
{/* 临时授权 */} {/* 免费版说明 */}

临时授权会按当前授权服务规则生成受限许可证

)}
{/* 联系信息 */}

如需许可证,请联系系统交付人员或管理员

{/* 临时授权弹窗 */} {freeTrialActivationEnabled && showFreeTrialModal && (
{ e.preventDefault(); handleFreeTrial(); }} >

申请临时授权

setCustomerName(e.target.value)} placeholder="请输入单位或团队名称" className="w-full px-3 py-2 border border-slate-300 dark:border-slate-700 rounded-lg focus:ring-2 focus:ring-slate-200 dark:focus:ring-slate-700 focus:border-slate-900 dark:focus:border-slate-500 bg-white dark:bg-slate-800 text-slate-900 dark:text-white" autoFocus required />
setContactInfo(e.target.value)} placeholder="手机号/微信/邮箱" className="w-full px-3 py-2 border border-slate-300 dark:border-slate-700 rounded-lg focus:ring-2 focus:ring-slate-200 dark:focus:ring-slate-700 focus:border-slate-900 dark:focus:border-slate-500 bg-white dark:bg-slate-800 text-slate-900 dark:text-white" required />

用于授权联系和交付核验

)}
); }