chore: 添加 .gitignore 规则,包含前端、pnpm、Node.js 和 AI 工具

This commit is contained in:
2026-03-20 09:14:58 +08:00
parent bf294f9f50
commit 176a727f6e
50 changed files with 6436 additions and 0 deletions

130
src/pages/AdminPage.jsx Normal file
View File

@@ -0,0 +1,130 @@
import { useState, useEffect } from 'react';
import { useLocation, useNavigate } from 'react-router-dom';
import { FiHome, FiBarChart2, FiUsers, FiList } from 'react-icons/fi';
import Layout from '../components/Layout.jsx';
import OverviewPage from './admin/OverviewPage.jsx';
import DepartmentsPage from './admin/DepartmentsPage.jsx';
import UsersPage from './admin/UsersPage.jsx';
import AdminProjectsPage from './admin/AdminProjectsPage.jsx';
import AddDepartmentPage from './admin/AddDepartmentPage.jsx';
import AddUserPage from './admin/AddUserPage.jsx';
import AddProjectPage from './admin/AddProjectPage.jsx';
function AdminPage() {
const location = useLocation();
const navigate = useNavigate();
const [currentPage, setCurrentPage] = useState(() => {
return localStorage.getItem('admin_currentPage') || 'overview';
});
useEffect(() => {
if (location.state?.fromHome) {
setCurrentPage('overview');
navigate('.', { replace: true, state: {} });
}
}, [location.state, navigate, setCurrentPage]);
useEffect(() => {
localStorage.setItem('admin_currentPage', currentPage);
}, [currentPage]);
const renderPage = () => {
switch (currentPage) {
case 'overview':
return <OverviewPage />;
case 'departments':
return <DepartmentsPage onAdd={() => setCurrentPage('addDepartment')} />;
case 'users':
return <UsersPage onAdd={() => setCurrentPage('addUser')} />;
case 'projects':
return <AdminProjectsPage onAdd={() => setCurrentPage('addProject')} />;
case 'addDepartment':
return <AddDepartmentPage onBack={() => setCurrentPage('departments')} />;
case 'addUser':
return <AddUserPage onBack={() => setCurrentPage('users')} />;
case 'addProject':
return <AddProjectPage onBack={() => setCurrentPage('projects')} />;
default:
return <div>Page not found</div>;
}
};
const getPageTitle = () => {
const titles = {
overview: '总览',
departments: '部门管理',
users: '用户管理',
projects: '项目管理',
addDepartment: '新增部门',
addUser: '新增用户',
addProject: '新增项目'
};
return titles[currentPage] || '';
};
const sidebar = (
<>
<div className="admin-sidebar-header">
<div className="sidebar-brand">
<div className="sidebar-logo-icon">
<span></span>
<span></span>
</div>
<div className="sidebar-brand-text">
<div className="sidebar-logo">GrandClaw</div>
<div className="sidebar-subtitle">运营管理台</div>
</div>
</div>
</div>
<nav className="admin-sidebar-nav">
<div
className={`admin-nav-item ${currentPage === 'overview' ? 'active' : ''}`}
onClick={() => setCurrentPage('overview')}
>
<span className="admin-nav-icon"><FiHome /></span>
<span className="admin-nav-text">总览</span>
</div>
<div
className={`admin-nav-item ${currentPage === 'departments' ? 'active' : ''}`}
onClick={() => setCurrentPage('departments')}
>
<span className="admin-nav-icon"><FiBarChart2 /></span>
<span className="admin-nav-text">部门管理</span>
</div>
<div
className={`admin-nav-item ${currentPage === 'users' ? 'active' : ''}`}
onClick={() => setCurrentPage('users')}
>
<span className="admin-nav-icon"><FiUsers /></span>
<span className="admin-nav-text">用户管理</span>
</div>
<div
className={`admin-nav-item ${currentPage === 'projects' ? 'active' : ''}`}
onClick={() => setCurrentPage('projects')}
>
<span className="admin-nav-icon"><FiList /></span>
<span className="admin-nav-text">项目管理</span>
</div>
</nav>
<div className="admin-sidebar-user">
<div className="user-avatar"></div>
<div className="admin-sidebar-user-info">
<div className="admin-sidebar-user-name">张三</div>
<div className="admin-sidebar-user-role">系统管理员</div>
</div>
</div>
</>
);
return (
<Layout
sidebar={sidebar}
headerTitle={getPageTitle()}
sidebarClassName="admin-sidebar"
>
{renderPage()}
</Layout>
);
}
export default AdminPage;

228
src/pages/ConsolePage.jsx Normal file
View File

@@ -0,0 +1,228 @@
import { useState, useEffect } from 'react';
import { useLocation, useNavigate } from 'react-router-dom';
import { FiPlus, FiClock, FiList, FiUsers } from 'react-icons/fi';
import { FaPuzzlePiece } from 'react-icons/fa';
import Layout from '../components/Layout.jsx';
import { conversations, getChatScenes } from '../data/conversations.js';
import { skills } from '../data/skills.js';
import ChatPage from './console/ChatPage.jsx';
import SkillsPage from './console/SkillsPage.jsx';
import SkillDetailPage from './console/SkillDetailPage.jsx';
import LogsPage from './console/LogsPage.jsx';
import TasksPage from './console/TasksPage.jsx';
import TaskDetailPage from './console/TaskDetailPage.jsx';
import AccountPage from './console/AccountPage.jsx';
import ProjectsPage from './console/ProjectsPage.jsx';
import MemberConfigPage from './console/MemberConfigPage.jsx';
import AddMemberPage from './console/AddMemberPage.jsx';
function ConsolePage() {
const location = useLocation();
const navigate = useNavigate();
const [currentPage, setCurrentPage] = useState(() => {
return localStorage.getItem('console_currentPage') || 'chat';
});
const [currentScene, setCurrentScene] = useState(() => {
return localStorage.getItem('console_currentScene') || 'welcome';
});
const [currentSkillId, setCurrentSkillId] = useState(null);
const [currentTaskId, setCurrentTaskId] = useState(null);
useEffect(() => {
if (location.state?.fromHome) {
setCurrentPage('chat');
setCurrentScene('welcome');
navigate('.', { replace: true, state: {} });
}
}, [location.state, navigate, setCurrentPage, setCurrentScene]);
useEffect(() => {
localStorage.setItem('console_currentPage', currentPage);
}, [currentPage]);
useEffect(() => {
localStorage.setItem('console_currentScene', currentScene);
}, [currentScene]);
const switchPage = (pageId, data = {}) => {
setCurrentPage(pageId);
if (data.skillId !== undefined) {
setCurrentSkillId(data.skillId);
}
};
const handleSkillClick = (skillId) => {
switchPage('skillDetail', { skillId });
};
const handleBack = () => {
switchPage('skills');
};
const switchChatScene = (scene) => {
setCurrentScene(scene);
if (currentPage !== 'chat') {
setCurrentPage('chat');
}
};
const createNewChat = () => {
setCurrentScene('welcome');
setCurrentPage('chat');
};
const activeScene = currentPage === 'chat' ? currentScene : null;
const renderPage = () => {
switch (currentPage) {
case 'chat':
return <ChatPage scene={currentScene} />;
case 'skills':
return <SkillsPage onSkillClick={handleSkillClick} />;
case 'skillDetail':
return <SkillDetailPage skillId={currentSkillId} onBack={handleBack} />;
case 'logs':
return <LogsPage />;
case 'scheduledTasks':
return <TasksPage
onViewDetail={(taskId) => {
setCurrentTaskId(taskId);
switchPage('taskDetail');
}}
/>;
case 'taskDetail':
return <TaskDetailPage
taskId={currentTaskId}
onBack={() => switchPage('scheduledTasks')}
/>;
case 'account':
return <AccountPage />;
case 'projects':
return <ProjectsPage onAddMember={() => switchPage('addMember')} />;
case 'memberConfig':
return <MemberConfigPage onBack={() => switchPage('projects')} />;
case 'addMember':
return <AddMemberPage onBack={() => switchPage('projects')} />;
default:
return <div>Page not found</div>;
}
};
const getPageTitle = () => {
const pageTitles = {
chat: '智能助手',
skills: '技能市场',
skillDetail: '技能详情',
logs: '日志查询',
scheduledTasks: '定时任务',
taskDetail: '任务详情',
account: '账号管理',
projects: '项目管理',
memberConfig: '成员配置',
addMember: '增加成员'
};
let title = pageTitles[currentPage] || '';
if (currentPage === 'chat') {
const conv = conversations.find(c => c.scene === currentScene);
title = conv?.title || '智能助手';
}
if (currentPage === 'skillDetail' && currentSkillId) {
const skill = skills.find(s => s.id === currentSkillId);
title = skill?.name || '技能详情';
}
return title;
};
const sidebar = (
<>
<div className="chat-sidebar-header">
<div className="sidebar-brand">
<div className="sidebar-logo-icon">
<span></span>
<span></span>
</div>
<div className="sidebar-brand-text">
<div className="sidebar-logo">GrandClaw</div>
<div className="sidebar-subtitle">企业级AI平台</div>
</div>
</div>
<div className="sidebar-divider"></div>
<button className="btn btn-primary" style={{ width: '100%' }} onClick={createNewChat}>
<span style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', gap: '6px' }}>
<FiPlus /> 新建对话
</span>
</button>
</div>
<div className="chat-sidebar-content">
{conversations.map(conv => (
<div
key={conv.id}
className={`conversation-item ${conv.scene === activeScene ? 'active' : ''}`}
onClick={() => switchChatScene(conv.scene)}
>
<div className="conversation-title">{conv.title}</div>
<div className="conversation-time">{conv.time}</div>
</div>
))}
</div>
<div className="chat-sidebar-project">
<label className="chat-sidebar-project-label">当前项目</label>
<select className="form-control chat-sidebar-project-select">
<option>企业 AI 智算平台</option>
<option>知识库管理系统</option>
<option>数据分析平台</option>
</select>
</div>
<div className="chat-sidebar-nav">
<div
className={`chat-nav-item ${currentPage === 'skills' ? 'active' : ''}`}
onClick={() => switchPage('skills')}
>
<span className="chat-nav-icon"><FaPuzzlePiece /></span>
<span className="chat-nav-text">技能市场</span>
</div>
<div
className={`chat-nav-item ${currentPage === 'scheduledTasks' ? 'active' : ''}`}
onClick={() => switchPage('scheduledTasks')}
>
<span className="chat-nav-icon"><FiClock /></span>
<span className="chat-nav-text">定时任务</span>
</div>
<div
className={`chat-nav-item ${currentPage === 'logs' ? 'active' : ''}`}
onClick={() => switchPage('logs')}
>
<span className="chat-nav-icon"><FiList /></span>
<span className="chat-nav-text">日志查询</span>
</div>
<div
className={`chat-nav-item ${currentPage === 'projects' ? 'active' : ''}`}
onClick={() => switchPage('projects')}
>
<span className="chat-nav-icon"><FiUsers /></span>
<span className="chat-nav-text">项目管理</span>
</div>
</div>
<div className="chat-sidebar-user" onClick={() => switchPage('account')}>
<div className="user-avatar"></div>
<div className="chat-sidebar-user-info">
<div className="chat-sidebar-user-name">张三</div>
<div className="chat-sidebar-user-role">AI 产品部</div>
</div>
</div>
</>
);
return (
<Layout
sidebar={sidebar}
headerTitle={getPageTitle()}
sidebarClassName="chat-sidebar"
contentClassName={currentPage === 'chat' ? 'page-content-full' : ''}
>
{renderPage()}
</Layout>
);
}
export default ConsolePage;

