feat(hands): restructure Hands UI with Chinese localization

Major changes:
- Add HandList.tsx component for left sidebar
- Add HandTaskPanel.tsx for middle content area
- Restructure Sidebar tabs: 分身/HANDS/Workflow
- Remove Hands tab from RightPanel
- Localize all UI text to Chinese
- Archive legacy OpenClaw documentation
- Add Hands integration lessons document
- Update feature checklist with new components

UI improvements:
- Left sidebar now shows Hands list with status icons
- Middle area shows selected Hand's tasks and results
- Consistent styling with Tailwind CSS
- Chinese status labels and buttons

Documentation:
- Create docs/archive/openclaw-legacy/ for old docs
- Add docs/knowledge-base/hands-integration-lessons.md
- Update docs/knowledge-base/feature-checklist.md
- Update docs/knowledge-base/README.md

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
iven
2026-03-14 23:16:32 +08:00
parent 67e1da635d
commit 07079293f4
126 changed files with 36229 additions and 1035 deletions

View File

@@ -2,15 +2,18 @@ import { useState } from 'react';
import {
Settings as SettingsIcon,
BarChart3,
Bot,
Puzzle,
Blocks,
MessageSquare,
FolderOpen,
Shield,
MessageCircle,
Info,
ArrowLeft,
Coins,
Cpu,
Zap,
HelpCircle,
ClipboardList,
Clock,
} from 'lucide-react';
import { General } from './General';
import { UsageStats } from './UsageStats';
@@ -21,6 +24,10 @@ import { IMChannels } from './IMChannels';
import { Workspace } from './Workspace';
import { Privacy } from './Privacy';
import { About } from './About';
import { Credits } from './Credits';
import { AuditLogsPanel } from '../AuditLogsPanel';
import { SecurityStatus } from '../SecurityStatus';
import { TaskList } from '../TaskList';
interface SettingsLayoutProps {
onBack: () => void;
@@ -29,25 +36,33 @@ interface SettingsLayoutProps {
type SettingsPage =
| 'general'
| 'usage'
| 'credits'
| 'models'
| 'mcp'
| 'skills'
| 'im'
| 'workspace'
| 'privacy'
| 'security'
| 'audit'
| 'tasks'
| 'feedback'
| 'about';
const menuItems: { id: SettingsPage; label: string; icon: React.ReactNode }[] = [
{ id: 'general', label: '通用', icon: <SettingsIcon className="w-4 h-4" /> },
{ id: 'usage', label: '用量统计', icon: <BarChart3 className="w-4 h-4" /> },
{ id: 'models', label: '模型与 API', icon: <Bot className="w-4 h-4" /> },
{ id: 'credits', label: '积分详情', icon: <Coins className="w-4 h-4" /> },
{ id: 'models', label: '模型与 API', icon: <Cpu className="w-4 h-4" /> },
{ id: 'mcp', label: 'MCP 服务', icon: <Puzzle className="w-4 h-4" /> },
{ id: 'skills', label: '技能', icon: <Blocks className="w-4 h-4" /> },
{ id: 'skills', label: '技能', icon: <Zap className="w-4 h-4" /> },
{ id: 'im', label: 'IM 频道', icon: <MessageSquare className="w-4 h-4" /> },
{ id: 'workspace', label: '工作区', icon: <FolderOpen className="w-4 h-4" /> },
{ id: 'privacy', label: '数据与隐私', icon: <Shield className="w-4 h-4" /> },
{ id: 'feedback', label: '提交反馈', icon: <MessageCircle className="w-4 h-4" /> },
{ id: 'security', label: '安全状态', icon: <Shield className="w-4 h-4" /> },
{ id: 'audit', label: '审计日志', icon: <ClipboardList className="w-4 h-4" /> },
{ id: 'tasks', label: '定时任务', icon: <Clock className="w-4 h-4" /> },
{ id: 'feedback', label: '提交反馈', icon: <HelpCircle className="w-4 h-4" /> },
{ id: 'about', label: '关于', icon: <Info className="w-4 h-4" /> },
];
@@ -58,12 +73,28 @@ export function SettingsLayout({ onBack }: SettingsLayoutProps) {
switch (activePage) {
case 'general': return <General />;
case 'usage': return <UsageStats />;
case 'credits': return <Credits />;
case 'models': return <ModelsAPI />;
case 'mcp': return <MCPServices />;
case 'skills': return <Skills />;
case 'im': return <IMChannels />;
case 'workspace': return <Workspace />;
case 'privacy': return <Privacy />;
case 'security': return (
<div className="max-w-3xl">
<h1 className="text-xl font-bold text-gray-900 mb-6"></h1>
<SecurityStatus />
</div>
);
case 'audit': return <AuditLogsPanel />;
case 'tasks': return (
<div className="max-w-3xl">
<h1 className="text-xl font-bold text-gray-900 mb-6"></h1>
<div className="bg-white rounded-xl border border-gray-200 p-6 shadow-sm">
<TaskList />
</div>
</div>
);
case 'feedback': return <Feedback />;
case 'about': return <About />;
default: return <General />;
@@ -71,40 +102,42 @@ export function SettingsLayout({ onBack }: SettingsLayoutProps) {
};
return (
<div className="h-screen flex bg-white">
<div className="h-screen flex bg-f9fafb overflow-hidden text-gray-800 text-sm">
{/* Left navigation */}
<aside className="w-56 bg-gray-50 border-r border-gray-200 flex flex-col flex-shrink-0">
<button
onClick={onBack}
className="flex items-center gap-2 px-4 py-3 text-sm text-gray-500 hover:text-gray-700 border-b border-gray-200"
>
<ArrowLeft className="w-4 h-4" />
</button>
<aside className="w-64 bg-gray-50 border-r border-gray-200 flex flex-col flex-shrink-0">
{/* 返回按钮 */}
<div className="p-4 border-b border-gray-200">
<button
onClick={onBack}
className="flex items-center gap-2 text-gray-500 hover:text-gray-700 transition-colors"
>
<ArrowLeft className="w-4 h-4" />
<span></span>
</button>
</div>
<nav className="flex-1 py-2">
{/* 导航菜单 */}
<nav className="flex-1 overflow-y-auto custom-scrollbar py-2 px-3 space-y-1">
{menuItems.map((item) => (
<button
key={item.id}
onClick={() => setActivePage(item.id)}
className={`w-full flex items-center gap-3 px-4 py-2.5 text-sm transition-colors ${
className={`w-full flex items-center gap-3 px-3 py-2.5 rounded-lg text-left transition-all ${
activePage === item.id
? 'bg-orange-50 text-orange-600 font-medium'
: 'text-gray-600 hover:bg-gray-100 hover:text-gray-900'
? 'bg-gray-200 text-gray-900 font-medium'
: 'text-gray-500 hover:bg-black/5 hover:text-gray-700'
}`}
>
{item.icon}
{item.label}
<span>{item.label}</span>
</button>
))}
</nav>
</aside>
{/* Main content */}
<main className="flex-1 overflow-y-auto">
<div className="max-w-2xl mx-auto px-8 py-8">
{renderPage()}
</div>
<main className="flex-1 overflow-y-auto custom-scrollbar bg-white p-8">
{renderPage()}
</main>
</div>
);
@@ -113,22 +146,36 @@ export function SettingsLayout({ onBack }: SettingsLayoutProps) {
// Simple feedback page (inline)
function Feedback() {
const [text, setText] = useState('');
const [copied, setCopied] = useState(false);
const handleCopy = async () => {
await navigator.clipboard.writeText(text.trim());
setCopied(true);
};
return (
<div>
<h1 className="text-2xl font-bold text-gray-900 mb-2"></h1>
<p className="text-sm text-gray-500 mb-6">便</p>
<textarea
value={text}
onChange={(e) => setText(e.target.value)}
placeholder="请尽量详细描述复现步骤、期望结果和实际结果"
className="w-full h-40 border border-gray-300 rounded-lg p-3 text-sm resize-none focus:outline-none focus:ring-2 focus:ring-orange-500 focus:border-transparent"
/>
<button
disabled={!text.trim()}
className="mt-4 px-6 py-2 bg-orange-500 text-white text-sm rounded-lg hover:bg-orange-600 disabled:opacity-50 disabled:cursor-not-allowed"
>
</button>
<div className="max-w-3xl">
<h1 className="text-xl font-bold text-gray-900 mb-6"></h1>
<div className="bg-white rounded-xl border border-gray-200 p-6 shadow-sm">
<p className="text-sm text-gray-500 mb-4">线</p>
<textarea
value={text}
onChange={(e) => {
setText(e.target.value);
if (copied) {
setCopied(false);
}
}}
placeholder="请尽量详细描述复现步骤、期望结果和实际结果"
className="w-full h-40 border border-gray-300 rounded-lg p-3 text-sm resize-none focus:outline-none focus:border-orange-400"
/>
<button
onClick={() => { handleCopy().catch(() => {}); }}
disabled={!text.trim()}
className="mt-4 px-6 py-2 bg-orange-500 text-white text-sm rounded-lg hover:bg-orange-600 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
>
{copied ? '已复制' : '复制反馈内容'}
</button>
</div>
</div>
);
}