671 lines
31 KiB
TypeScript
671 lines
31 KiB
TypeScript
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>
|
||
);
|
||
}
|