173
src/pages/DeveloperPage.jsx Normal file
View File

@@ -0,0 +1,173 @@
import { useState, useEffect } from 'react';
import { useLocation, useNavigate } from 'react-router-dom';
import { FiPlus, FiTerminal } from 'react-icons/fi';
import { FaPuzzlePiece } from 'react-icons/fa';
import Layout from '../components/Layout.jsx';
import { mySkills } from '../data/developerData.js';
import MySkillsPage from './developer/MySkillsPage.jsx';
import UploadSkillPage from './developer/UploadSkillPage.jsx';
import NewVersionPage from './developer/NewVersionPage.jsx';
import DevDocsPage from './developer/DevDocsPage.jsx';
import DevAccountPage from './developer/DevAccountPage.jsx';
import SkillEditorPage from './developer/SkillEditorPage.jsx';
function DeveloperPage() {
const location = useLocation();
const navigate = useNavigate();
const [currentPage, setCurrentPage] = useState(() => {
return localStorage.getItem('developer_currentPage') || 'mySkills';
});
const [currentSkillId, setCurrentSkillId] = useState(() => {
const saved = localStorage.getItem('developer_currentSkillId');
return saved ? JSON.parse(saved) : null;
});
const [newVersionSkillName, setNewVersionSkillName] = useState('');
useEffect(() => {
if (location.state?.fromHome) {
setCurrentPage('mySkills');
setCurrentSkillId(null);
navigate('.', { replace: true, state: {} });
}
}, [location.state, navigate, setCurrentPage, setCurrentSkillId]);
useEffect(() => {
localStorage.setItem('developer_currentPage', currentPage);
}, [currentPage]);
useEffect(() => {
localStorage.setItem('developer_currentSkillId', JSON.stringify(currentSkillId));
}, [currentSkillId]);
const switchPage = (pageId, data = {}) => {
setCurrentPage(pageId);
if (data.skillId !== undefined) {
setCurrentSkillId(data.skillId);
}
};
const openSkillEditor = (skillId) => {
setCurrentSkillId(skillId);
setCurrentPage('skillEditor');
};
const createNewProject = () => {
setCurrentPage('uploadSkill');
};
const openNewVersionPage = (skillName) => {
setNewVersionSkillName(skillName);
setCurrentPage('newVersion');
};
const handleBack = () => {
setCurrentPage('mySkills');
setCurrentSkillId(null);
};
const handleNewVersionBack = () => {
setCurrentPage('skillEditor');
setNewVersionSkillName('');
};
const renderPage = () => {
switch (currentPage) {
case 'mySkills':
return <MySkillsPage onSkillClick={openSkillEditor} />;
case 'uploadSkill':
return <UploadSkillPage />;
case 'devDocs':
return <DevDocsPage />;
case 'devAccount':
return <DevAccountPage />;
case 'skillEditor':
return <SkillEditorPage skillId={currentSkillId} onBack={handleBack} onUploadNewVersion={openNewVersionPage} />;
case 'newVersion':
return <NewVersionPage skillName={newVersionSkillName} onBack={handleNewVersionBack} />;
default:
return <div>Page not found</div>;
}
};
const getPageTitle = () => {
const titles = {
mySkills: '我的技能',
uploadSkill: '创建技能',
newVersion: '上传新版本',
devDocs: '开发文档',
devAccount: '开发者设置',
skillEditor: '技能详情'
};
return titles[currentPage] || '';
};
const sidebar = (
<>
<div className="chat-sidebar-header">
<div className="sidebar-brand">
<div className="sidebar-logo-icon">
<span></span>
<span></span>
</div>
<div className="sidebar-brand-text">
<div className="sidebar-logo">GrandClaw</div>
<div className="sidebar-subtitle">技能开发台</div>
</div>
</div>
<div className="sidebar-divider"></div>
<button className="btn btn-primary" style={{ width: '100%' }} onClick={createNewProject}>
<span style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', gap: '6px' }}>
<FiPlus /> 创建技能
</span>
</button>
</div>
<div className="chat-sidebar-content">
{mySkills.map(skill => (
<div
key={skill.id}
className={`conversation-item ${currentSkillId === skill.id && currentPage === 'skillEditor' ? 'active' : ''}`}
onClick={() => openSkillEditor(skill.id)}
>
<div className="conversation-title">{skill.name}</div>
<div className="conversation-time">{skill.status === 'published' ? '已发布' : '草稿'}</div>
</div>
))}
</div>
<div className="chat-sidebar-nav">
<div
className={`chat-nav-item ${currentPage === 'mySkills' ? 'active' : ''}`}
onClick={() => switchPage('mySkills')}
>
<span className="chat-nav-icon"><FaPuzzlePiece /></span>
<span className="chat-nav-text">我的技能</span>
</div>
<div
className={`chat-nav-item ${currentPage === 'devDocs' ? 'active' : ''}`}
onClick={() => switchPage('devDocs')}
>
<span className="chat-nav-icon"><FiTerminal /></span>
<span className="chat-nav-text">开发文档</span>
</div>
</div>
<div className="chat-sidebar-user" onClick={() => switchPage('devAccount')}>
<div className="user-avatar"></div>
<div className="chat-sidebar-user-info">
<div className="chat-sidebar-user-name">张三</div>
<div className="chat-sidebar-user-role">开发者</div>
</div>
</div>
</>
);
return (
<Layout
sidebar={sidebar}
headerTitle={getPageTitle()}
sidebarClassName="chat-sidebar"
>
{renderPage()}
</Layout>
);
}
export default DeveloperPage;

76
src/pages/HomePage.jsx Normal file
View File

@@ -0,0 +1,76 @@
import { Link } from 'react-router-dom';
import { FiSettings, FiCode, FiUsers, FiMonitor, FiList, FiLogIn } from 'react-icons/fi';
import { FaRobot, FaPuzzlePiece } from 'react-icons/fa';
function HomePage() {
return (
<div className="home-layout">
<header className="home-header">
<div className="home-logo">
<div className="sidebar-logo-icon">
<span></span>
<span></span>
</div>
GrandClaw
</div>
<nav className="home-nav">
<Link to="/console" state={{ fromHome: true }}>
<FiSettings /> 工作台
</Link>
<Link to="/developer?init=true" state={{ fromHome: true }}>
<FiCode /> 开发台
</Link>
<Link to="/admin" state={{ fromHome: true }}>
<FiUsers /> 管理台
</Link>
<Link to="/login" className="home-nav-login">
<FiLogIn /> 登录
</Link>
</nav>
</header>
<main className="home-main">
<div className="home-badge">
<span className="home-badge-dot"></span>
企业级 AI 平台
</div>
<h1 className="home-title">智能 <span>企业助手</span></h1>
<p className="home-desc">
基于容器化实例的 智能助手平台提供租户隔离技能市场安全审计等核心能力
</p>
<div className="home-buttons">
<Link to="/console" className="home-btn primary" state={{ fromHome: true }}>
<FaRobot /> 进入工作台
</Link>
</div>
<div className="home-features">
<div className="home-feature">
<div className="home-feature-icon">
<FiMonitor />
</div>
<div className="home-feature-title">容器化隔离</div>
<div className="home-feature-desc">每租户独享独立容器实例天然物理隔离数据安全有保障</div>
</div>
<div className="home-feature">
<div className="home-feature-icon">
<FaPuzzlePiece />
</div>
<div className="home-feature-title">技能市场</div>
<div className="home-feature-desc">丰富的技能生态按需订阅动态挂载无缝扩展能力</div>
</div>
<div className="home-feature">
<div className="home-feature-icon">
<FiList />
</div>
<div className="home-feature-title">安全审计</div>
<div className="home-feature-desc">完整的操作日志运行日志计费统计满足企业合规要求</div>
</div>
</div>
</main>
<footer className="home-footer">
© 2026 GrandClaw Team · 前端原型演示
</footer>
</div>
);
}
export default HomePage;

