671 lines
31 KiB
TypeScript
Raw Normal View History

import { useState, useEffect } from 'react';
import Head from 'next/head';
import { toast } from 'react-hot-toast';
import {
CogIcon,
ShieldCheckIcon,
CurrencyDollarIcon,
GlobeAltIcon,
BellIcon,
PhoneIcon,
CloudIcon,
KeyIcon
} from '@heroicons/react/24/outline';
import Layout from '@/components/Layout';
interface Settings {
// 基础设置
siteName: string;
siteDescription: string;
adminEmail: string;
supportEmail: string;
// 通话设置
maxCallDuration: number; // 分钟
callTimeout: number; // 秒
enableRecording: boolean;
enableVideoCall: boolean;
// 费用设置 - 修改为每个服务单独配置
serviceRates: {
ai_voice: number; // AI语音翻译费率元/分钟)
ai_video: number; // AI视频翻译费率元/分钟)
sign_language: number; // 手语翻译费率(元/分钟)
human_interpreter: number; // 真人翻译费率(元/分钟)
document_translation: number; // 文档翻译费率(元/字)
};
currency: string;
taxRate: number;
// 通知设置
emailNotifications: boolean;
smsNotifications: boolean;
pushNotifications: boolean;
// 安全设置
enableTwoFactor: boolean;
sessionTimeout: number; // 分钟
maxLoginAttempts: number;
// API设置
twilioAccountSid: string;
twilioAuthToken: string;
openaiApiKey: string;
// 语言设置
defaultLanguage: string;
supportedLanguages: string[];
}
export default function SettingsPage() {
const [settings, setSettings] = useState<Settings>({
siteName: '口译服务管理系统',
siteDescription: '专业的多语言口译服务平台',
adminEmail: 'admin@interpreter.com',
supportEmail: 'support@interpreter.com',
maxCallDuration: 120,
callTimeout: 30,
enableRecording: true,
enableVideoCall: true,
serviceRates: {
ai_voice: 2.0,
ai_video: 3.0,
sign_language: 5.0,
human_interpreter: 8.0,
document_translation: 0.1,
},
currency: 'CNY',
taxRate: 0.06,
emailNotifications: true,
smsNotifications: false,
pushNotifications: true,
enableTwoFactor: false,
sessionTimeout: 30,
maxLoginAttempts: 5,
twilioAccountSid: '',
twilioAuthToken: '',
openaiApiKey: '',
defaultLanguage: 'zh-CN',
supportedLanguages: ['zh-CN', 'en-US', 'ja-JP', 'ko-KR', 'fr-FR', 'de-DE', 'es-ES']
});
const [loading, setLoading] = useState(false);
const [activeTab, setActiveTab] = useState('basic');
// 保存设置
const saveSettings = async () => {
try {
setLoading(true);
// 这里应该调用API保存设置
// await api.updateSettings(settings);
// 模拟API调用
await new Promise(resolve => setTimeout(resolve, 1000));
toast.success('设置保存成功');
} catch (error) {
console.error('Error saving settings:', error);
toast.error('保存设置失败');
} finally {
setLoading(false);
}
};
// 重置设置
const resetSettings = () => {
setSettings({
siteName: '口译服务管理系统',
siteDescription: '专业的多语言口译服务平台',
adminEmail: 'admin@interpreter.com',
supportEmail: 'support@interpreter.com',
maxCallDuration: 120,
callTimeout: 30,
enableRecording: true,
enableVideoCall: true,
serviceRates: {
ai_voice: 2.0,
ai_video: 3.0,
sign_language: 5.0,
human_interpreter: 8.0,
document_translation: 0.1,
},
currency: 'CNY',
taxRate: 0.06,
emailNotifications: true,
smsNotifications: false,
pushNotifications: true,
enableTwoFactor: false,
sessionTimeout: 30,
maxLoginAttempts: 5,
twilioAccountSid: '',
twilioAuthToken: '',
openaiApiKey: '',
defaultLanguage: 'zh-CN',
supportedLanguages: ['zh-CN', 'en-US', 'ja-JP', 'ko-KR', 'fr-FR', 'de-DE', 'es-ES']
});
toast.success('设置已重置为默认值');
};
const tabs = [
{ id: 'basic', name: '基础设置', icon: CogIcon },
{ id: 'call', name: '通话设置', icon: PhoneIcon },
{ id: 'billing', name: '费用设置', icon: CurrencyDollarIcon },
{ id: 'notifications', name: '通知设置', icon: BellIcon },
{ id: 'security', name: '安全设置', icon: ShieldCheckIcon },
{ id: 'api', name: 'API设置', icon: KeyIcon },
{ id: 'language', name: '语言设置', icon: GlobeAltIcon }
];
return (
<Layout>
<Head>
<title> - </title>
</Head>
<div className="max-w-7xl mx-auto py-6 sm:px-6 lg:px-8">
<div className="px-4 py-6 sm:px-0">
{/* 页面标题 */}
<div className="flex items-center justify-between mb-6">
<h1 className="text-2xl font-bold text-gray-900"></h1>
<div className="flex space-x-3">
<button
onClick={resetSettings}
className="inline-flex items-center px-4 py-2 border border-gray-300 text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50"
>
</button>
<button
onClick={saveSettings}
disabled={loading}
className="inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md text-white bg-blue-600 hover:bg-blue-700 disabled:opacity-50"
>
{loading ? '保存中...' : '保存设置'}
</button>
</div>
</div>
<div className="bg-white shadow rounded-lg">
<div className="flex">
{/* 侧边栏标签 */}
<div className="w-64 border-r border-gray-200">
<nav className="space-y-1 p-4">
{tabs.map((tab) => {
const Icon = tab.icon;
return (
<button
key={tab.id}
onClick={() => setActiveTab(tab.id)}
className={`w-full flex items-center px-3 py-2 text-sm font-medium rounded-md ${
activeTab === tab.id
? 'bg-blue-50 text-blue-700 border-blue-500'
: 'text-gray-600 hover:bg-gray-50 hover:text-gray-900'
}`}
>
<Icon className="h-5 w-5 mr-3" />
{tab.name}
</button>
);
})}
</nav>
</div>
{/* 主内容区域 */}
<div className="flex-1 p-6">
{/* 基础设置 */}
{activeTab === 'basic' && (
<div className="space-y-6">
<h3 className="text-lg font-medium text-gray-900"></h3>
<div className="grid grid-cols-1 gap-6 sm:grid-cols-2">
<div>
<label className="block text-sm font-medium text-gray-700"></label>
<input
type="text"
value={settings.siteName}
onChange={(e) => setSettings({...settings, siteName: e.target.value})}
className="mt-1 block w-full border-gray-300 rounded-md shadow-sm focus:ring-blue-500 focus:border-blue-500"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700"></label>
<input
type="email"
value={settings.adminEmail}
onChange={(e) => setSettings({...settings, adminEmail: e.target.value})}
className="mt-1 block w-full border-gray-300 rounded-md shadow-sm focus:ring-blue-500 focus:border-blue-500"
/>
</div>
<div className="sm:col-span-2">
<label className="block text-sm font-medium text-gray-700"></label>
<textarea
rows={3}
value={settings.siteDescription}
onChange={(e) => setSettings({...settings, siteDescription: e.target.value})}
className="mt-1 block w-full border-gray-300 rounded-md shadow-sm focus:ring-blue-500 focus:border-blue-500"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700"></label>
<input
type="email"
value={settings.supportEmail}
onChange={(e) => setSettings({...settings, supportEmail: e.target.value})}
className="mt-1 block w-full border-gray-300 rounded-md shadow-sm focus:ring-blue-500 focus:border-blue-500"
/>
</div>
</div>
</div>
)}
{/* 通话设置 */}
{activeTab === 'call' && (
<div className="space-y-6">
<h3 className="text-lg font-medium text-gray-900"></h3>
<div className="grid grid-cols-1 gap-6 sm:grid-cols-2">
<div>
<label className="block text-sm font-medium text-gray-700"> ()</label>
<input
type="number"
value={settings.maxCallDuration}
onChange={(e) => setSettings({...settings, maxCallDuration: parseInt(e.target.value)})}
className="mt-1 block w-full border-gray-300 rounded-md shadow-sm focus:ring-blue-500 focus:border-blue-500"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700"> ()</label>
<input
type="number"
value={settings.callTimeout}
onChange={(e) => setSettings({...settings, callTimeout: parseInt(e.target.value)})}
className="mt-1 block w-full border-gray-300 rounded-md shadow-sm focus:ring-blue-500 focus:border-blue-500"
/>
</div>
<div className="sm:col-span-2">
<div className="flex items-center">
<input
type="checkbox"
checked={settings.enableRecording}
onChange={(e) => setSettings({...settings, enableRecording: e.target.checked})}
className="h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300 rounded"
/>
<label className="ml-2 block text-sm text-gray-900"></label>
</div>
</div>
<div>
<label className="block text-sm font-medium text-gray-700"></label>
<div className="mt-1">
<input
type="checkbox"
checked={settings.enableVideoCall}
onChange={(e) => setSettings({...settings, enableVideoCall: e.target.checked})}
className="h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300 rounded"
/>
</div>
</div>
</div>
</div>
)}
{/* 费用设置 */}
{activeTab === 'billing' && (
<div className="space-y-6">
<div>
<h3 className="text-lg font-medium text-gray-900 mb-4"></h3>
<div className="grid grid-cols-1 gap-6 sm:grid-cols-2">
<div>
<label className="block text-sm font-medium text-gray-700">
AI语音翻译费率
</label>
<div className="mt-1 relative rounded-md shadow-sm">
<input
type="number"
step="0.1"
min="0"
className="focus:ring-indigo-500 focus:border-indigo-500 block w-full pr-12 sm:text-sm border-gray-300 rounded-md"
value={settings.serviceRates.ai_voice}
onChange={(e) => setSettings({
...settings,
serviceRates: {
...settings.serviceRates,
ai_voice: parseFloat(e.target.value) || 0
}
})}
/>
<div className="absolute inset-y-0 right-0 pr-3 flex items-center pointer-events-none">
<span className="text-gray-500 sm:text-sm">/</span>
</div>
</div>
</div>
<div>
<label className="block text-sm font-medium text-gray-700">
AI视频翻译费率
</label>
<div className="mt-1 relative rounded-md shadow-sm">
<input
type="number"
step="0.1"
min="0"
className="focus:ring-indigo-500 focus:border-indigo-500 block w-full pr-12 sm:text-sm border-gray-300 rounded-md"
value={settings.serviceRates.ai_video}
onChange={(e) => setSettings({
...settings,
serviceRates: {
...settings.serviceRates,
ai_video: parseFloat(e.target.value) || 0
}
})}
/>
<div className="absolute inset-y-0 right-0 pr-3 flex items-center pointer-events-none">
<span className="text-gray-500 sm:text-sm">/</span>
</div>
</div>
</div>
<div>
<label className="block text-sm font-medium text-gray-700">
</label>
<div className="mt-1 relative rounded-md shadow-sm">
<input
type="number"
step="0.1"
min="0"
className="focus:ring-indigo-500 focus:border-indigo-500 block w-full pr-12 sm:text-sm border-gray-300 rounded-md"
value={settings.serviceRates.sign_language}
onChange={(e) => setSettings({
...settings,
serviceRates: {
...settings.serviceRates,
sign_language: parseFloat(e.target.value) || 0
}
})}
/>
<div className="absolute inset-y-0 right-0 pr-3 flex items-center pointer-events-none">
<span className="text-gray-500 sm:text-sm">/</span>
</div>
</div>
</div>
<div>
<label className="block text-sm font-medium text-gray-700">
</label>
<div className="mt-1 relative rounded-md shadow-sm">
<input
type="number"
step="0.1"
min="0"
className="focus:ring-indigo-500 focus:border-indigo-500 block w-full pr-12 sm:text-sm border-gray-300 rounded-md"
value={settings.serviceRates.human_interpreter}
onChange={(e) => setSettings({
...settings,
serviceRates: {
...settings.serviceRates,
human_interpreter: parseFloat(e.target.value) || 0
}
})}
/>
<div className="absolute inset-y-0 right-0 pr-3 flex items-center pointer-events-none">
<span className="text-gray-500 sm:text-sm">/</span>
</div>
</div>
</div>
<div>
<label className="block text-sm font-medium text-gray-700">
</label>
<div className="mt-1 relative rounded-md shadow-sm">
<input
type="number"
step="0.01"
min="0"
className="focus:ring-indigo-500 focus:border-indigo-500 block w-full pr-12 sm:text-sm border-gray-300 rounded-md"
value={settings.serviceRates.document_translation}
onChange={(e) => setSettings({
...settings,
serviceRates: {
...settings.serviceRates,
document_translation: parseFloat(e.target.value) || 0
}
})}
/>
<div className="absolute inset-y-0 right-0 pr-3 flex items-center pointer-events-none">
<span className="text-gray-500 sm:text-sm">/</span>
</div>
</div>
</div>
</div>
</div>
<div className="grid grid-cols-1 gap-6 sm:grid-cols-2">
<div>
<label className="block text-sm font-medium text-gray-700">
</label>
<select
className="mt-1 block w-full pl-3 pr-10 py-2 text-base border-gray-300 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm rounded-md"
value={settings.currency}
onChange={(e) => setSettings({...settings, currency: e.target.value})}
>
<option value="CNY"> (CNY)</option>
<option value="USD"> (USD)</option>
<option value="EUR"> (EUR)</option>
</select>
</div>
<div>
<label className="block text-sm font-medium text-gray-700">
(%)
</label>
<input
type="number"
step="0.01"
min="0"
max="1"
className="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm sm:text-sm border-gray-300 rounded-md"
value={settings.taxRate}
onChange={(e) => setSettings({...settings, taxRate: parseFloat(e.target.value) || 0})}
/>
</div>
</div>
</div>
)}
{/* 通知设置 */}
{activeTab === 'notifications' && (
<div className="space-y-6">
<h3 className="text-lg font-medium text-gray-900"></h3>
<div className="space-y-4">
<div className="flex items-center">
<input
type="checkbox"
checked={settings.emailNotifications}
onChange={(e) => setSettings({...settings, emailNotifications: e.target.checked})}
className="h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300 rounded"
/>
<label className="ml-2 block text-sm text-gray-900"></label>
</div>
<div className="flex items-center">
<input
type="checkbox"
checked={settings.smsNotifications}
onChange={(e) => setSettings({...settings, smsNotifications: e.target.checked})}
className="h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300 rounded"
/>
<label className="ml-2 block text-sm text-gray-900"></label>
</div>
<div className="flex items-center">
<input
type="checkbox"
checked={settings.pushNotifications}
onChange={(e) => setSettings({...settings, pushNotifications: e.target.checked})}
className="h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300 rounded"
/>
<label className="ml-2 block text-sm text-gray-900"></label>
</div>
</div>
</div>
)}
{/* 安全设置 */}
{activeTab === 'security' && (
<div className="space-y-6">
<h3 className="text-lg font-medium text-gray-900"></h3>
<div className="grid grid-cols-1 gap-6 sm:grid-cols-2">
<div className="sm:col-span-2">
<div className="flex items-center">
<input
type="checkbox"
checked={settings.enableTwoFactor}
onChange={(e) => setSettings({...settings, enableTwoFactor: e.target.checked})}
className="h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300 rounded"
/>
<label className="ml-2 block text-sm text-gray-900"></label>
</div>
</div>
<div>
<label className="block text-sm font-medium text-gray-700"> ()</label>
<input
type="number"
value={settings.sessionTimeout}
onChange={(e) => setSettings({...settings, sessionTimeout: parseInt(e.target.value)})}
className="mt-1 block w-full border-gray-300 rounded-md shadow-sm focus:ring-blue-500 focus:border-blue-500"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700"></label>
<input
type="number"
value={settings.maxLoginAttempts}
onChange={(e) => setSettings({...settings, maxLoginAttempts: parseInt(e.target.value)})}
className="mt-1 block w-full border-gray-300 rounded-md shadow-sm focus:ring-blue-500 focus:border-blue-500"
/>
</div>
</div>
</div>
)}
{/* API设置 */}
{activeTab === 'api' && (
<div className="space-y-6">
<h3 className="text-lg font-medium text-gray-900">API设置</h3>
<div className="space-y-6">
<div>
<label className="block text-sm font-medium text-gray-700">Twilio Account SID</label>
<input
type="text"
value={settings.twilioAccountSid}
onChange={(e) => setSettings({...settings, twilioAccountSid: e.target.value})}
className="mt-1 block w-full border-gray-300 rounded-md shadow-sm focus:ring-blue-500 focus:border-blue-500"
placeholder="ACxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700">Twilio Auth Token</label>
<input
type="password"
value={settings.twilioAuthToken}
onChange={(e) => setSettings({...settings, twilioAuthToken: e.target.value})}
className="mt-1 block w-full border-gray-300 rounded-md shadow-sm focus:ring-blue-500 focus:border-blue-500"
placeholder="••••••••••••••••••••••••••••••••"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700">OpenAI API Key</label>
<input
type="password"
value={settings.openaiApiKey}
onChange={(e) => setSettings({...settings, openaiApiKey: e.target.value})}
className="mt-1 block w-full border-gray-300 rounded-md shadow-sm focus:ring-blue-500 focus:border-blue-500"
placeholder="sk-••••••••••••••••••••••••••••••••••••••••••••••••"
/>
</div>
</div>
</div>
)}
{/* 语言设置 */}
{activeTab === 'language' && (
<div className="space-y-6">
<h3 className="text-lg font-medium text-gray-900"></h3>
<div>
<label className="block text-sm font-medium text-gray-700"></label>
<select
value={settings.defaultLanguage}
onChange={(e) => setSettings({...settings, defaultLanguage: e.target.value})}
className="mt-1 block w-full border-gray-300 rounded-md shadow-sm focus:ring-blue-500 focus:border-blue-500"
>
<option value="zh-CN"> ()</option>
<option value="en-US">English (US)</option>
<option value="ja-JP"></option>
<option value="ko-KR"></option>
<option value="es-ES">Español</option>
<option value="fr-FR">Français</option>
</select>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-2"></label>
<div className="space-y-2">
{[
{ code: 'zh-CN', name: '中文 (简体)' },
{ code: 'en-US', name: 'English (US)' },
{ code: 'ja-JP', name: '日本語' },
{ code: 'ko-KR', name: '한국어' },
{ code: 'es-ES', name: 'Español' },
{ code: 'fr-FR', name: 'Français' }
].map((lang) => (
<div key={lang.code} className="flex items-center">
<input
type="checkbox"
checked={settings.supportedLanguages.includes(lang.code)}
onChange={(e) => {
if (e.target.checked) {
setSettings({
...settings,
supportedLanguages: [...settings.supportedLanguages, lang.code]
});
} else {
setSettings({
...settings,
supportedLanguages: settings.supportedLanguages.filter(l => l !== lang.code)
});
}
}}
className="h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300 rounded"
/>
<label className="ml-2 block text-sm text-gray-900">{lang.name}</label>
</div>
))}
</div>
</div>
</div>
)}
</div>
</div>
</div>
</div>
</div>
</Layout>
);
}