refactor: migrate UI to Tailwind CSS for a modernized design system.

This commit is contained in:
CJACK
2026-02-01 05:33:50 +08:00
parent 1d1361616b
commit 1f6043ff70
12 changed files with 1087 additions and 1174 deletions

View File

@@ -1,10 +1,11 @@
import { useState } from 'react'
import { FileCode, Download, Upload, Copy, Check, AlertTriangle } from 'lucide-react'
import clsx from 'clsx'
// 模板配置
const TEMPLATES = {
full: {
name: '完整模板',
desc: '包含所有配置项',
name: 'Full Configuration',
desc: 'Includes keys, accounts, and model mappings',
config: {
keys: ["your-api-key-1", "your-api-key-2"],
accounts: [
@@ -19,8 +20,8 @@ const TEMPLATES = {
}
},
email_only: {
name: '邮箱账号模板',
desc: '仅邮箱账号',
name: 'Email Only',
desc: 'Batch import email accounts',
config: {
keys: ["your-api-key"],
accounts: [
@@ -31,8 +32,8 @@ const TEMPLATES = {
}
},
mobile_only: {
name: '手机号账号模板',
desc: '仅手机号账号',
name: 'Mobile Only',
desc: 'Batch import mobile number accounts',
config: {
keys: ["your-api-key"],
accounts: [
@@ -43,8 +44,8 @@ const TEMPLATES = {
}
},
keys_only: {
name: 'API Keys',
desc: '只添加 API Keys',
name: 'API Keys Only',
desc: 'Just adding API access keys',
config: {
keys: ["key-1", "key-2", "key-3"]
}
@@ -55,13 +56,13 @@ export default function BatchImport({ onRefresh, onMessage, authFetch }) {
const [jsonInput, setJsonInput] = useState('')
const [loading, setLoading] = useState(false)
const [result, setResult] = useState(null)
const [copied, setCopied] = useState(false)
// 使用 authFetch 或回退到普通 fetch
const apiFetch = authFetch || fetch
const handleImport = async () => {
if (!jsonInput.trim()) {
onMessage('error', '请输入 JSON 配置')
onMessage('error', 'Please enter JSON configuration')
return
}
@@ -69,7 +70,7 @@ export default function BatchImport({ onRefresh, onMessage, authFetch }) {
try {
config = JSON.parse(jsonInput)
} catch (e) {
onMessage('error', 'JSON 格式无效')
onMessage('error', 'Invalid JSON format')
return
}
@@ -84,13 +85,13 @@ export default function BatchImport({ onRefresh, onMessage, authFetch }) {
const data = await res.json()
if (res.ok) {
setResult(data)
onMessage('success', `导入成功: ${data.imported_keys} Key, ${data.imported_accounts} 个账号`)
onMessage('success', `Imported: ${data.imported_keys} Keys, ${data.imported_accounts} Accounts`)
onRefresh()
} else {
onMessage('error', data.detail || '导入失败')
onMessage('error', data.detail || 'Import failed')
}
} catch (e) {
onMessage('error', '网络错误')
onMessage('error', 'Network error')
} finally {
setLoading(false)
}
@@ -100,7 +101,7 @@ export default function BatchImport({ onRefresh, onMessage, authFetch }) {
const tpl = TEMPLATES[key]
if (tpl) {
setJsonInput(JSON.stringify(tpl.config, null, 2))
onMessage('info', `已加载「${tpl.name}`)
onMessage('info', `Loaded template: ${tpl.name}`)
}
}
@@ -110,10 +111,10 @@ export default function BatchImport({ onRefresh, onMessage, authFetch }) {
if (res.ok) {
const data = await res.json()
setJsonInput(JSON.stringify(JSON.parse(data.json), null, 2))
onMessage('success', '已加载当前配置')
onMessage('success', 'Configuration loaded')
}
} catch (e) {
onMessage('error', '获取配置失败')
onMessage('error', 'Failed to fetch config')
}
}
@@ -123,81 +124,109 @@ export default function BatchImport({ onRefresh, onMessage, authFetch }) {
if (res.ok) {
const data = await res.json()
await navigator.clipboard.writeText(data.base64)
onMessage('success', 'Base64 已复制到剪贴板')
setCopied(true)
setTimeout(() => setCopied(false), 2000)
onMessage('success', 'Base64 copied to clipboard')
}
} catch (e) {
onMessage('error', '复制失败')
onMessage('error', 'Copy failed')
}
}
return (
<div className="section">
{/* 模板选择 */}
<div className="card">
<div className="card-title" style={{ marginBottom: '1rem' }}>📋 快速模板</div>
<div className="grid grid-2">
{Object.entries(TEMPLATES).map(([key, tpl]) => (
<div
key={key}
style={{
padding: '1rem',
background: 'var(--bg-tertiary)',
borderRadius: 'var(--radius)',
cursor: 'pointer',
transition: 'all 0.2s',
border: '1px solid transparent'
}}
onClick={() => loadTemplate(key)}
onMouseEnter={e => e.currentTarget.style.borderColor = 'var(--accent)'}
onMouseLeave={e => e.currentTarget.style.borderColor = 'transparent'}
>
<div style={{ fontWeight: 600, marginBottom: '0.25rem' }}>{tpl.name}</div>
<div style={{ fontSize: '0.85rem', color: 'var(--text-secondary)' }}>{tpl.desc}</div>
</div>
))}
<div className="grid grid-cols-1 md:grid-cols-3 gap-6 h-[calc(100vh-140px)]">
{/* Templates Panel */}
<div className="md:col-span-1 space-y-4">
<div className="bg-card border border-border rounded-xl p-5 shadow-sm">
<h3 className="font-semibold flex items-center gap-2 mb-4">
<FileCode className="w-4 h-4 text-primary" />
Quick Templates
</h3>
<div className="space-y-3">
{Object.entries(TEMPLATES).map(([key, tpl]) => (
<button
key={key}
onClick={() => loadTemplate(key)}
className="w-full text-left p-3 rounded-lg border border-border bg-secondary/20 hover:bg-secondary/50 hover:border-primary/50 transition-all custom-focus group"
>
<div className="font-medium text-sm group-hover:text-primary transition-colors">{tpl.name}</div>
<div className="text-xs text-muted-foreground mt-0.5">{tpl.desc}</div>
</button>
))}
</div>
</div>
<div className="bg-linear-to-br from-primary/10 to-transparent border border-primary/20 rounded-xl p-5 shadow-sm">
<h3 className="font-semibold flex items-center gap-2 mb-2 text-primary">
<Download className="w-4 h-4" />
Export Data
</h3>
<p className="text-sm text-muted-foreground mb-4">
Get your configuration as a Base64 string for Vercel environment variables.
</p>
<button
onClick={copyBase64}
className="w-full btn btn-primary bg-primary/90 hover:bg-primary shadow-lg shadow-primary/20"
>
{copied ? <Check className="w-4 h-4 mr-2" /> : <Copy className="w-4 h-4 mr-2" />}
{copied ? 'Copied!' : 'Copy Base64 Config'}
</button>
<p className="text-[10px] text-muted-foreground mt-2 text-center">
Variable Name: <code className="bg-background px-1 py-0.5 rounded border border-border">DS2API_CONFIG_JSON</code>
</p>
</div>
</div>
{/* 导入区域 */}
<div className="card">
<div className="card-title" style={{ marginBottom: '1rem' }}>📦 批量导入</div>
{/* Editor Panel */}
<div className="md:col-span-2 flex flex-col bg-card border border-border rounded-xl shadow-sm overflow-hidden h-full">
<div className="p-4 border-b border-border flex items-center justify-between bg-muted/20">
<h3 className="font-semibold flex items-center gap-2">
<Upload className="w-4 h-4 text-primary" />
JSON Editor
</h3>
<div className="flex gap-2">
<button onClick={handleExport} className="btn btn-secondary text-xs h-8">
Load Current
</button>
<button onClick={handleImport} disabled={loading} className="btn btn-primary text-xs h-8">
{loading ? 'Importing...' : 'Simulate Import'}
</button>
</div>
</div>
<div className="form-group">
<label className="form-label">JSON 配置点击上方模板快速填充</label>
<div className="flex-1 relative">
<textarea
className="form-input"
style={{ minHeight: '200px' }}
className="absolute inset-0 w-full h-full p-4 font-mono text-sm bg-secondary/10 resize-none focus:outline-none custom-scrollbar"
value={jsonInput}
onChange={e => setJsonInput(e.target.value)}
placeholder='{\n "keys": ["你的API密钥"],\n "accounts": [\n {"email": "邮箱", "password": "密码", "token": ""}\n ]\n}'
placeholder={'{\n "keys": ["your-api-key"],\n "accounts": [\n {"email": "...", "password": "...", "token": ""}\n ]\n}'}
spellCheck={false}
/>
</div>
<div className="btn-group" style={{ marginBottom: '1rem' }}>
<button className="btn btn-secondary" onClick={handleExport}>
导出当前
</button>
<button className="btn btn-primary" onClick={handleImport} disabled={loading}>
{loading ? <span className="loading"></span> : '📥 导入配置'}
</button>
</div>
{result && (
<div className="alert alert-success">
导入完成{result.imported_keys} API Key{result.imported_accounts} 个账号
<div className={clsx(
"p-4 border-t",
result.imported_keys || result.imported_accounts ? "bg-emerald-500/10 border-emerald-500/20" : "bg-destructive/10 border-destructive/20"
)}>
<div className="flex items-start gap-3">
{result.imported_keys || result.imported_accounts ? (
<Check className="w-5 h-5 text-emerald-500 mt-0.5" />
) : (
<AlertTriangle className="w-5 h-5 text-destructive mt-0.5" />
)}
<div>
<h4 className={clsx("font-medium", result.imported_keys || result.imported_accounts ? "text-emerald-500" : "text-destructive")}>
Import Operation Completed
</h4>
<p className="text-sm opacity-80 mt-1">
Successfully imported {result.imported_keys} API keys and updated {result.imported_accounts} accounts.
</p>
</div>
</div>
</div>
)}
</div>
<div className="card">
<div className="card-title" style={{ marginBottom: '1rem' }}>📤 导出 Base64</div>
<p style={{ color: 'var(--text-secondary)', marginBottom: '1rem' }}>
导出 Base64 格式配置可直接粘贴到 Vercel 环境变量 <code>DS2API_CONFIG_JSON</code>
</p>
<button className="btn btn-success" onClick={copyBase64}>
📋 复制 Base64 到剪贴板
</button>
</div>
</div>
)
}