141
src/pages/LoginPage.jsx Normal file
View File

@@ -0,0 +1,141 @@
import { useState } from 'react';
import { Link, useNavigate } from 'react-router-dom';
import { FiArrowLeft } from 'react-icons/fi';
function generateCode() {
const chars = 'ABCDEFGHJKLMNPQRSTUVWXYZ23456789';
let code = '';
for (let i = 0; i < 4; i++) {
code += chars.charAt(Math.floor(Math.random() * chars.length));
}
return code;
}
function LoginPage() {
const navigate = useNavigate();
const [captchaCode, setCaptchaCode] = useState(generateCode());
const [captchaInput, setCaptchaInput] = useState('');
const refreshCaptcha = () => {
setCaptchaCode(generateCode());
setCaptchaInput('');
};
return (
<div style={{
minHeight: '100vh',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
background: '#F8FAFC',
padding: '20px'
}}>
<div style={{ width: '100%', maxWidth: '380px' }}>
<div style={{ textAlign: 'center', marginBottom: '32px' }}>
<div style={{
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
gap: '12px',
marginBottom: '20px'
}}>
<div className="sidebar-logo-icon home-logo-icon">
<span></span>
<span></span>
</div>
<span style={{ fontSize: '24px', fontWeight: '800', color: '#1E293B' }}>GrandClaw</span>
</div>
</div>
<div style={{
background: '#FFFFFF',
borderRadius: '16px',
padding: '32px',
boxShadow: '0 4px 24px rgba(15, 23, 42, 0.08)',
border: '1px solid #E2E8F0'
}}>
<h2 style={{ fontSize: '20px', fontWeight: '700', color: '#1E293B', marginBottom: '6px', textAlign: 'center' }}>
欢迎回来
</h2>
<p style={{ color: '#64748B', fontSize: '14px', textAlign: 'center', marginBottom: '28px' }}>
请登录您的账号以继续
</p>
<div className="form-group">
<input type="text" className="form-control" placeholder="用户名 / 邮箱" />
</div>
<div className="form-group">
<input type="password" className="form-control" placeholder="密码" />
</div>
<div className="form-group">
<div style={{ display: 'flex', gap: '12px' }}>
<input
type="text"
className="form-control"
placeholder="验证码"
value={captchaInput}
onChange={(e) => setCaptchaInput(e.target.value)}
style={{ flex: 1 }}
/>
<div
style={{
width: '100px',
height: '38px',
background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
borderRadius: '6px',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
fontSize: '20px',
fontWeight: '700',
color: '#FFFFFF',
letterSpacing: '6px',
cursor: 'pointer',
userSelect: 'none'
}}
onClick={refreshCaptcha}
title="点击刷新验证码"
>
{captchaCode}
</div>
</div>
</div>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '24px' }}>
<label style={{ display: 'flex', alignItems: 'center', gap: '6px', fontSize: '13px', color: '#64748B', cursor: 'pointer' }}>
<input type="checkbox" style={{ width: '14px', height: '14px', accentColor: '#3B82F6' }} />
记住我
</label>
<a href="#" style={{ fontSize: '13px', color: '#3B82F6', textDecoration: 'none' }}>
忘记密码
</a>
</div>
<button
className="btn btn-primary"
style={{ width: '100%', padding: '11px', fontSize: '14px', fontWeight: '600' }}
onClick={() => navigate('/console', { state: { fromHome: true } })}
>
登录
</button>
</div>
<div style={{ textAlign: 'center', marginTop: '24px' }}>
<Link
to="/"
style={{
display: 'inline-flex',
alignItems: 'center',
gap: '6px',
color: '#64748B',
fontSize: '14px',
textDecoration: 'none'
}}
>
<FiArrowLeft /> 返回首页
</Link>
</div>
</div>
</div>
);
}
export default LoginPage;

View File

@@ -0,0 +1,61 @@
import { useState } from 'react';
import ListSelector from '../../components/ListSelector.jsx';
const availableLeaders = [
{ id: 1, name: '张三', department: 'AI 产品部', phone: '138****8888', email: 'zhangsan@example.com' },
{ id: 2, name: '李四', department: '技术研发部', phone: '139****1234', email: 'lisi@example.com' },
{ id: 3, name: '王五', department: '数据分析部', phone: '136****5678', email: 'wangwu@example.com' },
{ id: 4, name: '赵六', department: 'AI 产品部', phone: '135****9012', email: 'zhaoliu@example.com' },
{ id: 5, name: '钱七', department: '运营部', phone: '137****3456', email: 'qianqi@example.com' }
];
function AddDepartmentPage({ onBack }) {
const [selectedLeader, setSelectedLeader] = useState(null);
const leaderColumns = [
{ key: 'name', label: '姓名' },
{ key: 'phone', label: '联系电话' },
{ key: 'email', label: '邮箱' }
];
const selectedLabel = selectedLeader
? `${availableLeaders.find(l => l.id === selectedLeader)?.name} - ${availableLeaders.find(l => l.id === selectedLeader)?.department}`
: null;
return (
<div className="card">
<div className="card-header">
<div className="card-title">新增部门</div>
</div>
<div className="card-body">
<div className="form-group">
<label className="form-label required">部门名称</label>
<input type="text" className="form-control" placeholder="请输入部门名称" />
</div>
<div className="form-group">
<label className="form-label">部门描述</label>
<textarea className="form-control" rows="3" placeholder="请输入部门描述"></textarea>
</div>
<div className="form-group">
<label className="form-label required">负责人</label>
<ListSelector
data={availableLeaders}
selectedIds={selectedLeader}
onChange={setSelectedLeader}
searchPlaceholder="搜索负责人..."
columns={leaderColumns}
multiSelect={false}
selectedLabel={selectedLabel}
onClearSelected={() => setSelectedLeader(null)}
/>
</div>
<div style={{ display: 'flex', gap: '12px', justifyContent: 'flex-end', marginTop: '24px' }}>
<button className="btn" onClick={onBack}>取消</button>
<button className="btn btn-primary">确定</button>
</div>
</div>
</div>
);
}
export default AddDepartmentPage;

View File

@@ -0,0 +1,61 @@
import { useState } from 'react';
import ListSelector from '../../components/ListSelector.jsx';
const availableLeaders = [
{ id: 1, name: '张三', department: 'AI 产品部', phone: '138****8888', email: 'zhangsan@example.com' },
{ id: 2, name: '李四', department: '技术研发部', phone: '139****1234', email: 'lisi@example.com' },
{ id: 3, name: '王五', department: '数据分析部', phone: '136****5678', email: 'wangwu@example.com' },
{ id: 4, name: '赵六', department: 'AI 产品部', phone: '135****9012', email: 'zhaoliu@example.com' },
{ id: 5, name: '钱七', department: '运营部', phone: '137****3456', email: 'qianqi@example.com' }
];
function AddProjectPage({ onBack }) {
const [selectedLeader, setSelectedLeader] = useState(null);
const leaderColumns = [
{ key: 'name', label: '姓名' },
{ key: 'department', label: '部门' },
{ key: 'phone', label: '联系电话' }
];
const selectedLabel = selectedLeader
? `${availableLeaders.find(l => l.id === selectedLeader)?.name} - ${availableLeaders.find(l => l.id === selectedLeader)?.department}`
: null;
return (
<div className="card">
<div className="card-header">
<div className="card-title">新增项目</div>
</div>
<div className="card-body">
<div className="form-group">
<label className="form-label required">项目名称</label>
<input type="text" className="form-control" placeholder="请输入项目名称" />
</div>
<div className="form-group">
<label className="form-label">项目描述</label>
<textarea className="form-control" rows="3" placeholder="请输入项目描述"></textarea>
</div>
<div className="form-group">
<label className="form-label required">负责人</label>
<ListSelector
data={availableLeaders}
selectedIds={selectedLeader}
onChange={setSelectedLeader}
searchPlaceholder="搜索负责人..."
columns={leaderColumns}
multiSelect={false}
selectedLabel={selectedLabel}
onClearSelected={() => setSelectedLeader(null)}
/>
</div>
<div style={{ display: 'flex', gap: '12px', justifyContent: 'flex-end', marginTop: '24px' }}>
<button className="btn" onClick={onBack}>取消</button>
<button className="btn btn-primary">确定</button>
</div>
</div>
</div>
);
}
export default AddProjectPage;

View File

