Quellcode durchsuchen

-dev:修复了前端页面的逻辑,优化了界面样式

LuoChinWen vor 1 Monat
Ursprung
Commit
af2da519f5
21 geänderte Dateien mit 1327 neuen und 1040 gelöschten Zeilen
  1. 0 326
      web/apps/lq_label/PROJECT_TASK_REDESIGN_SUMMARY.md
  2. 0 77
      web/apps/lq_label/THEME_FIX_QUICK_SUMMARY.md
  3. 0 264
      web/apps/lq_label/THEME_FIX_SUMMARY.md
  4. 0 0
      web/apps/lq_label/THEME_QUICK_START.md
  5. 0 289
      web/apps/lq_label/THEME_REDESIGN_SUMMARY.md
  6. 0 2
      web/apps/lq_label/src/app/app.tsx
  7. 4 0
      web/apps/lq_label/src/components/index.ts
  8. 1 0
      web/apps/lq_label/src/components/layout/top-bar.module.scss
  9. 2 0
      web/apps/lq_label/src/components/project-detail-modal/index.ts
  10. 450 0
      web/apps/lq_label/src/components/project-detail-modal/project-detail-modal.module.scss
  11. 361 0
      web/apps/lq_label/src/components/project-detail-modal/project-detail-modal.tsx
  12. 2 0
      web/apps/lq_label/src/components/project-edit-modal/index.ts
  13. 104 0
      web/apps/lq_label/src/components/project-edit-modal/project-edit-modal.module.scss
  14. 118 0
      web/apps/lq_label/src/components/project-edit-modal/project-edit-modal.tsx
  15. 94 3
      web/apps/lq_label/src/components/project-form/project-form.module.scss
  16. 20 40
      web/apps/lq_label/src/components/project-form/project-form.tsx
  17. 41 27
      web/apps/lq_label/src/components/theme-switcher/theme-switcher.module.scss
  18. 23 9
      web/apps/lq_label/src/components/theme-switcher/theme-switcher.tsx
  19. 72 0
      web/apps/lq_label/src/styles/global.scss
  20. 0 1
      web/apps/lq_label/src/views/index.ts
  21. 35 2
      web/apps/lq_label/src/views/project-list-view/project-list-view.tsx

+ 0 - 326
web/apps/lq_label/PROJECT_TASK_REDESIGN_SUMMARY.md

