# Design Document: Role-Based Menu and Project Config ## Overview 本设计文档描述了标注平台角色权限菜单优化和项目配置流程改进的技术实现方案。主要包括: 1. **角色权限菜单**:根据用户角色(admin/annotator)显示不同的侧边栏菜单 2. **外部项目管理页面**:新增管理员专属页面,管理外部API创建的项目 3. **项目管理页面优化**:仅显示进行中和已完成的项目 4. **选卡式项目配置**:提供可视化的标注类型选择和标签配置界面 5. **XML配置生成**:根据配置自动生成 Label Studio XML ## Architecture ### 系统架构图 ```mermaid graph TB subgraph "Frontend" subgraph "Components" SM[Sidebar Menu] TC[Task Type Cards] LE[Label Editor] XP[XML Preview] CW[Config Wizard] end subgraph "Pages" PL[Project List Page] EP[External Projects Page] PC[Project Config Page] CP[Create Project Page] end subgraph "State" UA[User Auth Atoms] PA[Project Atoms] end end subgraph "Services" XG[XML Generator] RF[Role Filter] end SM --> RF RF --> UA TC --> XG LE --> XG XG --> XP CW --> TC CW --> LE CW --> XP PL --> PA EP --> PA PC --> PA ``` ### 页面路由结构 ``` /projects # 项目管理(仅显示进行中和已完成) /projects/:id/config # 项目配置页面 /projects/create # 创建项目向导 /external-projects # 管理外部来源数据(管理员专属) /my-tasks # 我的任务 /annotations # 我的标注 /tasks # 任务管理(管理员专属) /users # 用户管理(管理员专属) ``` ### 菜单权限配置 ```typescript // 菜单项配置 const menuItems = [ { id: 'projects', label: '项目管理', path: '/projects', adminOnly: false }, { id: 'external-projects', label: '管理外部来源数据', path: '/external-projects', adminOnly: true }, { id: 'tasks', label: '任务管理', path: '/tasks', adminOnly: true }, { id: 'my-tasks', label: '我的任务', path: '/my-tasks', adminOnly: false }, { id: 'annotations', label: '我的标注', path: '/annotations', adminOnly: false }, { id: 'users', label: '用户管理', path: '/users', adminOnly: true }, ]; // 角色过滤逻辑 const visibleMenuItems = menuItems.filter(item => { if (item.adminOnly && currentUser?.role !== 'admin') { return false; } return true; }); ``` ## Components and Interfaces ### 1. Task Type Selector Component 选卡式标注类型选择组件。 ```typescript interface TaskTypeOption { id: TaskType; name: string; description: string; icon: React.ReactNode; dataFormat: 'text' | 'image'; } interface TaskTypeSelectorProps { selectedType: TaskType | null; onSelect: (type: TaskType) => void; } const TASK_TYPE_OPTIONS: TaskTypeOption[] = [ { id: 'text_classification', name: '文本分类', description: '对文本内容进行分类标注', icon: , dataFormat: 'text', }, { id: 'image_classification', name: '图像分类', description: '对图像进行分类标注', icon: , dataFormat: 'image', }, { id: 'object_detection', name: '目标检测', description: '在图像中标注目标边界框', icon: , dataFormat: 'image', }, { id: 'ner', name: '命名实体识别', description: '标注文本中的实体', icon: , dataFormat: 'text', }, ]; ``` ### 2. Label Editor Component 可视化标签编辑组件。 ```typescript interface LabelConfig { id: string; name: string; color: string; hotkey?: string; } interface LabelEditorProps { labels: LabelConfig[]; onChange: (labels: LabelConfig[]) => void; taskType: TaskType; } // 预定义颜色列表 const PRESET_COLORS = [ '#FF6B6B', '#4ECDC4', '#45B7D1', '#96CEB4', '#FFEAA7', '#DDA0DD', '#98D8C8', '#F7DC6F', '#BB8FCE', '#85C1E9', '#F8B500', '#00CED1', ]; ``` ### 3. XML Generator Service 根据配置生成 Label Studio XML。 ```typescript interface XMLGeneratorConfig { taskType: TaskType; labels: LabelConfig[]; choiceType?: 'single' | 'multiple'; // 用于分类任务 } class XMLGenerator { static generate(config: XMLGeneratorConfig): string { switch (config.taskType) { case 'text_classification': return this.generateTextClassification(config); case 'image_classification': return this.generateImageClassification(config); case 'object_detection': return this.generateObjectDetection(config); case 'ner': return this.generateNER(config); default: throw new Error(`Unsupported task type: ${config.taskType}`); } } private static generateTextClassification(config: XMLGeneratorConfig): string { const choiceAttr = config.choiceType === 'multiple' ? 'multiple' : 'single'; const labelsXML = config.labels .map(l => ` `) .join('\n'); return ` ${labelsXML} `; } private static generateImageClassification(config: XMLGeneratorConfig): string { const choiceAttr = config.choiceType === 'multiple' ? 'multiple' : 'single'; const labelsXML = config.labels .map(l => ` `) .join('\n'); return ` ${labelsXML} `; } private static generateObjectDetection(config: XMLGeneratorConfig): string { const labelsXML = config.labels .map(l => `