@@ -0,0 +1,74 @@
import { useState } from 'react';
import ListSelector from '../../components/ListSelector.jsx';
const availableDepartments = [
{ id: 1, name: 'AI 产品部', description: '负责AI产品规划与设计', head: '张三', memberCount: 8 },
{ id: 2, name: '技术研发部', description: '负责核心技术研发与实现', head: '李四', memberCount: 15 },
{ id: 3, name: '数据分析部', description: '负责数据分析与挖掘', head: '王五', memberCount: 10 },
{ id: 4, name: '运营部', description: '负责产品运营与推广', head: '钱七', memberCount: 6 },
{ id: 5, name: '测试部', description: '负责产品质量保障', head: '周九', memberCount: 5 },
{ id: 6, name: '客户服务部', description: '负责客户支持与服务', head: '吴十', memberCount: 12 }
];
function AddUserPage({ onBack }) {
const [selectedDepartment, setSelectedDepartment] = useState(null);
const departmentColumns = [
{ key: 'name', label: '部门名称' },
{ key: 'description', label: '部门描述' },
{ key: 'head', label: '负责人' }
];
const selectedLabel = selectedDepartment
? availableDepartments.find(d => d.id === selectedDepartment)?.name
: null;
return (
<div className="card">
<div className="card-header">
<div className="card-title">新增用户</div>
</div>
<div className="card-body">
<div className="form-group">
<label className="form-label required">姓名</label>
<input type="text" className="form-control" placeholder="请输入用户姓名" />
</div>
<div className="form-group">
<label className="form-label required">部门</label>
<ListSelector
data={availableDepartments}
selectedIds={selectedDepartment}
onChange={setSelectedDepartment}
searchPlaceholder="搜索部门..."
columns={departmentColumns}
multiSelect={false}
selectedLabel={selectedLabel}
onClearSelected={() => setSelectedDepartment(null)}
/>
</div>
<div className="form-group">
<label className="form-label required">角色</label>
<select className="form-control">
<option>成员</option>
<option>管理员</option>
<option>开发者</option>
</select>
</div>
<div className="form-group">
<label className="form-label required">邮箱</label>
<input type="email" className="form-control" placeholder="请输入邮箱" />
</div>
<div className="form-group">
<label className="form-label">手机号</label>
<input type="tel" className="form-control" placeholder="请输入手机号" />
</div>
<div style={{ display: 'flex', gap: '12px', justifyContent: 'flex-end', marginTop: '24px' }}>
<button className="btn" onClick={onBack}>取消</button>
<button className="btn btn-primary">确定</button>
</div>
</div>
</div>
);
}
export default AddUserPage;

View File

@@ -0,0 +1,93 @@
const adminProjects = [
{ id: 1, name: '企业 AI 智算平台', description: '企业级AI智能助手平台', owner: '张三', members: 8, status: '正常', createTime: '2026-01-15', lastActive: '2026-03-19 10:30' },
{ id: 2, name: '知识库管理系统', description: '智能知识库检索与管理', owner: '李四', members: 5, status: '正常', createTime: '2026-02-10', lastActive: '2026-03-18 16:20' },
{ id: 3, name: '数据分析平台', description: '大数据分析与可视化平台', owner: '王五', members: 12, status: '正常', createTime: '2026-01-20', lastActive: '2026-03-19 09:45' },
{ id: 4, name: '智能客服系统', description: 'AI驱动的客户服务系统', owner: '赵六', members: 6, status: '禁用', createTime: '2026-02-28', lastActive: '2026-03-10 14:15' },
{ id: 5, name: '文档自动化平台', description: '智能文档生成与处理', owner: '钱七', members: 4, status: '正常', createTime: '2026-03-01', lastActive: '2026-03-19 11:20' },
{ id: 6, name: '代码审查助手', description: '自动化代码审查与优化建议', owner: '孙八', members: 7, status: '正常', createTime: '2026-02-15', lastActive: '2026-03-17 15:30' }
];
function StatusTag({ status }) {
const statusClass = status === '正常' ? 'status-running' : status === '禁用' ? 'status-error' : '';
return <span className={`status ${statusClass}`}>{status}</span>;
}
function AdminProjectsPage({ onAdd }) {
return (
<>
<div className="card">
<div className="card-body">
<div className="search-bar">
<div className="search-item" style={{ flex: 1, minWidth: '200px' }}>
<label>关键词</label>
<input type="text" className="form-control" placeholder="搜索项目名称、描述..." />
</div>
<div className="search-item">
<label>状态</label>
<select className="form-control">
<option>全部</option>
<option>正常</option>
<option>禁用</option>
</select>
</div>
</div>
<div className="search-actions">
<button className="btn btn-primary">查询</button>
<button className="btn">重置</button>
</div>
</div>
</div>
<div className="card">
<div className="card-header" style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<div className="card-title">项目列表</div>
<button className="btn btn-primary btn-sm" onClick={onAdd}>新增项目</button>
</div>
<div className="card-body">
<div className="table-wrapper">
<table className="table">
<thead>
<tr>
<th>项目名称</th>
<th>项目描述</th>
<th>负责人</th>
<th>成员数</th>
<th>状态</th>
<th>创建时间</th>
<th style={{ width: '200px' }}>操作</th>
</tr>
</thead>
<tbody>
{adminProjects.map(project => (
<tr key={project.id}>
<td><strong>{project.name}</strong></td>
<td>{project.description}</td>
<td>{project.owner}</td>
<td>{project.members} </td>
<td><StatusTag status={project.status} /></td>
<td>{project.createTime}</td>
<td style={{ width: '200px' }}>
<div style={{ display: 'flex', gap: '8px' }}>
<button className={`text-btn ${project.status === '正常' ? 'text-btn-danger' : 'text-btn-primary'}`}>{project.status === '正常' ? '禁用' : '启用'}</button>
<button className="text-btn text-btn-primary">编辑</button>
<button className="text-btn text-btn-danger">删除</button>
</div>
</td>
</tr>
))}
</tbody>
</table>
</div>
<div className="pagination">
<div className="pagination-item"></div>
<div className="pagination-item active">1</div>
<div className="pagination-item">2</div>
<div className="pagination-item">3</div>
<div className="pagination-item"></div>
</div>
</div>
</div>
</>
);
}
export default AdminProjectsPage;

View File

@@ -0,0 +1,93 @@
const departments = [
{ id: 1, name: 'AI 产品部', description: '负责AI产品规划与设计', head: '张三', members: 8, status: '正常', createTime: '2025-06-01' },
{ id: 2, name: '技术研发部', description: '负责核心技术研发与实现', head: '李四', members: 15, status: '正常', createTime: '2025-06-01' },
{ id: 3, name: '数据分析部', description: '负责数据分析与挖掘', head: '王五', members: 10, status: '正常', createTime: '2025-06-01' },
{ id: 4, name: '运营部', description: '负责产品运营与推广', head: '钱七', members: 6, status: '正常', createTime: '2025-08-15' },
{ id: 5, name: '测试部', description: '负责产品质量保障', head: '周九', members: 5, status: '正常', createTime: '2025-07-01' },
{ id: 6, name: '客户服务部', description: '负责客户支持与服务', head: '吴十', members: 12, status: '禁用', createTime: '2025-09-10' }
];
function StatusTag({ status }) {
const statusClass = status === '正常' ? 'status-running' : status === '禁用' ? 'status-error' : '';
return <span className={`status ${statusClass}`}>{status}</span>;
}
function DepartmentsPage({ onAdd }) {
return (
<>
<div className="card">
<div className="card-body">
<div className="search-bar">
<div className="search-item" style={{ flex: 1, minWidth: '200px' }}>
<label>关键词</label>
<input type="text" className="form-control" placeholder="搜索部门名称、描述..." />
</div>
<div className="search-item">
<label>状态</label>
<select className="form-control">
<option>全部</option>
<option>正常</option>
<option>禁用</option>
</select>
</div>
</div>
<div className="search-actions">
<button className="btn btn-primary">查询</button>
<button className="btn">重置</button>
</div>
</div>
</div>
<div className="card">
<div className="card-header" style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<div className="card-title">部门列表</div>
<button className="btn btn-primary btn-sm" onClick={onAdd}>新增部门</button>
</div>
<div className="card-body">
<div className="table-wrapper">
<table className="table">
<thead>
<tr>
<th>部门名称</th>
<th>部门描述</th>
<th>负责人</th>
<th>成员数</th>
<th>状态</th>
<th>创建时间</th>
<th style={{ width: '200px' }}>操作</th>
</tr>
</thead>
<tbody>
{departments.map(dept => (
<tr key={dept.id}>
<td><strong>{dept.name}</strong></td>
<td>{dept.description}</td>
<td>{dept.head}</td>
<td>{dept.members} </td>
<td><StatusTag status={dept.status} /></td>
<td>{dept.createTime}</td>
<td style={{ width: '200px' }}>
<div style={{ display: 'flex', gap: '8px' }}>
<button className={`text-btn ${dept.status === '正常' ? 'text-btn-danger' : 'text-btn-primary'}`}>{dept.status === '正常' ? '禁用' : '启用'}</button>
<button className="text-btn text-btn-primary">编辑</button>
<button className="text-btn text-btn-danger">删除</button>
</div>
</td>
</tr>
))}
</tbody>
</table>
</div>
<div className="pagination">
<div className="pagination-item"></div>
<div className="pagination-item active">1</div>
<div className="pagination-item">2</div>
<div className="pagination-item">3</div>
<div className="pagination-item"></div>
</div>
</div>
</div>
</>
);
}
export default DepartmentsPage;