@@ -1,326 +0,0 @@
-# 项目和任务管理页面重新设计总结
-
-## 概述
-
-本次重新设计了项目管理(ProjectsView)和任务管理(TasksView)页面,采用现代化的表格设计和筛选方法,完全支持三种主题模式(白色、深色、护眼模式)。
-
-## 设计原则
-
-### 1. 现代简约风格
-- 无 emoji 表情
-- 无渐变色
-- 无紫色类色彩
-- 圆角设计(12px 卡片,8px 按钮)
-- 清晰的视觉层次
-
-### 2. 主题系统集成
-- 完全使用 CSS 变量(`--theme-*`)
-- 支持三种主题模式切换
-- 所有颜色都从主题配置中获取
-- 平滑的过渡动画
-
-### 3. 高效的信息展示
-- 清晰的表格布局
-- 直观的搜索和筛选功能
-- 快速的操作按钮
-- 友好的空状态提示
-
-## 主要改进
-
-### 项目管理页面(ProjectListView)
-
-#### 功能特性
-1. **搜索功能**
-   - 实时搜索项目名称和描述
-   - 搜索框带图标提示
-   - 响应式布局
-
-2. **表格设计**
-   - 现代化的表格样式
-   - 悬停高亮效果
-   - 固定表头(sticky header)
-   - 圆角边框
-
-3. **操作按钮**
-   - 查看详情(Eye 图标)
-   - 编辑项目(Edit 图标)
-   - 删除项目(Trash2 图标)
-   - 悬停时显示提示
-
-4. **状态显示**
-   - 任务数量徽章
-   - 活跃/非活跃状态区分
-   - 创建时间显示(日期+时间)
-
-5. **对话框**
-   - 创建项目对话框
-   - 删除确认对话框
-   - 模态遮罩层
-   - 平滑动画效果
-
-#### 视觉改进
-- 清晰的页面标题和副标题
-- 主操作按钮(创建项目)突出显示
-- 错误消息带图标提示
-- 空状态友好提示
-- 加载状态动画
-
-### 任务管理页面(TaskListView)
-
-#### 功能特性
-1. **状态筛选**
-   - 全部/待处理/进行中/已完成
-   - 按钮式筛选器
-   - 活跃状态高亮
-
-2. **搜索功能**
-   - 搜索任务名称、项目ID、负责人
-   - 实时过滤结果
-   - 搜索框带图标
-
-3. **表格设计**
-   - 7列信息展示
-   - 进度条可视化
-   - 状态徽章
-   - 操作按钮组
-
-4. **进度显示**
-   - 可视化进度条
-   - 百分比数字
-   - 平滑过渡动画
-
-5. **操作按钮**
-   - 开始标注(Play 图标,主色调)
-   - 查看详情(Eye 图标)
-   - 删除任务(Trash2 图标,危险色)
-
-#### 视觉改进
-- 状态徽章颜色区分(待处理/进行中/已完成)
-- 进度条动态宽度
-- 操作按钮悬停效果
-- 空状态和加载状态
-
-## 技术实现
-
-### 组件结构
-```
-views/
-├── project-list-view/
-│   ├── project-list-view.tsx       # 项目列表组件
-│   └── project-list-view.module.scss  # 样式文件
-└── task-list-view/
-    ├── task-list-view.tsx          # 任务列表组件
-    └── task-list-view.module.scss     # 样式文件
-```
-
-### 图标使用
-使用 Lucide React 图标库:
-- `Plus` - 创建按钮
-- `Search` - 搜索框
-- `Eye` - 查看详情
-- `Edit` - 编辑
-- `Trash2` - 删除
-- `Play` - 开始标注
-- `FolderOpen` - 空状态(项目)
-- `ListTodo` - 空状态(任务)
-- `AlertCircle` - 错误提示
-
-### 样式系统
-
-#### CSS 变量使用
-```scss
-// 背景色
---theme-background
---theme-background-secondary
---theme-background-tertiary
-
-// 文本色
---theme-headline
---theme-paragraph
---theme-paragraph-subtle
-
-// 交互元素
---theme-button
---theme-button-text
---theme-button-hover
---theme-button-active
-
-// 边框
---theme-border
---theme-border-subtle
-
-// 状态色
---theme-success
---theme-warning
---theme-error
---theme-info
-
-// 卡片
---theme-card-background
---theme-card-border
---theme-card-hover
-
-// 阴影
---theme-shadow
-```
-
-#### 关键样式特性
-1. **圆角设计**
-   - 卡片:12px
-   - 按钮:8px
-   - 输入框:8px
-   - 徽章:12px
-
-2. **间距系统**
-   - 小间距:8px
-   - 中间距:12px
-   - 大间距:16px
-   - 舒适间距:24px
-
-3. **过渡动画**
-   - 按钮悬停:0.2s ease
-   - 表格行悬停:0.15s ease
-   - 进度条:0.3s ease
-   - 对话框:0.3s ease
-
-4. **响应式设计**
-   - 搜索框最大宽度:400px
-   - 对话框最大宽度:480px-600px
-   - 表格自适应滚动
-
-## 用户体验改进
-
-### 1. 搜索和筛选
-- **项目页面**:按名称和描述搜索
-- **任务页面**:按状态筛选 + 按名称/项目/负责人搜索
-- 实时过滤,无需提交
-- 清晰的筛选状态指示
-
-### 2. 空状态处理
-- 友好的图标和文案
-- 引导用户进行下一步操作
-- 区分"无数据"和"无匹配结果"
-
-### 3. 加载状态
-- 旋转加载动画
-- 加载文案提示
-- 不阻塞页面布局
-
-### 4. 错误处理
-- 醒目的错误提示
-- 带图标的错误消息
-- 不影响其他功能使用
-
-### 5. 操作反馈
-- 按钮悬停效果
-- 表格行悬停高亮
-- 删除确认对话框
-- 提交中状态显示
-
-## 主题适配
-
-### 白色主题(Light)
-- 背景:纯白色
-- 主色调:蓝色(#2563eb)
-- 清晰的边框和阴影
-- 高对比度文本
-
-### 深色主题(Dark)
-- 背景:深蓝灰色(#0f172a)
-- 主色调:亮蓝色(#3b82f6)
-- 柔和的边框
-- 舒适的文本对比度
-
-### 护眼主题(Eye-Care)
-- 背景:暖米色(#f9f4ef)
-- 主色调:棕色(#8c7851)
-- 温暖的色调
-- 适合长时间使用
-
-## 无障碍性
-
-1. **语义化 HTML**
-   - 使用 `<table>` 标签
-   - 正确的标题层级
-   - 按钮带 `title` 属性
-
-2. **键盘导航**
-   - 所有交互元素可聚焦
-   - Tab 键顺序合理
-
-3. **颜色对比度**
-   - 符合 WCAG 2.1 AA 标准
-   - 状态不仅依赖颜色
-
-4. **视觉反馈**
-   - 悬停状态
-   - 焦点状态
-   - 活跃状态
-
-## 性能优化
-
-1. **CSS 优化**
-   - 使用 CSS 变量减少重复
-   - 硬件加速的动画(transform)
-   - 避免昂贵的 CSS 属性
-
-2. **渲染优化**
-   - 条件渲染对话框
-   - 虚拟化长列表(未来可添加)
-   - 防抖搜索(未来可添加)
-
-3. **状态管理**
-   - 使用 Jotai 原子状态
-   - 避免不必要的重渲染
-   - 合理的状态分离
-
-## 未来改进方向
-
-1. **功能增强**
-   - 批量操作(多选)
-   - 排序功能
-   - 分页或虚拟滚动
-   - 导出数据
-
-2. **交互优化**
-   - 拖拽排序
-   - 快捷键支持
-   - 右键菜单
-   - 撤销/重做
-
-3. **视觉增强**
-   - 更多动画效果
-   - 数据可视化
-   - 自定义主题
-   - 暗色模式优化
-
-4. **性能提升**
-   - 虚拟滚动
-   - 懒加载
-   - 缓存策略
-   - 离线支持
-
-## 文件清单
-
-### 修改的文件
-- `web/apps/lq_label/src/views/project-list-view/project-list-view.tsx`
-- `web/apps/lq_label/src/views/task-list-view/task-list-view.tsx`
-
-### 新增的文件
-- `web/apps/lq_label/src/views/project-list-view/project-list-view.module.scss`
-- `web/apps/lq_label/src/views/task-list-view/task-list-view.module.scss`
-- `web/apps/lq_label/PROJECT_TASK_REDESIGN_SUMMARY.md`(本文件)
-
-## 总结
-
-本次重新设计完全遵循了用户的要求:
-- ✅ 现代化、标准化的设计
-- ✅ 无 emoji、无渐变色、无紫色
-- ✅ 后台管理风格的布局
-- ✅ 三种主题模式支持
-- ✅ 圆角和艺术化的文本放置
-- ✅ 高效的标注效率和信息获取
-- ✅ 使用 Lucide React 图标
-- ✅ 现代化的表格样式和筛选方法
-
-页面现在更加专业、清晰、易用,完全符合现代标注平台的设计标准。

+ 0 - 77
web/apps/lq_label/THEME_FIX_QUICK_SUMMARY.md

@@ -1,77 +0,0 @@
-# 主题颜色控制修复 - 快速总结
-
-## 修复的问题
-
-1. ✅ **标注页面和编辑器测试页面的 Header Bar** - 现在完全受主题控制
-2. ✅ **项目列表页面的徽章样式** - 在深色模式下有更好的对比度
-3. ✅ **SCSS 语法错误** - 修复了重复内容和缺少闭合括号的问题
-
-## 修改的文件
-
-### 样式文件
-- `web/apps/lq_label/src/views/annotation-view/annotation-view.module.scss` - 添加主题感知的 header 样式
-- `web/apps/lq_label/src/views/editor-test/editor-test.module.scss` - 添加主题感知的 header 样式
-- `web/apps/lq_label/src/views/project-list-view/project-list-view.module.scss` - 优化 badgeInactive 样式
-
-### 组件文件
-- `web/apps/lq_label/src/views/annotation-view/annotation-view.tsx` - 使用 CSS 模块类替代 Tailwind 类
-- `web/apps/lq_label/src/views/editor-test/editor-test.tsx` - 使用 CSS 模块类替代 Tailwind 类
-
-## 关键改进
-
-### 1. Header Bar 主题控制
-**之前**:使用 Tailwind 类
-```tsx
-<div className="flex items-center justify-between p-comfortable border-b border-neutral-border bg-primary-background">
-```
-
-**现在**:使用 CSS 模块和主题变量
-```tsx
-<div className={styles.header}>
-```
-
-```scss
-.header {
-  display: flex;
-  align-items: center;
-  justify-content: space-between;
-  padding: 16px 24px;
-  border-bottom: 1px solid var(--theme-border);
-  background: var(--theme-background);
-}
-```
-
-### 2. 徽章样式优化
-**之前**:对比度不够
-```scss
-.badgeInactive {
-  background: var(--theme-background-secondary);
-  color: var(--theme-paragraph-subtle);
-}
-```
-
-**现在**:更好的对比度
-```scss
-.badgeInactive {
-  background: var(--theme-background-tertiary);
-  color: var(--theme-paragraph);
-  border: 1px solid var(--theme-border);
-}
-```
-
-## 测试建议
-
-1. 切换到深色模式,检查标注页面的 header bar 是否正确显示
-2. 切换到护眼模式,检查所有文本是否清晰可读
-3. 在项目列表中查看 0 任务的徽章,确认对比度足够
-
-## 主题变量参考
-
-- `--theme-background` - 主背景色
-- `--theme-border` - 边框颜色
-- `--theme-headline` - 标题文本色
-- `--theme-paragraph` - 正文文本色
-- `--theme-paragraph-subtle` - 次要文本色
-- `--theme-background-tertiary` - 第三级背景色
-
-所有修改都遵循主题系统,确保在三种主题模式(白色、深色、护眼)下都有良好的视觉效果。

+ 0 - 264
web/apps/lq_label/THEME_FIX_SUMMARY.md

@@ -1,264 +0,0 @@
-# 主题颜色控制修复总结
-
-## 问题描述
-
-用户报告了以下主题相关的问题:
-
-1. **标注页面 Header Bar 颜色不受主题控制**
-   - 使用了 Tailwind 类 `bg-primary-background`、`border-neutral-border` 等
-   - 这些类不会随主题切换而改变
-
-2. **项目列表页面标题在深色模式下显示不佳**
-   - 标题颜色在深色模式下为黑色,对比度不够
-
-3. **任务数量徽章在深色模式下显示不佳**
-   - 当任务数量为 0 时,`badgeInactive` 样式在深色模式下不够明显
-
-## 修复方案
-
-### 1. 标注页面(AnnotationView)
-
-#### 问题
-Header bar 使用了 Tailwind 类,不受主题系统控制:
-```tsx
-<div className="flex items-center justify-between p-comfortable border-b border-neutral-border bg-primary-background">
-```
-
-#### 解决方案
-在 `annotation-view.module.scss` 中添加主题感知的样式类:
-
-```scss
-.header {
-  display: flex;
-  align-items: center;
-  justify-content: space-between;
-  padding: 16px 24px;
-  border-bottom: 1px solid var(--theme-border);
-  background: var(--theme-background);
-}
-
-.headerLeft {
-  display: flex;
-  align-items: center;
-  gap: 24px;
-}
-
-.headerRight {
-  display: flex;
-  align-items: center;
-  gap: 12px;
-}
-
-.taskInfo {
-  display: flex;
-  flex-direction: column;
-  gap: 4px;
-}
-
-.taskName {
-  font-size: 16px;
-  font-weight: 600;
-  color: var(--theme-headline);
-  margin: 0;
-}
-
-.taskMeta {
-  font-size: 13px;
-  color: var(--theme-paragraph-subtle);
-  margin: 0;
-}
-```
-
-在 TSX 文件中替换为 CSS 模块类:
-```tsx
-<div className={styles.header}>
-  <div className={styles.headerLeft}>
-    {/* ... */}
-    <div className={styles.taskInfo}>
-      <h1 className={styles.taskName}>{currentTask.name}</h1>
-      <p className={styles.taskMeta}>项目: {currentProject.name} | 进度: {currentTask.progress}%</p>
-    </div>
-  </div>
-  <div className={styles.headerRight}>
-    {/* ... */}
-  </div>
-</div>
-```
-
-### 2. 编辑器测试页面(EditorTest)
-
-#### 问题
-与标注页面相同,使用了 Tailwind 类。
-
-#### 解决方案
-添加相同的样式类到 `editor-test.module.scss`:
-
-```scss
-.header {
-  display: flex;
-  align-items: center;
-  justify-content: space-between;
-  padding: 16px 24px;
-  border-bottom: 1px solid var(--theme-border);
-  background: var(--theme-background);
-}
-
-.headerLeft {
-  display: flex;
-  align-items: center;
-  gap: 24px;
-}
-
-.headerRight {
-  display: flex;
-  align-items: center;
-  gap: 12px;
-}
-
-.headerInfo {
-  display: flex;
-  flex-direction: column;
-  gap: 4px;
-}
-
-.headerTitle {
-  font-size: 16px;
-  font-weight: 600;
-  color: var(--theme-headline);
-  margin: 0;
-}
-
-.headerSubtitle {
-  font-size: 13px;
-  color: var(--theme-paragraph-subtle);
-  margin: 0;
-}
-```
-
-### 3. 项目列表页面徽章样式优化
-
-#### 问题
-`badgeInactive` 在深色模式下对比度不够:
-```scss
-.badgeInactive {
-  background: var(--theme-background-secondary);
-  color: var(--theme-paragraph-subtle);
-}
-```
-
-#### 解决方案
-使用更深的背景色和更明显的文本色,并添加边框:
-
-```scss
-.badgeInactive {
-  background: var(--theme-background-tertiary);
-  color: var(--theme-paragraph);
-  border: 1px solid var(--theme-border);
-}
-```
-
-这样在三种主题下都有更好的对比度:
-- **白色主题**:浅灰背景 + 深灰文本 + 边框
-- **深色主题**:深灰背景 + 浅灰文本 + 边框
-- **护眼主题**:暖色背景 + 棕色文本 + 边框
-
-### 4. 修复 CSS 警告
-
-#### 问题
-SCSS 警告:需要同时定义标准属性 `line-clamp`
-
-#### 解决方案
-```scss
-.projectDescription {
-  font-size: 13px;
-  color: var(--theme-paragraph-subtle);
-  display: -webkit-box;
-  -webkit-line-clamp: 1;
-  -webkit-box-orient: vertical;
-  line-clamp: 1;  // 添加标准属性
-  overflow: hidden;
-}
-```
-
-## 主题变量使用
-
-所有修复都使用了主题系统的 CSS 变量:
-
-### 背景色
-- `--theme-background` - 主背景色
-- `--theme-background-secondary` - 次要背景色
-- `--theme-background-tertiary` - 第三级背景色
-
-### 文本色
-- `--theme-headline` - 标题文本色
-- `--theme-paragraph` - 正文文本色
-- `--theme-paragraph-subtle` - 次要文本色
-
-### 边框
-- `--theme-border` - 边框颜色
-
-### 按钮
-- `--theme-button` - 按钮背景色
-- `--theme-button-text` - 按钮文本色
-
-## 三种主题下的效果
-
-### 白色主题(Light)
-- Header 背景:纯白色 (#ffffff)
-- 标题文本:深黑色 (#1a1a1a)
-- 边框:浅灰色 (#e5e7eb)
-- 徽章背景:浅灰色 (#f1f3f5)
-- 徽章文本:中灰色 (#4a4a4a)
-
-### 深色主题(Dark)
-- Header 背景:深蓝灰 (#0f172a)
-- 标题文本:浅白色 (#f1f5f9)
-- 边框:深灰色 (#334155)
-- 徽章背景:中灰色 (#334155)
-- 徽章文本:浅灰色 (#cbd5e1)
-
-### 护眼主题(Eye-Care)
-- Header 背景:暖米色 (#f9f4ef)
-- 标题文本:深蓝色 (#020826)
-- 边框:暖灰色 (#d4c4b0)
-- 徽章背景:暖灰色 (#e5d4c1)
-- 徽章文本:棕色 (#716040)
-
-## 修改的文件
-
-### 新增样式
-- `web/apps/lq_label/src/views/annotation-view/annotation-view.module.scss` - 添加 header 相关样式
-- `web/apps/lq_label/src/views/editor-test/editor-test.module.scss` - 添加 header 相关样式
-
-### 修改的文件
-- `web/apps/lq_label/src/views/annotation-view/annotation-view.tsx` - 替换 Tailwind 类为 CSS 模块
-- `web/apps/lq_label/src/views/editor-test/editor-test.tsx` - 替换 Tailwind 类为 CSS 模块
-- `web/apps/lq_label/src/views/project-list-view/project-list-view.module.scss` - 优化 badgeInactive 样式
-
-## 测试建议
-
-1. **切换主题测试**
-   - 在标注页面切换三种主题
-   - 检查 header bar 的背景色和文本色是否正确变化
-   - 检查边框颜色是否跟随主题
-
-2. **项目列表测试**
-   - 在深色模式下查看项目列表
-   - 检查标题文本是否清晰可读
-   - 检查 0 任务的徽章是否有足够对比度
-
-3. **任务列表测试**
-   - 在三种主题下查看任务列表
-   - 检查所有文本和徽章的可读性
-
-## 总结
-
-本次修复确保了所有页面元素都正确使用主题系统的 CSS 变量,实现了:
-
-✅ 标注页面 header bar 完全受主题控制
-✅ 编辑器测试页面 header bar 完全受主题控制
-✅ 项目列表页面在所有主题下都有良好的对比度
-✅ 任务数量徽章在深色模式下更加明显
-✅ 修复了 CSS 警告
-
-所有修改都遵循了主题系统的设计原则,确保在三种主题模式下都有良好的视觉效果和可读性。

+ 0 - 0
web/apps/lq_label/THEME_QUICK_START.md


+ 0 - 289
web/apps/lq_label/THEME_REDESIGN_SUMMARY.md

@@ -1,289 +0,0 @@
-# 前端界面重新设计总结
-
-## 设计目标
-
-系统性地重新设计前端界面,打造现代化、专业的标注平台,遵循以下原则:
-
-1. **现代简约风格** - 干净整洁,无 emoji,专业感
-2. **三种主题模式** - 白色、深色、护眼模式
-3. **后台管理风格** - 侧边栏 + 顶部导航 + 内容区
-4. **高效信息架构** - 圆角设计,清晰的视觉层次
-5. **标注效率优先** - 减少干扰,突出核心功能
-
-## 实现内容
-
-### 1. 主题系统
-
-#### 创建的文件
-
-**`src/theme/theme-config.ts`**
-- 定义三种主题模式的颜色配置
-- 白色模式:干净专业,使用蓝色作为主色调
-- 深色模式:现代舒适,使用深蓝灰色背景
-- 护眼模式:温暖舒适,使用指定的护眼色彩方案
-  - background: #f9f4ef
-  - headline: #020826
-  - paragraph: #716040
-  - button: #8c7851
-  - buttontext: #fffffe
-
-**`src/atoms/theme-atoms.ts`**
-- 使用 Jotai 管理主题状态
-- 支持 localStorage 持久化
-- 提供派生 atoms 用于主题判断
-
-**`src/components/theme-provider/`**
-- ThemeProvider 组件
-- 自动应用 CSS 变量到文档根元素
-- 监听主题变化并实时更新
-
-**`src/components/theme-switcher/`**
-- 主题切换组件
-- 三个按钮:白色、深色、护眼
-- 带图标和标签,响应式设计
-
-### 2. 布局系统
-
-#### 重新设计的组件
-
-**`src/components/layout/layout.tsx` + `layout.module.scss`**
-- 现代后台管理布局
-- 侧边栏 + 顶部栏 + 内容区
-- 响应式设计,移动端友好
-
-**`src/components/layout/top-bar.tsx` + `top-bar.module.scss`**
-- 新增顶部导航栏
-- 显示当前页面标题
-- 集成主题切换器
-- 固定高度 64px(移动端 56px)
-
-**`src/components/layout/sidebar.tsx` + `sidebar.module.scss`**
-- 完全重新设计的侧边栏
-- 移除所有 emoji
-- 添加自定义 Logo(SVG 图标)
-- 现代化菜单项设计
-- 活动状态使用主题色高亮
-- 移动端滑动抽屉 + 遮罩层
-
-### 3. 首页重新设计
-
-**`src/views/home-view.tsx` + `home-view.module.scss`**
-- 移除所有 emoji
-- Hero Section:大标题 + 描述 + 双按钮
-- Features Grid:三个功能卡片,悬停效果
-- Stats Section:三个统计项
-- 使用 CSS 模块化样式
-- 完全响应式设计
-
-### 4. 全局样式
-
-**`src/styles/global.scss`**
-- 基础重置样式
-- 主题变量应用
-- 滚动条样式
-- 焦点样式
-- 选择样式
-- 工具类
-- 卡片样式
-- 按钮基础样式
-- 状态颜色
-- 加载动画
-
-## 主题色彩方案
-
-### 白色模式(Light)
-- 背景:#ffffff, #f8f9fa, #f1f3f5
-- 文字:#1a1a1a, #4a4a4a, #6b7280
-- 按钮:#2563eb(蓝色)
-- 边框:#e5e7eb
-
-### 深色模式(Dark)
-- 背景:#0f172a, #1e293b, #334155
-- 文字:#f1f5f9, #cbd5e1, #94a3b8
-- 按钮:#3b82f6(亮蓝色)
-- 边框:#334155
-
-### 护眼模式(Eye-Care)
-- 背景:#f9f4ef, #eaddcf, #e5d4c1
-- 文字:#020826, #716040, #8c7851
-- 按钮:#8c7851(棕色)
-- 边框:#d4c4b0
-
-## 设计特点
-
-### 1. 无渐变色
-- 所有颜色使用纯色
-- 避免使用渐变效果
-- 保持界面简洁专业
-
-### 2. 无紫色
-- 主色调使用蓝色系(白色/深色模式)
-- 护眼模式使用棕色系
-- 避免使用紫色或类似颜色
-
-### 3. 圆角设计
-- 卡片:12px 圆角
-- 按钮:8-10px 圆角
-- 输入框:8px 圆角
-- Logo 图标:8px 圆角
-
-### 4. 文本层次
-- 标题:大字号,粗体,headline 颜色
-- 正文:中等字号,paragraph 颜色
-- 辅助文本:小字号,paragraph-subtle 颜色
-
-### 5. 交互反馈
-- 悬停:背景色变化 + 边框高亮
-- 活动:主题色背景 + 白色文字
-- 点击:轻微缩放或位移
-- 过渡:0.2-0.3s ease 动画
-
-### 6. 响应式设计
-- 桌面端:侧边栏固定,内容区自适应
-- 平板端:侧边栏可收起
-- 移动端:汉堡菜单 + 滑动抽屉
-
-## 技术实现
-
-### CSS 变量系统
-所有颜色通过 CSS 变量定义:
-```css
---theme-background
---theme-headline
---theme-paragraph
---theme-button
---theme-border
-...
-```
-
-### 主题切换流程
-1. 用户点击主题按钮
-2. 更新 Jotai atom 状态
-3. ThemeProvider 监听变化
-4. 应用新的 CSS 变量
-5. 所有组件自动更新
-
-### 样式组织
-- 全局样式:`global.scss`
-- 组件样式:`component.module.scss`
-- 主题配置:`theme-config.ts`
-- 避免内联样式
-
-## LabelStudio 编辑器
-
-### 主题适配策略
-- **暂不修改编辑器内部样式**
-- 编辑器保持默认样式
-- 只修改编辑器容器的背景和边框
-- 避免样式冲突和复杂度
-
-### 未来优化方向
-如果需要编辑器主题适配:
-1. 研究 LabelStudio 的主题系统
-2. 创建自定义 CSS 覆盖
-3. 测试所有标注类型
-4. 确保不影响功能
-
-## 使用方法
-
-### 切换主题
-1. 点击顶部栏右侧的主题切换器
-2. 选择白色、深色或护眼模式
-3. 主题自动保存到 localStorage
-4. 下次访问自动应用上次选择的主题
-
-### 自定义主题
-修改 `src/theme/theme-config.ts` 中的颜色值:
-```typescript
-export const themes: Record<ThemeMode, ThemeColors> = {
-  light: {
-    background: '#ffffff',
-    // ... 其他颜色
-  },
-  // ...
-};
-```
-
-### 添加新主题
-1. 在 `ThemeMode` 类型中添加新模式
-2. 在 `themes` 对象中添加颜色配置
-3. 在 `ThemeSwitcher` 中添加按钮
-4. 更新 CSS 变量映射
-
-## 文件清单
-
-### 新增文件
-- `src/theme/theme-config.ts` - 主题配置
-- `src/atoms/theme-atoms.ts` - 主题状态管理
-- `src/components/theme-provider/` - 主题提供者
-- `src/components/theme-switcher/` - 主题切换器
-- `src/components/layout/top-bar.tsx` - 顶部栏
-- `src/components/layout/top-bar.module.scss` - 顶部栏样式
-- `src/components/layout/layout.module.scss` - 布局样式
-- `src/components/layout/sidebar.module.scss` - 侧边栏样式
-- `src/views/home-view.module.scss` - 首页样式
-- `src/styles/global.scss` - 全局样式
-
-### 修改文件
-- `src/app/app.tsx` - 集成 ThemeProvider
-- `src/main.tsx` - 导入全局样式
-- `src/components/layout/layout.tsx` - 重新设计布局
-- `src/components/layout/sidebar.tsx` - 重新设计侧边栏
-- `src/views/home-view.tsx` - 重新设计首页
-
-## 下一步建议
-
-### 1. 其他页面重新设计
-- ProjectsView - 项目列表页
-- ProjectDetailView - 项目详情页
-- TasksView - 任务列表页
-- AnnotationView - 标注页面
-- 使用相同的设计语言和组件
-
-### 2. 组件库扩展
-- 创建统一的 Button 组件
-- 创建统一的 Card 组件
-- 创建统一的 Input 组件
-- 创建统一的 Table 组件
-
-### 3. 动画优化
-- 添加页面切换动画
-- 添加列表项加载动画
-- 添加骨架屏
-- 优化过渡效果
-
-### 4. 无障碍性
-- 添加 ARIA 标签
-- 键盘导航支持
-- 屏幕阅读器优化
-- 对比度检查
-
-### 5. 性能优化
-- 懒加载组件
-- 图片优化
-- 代码分割
-- 缓存策略
-
-## 总结
-
-成功实现了现代化、专业的前端界面重新设计:
-
-✅ 三种主题模式(白色、深色、护眼)
-✅ 后台管理风格布局
-✅ 移除所有 emoji
-✅ 圆角设计
-✅ 清晰的视觉层次
-✅ 响应式设计
-✅ 主题持久化
-✅ 平滑过渡动画
-✅ 专业的色彩方案
-✅ 模块化样式组织
-
-用户现在可以:
-1. 在三种主题之间自由切换
-2. 享受现代化的界面设计
-3. 在不同设备上获得良好体验
-4. 长时间使用护眼模式减少疲劳
-5. 获得高效的标注工作流程
-
-这为标注平台提供了完整的现代化界面基础!

+ 0 - 2
web/apps/lq_label/src/app/app.tsx

@@ -5,7 +5,6 @@ import {
   HomeView,
   NotFoundView,
   ProjectsView,
-  ProjectDetailView,
   ProjectEditView,
   TasksView,
   AnnotationsView,
@@ -38,7 +37,6 @@ export function App() {
 
             {/* Projects Routes */}
             <Route path="/projects" element={<ProjectsView />} />
-            <Route path="/projects/:id" element={<ProjectDetailView />} />
             <Route path="/projects/:id/edit" element={<ProjectEditView />} />
 
             {/* Tasks Routes */}

+ 4 - 0
web/apps/lq_label/src/components/index.ts

@@ -12,3 +12,7 @@ export * from './layout';
 export * from './project-form';
 export * from './task-form';
 
+// Modal components
+export * from './project-detail-modal';
+export * from './project-edit-modal';
+

+ 1 - 0
web/apps/lq_label/src/components/layout/top-bar.module.scss

@@ -34,4 +34,5 @@
   display: flex;
   align-items: center;
   gap: 16px;
+  height: 100%;
 }

+ 2 - 0
web/apps/lq_label/src/components/project-detail-modal/index.ts

@@ -0,0 +1,2 @@
+export { ProjectDetailModal } from './project-detail-modal';
+export type { ProjectDetailModalProps } from './project-detail-modal';

+ 450 - 0
web/apps/lq_label/src/components/project-detail-modal/project-detail-modal.module.scss

@@ -0,0 +1,450 @@
+// ProjectDetailModal Styles
+.overlay {
+  position: fixed;
+  top: 0;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  background: rgba(0, 0, 0, 0.5);
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  z-index: 1000;
+  padding: var(--spacing-base);
+}
+
+.modal {
+  background: var(--theme-background);
+  border-radius: var(--corner-radius-medium);
+  box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
+  max-width: 900px;
+  width: 100%;
+  max-height: 90vh;
+  display: flex;
+  flex-direction: column;
+  overflow: hidden;
+}
+
+.header {
+  padding: var(--spacing-base);
+  border-bottom: 1px solid var(--theme-border);
+}
+
+.headerContent {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+}
+
+.title {
+  font-size: var(--font-size-headline-small);
+  font-weight: 600;
+  color: var(--theme-headline);
+  margin: 0;
+}
+
+.closeButton {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  width: 32px;
+  height: 32px;
+  border-radius: var(--corner-radius-small);
+  border: none;
+  background: transparent;
+  color: var(--theme-paragraph-subtle);
+  cursor: pointer;
+  transition: all 0.2s;
+
+  &:hover {
+    background: var(--theme-background-secondary);
+    color: var(--theme-headline);
+  }
+}
+
+.content {
+  flex: 1;
+  overflow-y: auto;
+  padding: var(--spacing-base);
+}
+
+.loadingState,
+.errorState {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  padding: var(--spacing-widest);
+  gap: var(--spacing-base);
+}
+
+.spinner {
+  width: 40px;
+  height: 40px;
+  border: 3px solid var(--theme-background-secondary);
+  border-top-color: var(--theme-button);
+  border-radius: 50%;
+  animation: spin 0.8s linear infinite;
+}
+
+@keyframes spin {
+  to {
+    transform: rotate(360deg);
+  }
+}
+
+.errorState {
+  color: var(--theme-error);
+}
+
+.section {
+  margin-bottom: var(--spacing-widest);
+
+  &:last-child {
+    margin-bottom: 0;
+  }
+}
+
+.sectionHeader {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  margin-bottom: var(--spacing-base);
+}
+
+.sectionTitle {
+  font-size: var(--font-size-title-large);
+  font-weight: 600;
+  color: var(--theme-headline);
+  margin: 0;
+}
+
+.description {
+  font-size: var(--font-size-body-medium);
+  color: var(--theme-paragraph);
+  margin-bottom: var(--spacing-base);
+  line-height: 1.6;
+}
+
+.editButton,
+.createButton {
+  display: flex;
+  align-items: center;
+  gap: var(--spacing-tighter);
+  padding: var(--spacing-tight) var(--spacing-base);
+  border-radius: var(--corner-radius-small);
+  border: 1px solid var(--theme-border);
+  background: var(--theme-background);
+  color: var(--theme-headline);
+  font-size: var(--font-size-body-small);
+  font-weight: 500;
+  cursor: pointer;
+  transition: all 0.2s;
+
+  &:hover {
+    background: var(--theme-background-secondary);
+    border-color: var(--theme-button);
+  }
+}
+
+.createButton {
+  background: var(--theme-button);
+  color: var(--theme-button-text);
+  border-color: var(--theme-button);
+
+  &:hover {
+    background: var(--theme-button-hover);
+  }
+}
+
+.infoGrid {
+  display: grid;
+  grid-template-columns: repeat(2, 1fr);
+  gap: var(--spacing-base);
+  margin-bottom: var(--spacing-base);
+}
+
+.infoItem {
+  display: flex;
+  flex-direction: column;
+  gap: var(--spacing-tighter);
+}
+
+.infoLabel {
+  font-size: var(--font-size-body-small);
+  color: var(--theme-paragraph-subtle);
+  font-weight: 500;
+}
+
+.infoValue {
+  font-size: var(--font-size-body-medium);
+  color: var(--theme-headline);
+}
+
+.configSection {
+  display: flex;
+  flex-direction: column;
+  gap: var(--spacing-tighter);
+}
+
+.configCode {
+  font-family: var(--font-family-monospace);
+  font-size: var(--font-size-body-small);
+  color: var(--theme-headline);
+  background: var(--theme-background-secondary);
+  padding: var(--spacing-base);
+  border-radius: var(--corner-radius-small);
+  border: 1px solid var(--theme-border);
+  overflow-x: auto;
+  white-space: pre-wrap;
+  word-break: break-all;
+}
+
+.taskList {
+  display: flex;
+  flex-direction: column;
+  gap: var(--spacing-tight);
+}
+
+.taskItem {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  padding: var(--spacing-base);
+  border-radius: var(--corner-radius-small);
+  border: 1px solid var(--theme-border);
+  background: var(--theme-background);
+  transition: all 0.2s;
+
+  &:hover {
+    background: var(--theme-background-secondary);
+    border-color: var(--theme-button);
+  }
+}
+
+.taskInfo {
+  flex: 1;
+  display: flex;
+  flex-direction: column;
+  gap: var(--spacing-tighter);
+}
+
+.taskName {
+  font-size: var(--font-size-body-medium);
+  font-weight: 500;
+  color: var(--theme-headline);
+}
+
+.taskMeta {
+  display: flex;
+  align-items: center;
+  gap: var(--spacing-base);
+  flex-wrap: wrap;
+}
+
+.badge {
+  display: inline-flex;
+  align-items: center;
+  padding: 2px var(--spacing-tight);
+  border-radius: var(--corner-radius-smallest);
+  font-size: var(--font-size-body-small);
+  font-weight: 500;
+}
+
+.badgePending {
+  background: rgba(156, 163, 175, 0.1);
+  color: rgb(75, 85, 99);
+}
+
+.badgeInProgress {
+  background: rgba(59, 130, 246, 0.1);
+  color: rgb(37, 99, 235);
+}
+
+.badgeCompleted {
+  background: rgba(34, 197, 94, 0.1);
+  color: rgb(22, 163, 74);
+}
+
+.taskProgress {
+  font-size: var(--font-size-body-small);
+  color: var(--theme-paragraph-subtle);
+}
+
+.taskAssignee {
+  font-size: var(--font-size-body-small);
+  color: var(--theme-paragraph-subtle);
+}
+
+.taskActions {
+  display: flex;
+  align-items: center;
+  gap: var(--spacing-tight);
+}
+
+.actionButton {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  width: 32px;
+  height: 32px;
+  border-radius: var(--corner-radius-small);
+  border: 1px solid var(--theme-border);
+  background: var(--theme-background);
+  color: var(--theme-paragraph-subtle);
+  cursor: pointer;
+  transition: all 0.2s;
+
+  &:hover {
+    background: var(--theme-background-secondary);
+    color: var(--theme-headline);
+    border-color: var(--theme-button);
+  }
+}
+
+.actionButtonPrimary {
+  background: var(--theme-button);
+  color: var(--theme-button-text);
+  border-color: var(--theme-button);
+
+  &:hover {
+    background: var(--theme-button-hover);
+  }
+}
+
+.actionButtonDanger {
+  &:hover {
+    background: var(--theme-error);
+    color: white;
+    border-color: var(--theme-error);
+  }
+}
+
+.emptyState {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  padding: var(--spacing-widest);
+  gap: var(--spacing-base);
+  color: var(--theme-paragraph-subtle);
+}
+
+.emptyButton {
+  display: flex;
+  align-items: center;
+  gap: var(--spacing-tighter);
+  padding: var(--spacing-tight) var(--spacing-base);
+  border-radius: var(--corner-radius-small);
+  border: 1px solid var(--theme-border);
+  background: var(--theme-button);
+  color: var(--theme-button-text);
+  font-size: var(--font-size-body-small);
+  font-weight: 500;
+  cursor: pointer;
+  transition: all 0.2s;
+
+  &:hover {
+    background: var(--theme-button-hover);
+  }
+}
+
+// Dialog styles for nested dialogs
+.dialogOverlay {
+  position: fixed;
+  top: 0;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  background: rgba(0, 0, 0, 0.5);
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  z-index: 1100;
+  padding: var(--spacing-base);
+}
+
+.dialog {
+  background: var(--theme-background);
+  border-radius: var(--corner-radius-medium);
+  box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
+  max-width: 600px;
+  width: 100%;
+  max-height: 90vh;
+  overflow-y: auto;
+}
+
+.dialogSmall {
+  background: var(--theme-background);
+  border-radius: var(--corner-radius-medium);
+  box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
+  max-width: 450px;
+  width: 100%;
+}
+
+.dialogHeader {
+  padding: var(--spacing-base);
+  border-bottom: 1px solid var(--theme-border);
+
+  h2 {
+    font-size: var(--font-size-title-large);
+    font-weight: 600;
+    color: var(--theme-headline);
+    margin: 0 0 var(--spacing-tight) 0;
+  }
+
+  p {
+    font-size: var(--font-size-body-medium);
+    color: var(--theme-paragraph);
+    margin: 0;
+    line-height: 1.6;
+  }
+}
+
+.dialogTitleDanger {
+  color: var(--theme-error) !important;
+}
+
+.dialogFooter {
+  display: flex;
+  align-items: center;
+  justify-content: flex-end;
+  gap: var(--spacing-base);
+  padding: var(--spacing-base);
+  border-top: 1px solid var(--theme-border);
+}
+
+.cancelButton,
+.deleteButton {
+  padding: var(--spacing-tight) var(--spacing-base);
+  border-radius: var(--corner-radius-small);
+  font-size: var(--font-size-body-medium);
+  font-weight: 500;
+  cursor: pointer;
+  transition: all 0.2s;
+  border: 1px solid var(--theme-border);
+
+  &:disabled {
+    opacity: 0.5;
+    cursor: not-allowed;
+  }
+}
+
+.cancelButton {
+  background: var(--theme-background);
+  color: var(--theme-headline);
+
+  &:hover:not(:disabled) {
+    background: var(--theme-background-secondary);
+  }
+}
+
+.deleteButton {
+  background: var(--theme-error);
+  color: white;
+  border-color: var(--theme-error);
+
+  &:hover:not(:disabled) {
+    opacity: 0.9;
+  }
+}

+ 361 - 0
web/apps/lq_label/src/components/project-detail-modal/project-detail-modal.tsx

@@ -0,0 +1,361 @@
+/**
+ * ProjectDetailModal Component
+ * 
+ * Modal dialog for displaying project details and associated tasks.
+ * Requirements: 1.5, 1.6
+ */
+import React, { useEffect, useState } from 'react';
+import { useNavigate } from 'react-router-dom';
+import { X, Edit, Plus, Play, Eye, Trash2 } from 'lucide-react';
+import { getProject, getProjectTasks, createTask, deleteTask } from '../../services/api';
+import { type Project } from '../../atoms/project-atoms';
+import { type Task } from '../../atoms/task-atoms';
+import { TaskForm, type TaskFormData } from '../task-form';
+import { ProjectEditModal } from '../project-edit-modal';
+import styles from './project-detail-modal.module.scss';
+
+export interface ProjectDetailModalProps {
+  projectId: string;
+  isOpen: boolean;
+  onClose: () => void;
+  onProjectUpdated?: () => void;
+}
+
+export const ProjectDetailModal: React.FC<ProjectDetailModalProps> = ({
+  projectId,
+  isOpen,
+  onClose,
+  onProjectUpdated,
+}) => {
+  const navigate = useNavigate();
+  const [project, setProject] = useState<Project | null>(null);
+  const [tasks, setTasks] = useState<Task[]>([]);
+  const [loading, setLoading] = useState(false);
+  const [error, setError] = useState<string | null>(null);
+  
+  // Dialog state
+  const [isCreateTaskDialogOpen, setIsCreateTaskDialogOpen] = useState(false);
+  const [isDeleteTaskDialogOpen, setIsDeleteTaskDialogOpen] = useState(false);
+  const [isEditProjectModalOpen, setIsEditProjectModalOpen] = useState(false);
+  const [taskToDelete, setTaskToDelete] = useState<Task | null>(null);
+  const [isSubmitting, setIsSubmitting] = useState(false);
+
+  // Load project details and tasks
+  useEffect(() => {
+    if (!isOpen || !projectId) return;
+
+    const loadProjectData = async () => {
+      try {
+        setLoading(true);
+        setError(null);
+        
+        const [projectData, tasksData] = await Promise.all([
+          getProject(projectId),
+          getProjectTasks(projectId),
+        ]);
+        
+        setProject(projectData);
+        setTasks(tasksData);
+      } catch (err: any) {
+        setError(err.message || '加载项目详情失败');
+      } finally {
+        setLoading(false);
+      }
+    };
+
+    loadProjectData();
+  }, [isOpen, projectId]);
+
+  const handleEditProject = () => {
+    setIsEditProjectModalOpen(true);
+  };
+
+  const handleProjectUpdated = (updatedProject: Project) => {
+    setProject(updatedProject);
+    onProjectUpdated?.();
+  };
+
+  const handleCreateTask = async (formData: TaskFormData) => {
+    try {
+      setIsSubmitting(true);
+      
+      const newTask = await createTask({
+        project_id: formData.project_id,
+        name: formData.name,
+        data: formData.data,
+        assigned_to: formData.assigned_to || null,
+      });
+      
+      setTasks((prevTasks) => [...prevTasks, newTask]);
+      
+      if (project) {
+        setProject({
+          ...project,
+          task_count: project.task_count + 1,
+        });
+      }
+      
+      setIsCreateTaskDialogOpen(false);
+      onProjectUpdated?.();
+    } catch (err: any) {
+      throw new Error(err.message || '创建任务失败');
+    } finally {
+      setIsSubmitting(false);
+    }
+  };
+
+  const handleDeleteTask = async () => {
+    if (!taskToDelete) return;
+
+    try {
+      setIsSubmitting(true);
+      await deleteTask(taskToDelete.id);
+      
+      setTasks((prevTasks) => prevTasks.filter((t) => t.id !== taskToDelete.id));
+      
+      if (project) {
+        setProject({
+          ...project,
+          task_count: project.task_count - 1,
+        });
+      }
+      
+      setIsDeleteTaskDialogOpen(false);
+      setTaskToDelete(null);
+      onProjectUpdated?.();
+    } catch (err: any) {
+      setError(err.message || '删除任务失败');
+    } finally {
+      setIsSubmitting(false);
+    }
+  };
+
+  const handleStartAnnotation = (task: Task) => {
+    onClose();
+    navigate(`/tasks/${task.id}/annotate`);
+  };
+
+  const getStatusBadge = (status: Task['status']) => {
+    const statusMap = {
+      pending: { label: '待处理', className: styles.badgePending },
+      in_progress: { label: '进行中', className: styles.badgeInProgress },
+      completed: { label: '已完成', className: styles.badgeCompleted },
+    };
+    return statusMap[status];
+  };
+
+  if (!isOpen) return null;
+
+  return (
+    <>
+      <div className={styles.overlay} onClick={onClose}>
+        <div className={styles.modal} onClick={(e) => e.stopPropagation()}>
+          {/* Header */}
+          <div className={styles.header}>
+            <div className={styles.headerContent}>
+              <h2 className={styles.title}>项目详情</h2>
+              <button className={styles.closeButton} onClick={onClose}>
+                <X size={20} />
+              </button>
+            </div>
+          </div>
+
+          {/* Content */}
+          <div className={styles.content}>
+            {loading ? (
+              <div className={styles.loadingState}>
+                <div className={styles.spinner} />
+                <p>加载中...</p>
+              </div>
+            ) : error ? (
+              <div className={styles.errorState}>
+                <p>{error}</p>
+              </div>
+            ) : project ? (
+              <>
+                {/* Project Info */}
+                <div className={styles.section}>
+                  <div className={styles.sectionHeader}>
+                    <h3 className={styles.sectionTitle}>{project.name}</h3>
+                    <button className={styles.editButton} onClick={handleEditProject}>
+                      <Edit size={16} />
+                      <span>编辑项目</span>
+                    </button>
+                  </div>
+                  
+                  {project.description && (
+                    <p className={styles.description}>{project.description}</p>
+                  )}
+
+                  <div className={styles.infoGrid}>
+                    <div className={styles.infoItem}>
+                      <span className={styles.infoLabel}>创建时间</span>
+                      <span className={styles.infoValue}>
+                        {new Date(project.created_at).toLocaleString('zh-CN', {
+                          year: 'numeric',
+                          month: '2-digit',
+                          day: '2-digit',
+                          hour: '2-digit',
+                          minute: '2-digit',
+                        })}
+                      </span>
+                    </div>
+                    <div className={styles.infoItem}>
+                      <span className={styles.infoLabel}>任务数量</span>
+                      <span className={styles.infoValue}>{project.task_count} 个任务</span>
+                    </div>
+                  </div>
+
+                  <div className={styles.configSection}>
+                    <span className={styles.infoLabel}>标注配置</span>
+                    <pre className={styles.configCode}>{project.config}</pre>
+                  </div>
+                </div>
+
+                {/* Tasks Section */}
+                <div className={styles.section}>
+                  <div className={styles.sectionHeader}>
+                    <h3 className={styles.sectionTitle}>关联任务</h3>
+                    <button
+                      className={styles.createButton}
+                      onClick={() => setIsCreateTaskDialogOpen(true)}
+                    >
+                      <Plus size={16} />
+                      <span>创建任务</span>
+                    </button>
+                  </div>
+
+                  {tasks.length === 0 ? (
+                    <div className={styles.emptyState}>
+                      <p>暂无任务</p>
+                      <button
+                        className={styles.emptyButton}
+                        onClick={() => setIsCreateTaskDialogOpen(true)}
+                      >
+                        <Plus size={16} />
+                        <span>创建任务</span>
+                      </button>
+                    </div>
+                  ) : (
+                    <div className={styles.taskList}>
+                      {tasks.map((task) => {
+                        const status = getStatusBadge(task.status);
+                        return (
+                          <div key={task.id} className={styles.taskItem}>
+                            <div className={styles.taskInfo}>
+                              <span className={styles.taskName}>{task.name}</span>
+                              <div className={styles.taskMeta}>
+                                <span className={`${styles.badge} ${status.className}`}>
+                                  {status.label}
+                                </span>
+                                <span className={styles.taskProgress}>{task.progress}%</span>
+                                {task.assigned_to && (
+                                  <span className={styles.taskAssignee}>
+                                    分配给: {task.assigned_to}
+                                  </span>
+                                )}
+                              </div>
+                            </div>
+                            <div className={styles.taskActions}>
+                              <button
+                                className={`${styles.actionButton} ${styles.actionButtonPrimary}`}
+                                onClick={() => handleStartAnnotation(task)}
+                                title="开始标注"
+                              >
+                                <Play size={16} />
+                              </button>
+                              <button
+                                className={`${styles.actionButton} ${styles.actionButtonDanger}`}
+                                onClick={() => {
+                                  setTaskToDelete(task);
+                                  setIsDeleteTaskDialogOpen(true);
+                                }}
+                                title="删除任务"
+                              >
+                                <Trash2 size={16} />
+                              </button>
+                            </div>
+                          </div>
+                        );
+                      })}
+                    </div>
+                  )}
+                </div>
+              </>
+            ) : null}
+          </div>
+        </div>
+      </div>
+
+      {/* Create Task Dialog */}
+      {isCreateTaskDialogOpen && (
+        <div className={styles.dialogOverlay} onClick={() => setIsCreateTaskDialogOpen(false)}>
+          <div className={styles.dialog} onClick={(e) => e.stopPropagation()}>
+            <div className={styles.dialogHeader}>
+              <h2>创建任务</h2>
+              <button
+                className={styles.closeButton}
+                onClick={() => setIsCreateTaskDialogOpen(false)}
+              >
+                <X size={20} />
+              </button>
+            </div>
+            <TaskForm
+              projectId={projectId}
+              onSubmit={handleCreateTask}
+              onCancel={() => setIsCreateTaskDialogOpen(false)}
+              submitLabel="创建任务"
+              isSubmitting={isSubmitting}
+            />
+          </div>
+        </div>
+      )}
+
+      {/* Delete Task Confirmation Dialog */}
+      {isDeleteTaskDialogOpen && (
+        <div className={styles.dialogOverlay} onClick={() => setIsDeleteTaskDialogOpen(false)}>
+          <div className={styles.dialogSmall} onClick={(e) => e.stopPropagation()}>
+            <div className={styles.dialogHeader}>
+              <h2 className={styles.dialogTitleDanger}>确认删除</h2>
+              <p>
+                确定要删除任务 <strong>"{taskToDelete?.name}"</strong> 吗?
+                <br />
+                <br />
+                此操作将同时删除该任务的所有标注结果,且无法撤销。
+              </p>
+            </div>
+            <div className={styles.dialogFooter}>
+              <button
+                className={styles.cancelButton}
+                onClick={() => {
+                  setIsDeleteTaskDialogOpen(false);
+                  setTaskToDelete(null);
+                }}
+                disabled={isSubmitting}
+              >
+                取消
+              </button>
+              <button
+                className={styles.deleteButton}
+                onClick={handleDeleteTask}
+                disabled={isSubmitting}
+              >
+                {isSubmitting ? '删除中...' : '确认删除'}
+              </button>
+            </div>
+          </div>
+        </div>
+      )}
+
+      {/* Project Edit Modal */}
+      {isEditProjectModalOpen && (
+        <ProjectEditModal
+          projectId={projectId}
+          isOpen={isEditProjectModalOpen}
+          onClose={() => setIsEditProjectModalOpen(false)}
+          onProjectUpdated={handleProjectUpdated}
+        />
+      )}
+    </>
+  );
+};

+ 2 - 0
web/apps/lq_label/src/components/project-edit-modal/index.ts

@@ -0,0 +1,2 @@
+export { ProjectEditModal } from './project-edit-modal';
+export type { ProjectEditModalProps } from './project-edit-modal';

+ 104 - 0
web/apps/lq_label/src/components/project-edit-modal/project-edit-modal.module.scss

@@ -0,0 +1,104 @@
+// ProjectEditModal Styles
+.overlay {
+  position: fixed;
+  top: 0;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  background: rgba(0, 0, 0, 0.5);
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  z-index: 1000;
+  padding: var(--spacing-base);
+}
+
+.modal {
+  background: var(--theme-background);
+  border-radius: var(--corner-radius-medium);
+  box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
+  max-width: 700px;
+  width: 100%;
+  max-height: 90vh;
+  display: flex;
+  flex-direction: column;
+  overflow: hidden;
+}
+
+.header {
+  padding: var(--spacing-base);
+  border-bottom: 1px solid var(--theme-border);
+}
+
+.headerContent {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+}
+
+.title {
+  font-size: var(--font-size-headline-small);
+  font-weight: 600;
+  color: var(--theme-headline);
+  margin: 0;
+}
+
+.subtitle {
+  font-size: var(--font-size-body-medium);
+  color: var(--theme-paragraph-subtle);
+  margin: var(--spacing-tighter) 0 0 0;
+}
+
+.closeButton {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  width: 32px;
+  height: 32px;
+  border-radius: var(--corner-radius-small);
+  border: none;
+  background: transparent;
+  color: var(--theme-paragraph-subtle);
+  cursor: pointer;
+  transition: all 0.2s;
+
+  &:hover {
+    background: var(--theme-background-secondary);
+    color: var(--theme-headline);
+  }
+}
+
+.content {
+  flex: 1;
+  overflow-y: auto;
+  padding: var(--spacing-base);
+}
+
+.loadingState,
+.errorState {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  padding: var(--spacing-widest);
+  gap: var(--spacing-base);
+}
+
+.spinner {
+  width: 40px;
+  height: 40px;
+  border: 3px solid var(--theme-background-secondary);
+  border-top-color: var(--theme-button);
+  border-radius: 50%;
+  animation: spin 0.8s linear infinite;
+}
+
+@keyframes spin {
+  to {
+    transform: rotate(360deg);
+  }
+}
+
+.errorState {
+  color: var(--theme-error);
+}

+ 118 - 0
web/apps/lq_label/src/components/project-edit-modal/project-edit-modal.tsx

@@ -0,0 +1,118 @@
+/**
+ * ProjectEditModal Component
+ * 
+ * Modal dialog for editing project details.
+ * Requirements: 1.6
+ */
+import React, { useEffect, useState } from 'react';
+import { X } from 'lucide-react';
+import { getProject, updateProject } from '../../services/api';
+import { type Project } from '../../atoms/project-atoms';
+import { ProjectForm, type ProjectFormData } from '../project-form';
+import styles from './project-edit-modal.module.scss';
+
+export interface ProjectEditModalProps {
+  projectId: string;
+  isOpen: boolean;
+  onClose: () => void;
+  onProjectUpdated?: (project: Project) => void;
+}
+
+export const ProjectEditModal: React.FC<ProjectEditModalProps> = ({
+  projectId,
+  isOpen,
+  onClose,
+  onProjectUpdated,
+}) => {
+  const [project, setProject] = useState<Project | null>(null);
+  const [loading, setLoading] = useState(false);
+  const [error, setError] = useState<string | null>(null);
+  const [isSubmitting, setIsSubmitting] = useState(false);
+
+  // Load project details
+  useEffect(() => {
+    if (!isOpen || !projectId) return;
+
+    const loadProject = async () => {
+      try {
+        setLoading(true);
+        setError(null);
+        const projectData = await getProject(projectId);
+        setProject(projectData);
+      } catch (err: any) {
+        setError(err.message || '加载项目失败');
+      } finally {
+        setLoading(false);
+      }
+    };
+
+    loadProject();
+  }, [isOpen, projectId]);
+
+  const handleSubmit = async (data: ProjectFormData) => {
+    try {
+      setIsSubmitting(true);
+      
+      const updatedProject = await updateProject(projectId, {
+        name: data.name,
+        description: data.description,
+        config: data.config,
+      });
+      
+      setProject(updatedProject);
+      onProjectUpdated?.(updatedProject);
+      onClose();
+    } catch (err: any) {
+      throw new Error(err.message || '更新项目失败');
+    } finally {
+      setIsSubmitting(false);
+    }
+  };
+
+  if (!isOpen) return null;
+
+  return (
+    <div className={styles.overlay} onClick={onClose}>
+      <div className={styles.modal} onClick={(e) => e.stopPropagation()}>
+        {/* Header */}
+        <div className={styles.header}>
+          <div className={styles.headerContent}>
+            <h2 className={styles.title}>编辑项目</h2>
+            <button className={styles.closeButton} onClick={onClose}>
+              <X size={20} />
+            </button>
+          </div>
+          {project && (
+            <p className={styles.subtitle}>{project.name}</p>
+          )}
+        </div>
+
+        {/* Content */}
+        <div className={styles.content}>
+          {loading ? (
+            <div className={styles.loadingState}>
+              <div className={styles.spinner} />
+              <p>加载中...</p>
+            </div>
+          ) : error ? (
+            <div className={styles.errorState}>
+              <p>{error}</p>
+            </div>
+          ) : project ? (
+            <ProjectForm
+              initialData={{
+                name: project.name,
+                description: project.description,
+                config: project.config,
+              }}
+              onSubmit={handleSubmit}
+              onCancel={onClose}
+              submitLabel="保存更改"
+              isSubmitting={isSubmitting}
+            />
+          ) : null}
+        </div>
+      </div>
+    </div>
+  );
+};

+ 94 - 3
web/apps/lq_label/src/components/project-form/project-form.module.scss

@@ -1,3 +1,94 @@
-// ProjectForm component styles
-// Currently using Tailwind CSS utility classes
-// Add custom styles here if needed
+// ProjectForm Styles
+.form {
+  display: flex;
+  flex-direction: column;
+  gap: var(--spacing-base);
+}
+
+.field {
+  display: flex;
+  flex-direction: column;
+  gap: var(--spacing-tight);
+}
+
+.label {
+  font-size: var(--font-size-body-medium);
+  font-weight: 600;
+  color: var(--theme-headline);
+
+  .required {
+    color: var(--theme-error);
+  }
+}
+
+.input,
+.textarea {
+  padding: var(--spacing-tight) var(--spacing-base);
+  border: 1px solid var(--theme-border);
+  border-radius: var(--corner-radius-small);
+  font-size: var(--font-size-body-medium);
+  background: var(--theme-background);
+  color: var(--theme-headline);
+  transition: all 0.2s;
+
+  &:focus {
+    outline: none;
+    border-color: var(--theme-button);
+    box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.1);
+  }
+
+  &::placeholder {
+    color: var(--theme-paragraph-subtle);
+  }
+
+  &:disabled {
+    opacity: 0.5;
+    cursor: not-allowed;
+    background: var(--theme-background-secondary);
+  }
+
+  &.error {
+    border-color: var(--theme-error);
+
+    &:focus {
+      box-shadow: 0 0 0 3px rgba(239, 68, 68, 0.1);
+    }
+  }
+}
+
+.textarea {
+  resize: none;
+  font-family: inherit;
+  line-height: 1.5;
+}
+
+.textareaCode {
+  font-family: var(--font-family-monospace);
+  font-size: var(--font-size-body-small);
+}
+
+.errorMessage {
+  font-size: var(--font-size-body-small);
+  color: var(--theme-error);
+}
+
+.hint {
+  font-size: var(--font-size-body-small);
+  color: var(--theme-paragraph-subtle);
+}
+
+.submitError {
+  background: rgba(239, 68, 68, 0.1);
+  color: var(--theme-error);
+  padding: var(--spacing-base);
+  border-radius: var(--corner-radius-small);
+  border: 1px solid var(--theme-error);
+}
+
+.actions {
+  display: flex;
+  align-items: center;
+  justify-content: flex-end;
+  gap: var(--spacing-tight);
+  padding-top: var(--spacing-base);
+}

+ 20 - 40
web/apps/lq_label/src/components/project-form/project-form.tsx

@@ -6,6 +6,7 @@
  */
 import React, { useState, useEffect } from 'react';
 import { Button } from '@humansignal/ui';
+import styles from './project-form.module.scss';
 
 export interface ProjectFormData {
   name: string;
@@ -154,14 +155,11 @@ export const ProjectForm: React.FC<ProjectFormProps> = ({
   };
 
   return (
-    <form onSubmit={handleSubmit} className="flex flex-col gap-comfortable">
+    <form onSubmit={handleSubmit} className={styles.form}>
       {/* Name field */}
-      <div className="flex flex-col gap-tight">
-        <label
-          htmlFor="project-name"
-          className="text-body-medium font-semibold text-primary-foreground"
-        >
-          项目名称 <span className="text-error-foreground">*</span>
+      <div className={styles.field}>
+        <label htmlFor="project-name" className={styles.label}>
+          项目名称 <span className={styles.required}>*</span>
         </label>
         <input
           id="project-name"
@@ -169,11 +167,7 @@ export const ProjectForm: React.FC<ProjectFormProps> = ({
           value={formData.name}
           onChange={(e) => handleFieldChange('name', e.target.value)}
           onBlur={() => handleFieldBlur('name')}
-          className={`px-comfortable py-tight border rounded-lg text-body-medium bg-primary-background text-primary-foreground focus:outline-none focus:ring-2 ${
-            formErrors.name
-              ? 'border-error-border focus:ring-error-border'
-              : 'border-neutral-border focus:ring-primary-border'
-          }`}
+          className={`${styles.input} ${formErrors.name ? styles.error : ''}`}
           placeholder="输入项目名称"
           disabled={isSubmitting}
           aria-invalid={!!formErrors.name}
@@ -181,30 +175,23 @@ export const ProjectForm: React.FC<ProjectFormProps> = ({
           maxLength={100}
         />
         {formErrors.name && (
-          <span id="name-error" className="text-body-small text-error-foreground">
+          <span id="name-error" className={styles.errorMessage}>
             {formErrors.name}
           </span>
         )}
       </div>
 
       {/* Description field */}
-      <div className="flex flex-col gap-tight">
-        <label
-          htmlFor="project-description"
-          className="text-body-medium font-semibold text-primary-foreground"
-        >
-          项目描述 <span className="text-error-foreground">*</span>
+      <div className={styles.field}>
+        <label htmlFor="project-description" className={styles.label}>
+          项目描述 <span className={styles.required}>*</span>
         </label>
         <textarea
           id="project-description"
           value={formData.description}
           onChange={(e) => handleFieldChange('description', e.target.value)}
           onBlur={() => handleFieldBlur('description')}
-          className={`px-comfortable py-tight border rounded-lg text-body-medium bg-primary-background text-primary-foreground focus:outline-none focus:ring-2 resize-none ${
-            formErrors.description
-              ? 'border-error-border focus:ring-error-border'
-              : 'border-neutral-border focus:ring-primary-border'
-          }`}
+          className={`${styles.textarea} ${formErrors.description ? styles.error : ''}`}
           placeholder="输入项目描述"
           rows={3}
           disabled={isSubmitting}
@@ -213,30 +200,23 @@ export const ProjectForm: React.FC<ProjectFormProps> = ({
           maxLength={500}
         />
         {formErrors.description && (
-          <span id="description-error" className="text-body-small text-error-foreground">
+          <span id="description-error" className={styles.errorMessage}>
             {formErrors.description}
           </span>
         )}
       </div>
 
       {/* Config field */}
-      <div className="flex flex-col gap-tight">
-        <label
-          htmlFor="project-config"
-          className="text-body-medium font-semibold text-primary-foreground"
-        >
-          标注配置 <span className="text-error-foreground">*</span>
+      <div className={styles.field}>
+        <label htmlFor="project-config" className={styles.label}>
+          标注配置 <span className={styles.required}>*</span>
         </label>
         <textarea
           id="project-config"
           value={formData.config}
           onChange={(e) => handleFieldChange('config', e.target.value)}
           onBlur={() => handleFieldBlur('config')}
-          className={`px-comfortable py-tight border rounded-lg text-body-small font-mono bg-primary-background text-primary-foreground focus:outline-none focus:ring-2 resize-none ${
-            formErrors.config
-              ? 'border-error-border focus:ring-error-border'
-              : 'border-neutral-border focus:ring-primary-border'
-          }`}
+          className={`${styles.textarea} ${styles.textareaCode} ${formErrors.config ? styles.error : ''}`}
           placeholder='输入 Label Studio 配置 XML,例如:<View><Text name="text" value="$text"/></View>'
           rows={8}
           disabled={isSubmitting}
@@ -244,24 +224,24 @@ export const ProjectForm: React.FC<ProjectFormProps> = ({
           aria-describedby={formErrors.config ? 'config-error' : undefined}
         />
         {formErrors.config && (
-          <span id="config-error" className="text-body-small text-error-foreground">
+          <span id="config-error" className={styles.errorMessage}>
             {formErrors.config}
           </span>
         )}
-        <span className="text-body-small text-secondary-foreground">
+        <span className={styles.hint}>
           请输入有效的 Label Studio XML 配置
         </span>
       </div>
 
       {/* Submit error */}
       {formErrors.submit && (
-        <div className="bg-error-background text-error-foreground p-comfortable rounded-lg border border-error-border">
+        <div className={styles.submitError}>
           {formErrors.submit}
         </div>
       )}
 
       {/* Form actions */}
-      <div className="flex items-center justify-end gap-tight pt-comfortable">
+      <div className={styles.actions}>
         <Button
           type="button"
           variant="neutral"

+ 41 - 27
web/apps/lq_label/src/components/theme-switcher/theme-switcher.module.scss

@@ -1,56 +1,70 @@
 .root {
   display: flex;
-  flex-direction: column;
-  gap: 8px;
-}
-
-.label {
-  font-size: 12px;
-  font-weight: 500;
-  color: var(--theme-paragraph-subtle);
-  text-transform: uppercase;
-  letter-spacing: 0.5px;
+  align-items: center;
 }
 
-.buttons {
+.slider {
+  position: relative;
   display: flex;
-  gap: 6px;
+  align-items: center;
   background: var(--theme-background-secondary);
-  padding: 4px;
-  border-radius: 8px;
-  border: 1px solid var(--theme-border-subtle);
+  padding: 6px;
+  border-radius: 12px;
+  border: 1px solid var(--theme-border);
+  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
+  gap: 4px;
+}
+
+.indicator {
+  position: absolute;
+  top: 6px;
+  left: 6px;
+  width: 72px;
+  height: calc(100% - 12px);
+  background: var(--theme-button);
+  border-radius: 10px;
+  transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);
+  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
+  pointer-events: none;
+  z-index: 0;
 }
 
 .button {
+  position: relative;
   display: flex;
   align-items: center;
   justify-content: center;
   gap: 6px;
-  padding: 8px 16px;
+  width: 72px;
+  height: 44px;
+  padding: 0 12px;
   border: none;
   background: transparent;
   color: var(--theme-paragraph);
-  font-size: 13px;
-  font-weight: 500;
-  border-radius: 6px;
   cursor: pointer;
-  transition: all 0.2s ease;
+  transition: color 0.2s ease;
+  z-index: 1;
+  border-radius: 10px;
+  font-size: 14px;
+  font-weight: 500;
   white-space: nowrap;
+  flex-shrink: 0;
 
   &:hover {
-    background: var(--theme-background);
     color: var(--theme-headline);
   }
 
   &.active {
-    background: var(--theme-button);
     color: var(--theme-button-text);
-    box-shadow: 0 1px 3px var(--theme-shadow);
   }
-}
 
-.buttonLabel {
-  @media (max-width: 640px) {
-    display: none;
+  svg {
+    width: 18px;
+    height: 18px;
+    flex-shrink: 0;
   }
 }
+
+.label {
+  line-height: 1;
+}

+ 23 - 9
web/apps/lq_label/src/components/theme-switcher/theme-switcher.tsx

@@ -2,6 +2,7 @@
  * Theme Switcher Component
  * 
  * Allows users to switch between light, dark, and eye-care themes
+ * with a sliding animation indicator
  */
 import React from 'react';
 import { useAtom } from 'jotai';
@@ -13,26 +14,39 @@ import styles from './theme-switcher.module.scss';
 export const ThemeSwitcher: React.FC = () => {
   const [themeMode, setThemeMode] = useAtom(themeModeAtom);
 
-  const themes: Array<{ mode: ThemeMode; label: string; icon: React.ReactNode }> = [
-    { mode: 'light', label: '白色', icon: <Sun size={16} /> },
-    { mode: 'dark', label: '深色', icon: <Moon size={16} /> },
-    { mode: 'eye-care', label: '护眼', icon: <Eye size={16} /> },
+  const themes: Array<{ mode: ThemeMode; icon: React.ReactNode; label: string }> = [
+    { mode: 'light', icon: <Sun size={16} />, label: '白色' },
+    { mode: 'dark', icon: <Moon size={16} />, label: '深色' },
+    { mode: 'eye-care', icon: <Eye size={16} />, label: '护眼' },
   ];
 
+  const activeIndex = themes.findIndex((theme) => theme.mode === themeMode);
+  
+  // 按钮宽度 72px + 间距 4px = 76px
+  const buttonWidth = 72;
+  const gap = 4;
+  const translateX = activeIndex * (buttonWidth + gap);
+
   return (
     <div className={styles.root}>
-      <div className={styles.label}>主题</div>
-      <div className={styles.buttons}>
+      <div className={styles.slider}>
+        <div
+          className={styles.indicator}
+          style={{
+            transform: `translateX(${translateX}px)`,
+          }}
+        />
         {themes.map((theme) => (
           <button
             key={theme.mode}
             onClick={() => setThemeMode(theme.mode)}
             className={`${styles.button} ${themeMode === theme.mode ? styles.active : ''}`}
-            title={theme.label}
-            aria-label={`切换到${theme.label}模式`}
+            title={`${theme.label}模式`}
+            aria-label={`${theme.label}模式`}
+            aria-pressed={themeMode === theme.mode}
           >
             {theme.icon}
-            <span className={styles.buttonLabel}>{theme.label}</span>
+            <span className={styles.label}>{theme.label}</span>
           </button>
         ))}
       </div>

+ 72 - 0
web/apps/lq_label/src/styles/global.scss

@@ -84,6 +84,78 @@ a {
   color: var(--theme-button-text);
 }
 
+/* Override @humansignal/ui Button styles to use theme colors */
+:global {
+  /* Button base overrides */
+  .button_base__1vMSr {
+    --text-color: var(--theme-headline);
+    --background-color: transparent;
+    --border-color: var(--theme-border);
+    --background-color-hover: var(--theme-background-secondary);
+    --border-color-hover: var(--theme-button);
+    --background-color-active: var(--theme-background-tertiary);
+  }
+
+  /* Primary button variant */
+  .button_variant-primary__bdYre {
+    --text-color: var(--theme-button-text) !important;
+    --background-color: var(--theme-button) !important;
+    --border-color: var(--theme-button) !important;
+    --background-color-hover: var(--theme-button-hover) !important;
+    --border-color-hover: var(--theme-button-hover) !important;
+    --background-color-active: var(--theme-button-active) !important;
+  }
+
+  /* Neutral button variant */
+  .button_variant-neutral__3kZXm {
+    --text-color: var(--theme-headline);
+    --background-color: transparent;
+    --border-color: var(--theme-border);
+    --background-color-hover: var(--theme-background-secondary);
+    --border-color-hover: var(--theme-button);
+    --background-color-active: var(--theme-background-tertiary);
+  }
+
+  /* Input fields */
+  input[type="text"],
+  input[type="email"],
+  input[type="password"],
+  input[type="number"],
+  textarea,
+  select {
+    background: var(--theme-background) !important;
+    color: var(--theme-headline) !important;
+    border-color: var(--theme-border) !important;
+
+    &:focus {
+      border-color: var(--theme-button) !important;
+      outline-color: var(--theme-button) !important;
+    }
+
+    &::placeholder {
+      color: var(--theme-paragraph-subtle) !important;
+    }
+  }
+
+  /* Form labels */
+  label {
+    color: var(--theme-headline) !important;
+  }
+
+  /* Error states */
+  .text-error-foreground,
+  [class*="error"] {
+    color: var(--theme-error) !important;
+  }
+
+  /* Disabled states */
+  :disabled,
+  [disabled] {
+    opacity: 0.5;
+    cursor: not-allowed;
+  }
+}
+
 /* Utility Classes */
 .text-headline {
   color: var(--theme-headline);

+ 0 - 1
web/apps/lq_label/src/views/index.ts

@@ -11,7 +11,6 @@
 export { HomeView } from './home-view';
 export { NotFoundView } from './not-found-view';
 export { ProjectsView } from './projects-view';
-export { ProjectDetailView } from './project-detail-view';
 export { ProjectEditView } from './project-edit-view';
 export { TasksView } from './tasks-view';
 export { AnnotationsView } from './annotations-view';

+ 35 - 2
web/apps/lq_label/src/views/project-list-view/project-list-view.tsx

@@ -29,6 +29,8 @@ import {
   deleteProject,
 } from '../../services/api';
 import { ProjectForm, type ProjectFormData } from '../../components/project-form';
+import { ProjectDetailModal } from '../../components/project-detail-modal';
+import { ProjectEditModal } from '../../components/project-edit-modal';
 import styles from './project-list-view.module.scss';
 
 export const ProjectListView: React.FC = () => {
@@ -40,6 +42,9 @@ export const ProjectListView: React.FC = () => {
   // Dialog state
   const [isCreateDialogOpen, setIsCreateDialogOpen] = useState(false);
   const [isDeleteDialogOpen, setIsDeleteDialogOpen] = useState(false);
+  const [isDetailModalOpen, setIsDetailModalOpen] = useState(false);
+  const [isEditModalOpen, setIsEditModalOpen] = useState(false);
+  const [selectedProjectId, setSelectedProjectId] = useState<string | null>(null);
   const [projectToDelete, setProjectToDelete] = useState<Project | null>(null);
   const [isSubmitting, setIsSubmitting] = useState(false);
   
@@ -99,11 +104,13 @@ export const ProjectListView: React.FC = () => {
   };
 
   const handleViewProject = (project: Project) => {
-    navigate(`/projects/${project.id}`);
+    setSelectedProjectId(project.id);
+    setIsDetailModalOpen(true);
   };
 
   const handleEditProject = (project: Project) => {
-    navigate(`/projects/${project.id}/edit`);
+    setSelectedProjectId(project.id);
+    setIsEditModalOpen(true);
   };
 
   // Filter projects based on search query
@@ -328,6 +335,32 @@ export const ProjectListView: React.FC = () => {
           </div>
         </div>
       )}
+
+      {/* Project Detail Modal */}
+      {selectedProjectId && (
+        <ProjectDetailModal
+          projectId={selectedProjectId}
+          isOpen={isDetailModalOpen}
+          onClose={() => {
+            setIsDetailModalOpen(false);
+            setSelectedProjectId(null);
+          }}
+          onProjectUpdated={loadProjects}
+        />
+      )}
+
+      {/* Project Edit Modal */}
+      {selectedProjectId && (
+        <ProjectEditModal
+          projectId={selectedProjectId}
+          isOpen={isEditModalOpen}
+          onClose={() => {
+            setIsEditModalOpen(false);
+            setSelectedProjectId(null);
+          }}
+          onProjectUpdated={loadProjects}
+        />
+      )}
     </div>
   );
 };