View File

@@ -0,0 +1,18 @@
function OverviewPage() {
return (
<div className="card">
<div className="card-header">
<div className="card-title">运营总览</div>
</div>
<div className="card-body">
<div style={{ textAlign: 'center', padding: '60px 20px', color: 'var(--color-text-3)' }}>
<div style={{ fontSize: '48px', marginBottom: '16px' }}></div>
<div style={{ fontSize: '18px', fontWeight: 600, marginBottom: '8px' }}>运营总览页面</div>
<div>此处展示平台运营数据概览</div>
</div>
</div>
</div>
);
}
export default OverviewPage;

View File

@@ -0,0 +1,113 @@
const adminUsers = [
{ id: 1, name: '张三', department: 'AI 产品部', role: '管理员', email: 'zhangsan@example.com', phone: '138****8888', status: '正常', lastLogin: '2026-03-19 10:30' },
{ id: 2, name: '李四', department: '技术研发部', role: '开发者', email: 'lisi@example.com', phone: '139****1234', status: '正常', lastLogin: '2026-03-19 09:15' },
{ id: 3, name: '王五', department: '数据分析部', role: '成员', email: 'wangwu@example.com', phone: '136****5678', status: '禁用', lastLogin: '2026-03-15 18:20' },
{ id: 4, name: '赵六', department: 'AI 产品部', role: '管理员', email: 'zhaoliu@example.com', phone: '135****9012', status: '正常', lastLogin: '2026-03-19 11:45' },
{ id: 5, name: '钱七', department: '技术研发部', role: '开发者', email: 'qianqi@example.com', phone: '137****3456', status: '正常', lastLogin: '2026-03-18 16:30' },
{ id: 6, name: '孙八', department: '技术研发部', role: '成员', email: 'sunba@example.com', phone: '133****7890', status: '正常', lastLogin: '2026-03-19 08:00' },
{ id: 7, name: '周九', department: '测试部', role: '成员', email: 'zhoujiu@example.com', phone: '158****2345', status: '正常', lastLogin: '2026-03-17 14:20' },
{ id: 8, name: '吴十', department: '技术研发部', role: '开发者', email: 'wushi@example.com', phone: '159****6789', status: '正常', lastLogin: '2026-03-19 12:10' }
];
function StatusTag({ status }) {
const statusClass = status === '正常' ? 'status-running' : status === '禁用' ? 'status-error' : '';
return <span className={`status ${statusClass}`}>{status}</span>;
}
function RoleTag({ role }) {
const roleClass = role === '管理员' ? 'role-admin' : role === '开发者' ? 'role-developer' : 'role-member';
return <span className={`status ${roleClass}`}>{role}</span>;
}
function UsersPage({ onAdd }) {
return (
<>
<div className="card">
<div className="card-body">
<div className="search-bar">
<div className="search-item" style={{ flex: 1, minWidth: '200px' }}>
<label>关键词</label>
<input type="text" className="form-control" placeholder="搜索用户姓名、邮箱..." />
</div>
<div className="search-item">
<label>部门</label>
<select className="form-control">
<option>全部部门</option>
<option>AI 产品部</option>
<option>技术研发部</option>
<option>数据分析部</option>
<option>运营部</option>
<option>测试部</option>
</select>
</div>
<div className="search-item">
<label>状态</label>
<select className="form-control">
<option>全部</option>
<option>正常</option>
<option>禁用</option>
</select>
</div>
</div>
<div className="search-actions">
<button className="btn btn-primary">查询</button>
<button className="btn">重置</button>
</div>
</div>
</div>
<div className="card">
<div className="card-header" style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<div className="card-title">用户列表</div>
<button className="btn btn-primary btn-sm" onClick={onAdd}>新增用户</button>
</div>
<div className="card-body">
<div className="table-wrapper">
<table className="table">
<thead>
<tr>
<th>姓名</th>
<th>部门</th>
<th>角色</th>
<th>邮箱</th>
<th>手机号</th>
<th>状态</th>
<th>最后登录</th>
<th style={{ width: '200px' }}>操作</th>
</tr>
</thead>
<tbody>
{adminUsers.map(user => (
<tr key={user.id}>
<td><strong>{user.name}</strong></td>
<td>{user.department}</td>
<td><RoleTag role={user.role} /></td>
<td>{user.email}</td>
<td>{user.phone}</td>
<td><StatusTag status={user.status} /></td>
<td>{user.lastLogin}</td>
<td style={{ width: '200px' }}>
<div style={{ display: 'flex', gap: '8px' }}>
<button className={`text-btn ${user.status === '正常' ? 'text-btn-danger' : 'text-btn-primary'}`}>{user.status === '正常' ? '禁用' : '启用'}</button>
<button className="text-btn text-btn-primary">编辑</button>
<button className="text-btn text-btn-danger">删除</button>
</div>
</td>
</tr>
))}
</tbody>
</table>
</div>
<div className="pagination">
<div className="pagination-item"></div>
<div className="pagination-item active">1</div>
<div className="pagination-item">2</div>
<div className="pagination-item">3</div>
<div className="pagination-item"></div>
</div>
</div>
</div>
</>
);
}
export default UsersPage;

View File

@@ -0,0 +1,85 @@
function AccountPage() {
return (
<>
<div className="card">
<div className="card-header">
<div className="card-title">账号信息</div>
</div>
<div className="card-body">
{/* 头像区域 */}
<div style={{ textAlign: 'center', marginBottom: '24px', paddingBottom: '24px', borderBottom: '1px solid var(--color-border-2)' }}>
<div style={{
width: '100px',
height: '100px',
borderRadius: '50%',
background: 'linear-gradient(135deg, #3B82F6, #8B5CF6)',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
color: '#fff',
fontSize: '36px',
margin: '0 auto 12px'
}}></div>
<button className="btn btn-sm">更换头像</button>
</div>
{/* 表单区域 */}
<div className="form-row">
<div className="form-col">
<div className="form-group">
<label className="form-label">用户名</label>
<input type="text" className="form-control" defaultValue="zhangsan" readOnly style={{ background: '#F8FAFC' }} />
</div>
</div>
<div className="form-col">
<div className="form-group">
<label className="form-label">姓名</label>
<input type="text" className="form-control" defaultValue="张三" />
</div>
</div>
</div>
<div className="form-row">
<div className="form-col">
<div className="form-group">
<label className="form-label">邮箱</label>
<input type="email" className="form-control" defaultValue="zhangsan@example.com" />
</div>
</div>
<div className="form-col">
<div className="form-group">
<label className="form-label">手机号</label>
<input type="text" className="form-control" defaultValue="138****8888" />
</div>
</div>
</div>
<div className="form-group">
<label className="form-label">所属部门</label>
<input type="text" className="form-control" defaultValue="AI 产品部" readOnly style={{ background: '#F8FAFC' }} />
</div>
<button className="btn btn-primary">保存修改</button>
</div>
</div>
<div className="card">
<div className="card-header">
<div className="card-title">修改密码</div>
</div>
<div className="card-body">
<div className="form-group">
<label className="form-label">当前密码</label>
<input type="password" className="form-control" placeholder="请输入当前密码" />
</div>
<div className="form-group">
<label className="form-label">新密码</label>
<input type="password" className="form-control" placeholder="请输入新密码" />
</div>
<div className="form-group">
<label className="form-label">确认新密码</label>
<input type="password" className="form-control" placeholder="请再次输入新密码" />
</div>
<button className="btn btn-primary">更新密码</button>
</div>
</div>
</>
);
}
export default AccountPage;

View File

@@ -0,0 +1,59 @@
import { useState } from 'react';
import ListSelector from '../../components/ListSelector.jsx';
const availableMembers = [
{ id: 1, name: '陈十一', department: 'AI 产品部', email: 'chenshiyi@example.com' },
{ id: 2, name: '郑十二', department: '技术研发部', email: 'zhengshier@example.com' },
{ id: 3, name: '冯十三', department: '数据分析部', email: 'fengshisan@example.com' },
{ id: 4, name: '卫十四', department: '运营部', email: 'weishisi@example.com' },
{ id: 5, name: '蒋十五', department: '测试部', email: 'jiangshiwu@example.com' },
{ id: 6, name: '沈十六', department: 'AI 产品部', email: 'shenshiliu@example.com' },
{ id: 7, name: '韩十七', department: '技术研发部', email: 'hanshiqi@example.com' },
{ id: 8, name: '杨十八', department: '数据分析部', email: 'yangshiba@example.com' }
];
function AddMemberPage({ onBack }) {
const [selectedMembers, setSelectedMembers] = useState([]);
const memberColumns = [
{ key: 'name', label: '姓名' },
{ key: 'department', label: '部门' },
{ key: 'email', label: '邮箱' }
];
const selectedLabel = selectedMembers.length > 0
? `已选择 ${selectedMembers.length} 位成员`
: null;
const handleAdd = () => {
onBack();
};
return (
<div className="card">
<div className="card-header" style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<div className="card-title">增加成员</div>
<button className="btn btn-primary btn-sm" onClick={onBack}>返回列表</button>
</div>
<div className="card-body">
<ListSelector
data={availableMembers}
selectedIds={selectedMembers}
onChange={setSelectedMembers}
searchPlaceholder="搜索成员姓名、部门、邮箱..."
columns={memberColumns}
multiSelect={true}
selectedLabel={selectedLabel}
onClearSelected={() => setSelectedMembers([])}
/>
<div style={{ display: 'flex', justifyContent: 'flex-end', marginTop: '16px' }}>
<button className="btn btn-primary" onClick={handleAdd} disabled={selectedMembers.length === 0}>
添加选中成员 ({selectedMembers.length})
</button>
</div>
</div>
</div>
);
}
export default AddMemberPage;

View File

@@ -0,0 +1,69 @@
import { useEffect, useRef } from 'react';
import { getChatScenes } from '../../data/conversations.js';
import { FiPaperclip, FiCode, FiSend } from 'react-icons/fi';
function ChatPage({ scene }) {
const chatScenes = getChatScenes();
const html = chatScenes[scene] || '';
const chatMessagesRef = useRef(null);
useEffect(() => {
if (!chatMessagesRef.current) return;
const thinkingElements = chatMessagesRef.current.querySelectorAll('.message-thinking');
const handleClick = (event) => {
const thinkingElement = event.currentTarget;
thinkingElement.classList.toggle('expanded');
};
thinkingElements.forEach(el => {
el.addEventListener('click', handleClick);
el.style.cursor = 'pointer';
});
return () => {
thinkingElements.forEach(el => {
el.removeEventListener('click', handleClick);
});
};
}, [scene, html]); // 依赖场景和html内容
return (
<div className="chat-layout" style={{ height: '100%' }}>
<div className="chat-content">
<div className="chat-messages" style={{ padding: '16px 24px 8px' }}>
<div ref={chatMessagesRef} dangerouslySetInnerHTML={{ __html: html }} />
</div>
<div className="chat-input-wrapper" style={{ padding: '12px 24px 20px' }}>
<div className="chat-input-container">
<div className="chat-input-box">
<div className="chat-input-main">
<textarea
className="chat-input"
placeholder="输入消息... Enter 发送Shift+Enter 换行"
rows="1"
/>
<div className="chat-input-actions">
<div className="chat-input-tools">
<div className="chat-input-tool" title="上传文件">
<FiPaperclip />
</div>
<div className="chat-input-tool" title="代码块">
<FiCode />
</div>
</div>
<button className="chat-send-btn">
<FiSend />
</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
);
}
export default ChatPage;

View File

@@ -0,0 +1,94 @@
import { logs } from '../../data/logs.js';
function LogsPage() {
return (
<>
<div className="card">
<div className="card-body">
<div className="search-bar">
<div className="search-item" style={{ flex: 1, minWidth: '200px' }}>
<label>关键词</label>
<input type="text" className="form-control" placeholder="搜索操作、详情..." />
</div>
<div className="search-item">
<label>用户</label>
<select className="form-control">
<option>全部用户</option>
<option>张三</option>
<option>李四</option>
<option>王五</option>
</select>
</div>
<div className="search-item">
<label>类型</label>
<select className="form-control">
<option>全部类型</option>
<option>登录</option>
<option>实例操作</option>
<option>技能</option>
<option>配置修改</option>
<option>文件上传</option>
</select>
</div>
<div className="search-item">
<label>状态</label>
<select className="form-control">
<option>全部</option>
<option>成功</option>
<option>失败</option>
<option>警告</option>
</select>
</div>
</div>
<div className="search-actions">
<button className="btn btn-primary">查询</button>
<button className="btn">重置</button>
</div>
</div>
</div>
<div className="card">
<div className="card-header" style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<div className="card-title">日志列表</div>
<button className="btn btn-primary btn-sm">导出日志</button>
</div>
<div className="card-body">
<div className="table-wrapper">
<table className="table">
<thead>
<tr>
<th>时间</th>
<th>用户</th>
<th>类型</th>
<th>操作</th>
<th>状态</th>
<th>详情</th>
</tr>
</thead>
<tbody>
{logs.map((log, index) => (
<tr key={index}>
<td>{log.time}</td>
<td>{log.user}</td>
<td>{log.type}</td>
<td>{log.action}</td>
<td><span className={`status ${log.status === '成功' ? 'status-running' : log.status === '失败' ? 'status-error' : 'status-warning'}`}>{log.status}</span></td>
<td>{log.detail}</td>
</tr>
))}
</tbody>
</table>
</div>
<div className="pagination">
<div className="pagination-item"></div>
<div className="pagination-item active">1</div>
<div className="pagination-item">2</div>
<div className="pagination-item">3</div>
<div className="pagination-item"></div>
</div>
</div>
</div>
</>
);
}
export default LogsPage;

View File

@@ -0,0 +1,17 @@
function MemberConfigPage({ onBack }) {
return (
<div className="card">
<div className="card-header">
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<div className="card-title">成员配置</div>
<button className="btn btn-primary" onClick={onBack}>返回列表</button>
</div>
</div>
<div className="card-body">
<p>成员配置页面内容</p>
</div>
</div>
);
}
export default MemberConfigPage;

View File

@@ -0,0 +1,45 @@
import { projectMembers } from '../../data/members.js';
function ProjectsPage({ onAddMember }) {
return (
<div className="card">
<div className="card-header" style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<div className="card-title">项目管理</div>
<button className="btn btn-primary btn-sm" onClick={onAddMember}>增加成员</button>
</div>
<div className="card-body">
<div className="table-wrapper">
<table className="table">
<thead>
<tr>
<th>成员</th>
<th style={{ width: '100px' }}>角色</th>
<th style={{ width: '80px' }}>操作</th>
</tr>
</thead>
<tbody>
{projectMembers.map(member => (
<tr key={member.id}>
<td>
<div style={{ display: 'flex', alignItems: 'center', gap: '10px' }}>
<div className="user-avatar" style={{ width: '32px', height: '32px', fontSize: '13px' }}>
{member.name.charAt(0)}
</div>
{member.name}
</div>
</td>
<td><span className={`status ${member.role === '管理员' ? 'role-admin' : 'role-member'}`}>{member.role}</span></td>
<td style={{ width: '80px' }}>
<button className="text-btn text-btn-primary">配置</button>
</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
</div>
);
}
export default ProjectsPage;

View File

@@ -0,0 +1,77 @@
import { FiChevronLeft, FiFile } from 'react-icons/fi';
import { skills, getSkillIcon, skillFiles, skillVersions } from '../../data/skills.js';
function SkillDetailPage({ skillId, onBack }) {
const skill = skills.find(s => s.id === skillId);
if (!skill) {
return <div>Skill not found</div>;
}
return (
<>
<div className="skill-back-btn" onClick={onBack}>
<FiChevronLeft /> 返回技能市场
</div>
<div className="card">
<div className="card-body">
<div className="skill-detail-header">
<div className="skill-detail-icon">{getSkillIcon(skill.id)}</div>
<div className="skill-detail-main">
<h2 style={{ fontSize: '22px', fontWeight: 800, marginBottom: '6px' }}>{skill.name}</h2>
<p style={{ color: '#64748B', marginBottom: '8px' }}>by {skill.author}</p>
<div className="skill-detail-tags">
{skill.tags.map(tag => (
<span key={tag} className="skill-detail-tag">{tag}</span>
))}
</div>
<div className="skill-detail-stats">
<span>👤 {skill.subs.toLocaleString()} 订阅</span>
<span> {skill.rating} 评分</span>
</div>
</div>
<div style={{ flexShrink: 0 }}>
<button className={`btn ${skill.subscribed ? '' : 'btn-primary'}`}>
{skill.subscribed ? '取消订阅' : '立即订阅'}
</button>
</div>
</div>
<div className="skill-detail-section">
<h3>使用说明</h3>
<p style={{ color: '#475569', lineHeight: 1.8 }}>
{skill.desc}安装后您可以在对话中直接调用该技能例如您可以说
</p>
<div style={{ marginTop: '12px', padding: '12px 16px', background: '#F8FAFC', borderRadius: '8px', color: '#3B82F6', fontFamily: 'monospace' }}>
"帮我用这个技能 查询一下数据"
</div>
</div>
<div className="skill-detail-section">
<h3>文件列表</h3>
{skillFiles.map(file => (
<div key={file.name} className="file-list-item">
<div className="file-icon"><FiFile /></div>
<div className="file-info">
<div className="file-name">{file.name}</div>
<div className="file-size">{file.type} · {file.size}</div>
</div>
</div>
))}
</div>
<div className="skill-detail-section">
<h3>版本历史</h3>
{skillVersions.map(ver => (
<div key={ver.version} className="version-list-item">
<div className="version-info">
<span className={`version-tag ${ver.current ? 'current' : ''}`}>{ver.version}</span>
<span className="version-desc">{ver.desc}</span>
</div>
<div className="version-date">{ver.date}</div>
</div>
))}
</div>
</div>
</div>
</>
);
}
export default SkillDetailPage;

View File

@@ -0,0 +1,101 @@
import { useState } from 'react';
import { FiUser, FiStar, FiSearch } from 'react-icons/fi';
import { skills, getSkillIcon } from '../../data/skills.js';
function SkillCard({ skill, onClick }) {
return (
<div className="skill-card" onClick={onClick}>
<div className="skill-header">
<div className="skill-icon">{getSkillIcon(skill.id)}</div>
<div className="skill-info">
<div className="skill-name">{skill.name}</div>
<div className="skill-author">{skill.author}</div>
</div>
</div>
<div className="skill-desc">{skill.desc}</div>
<div className="skill-tags">
{skill.tags.map(tag => (
<span key={tag} className="skill-tag">{tag}</span>
))}
</div>
<div className="skill-footer">
<div className="skill-stats">
<span style={{ display: 'flex', alignItems: 'center', gap: '4px', color: '#94A3B8', fontSize: '13px' }}>
<FiUser /> {skill.subs}
</span>
<span style={{ display: 'flex', alignItems: 'center', gap: '4px', color: '#94A3B8', fontSize: '13px' }}>
<FiStar /> {skill.rating}
</span>
</div>
<button className={`btn ${skill.subscribed ? 'btn-primary' : ''} btn-sm`} onClick={e => e.stopPropagation()}>
{skill.subscribed ? '已订阅' : '订阅'}
</button>
</div>
</div>
);
}
function SkillsPage({ onSkillClick }) {
const [filter, setFilter] = useState('all');
const [sort, setSort] = useState('subs');
const filteredSkills = filter === 'subscribed'
? skills.filter(s => s.subscribed)
: [...skills];
filteredSkills.sort((a, b) => {
if (sort === 'subs') return b.subs - a.subs;
if (sort === 'rating') return b.rating - a.rating;
return 0;
});
return (
<>
<div className="card">
<div className="card-body">
<div className="search-bar">
<div className="search-item" style={{ flex: 1, minWidth: '200px' }}>
<label>关键词</label>
<input type="text" className="form-control" placeholder="搜索技能..." />
</div>
<div className="search-item">
<label>分类</label>
<select className="form-control">
<option>全部分类</option>
<option>开发工具</option>
<option>数据分析</option>
<option>办公效率</option>
<option>业务系统</option>
</select>
</div>
<div className="search-item">
<label>排序</label>
<select className="form-control" value={sort} onChange={e => setSort(e.target.value)}>
<option value="subs">订阅数</option>
<option value="rating">评分</option>
</select>
</div>
</div>
<div className="search-actions">
<button className="btn btn-primary"><FiSearch /> 查询</button>
</div>
</div>
</div>
<div style={{ marginBottom: '16px' }}>
<div className="btn-group">
<button className={`btn ${filter === 'all' ? 'btn-primary' : ''}`} onClick={() => setFilter('all')}>全部技能</button>
<button className={`btn ${filter === 'subscribed' ? 'btn-primary' : ''}`} onClick={() => setFilter('subscribed')}>
已订阅 ({skills.filter(s => s.subscribed).length})
</button>
</div>
</div>
<div className="skill-grid">
{filteredSkills.map(skill => (
<SkillCard key={skill.id} skill={skill} onClick={() => onSkillClick(skill.id)} />
))}
</div>
</>
);
}
export default SkillsPage;

View File

@@ -0,0 +1,89 @@
import { scheduledTasks } from '../../data/tasks.js';
function TaskDetailPage({ taskId, onBack }) {
const task = scheduledTasks.find(t => t.id === taskId);
if (!task) {
return (
<div className="card">
<div className="card-header">
<div className="card-title">任务详情</div>
</div>
<div className="card-body">
<p>任务不存在</p>
<button className="btn btn-primary btn-sm" onClick={onBack} style={{ marginTop: '16px' }}>返回列表</button>
</div>
</div>
);
}
return (
<div className="card">
<div className="card-header" style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<div className="card-title">任务详情</div>
<button className="btn btn-primary btn-sm" onClick={onBack}>返回列表</button>
</div>
<div className="card-body">
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(2, 1fr)', gap: '20px', marginBottom: '24px' }}>
<div>
<div style={{ fontSize: '13px', color: '#64748B', marginBottom: '6px' }}>任务名称</div>
<div style={{ fontSize: '15px', fontWeight: '600', color: '#1E293B' }}>{task.name}</div>
</div>
<div>
<div style={{ fontSize: '13px', color: '#64748B', marginBottom: '6px' }}>执行频率</div>
<div style={{ fontSize: '15px', fontWeight: '600', color: '#1E293B' }}>{task.frequency}</div>
</div>
<div>
<div style={{ fontSize: '13px', color: '#64748B', marginBottom: '6px' }}>上次触发</div>
<div style={{ fontSize: '15px', fontWeight: '600', color: '#1E293B' }}>{task.lastTriggered}</div>
</div>
<div>
<div style={{ fontSize: '13px', color: '#64748B', marginBottom: '6px' }}>下次触发</div>
<div style={{ fontSize: '15px', fontWeight: '600', color: '#1E293B' }}>{task.nextTrigger}</div>
</div>
</div>
<div style={{ marginBottom: '24px' }}>
<div style={{ fontSize: '14px', fontWeight: '600', color: '#1E293B', marginBottom: '12px' }}>执行内容</div>
<textarea
className="form-control"
rows="4"
style={{ background: '#F8FAFC', color: '#475569' }}
value={task.prompt || '请分析本月的销售数据,生成一份可视化报表。'}
readOnly
/>
</div>
<div>
<div style={{ fontSize: '14px', fontWeight: '600', color: '#1E293B', marginBottom: '12px' }}>执行日志</div>
<div style={{ border: '1px solid #E2E8F0', borderRadius: '8px', overflow: 'hidden' }}>
<table className="table" style={{ marginBottom: '0' }}>
<thead>
<tr>
<th style={{ width: '180px', whiteSpace: 'nowrap' }}>执行时间</th>
<th style={{ width: '100px', whiteSpace: 'nowrap' }}>状态</th>
<th>执行信息</th>
</tr>
</thead>
<tbody>
{task.logs.map((log, index) => (
<tr key={index}>
<td style={{ color: '#64748B', fontSize: '13px', whiteSpace: 'nowrap' }}>{log.time}</td>
<td>
<span className={`status ${log.status === '成功' ? 'status-running' : 'status-error'}`}>
{log.status}
</span>
</td>
<td style={{ fontSize: '14px', color: '#475569' }}>{log.message}</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
</div>
</div>
);
}
export default TaskDetailPage;

View File

@@ -0,0 +1,60 @@
import { useState } from 'react';
import { scheduledTasks } from '../../data/tasks.js';
function TasksPage({ onViewDetail }) {
const [tasks, setTasks] = useState(scheduledTasks);
const toggleTask = (taskId) => {
setTasks(prev => prev.map(task =>
task.id === taskId ? { ...task, enabled: !task.enabled } : task
));
};
return (
<div className="card">
<div className="card-header">
<div className="card-title">定时任务</div>
</div>
<div className="card-body">
<table className="table">
<thead>
<tr>
<th>任务名称</th>
<th>频率</th>
<th>上次触发</th>
<th>下次触发</th>
<th>上次运行状态</th>
<th>状态</th>
<th>操作</th>
</tr>
</thead>
<tbody>
{tasks.map(task => (
<tr key={task.id}>
<td>{task.name}</td>
<td>{task.frequency}</td>
<td>{task.lastTriggered}</td>
<td>{task.nextTrigger}</td>
<td>
<span className={`status ${task.lastStatus === '成功' ? 'status-running' : task.lastStatus === '失败' ? 'status-error' : 'status-warning'}`}>
{task.lastStatus}
</span>
</td>
<td><span className={`status ${task.enabled ? 'status-running' : 'status-stopped'}`}>{task.enabled ? '启用' : '禁用'}</span></td>
<td>
<button className={`text-btn ${task.enabled ? 'text-btn-danger' : 'text-btn-primary'}`} onClick={() => toggleTask(task.id)}>
{task.enabled ? '禁用' : '启用'}
</button>
<button className="text-btn text-btn-primary" style={{ marginLeft: '8px' }} onClick={() => onViewDetail(task.id)}>详情</button>
<button className="text-btn text-btn-danger" style={{ marginLeft: '8px' }}>删除</button>
</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
);
}
export default TasksPage;

View File

@@ -0,0 +1,24 @@
function DevAccountPage() {
return (
<div className="card">
<div className="card-header">
<div className="card-title">开发者设置</div>
</div>
<div className="card-body">
<div style={{ display: 'flex', gap: '20px', alignItems: 'flex-start' }}>
<div className="user-avatar" style={{ width: '72px', height: '72px', fontSize: '32px' }}></div>
<div>
<h2 style={{ marginBottom: '8px' }}>张三</h2>
<div style={{ color: '#64748B', marginBottom: '12px' }}>开发者</div>
<div style={{ display: 'flex', gap: '8px' }}>
<button className="btn btn-primary">编辑资料</button>
<button className="btn">API 密钥管理</button>
</div>
</div>
</div>
</div>
</div>
);
}
export default DevAccountPage;

View File

@@ -0,0 +1,29 @@
import { devDocs } from '../../data/developerData.js';
function DevDocsPage() {
const categories = [...new Set(devDocs.map(doc => doc.category))];
return (
<div className="card">
<div className="card-header">
<div className="card-title">开发文档</div>
</div>
<div className="card-body">
{categories.map(category => (
<div key={category} style={{ marginBottom: '24px' }}>
<h3 style={{ marginBottom: '12px', fontSize: '16px', fontWeight: 600 }}>{category}</h3>
<div style={{ display: 'grid', gap: '12px' }}>
{devDocs.filter(doc => doc.category === category).map(doc => (
<div key={doc.id} style={{ padding: '12px', background: '#F8FAFC', borderRadius: '8px', cursor: 'pointer' }}>
<div style={{ fontWeight: 600, marginBottom: '4px' }}>{doc.title}</div>
<div style={{ fontSize: '14px', color: '#64748B' }}>{doc.content}</div>
</div>
))}
</div>
</div>
))}
</div>
</div>
);
}
export default DevDocsPage;

View File

@@ -0,0 +1,50 @@
import { mySkills } from '../../data/developerData.js';
function MySkillsPage({ onSkillClick }) {
return (
<div className="card">
<div className="card-header">
<div className="card-title">我的技能</div>
</div>
<div className="card-body">
<table className="table">
<thead>
<tr>
<th>技能名称</th>
<th>分类</th>
<th>版本</th>
<th>状态</th>
<th>安装量</th>
<th>评分</th>
<th>操作</th>
</tr>
</thead>
<tbody>
{mySkills.map(skill => (
<tr key={skill.id} onClick={() => onSkillClick(skill.id)} style={{ cursor: 'pointer' }}>
<td>
<div style={{ fontWeight: 600 }}>{skill.name}</div>
<div style={{ fontSize: '12px', color: '#94A3B8' }}>{skill.desc}</div>
</td>
<td>{skill.category}</td>
<td>{skill.version}</td>
<td><span className={`status ${skill.status === 'published' ? 'status-running' : 'status-stopped'}`}>
{skill.status === 'published' ? '已发布' : '草稿'}
</span></td>
<td>{skill.installs}</td>
<td>{skill.rating || '-'}</td>
<td>
<button className="text-btn text-btn-primary" onClick={e => { e.stopPropagation(); onSkillClick(skill.id); }}>
编辑
</button>
</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
);
}
export default MySkillsPage;

View File

@@ -0,0 +1,43 @@
import { FiUpload } from 'react-icons/fi';
function NewVersionPage({ skillName, onBack }) {
return (
<div className="card">
<div className="card-header">
<div className="card-title">上传新版本</div>
</div>
<div className="card-body">
<div className="form-group">
<label className="form-label">技能名称</label>
<input type="text" className="form-control" defaultValue={skillName} />
</div>
<div className="form-group">
<label className="form-label">技能描述</label>
<textarea className="form-control" rows="3" placeholder="请输入技能描述"></textarea>
</div>
<div className="form-group">
<label className="form-label">技能分类</label>
<select className="form-control">
<option>信息查询</option>
<option>效率工具</option>
<option>开发工具</option>
</select>
</div>
<div className="form-group">
<label className="form-label">技能包上传</label>
<div style={{ border: '2px dashed #E2E8F0', borderRadius: '8px', padding: '40px', textAlign: 'center', color: '#94A3B8' }}>
<FiUpload size={48} style={{ marginBottom: '16px' }} />
<div>点击或拖拽文件到此处上传</div>
<div style={{ fontSize: '12px', marginTop: '8px' }}>支持 .zip 格式</div>
</div>
</div>
<div style={{ display: 'flex', gap: '12px', justifyContent: 'flex-end' }}>
<button className="btn" onClick={onBack}>取消</button>
<button className="btn btn-primary">提交审核</button>
</div>
</div>
</div>
);
}
export default NewVersionPage;

View File

@@ -0,0 +1,118 @@
import { FiChevronLeft, FiUpload, FiDownload } from 'react-icons/fi';
import { mySkills } from '../../data/developerData.js';
function SkillEditorPage({ skillId, onBack, onUploadNewVersion }) {
const skill = mySkills.find(s => s.id === skillId);
if (!skill) {
return <div>Skill not found</div>;
}
return (
<>
<div className="dev-back-btn" onClick={onBack}>
<FiChevronLeft /> 返回我的技能
</div>
<div className="card">
<div className="card-header">
<div className="card-title">配置信息</div>
</div>
<div className="card-body">
<div className="dev-detail-header">
<div className="dev-detail-icon">{skill.name.charAt(0)}</div>
<div className="dev-detail-main">
<h2 style={{ marginBottom: '8px' }}>{skill.name}</h2>
<div style={{ color: '#64748B', marginBottom: '12px' }}>{skill.category}</div>
<div className="dev-detail-tags">
{skill.tags.map(tag => (
<span key={tag} className="dev-detail-tag">{tag}</span>
))}
</div>
<div className="dev-detail-stats">
<span>版本: {skill.version}</span>
<span>安装量: {skill.installs}</span>
<span>评分: {skill.rating || '-'}</span>
</div>
</div>
</div>
<div className="dev-detail-section">
<h3>基本信息</h3>
<div className="dev-info-row">
<span className="dev-info-label">技能名称</span>
<span className="dev-info-value">{skill.name}</span>
</div>
<div className="dev-info-row">
<span className="dev-info-label">技能描述</span>
<span className="dev-info-value">{skill.desc}</span>
</div>
</div>
</div>
</div>
<div className="card" style={{ marginTop: '24px' }}>
<div className="card-header">
<div className="card-title">技能包管理</div>
</div>
<div className="card-body">
<div style={{ display: 'flex', gap: '12px', marginBottom: '24px' }}>
<button className="btn btn-primary" onClick={() => onUploadNewVersion(skill.name)}><FiUpload /> 上传新版本</button>
</div>
<h4 style={{ marginBottom: '12px' }}>版本历史</h4>
<div className="table-wrapper" style={{ margin: 0, padding: 0 }}>
<table className="table" style={{ tableLayout: 'fixed' }}>
<colgroup>
<col style={{ width: '100px' }} />
<col />
<col style={{ width: '120px' }} />
<col style={{ width: '120px' }} />
<col style={{ width: '100px' }} />
<col style={{ width: '160px' }} />
</colgroup>
<thead>
<tr>
<th>版本号</th>
<th>版本说明</th>
<th>状态</th>
<th>更新时间</th>
<th>是否启用</th>
<th>操作</th>
</tr>
</thead>
<tbody>
{skill.versions.map(ver => (
<tr key={ver.version}>
<td>{ver.version}</td>
<td>{ver.desc}</td>
<td>
{ver.status === 'pending' ? (
<span className="status status-warning">审核中</span>
) : ver.status === 'rejected' ? (
<span className="status status-error">审核拒绝</span>
) : (
<span className="status status-running">审核通过</span>
)}
</td>
<td>{ver.date}</td>
<td>
{ver.enabled ? (
<span className="status status-running">已启用</span>
) : (
<span className="status status-stopped">未启用</span>
)}
</td>
<td>
<div className="btn-group">
{!ver.enabled && <button className="text-btn text-btn-success">启用</button>}
<button className="text-btn">下载</button>
</div>
</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
</div>
</>
);
}
export default SkillEditorPage;

View File

@@ -0,0 +1,81 @@
import { FiUpload, FiX } from 'react-icons/fi';
import { useState } from 'react';
function UploadSkillPage() {
const [tags, setTags] = useState([]);
const [tagInput, setTagInput] = useState('');
const handleTagKeyDown = (e) => {
if (e.key === 'Enter' && tagInput.trim()) {
e.preventDefault();
if (!tags.includes(tagInput.trim())) {
setTags([...tags, tagInput.trim()]);
}
setTagInput('');
}
};
const removeTag = (tagToRemove) => {
setTags(tags.filter(tag => tag !== tagToRemove));
};
return (
<div className="card">
<div className="card-header">
<div className="card-title">创建技能</div>
</div>
<div className="card-body">
<div className="form-group">
<label className="form-label required">技能名称</label>
<input type="text" className="form-control" placeholder="请输入技能名称" required />
</div>
<div className="form-group">
<label className="form-label required">技能描述</label>
<textarea className="form-control" rows="3" placeholder="请输入技能描述" required></textarea>
</div>
<div className="form-group">
<label className="form-label">技能分类</label>
<select className="form-control">
<option>信息查询</option>
<option>效率工具</option>
<option>开发工具</option>
</select>
</div>
<div className="form-group">
<label className="form-label">标签</label>
<div className="tag-input-container">
{tags.map(tag => (
<span key={tag} className="tag-item">
{tag}
<span className="tag-remove" onClick={() => removeTag(tag)}><FiX /></span>
</span>
))}
<input
type="text"
className="tag-input"
placeholder={tags.length === 0 ? '输入标签后按回车添加' : ''}
value={tagInput}
onChange={(e) => setTagInput(e.target.value)}
onKeyDown={handleTagKeyDown}
/>
</div>
<div style={{ fontSize: '12px', color: '#94A3B8', marginTop: '6px' }}>按回车添加标签最多5个</div>
</div>
<div className="form-group">
<label className="form-label required">技能包上传</label>
<div style={{ border: '2px dashed #E2E8F0', borderRadius: '8px', padding: '40px', textAlign: 'center', color: '#94A3B8' }}>
<FiUpload size={48} style={{ marginBottom: '16px' }} />
<div>点击或拖拽文件到此处上传</div>
<div style={{ fontSize: '12px', marginTop: '8px' }}>支持 .zip 格式</div>
</div>
</div>
<div style={{ display: 'flex', gap: '12px', justifyContent: 'flex-end' }}>
<button className="btn">取消</button>
<button className="btn btn-primary">创建技能</button>
</div>
</div>
</div>
);
}
export default UploadSkillPage;