切换演示数据到 demo 目录

- 默认演示视频和 DICOM 路径改为 demo/演视LC视频序列.mp4 与 demo/演视DICOM序列/。

- 演示 DICOM 项目名统一为“演视DICOM序列”,并兼容迁移旧“演示DICOM序列”名称。

- 恢复演示出厂设置测试改为验证新文件名上传路径和新项目名。

- 同步更新用户管理提示、API 契约、安装文档、实现地图和项目指南。

- 本地与 ../Seg_Server_Docker 已实际放入 demo 演示视频和 DICOM 测试影像;数据文件受 .gitignore 保护不进入提交。
This commit is contained in:
2026-05-07 15:58:29 +08:00
parent 6f4d4efeaf
commit b1131c9126
16 changed files with 42 additions and 32 deletions

View File

@@ -194,7 +194,7 @@ uvicorn main:app --host 0.0.0.0 --port 8000 --reload
- 检查/创建 MinIO bucket `seg-media`
- 测试 Redis 连接;
- 后台 seed 默认模板包括“腹腔镜胆囊切除术”和“头颈部CT分割”
- 如果本地存在 `demo_video_path`配置的 `demo_dicom_dir` DICOM 序列,后台 seed 默认演示视频项目和演示 DICOM 项目DICOM 会按文件名自然顺序生成帧。
- 如果本地存在 `demo/演视LC视频序列.mp4``demo/演视DICOM序列/` DICOM 序列,后台 seed 默认演示视频项目和演示 DICOM 项目DICOM 会按文件名自然顺序生成帧。
- API 路由包括:
- `POST /api/auth/login`
- `GET /api/auth/me`
@@ -238,7 +238,7 @@ uvicorn main:app --host 0.0.0.0 --port 8000 --reload
## 主要业务流程
1. 登录:`Login.tsx` 调用 `POST /api/auth/login`,后端用 `users` 表和密码哈希校验凭证,默认启动时会种子化唯一管理员 `admin / 123456`;成功后返回签名 JWT`GET /api/auth/me` 可读取当前用户;角色只包括 `admin``annotator`,非默认 admin 的历史管理员或旧 `viewer` 会归一为 `annotator`;写入类业务接口要求 `admin/annotator`,用户管理、审计日志和演示出厂设置后台仅 `admin` 可用。
2. 用户管理:`Sidebar` 仅对 `admin` 显示“用户管理”,该入口使用用户图标;侧栏底部当前用户退出入口使用退出图标,且弹出提示不接收鼠标事件,避免覆盖工作区按钮;`UserAdmin.tsx` 调用 `/api/admin/users` 新增标注员、停用/启用、改密码和删除用户,并调用 `/api/admin/audit-logs` 展示登录和管理操作审计;系统不允许新增第二个管理员,也不再支持观察员角色;演示部署可通过“恢复演示出厂设置”二次确认后调用 `/api/admin/demo-factory-reset`,清空演示数据,只保留默认 admin、名为“演视LC视频序列”的演示视频项目和一个已按文件名自然顺序生成帧的演示 DICOM 项目同时按内置权威定义恢复“腹腔镜胆囊切除术”和“头颈部CT分割”系统模板缺失的会重建被修改或删减的语义分类树会覆盖回默认状态。
2. 用户管理:`Sidebar` 仅对 `admin` 显示“用户管理”,该入口使用用户图标;侧栏底部当前用户退出入口使用退出图标,且弹出提示不接收鼠标事件,避免覆盖工作区按钮;`UserAdmin.tsx` 调用 `/api/admin/users` 新增标注员、停用/启用、改密码和删除用户,并调用 `/api/admin/audit-logs` 展示登录和管理操作审计;系统不允许新增第二个管理员,也不再支持观察员角色;演示部署可通过“恢复演示出厂设置”二次确认后调用 `/api/admin/demo-factory-reset`,清空演示数据,只保留默认 admin、名为“演视LC视频序列”的演示视频项目和名为“演视DICOM序列”的已按文件名自然顺序生成帧的演示 DICOM 项目同时按内置权威定义恢复“腹腔镜胆囊切除术”和“头颈部CT分割”系统模板缺失的会重建被修改或删减的语义分类树会覆盖回默认状态。
3. 项目管理:`ProjectLibrary.tsx` 调用项目 API 创建项目、拉取列表、重命名项目、复制项目和删除项目项目库为所有登录用户共享标注员和管理员在项目创建、导入、解析、标注、AI 推理、任务查看、导出和删除方面能力一致;项目卡片删除按钮旁提供复制入口,复制时可选择“新项目重置”(复制项目媒体和已生成帧序列,但清空标注/mask或“全内容复制”复制项目、帧序列、标注和关联 mask 元数据任务运行历史不复制删除当前项目后会清空工作区当前项目、帧、mask 和选区。
4. 上传资源:视频走 `/api/media/upload`只上传源文件并关联项目不自动拆帧项目库在视频上传期间显示导入进度条、百分比和已上传字节。只有视频项目在尚未生成帧、未处于项目名称编辑状态且未解析中时显示“生成帧”DICOM 项目不显示生成帧入口DICOM 批量走 `/api/media/upload/dicom`,前端和后端都会按文件名自然顺序排序 `.dcm` 文件,避免 `10.dcm` 排在 `2.dcm` 前导致切片错位DICOM 上传期间显示导入进度条、本次有效文件数量和已上传字节,上传完成后轮询解析任务进度直到完成、失败或取消。
5. 生成帧入队:用户在项目库点击“生成帧”,选择目标 FPS 后前端调用 `/api/media/parse`;后端创建 `ProcessingTask` 并投递 Celery接口支持 `parse_fps``max_frames``target_width` 标准帧序列参数;项目库会继续轮询任务进度,解析成功后重新拉取项目列表和当前项目对象,使后端生成的 `thumbnail_url` 立即显示为项目封面;项目库和模板库的成功/失败短反馈使用非阻塞 `TransientNotice`,会自动消失。

View File

@@ -417,7 +417,7 @@ cd ~/Desktop/Seg_Server
后端启动时会自动种子化默认管理员 `admin / 123456`,密码以哈希形式存入 `users` 表。登录成功返回签名 JWT前端会把 token 写入 `localStorage` 并通过 `Authorization: Bearer <token>` 调用业务接口;页面刷新后会用 `/api/auth/me` 恢复当前用户。
当前项目、帧、标注、任务、Dashboard 和导出接口已经按当前 JWT 用户拥有的项目隔离;模板支持系统模板(`owner_user_id IS NULL`)和用户模板。角色分为 `admin``annotator``viewer``admin/annotator` 可调用写入类业务接口,`viewer` 只能读取;管理员会在侧栏看到“用户管理”,可通过 `/api/admin/users` 新增、停用/启用、改角色、改密码和删除无项目用户,并通过 `/api/admin/audit-logs` 查看登录与用户管理审计。演示部署还提供“恢复演示出厂设置”,站内二次确认后调用 `/api/admin/demo-factory-reset`,只保留默认 admin、演示视频项目和一个已按文件名自然顺序生成帧的演示 DICOM 项目。生产部署时必须在 `backend/.env` 覆盖 `JWT_SECRET_KEY` 并修改默认管理员密码。
当前项目、帧、标注、任务、Dashboard 和导出接口已经按当前 JWT 用户拥有的项目隔离;模板支持系统模板(`owner_user_id IS NULL`)和用户模板。角色分为 `admin``annotator``viewer``admin/annotator` 可调用写入类业务接口,`viewer` 只能读取;管理员会在侧栏看到“用户管理”,可通过 `/api/admin/users` 新增、停用/启用、改角色、改密码和删除无项目用户,并通过 `/api/admin/audit-logs` 查看登录与用户管理审计。演示部署还提供“恢复演示出厂设置”,站内二次确认后调用 `/api/admin/demo-factory-reset`直接从 `demo/` 读取“演视LC视频序列”和“演视DICOM序列”只保留默认 admin、演示视频项目和一个已按文件名自然顺序生成帧的演示 DICOM 项目。生产部署时必须在 `backend/.env` 覆盖 `JWT_SECRET_KEY` 并修改默认管理员密码。
系统默认模板会在后端启动时幂等补齐当前包括“腹腔镜胆囊切除术”和“头颈部CT分割”头颈部 CT 默认分类名使用纯中文,不带括号英文翻译。所有新建、复制、导入和后端返回的模板都会归一化带上黑色 `maskid: 0` 的“待分类”保留类并固定在语义分类树最后。恢复演示出厂设置只删除用户私有模板并会按内置权威定义重建缺失的默认系统模板、覆盖恢复被修改或删减的默认语义分类树。模板库左侧“生效中模板架构清单”里的复制按钮会把任一模板复制成当前用户私有副本并保留分类名称、颜色、maskid、内部层级顺序和规则。

View File

@@ -38,8 +38,8 @@ class Settings(BaseSettings):
access_token_expire_minutes: int = 60 * 24
default_admin_username: str = "admin"
default_admin_password: str = "123456"
demo_video_path: str = "/home/wkmgc/Desktop/Seg_Server/Data_MyVideo_1.mp4"
demo_dicom_dir: str = "/home/wkmgc/Desktop/Seg_Server/2024_2_5_王芳/※2F458C45CFAA4C7CB76A39AA2BFE436B"
demo_video_path: str = "/home/wkmgc/Desktop/Seg_Server/demo/演视LC视频序列.mp4"
demo_dicom_dir: str = "/home/wkmgc/Desktop/Seg_Server/demo/演视DICOM序列"
class Config:
env_file = ".env"

View File

@@ -68,6 +68,7 @@ def _seed_default_project_sync() -> None:
from services.demo_media import (
DEMO_DICOM_PROJECT_NAME,
DEMO_VIDEO_PROJECT_NAME,
LEGACY_DEMO_DICOM_PROJECT_NAMES,
LEGACY_DEMO_VIDEO_PROJECT_NAMES,
create_parsed_dicom_demo_project,
create_unparsed_video_demo_project,
@@ -85,6 +86,14 @@ def _seed_default_project_sync() -> None:
if legacy_video is not None:
legacy_video.name = DEMO_VIDEO_PROJECT_NAME
db.commit()
legacy_dicom = (
db.query(Project)
.filter(Project.name.in_(LEGACY_DEMO_DICOM_PROJECT_NAMES))
.first()
)
if legacy_dicom is not None:
legacy_dicom.name = DEMO_DICOM_PROJECT_NAME
db.commit()
existing_video = db.query(Project).filter(Project.name == DEMO_VIDEO_PROJECT_NAME).first()
if existing_video is None and os.path.exists(settings.demo_video_path):
video_project = create_unparsed_video_demo_project(

View File

@@ -15,10 +15,11 @@ from models import Frame, Project, User
from services.frame_parser import natural_filename_key, parse_dicom, upload_frames_to_minio
from statuses import PROJECT_STATUS_PENDING, PROJECT_STATUS_READY
DEMO_DICOM_PROJECT_NAME = "DICOM序列"
DEMO_DICOM_PROJECT_NAME = "DICOM序列"
DEMO_DICOM_PARSE_FPS = 30.0
DEMO_VIDEO_PROJECT_NAME = "演视LC视频序列"
LEGACY_DEMO_VIDEO_PROJECT_NAMES = {"Data_MyVideo_1"}
LEGACY_DEMO_DICOM_PROJECT_NAMES = {"演示DICOM序列"}
def demo_dicom_files(dicom_dir: str) -> list[Path]:

View File

@@ -118,7 +118,7 @@ def test_admin_cannot_delete_self_but_can_delete_project_author(client, db_sessi
def test_demo_factory_reset_leaves_admin_and_parsed_demo_dicom(client, db_session, monkeypatch, tmp_path):
video_path = tmp_path / "Data_MyVideo_1.mp4"
video_path = tmp_path / "演视LC视频序列.mp4"
video_path.write_bytes(b"demo-video")
monkeypatch.setattr("routers.admin.settings.demo_video_path", str(video_path))
dicom_dir = tmp_path / "dicom"
@@ -189,12 +189,12 @@ def test_demo_factory_reset_leaves_admin_and_parsed_demo_dicom(client, db_sessio
data = response.json()
assert data["message"] == "演示环境已恢复出厂设置"
assert data["admin_user"]["username"] == "admin"
assert data["project"]["name"] == "DICOM序列"
assert data["project"]["name"] == "DICOM序列"
assert data["project"]["status"] == PROJECT_STATUS_READY
assert data["project"]["source_type"] == "dicom"
assert data["project"]["frame_count"] == 3
assert data["project"]["video_path"] == f"uploads/{data['project']['id']}/dicom"
assert [project["name"] for project in data["projects"]] == ["演视LC视频序列", "DICOM序列"]
assert [project["name"] for project in data["projects"]] == ["演视LC视频序列", "DICOM序列"]
assert data["projects"][0]["status"] == "pending"
assert data["projects"][0]["source_type"] == "video"
assert data["projects"][0]["frame_count"] == 0
@@ -202,7 +202,7 @@ def test_demo_factory_reset_leaves_admin_and_parsed_demo_dicom(client, db_sessio
assert data["projects"][1]["source_type"] == "dicom"
assert data["projects"][1]["frame_count"] == 3
assert [item["object_name"] for item in uploaded] == [
f"uploads/{data['projects'][0]['id']}/Data_MyVideo_1.mp4",
f"uploads/{data['projects'][0]['id']}/演视LC视频序列.mp4",
f"uploads/{data['project']['id']}/dicom/1.dcm",
f"uploads/{data['project']['id']}/dicom/2.dcm",
f"uploads/{data['project']['id']}/dicom/10.dcm",

View File

@@ -24,7 +24,7 @@
- 检查 MinIO bucket。
- 测试 Redis。
- Seed 默认模板。
- 如果存在 `demo_video_path`配置的 `demo_dicom_dir` DICOM 序列创建名为“演视LC视频序列”的默认演示视频项目和演示 DICOM 项目DICOM 按文件名自然顺序生成帧;启动时会把旧显示名 `Data_MyVideo_1` 迁移为新显示名。
- 如果存在 `demo/演视LC视频序列.mp4``demo/演视DICOM序列/`创建名为“演视LC视频序列”的默认演示视频项目和名为“演视DICOM序列”的演示 DICOM 项目DICOM 按文件名自然顺序生成帧;启动时会把旧显示名 `Data_MyVideo_1` / `演示DICOM序列` 迁移为新显示名。
## 前端模块切换
@@ -72,7 +72,7 @@
2. `UserAdmin.tsx` 调用 `GET/POST/PATCH/DELETE /api/admin/users` 完成标注员新增、停用/启用、改密码和删除用户;不提供观察员或第二个管理员入口。
3. `UserAdmin.tsx` 调用 `GET /api/admin/audit-logs` 展示登录成功/失败以及用户管理操作审计。
4. `UserAdmin.tsx` 危险区“恢复演示出厂设置”需要浏览器确认和输入 `RESET_DEMO_FACTORY`,随后调用 `POST /api/admin/demo-factory-reset`
5. 后端 `backend/routers/admin.py` 会阻止管理员删除、停用、改名或降级自己项目库已共享因此删除标注员不会删除或迁移项目演示出厂重置会清空其它用户、项目帧、标注、任务和私有模板重新创建名为“演视LC视频序列”的演示视频项目和一个已按文件名自然顺序生成帧的演示 DICOM 项目。
5. 后端 `backend/routers/admin.py` 会阻止管理员删除、停用、改名或降级自己;项目库已共享,因此删除标注员不会删除或迁移项目;演示出厂重置会清空其它用户、项目帧、标注、任务和私有模板,直接从 `demo/` 重新创建名为“演视LC视频序列”的演示视频项目和名为“演视DICOM序列”的已自然排序演示 DICOM 项目。
### 项目与拆帧

View File

@@ -36,7 +36,7 @@
| 启停用 / 改密码 | 真实可用 | 调用 `PATCH /api/admin/users/{id}`;后端禁止管理员把自己降级、改名或停用,避免锁死后台 |
| 删除用户 | 真实可用 | 调用 `DELETE /api/admin/users/{id}`;后端禁止删除自己,且用户名下仍有项目时返回 409避免悬空项目数据 |
| 审计日志 | 真实可用 | 调用 `GET /api/admin/audit-logs`,展示登录成功/失败、用户新增、修改和删除等管理操作 |
| 恢复演示出厂设置 | 真实可用 | 管理员点击危险区按钮后先浏览器确认,再输入 `RESET_DEMO_FACTORY`;前端调用 `POST /api/admin/demo-factory-reset`,后端只保留默认 admin、演示视频项目和一个已按文件名自然顺序生成帧的演示 DICOM 项目并清空用户、项目帧、标注、任务和私有模板等演示数据“腹腔镜胆囊切除术”和“头颈部CT分割”系统模板会按内置默认定义重建或覆盖恢复 |
| 恢复演示出厂设置 | 真实可用 | 管理员点击危险区按钮后先浏览器确认,再输入 `RESET_DEMO_FACTORY`;前端调用 `POST /api/admin/demo-factory-reset`,后端直接从 `demo/` 读取“演视LC视频序列”和“演视DICOM序列”只保留默认 admin、演示视频项目和一个已按文件名自然顺序生成帧的演示 DICOM 项目并清空用户、项目帧、标注、任务和私有模板等演示数据“腹腔镜胆囊切除术”和“头颈部CT分割”系统模板会按内置默认定义重建或覆盖恢复 |
## Dashboard 系统概况

View File

@@ -63,7 +63,7 @@ Authorization: Bearer <token>
| GET | `/api/auth/me` | 当前用户 |
| GET/POST/PATCH/DELETE | `/api/admin/users` | 管理员用户管理 |
| GET | `/api/admin/audit-logs` | 管理员审计日志 |
| POST | `/api/admin/demo-factory-reset` | 演示部署恢复出厂设置;请求体需 `confirmation=RESET_DEMO_FACTORY`;重置后保留默认 admin、演示视频项目和一个已按文件名自然顺序生成帧的演示 DICOM 项目同时按内置权威定义重建缺失的“腹腔镜胆囊切除术”“头颈部CT分割”系统模板并覆盖恢复被修改或删减的默认语义分类树响应包含兼容单个 `project` 和完整 `projects` 列表 |
| POST | `/api/admin/demo-factory-reset` | 演示部署恢复出厂设置;请求体需 `confirmation=RESET_DEMO_FACTORY`;重置后保留默认 admin、`demo/演视LC视频序列.mp4` 创建的演示视频项目和从 `demo/演视DICOM序列/` 创建的已按文件名自然顺序生成帧的演示 DICOM 项目同时按内置权威定义重建缺失的“腹腔镜胆囊切除术”“头颈部CT分割”系统模板并覆盖恢复被修改或删减的默认语义分类树响应包含兼容单个 `project` 和完整 `projects` 列表 |
| POST | `/api/projects` | 创建项目 |
| GET | `/api/projects` | 项目列表 |
| GET | `/api/projects/{project_id}` | 项目详情 |

View File

@@ -16,7 +16,7 @@
- 角色只包括唯一默认 `admin``annotator`;历史 `viewer` 或额外管理员会归一为标注员;用户管理、审计日志和演示环境出厂设置后台仅默认 `admin` 可用。
- 管理员侧栏显示“用户管理”入口;管理员可以新增标注员、停用/启用、修改密码、删除用户。
- 系统记录登录成功/失败和用户管理操作到 `audit_logs`,管理员后台可查看最近审计日志。
- 管理员后台提供“恢复演示出厂设置”危险操作;前端必须二次确认,后端也必须校验 `confirmation=RESET_DEMO_FACTORY`,执行后只保留默认 admin 账号、系统模板、演示视频项目和一个已按文件名自然顺序生成帧的演示 DICOM 项目,清空其它用户、项目、帧、标注、任务、用户模板和旧审计记录,并写入本次重置审计。
- 管理员后台提供“恢复演示出厂设置”危险操作;前端必须二次确认,后端也必须校验 `confirmation=RESET_DEMO_FACTORY`,执行后只保留默认 admin 账号、系统模板、`demo/演视LC视频序列.mp4` 创建的演示视频项目和从 `demo/演视DICOM序列/` 创建的已按文件名自然顺序生成帧的演示 DICOM 项目,清空其它用户、项目、帧、标注、任务、用户模板和旧审计记录,并写入本次重置审计。
- 系统默认模板至少包含“腹腔镜胆囊切除术”和“头颈部CT分割”头颈部 CT 默认分类名必须使用纯中文,不带括号英文翻译;恢复演示出厂设置不得删除系统默认模板,并必须重建缺失的默认模板、覆盖恢复被修改或删减的默认语义分类树。
## R2 项目管理

View File

@@ -89,7 +89,7 @@
3. `Template.owner_user_id` 支持用户模板;`owner_user_id IS NULL` 的模板视为系统模板,可作为默认分类体系对用户可见。
4. 角色只分为唯一默认 `admin``annotator``admin/annotator` 可调用写入类业务接口;`/api/admin/*` 仅允许默认 `admin`,用于用户管理、审计日志和演示环境出厂设置。
5. `UserAdmin.tsx` 仅在当前用户角色为 `admin` 时从 `Sidebar` 展示,调用 `/api/admin/users` 完成标注员新增、停用/启用、密码修改和删除用户,调用 `/api/admin/audit-logs` 展示登录和管理操作审计;改密码、删除用户和危险区“恢复演示出厂设置”均使用站内弹窗确认,恢复出厂设置要求输入 `RESET_DEMO_FACTORY` 后调用 `/api/admin/demo-factory-reset`
6. `POST /api/admin/demo-factory-reset` 仅允许 `admin`,会重置默认 admin 密码/角色/启用状态删除其它用户、项目、帧、标注、mask、任务、用户模板和旧审计重新创建 `settings.demo_video_path` 指向且显示名为“演视LC视频序列”的演示视频项目以及 `settings.demo_dicom_dir` 指向的演示 DICOM 项目DICOM 会按文件名自然顺序上传和生成帧;系统模板保留以保证重置后仍可标注。
6. `POST /api/admin/demo-factory-reset` 仅允许 `admin`,会重置默认 admin 密码/角色/启用状态删除其它用户、项目、帧、标注、mask、任务、用户模板和旧审计重新创建 `demo/演视LC视频序列.mp4` 指向且显示名为“演视LC视频序列”的演示视频项目以及 `demo/演视DICOM序列/` 指向且显示名为“演视DICOM序列”的演示 DICOM 项目DICOM 会按文件名自然顺序上传和生成帧;系统模板保留以保证重置后仍可标注。
7. 缺失、过期或伪造的 Bearer token 会在业务路由返回 401权限不足返回 403其他用户项目资源对当前用户表现为 404。
### 项目导入与生成帧

View File

@@ -32,7 +32,7 @@
| 需求 | 功能点 | 对应测试 | 当前状态 |
|------|--------|----------|----------|
| R1 | 登录页 logo 和系统标题文案、唯一默认管理员、JWT 写入、当前用户写入、刷新恢复基础状态、失败提示、后端 401、`/api/auth/me`、管理员用户管理入口图标、底部退出入口图标和 tooltip 命中范围、角色权限、审计日志、演示出厂设置二次确认、重置后只保留 admin、名为“演视LC视频序列”的演示视频项目和已生成帧自然排序演示 DICOM 项目 | `Login.test.tsx`, `Sidebar.test.tsx`, `UserAdmin.test.tsx`, `useStore.test.ts`, `test_auth.py`, `test_admin.py` | 已覆盖 |
| R1 | 登录页 logo 和系统标题文案、唯一默认管理员、JWT 写入、当前用户写入、刷新恢复基础状态、失败提示、后端 401、`/api/auth/me`、管理员用户管理入口图标、底部退出入口图标和 tooltip 命中范围、角色权限、审计日志、演示出厂设置二次确认、重置后只保留 admin、名为“演视LC视频序列”的演示视频项目和名为“演视DICOM序列”的已生成帧自然排序演示 DICOM 项目 | `Login.test.tsx`, `Sidebar.test.tsx`, `UserAdmin.test.tsx`, `useStore.test.ts`, `test_auth.py`, `test_admin.py` | 已覆盖 |
| R2 | 项目列表/创建/选择/重命名/复制、重命名时不触发生成帧、DICOM 不显示生成帧、项目复制 reset/full、项目按用户隔离、视频导入、DICOM 导入、DICOM 前端选择自然排序、后端项目和帧 CRUD | `ProjectLibrary.test.tsx`, `api.test.ts`, `test_projects.py` | 已覆盖 |
| R3 | 文件类型校验、自动/指定项目上传、视频导入与生成帧分离、视频/DICOM 上传进度可视化、DICOM 导入显示有效文件数量并在上传后持续显示解析任务进度、显式 FPS 生成帧、视频生成帧完成后自动刷新项目封面、项目卡片 FPS 徽标显示 `parse_fps`、视频/DICOM 拆帧任务、DICOM 上传/下载/读取自然排序、非阻塞自动消失操作提示、`parse_fps/max_frames/target_width`、标准帧序列 metadata、任务查询、取消、重试、worker 取消停止 | `ProjectLibrary.test.tsx`, `TransientNotice.test.tsx`, `api.test.ts`, `test_media.py`, `test_tasks.py` | 已覆盖 |
| R4 | 工作区加载帧、无帧项目不自动解析、工作区短状态自动消失、后端标注回显保留本地未保存 draft mask、Canvas/AI 底图居中适配且保留边距、工作区 mask 透明度、选中 mask 后跨帧自动跟随同一传播链结果、左侧工具栏当前帧清空优先作用于选中 mask、无传播链时直接执行、有传播链时可选当前帧/传播所有帧/取消、清空人工/AI 标注帧前二次确认、取消确认不删除、仅自动传播帧不确认、删除单个传播 mask 后空帧不保留传播历史颜色、传播权重下拉深色可读配色、缩略图/range/视频处理进度条、视频处理进度条点击跳帧、人工/AI 标注帧红色竖线和标识点击跳帧、自动传播帧蓝色区段和标识点击跳帧、最近自动传播历史片段同一蓝色系按新旧递进显示,旧记录第 5 次后统一阈值色、当前帧白色贯穿线、传播范围洋红/黄绿色边界贯穿线、缩略图红/蓝边框、人工/AI 标注帧叠加传播状态时红框优先保留并显示蓝色内描边、当前人工/AI 标注帧青色外框加红色内描边、普通状态不显示传播范围黄色选区、播放进度条/视频处理进度条拖拽选择传播范围、Canvas/AI 画布拖拽平移回写 position state、左右方向键切帧、播放、按 FPS 显示时间 | `VideoWorkspace.test.tsx`, `FrameTimeline.test.tsx`, `CanvasArea.test.tsx`, `AISegmentation.test.tsx` | 已覆盖 |

View File

@@ -192,12 +192,12 @@ jwt_secret_key=change-this-to-a-long-random-production-secret
access_token_expire_minutes=1440
default_admin_username=admin
default_admin_password=123456
demo_video_path=/home/wkmgc/Desktop/Seg_Server/Data_MyVideo_1.mp4
demo_dicom_dir=/home/wkmgc/Desktop/Seg_Server/2024_2_5_王芳/※2F458C45CFAA4C7CB76A39AA2BFE436B
demo_video_path=/home/wkmgc/Desktop/Seg_Server/demo/演视LC视频序列.mp4
demo_dicom_dir=/home/wkmgc/Desktop/Seg_Server/demo/演视DICOM序列
EOF
```
`demo_video_path` 仍指向本地源视频文件;系统 seed 和“恢复演示出厂设置”时的项目显示名固定为“演视LC视频序列”
演示视频和 DICOM 测试影像统一放在项目根目录 `demo/` 下;系统 seed 和“恢复演示出厂设置”会直接读取 `demo/演视LC视频序列.mp4``demo/演视DICOM序列/`
如果前端通过局域网 IP 访问,例如 `http://192.168.3.11:3000`,需要把该地址加入 `cors_origins`,同时前端也要配置 API 地址。
@@ -317,7 +317,7 @@ admin / 123456
首次启动会自动创建默认管理员,密码以哈希形式写入 `users` 表;登录返回签名 JWT业务接口会校验 `Authorization: Bearer <token>`。生产环境必须修改 `jwt_secret_key` 和默认管理员密码。
默认管理员登录后会看到“用户管理”后台,可新增标注员、停用/启用用户、重置密码、删除用户并查看登录与用户管理审计日志。系统只支持唯一默认 `admin``annotator` 两类角色标注员不能新增用户、查看审计日志或恢复演示出厂设置但可以和管理员共享同一项目库并执行项目管理、标注、AI 推理、任务和导出等业务操作。演示部署可在该后台使用“恢复演示出厂设置”,二次确认后只保留默认 admin、名为“演视LC视频序列”的演示视频项目和一个已按文件名自然顺序生成帧的演示 DICOM 项目;视频来自 `demo_video_path`DICOM 序列来自 `demo_dicom_dir`
默认管理员登录后会看到“用户管理”后台,可新增标注员、停用/启用用户、重置密码、删除用户并查看登录与用户管理审计日志。系统只支持唯一默认 `admin``annotator` 两类角色标注员不能新增用户、查看审计日志或恢复演示出厂设置但可以和管理员共享同一项目库并执行项目管理、标注、AI 推理、任务和导出等业务操作。演示部署可在该后台使用“恢复演示出厂设置”,二次确认后只保留默认 admin、名为“演视LC视频序列”的演示视频项目和名为“演视DICOM序列”的已按文件名自然顺序生成帧的演示 DICOM 项目;视频来自 `demo_video_path`DICOM 序列来自 `demo_dicom_dir`
---

View File

@@ -103,7 +103,7 @@ describe('UserAdmin', () => {
admin_user: { id: 1, username: 'admin', role: 'admin', is_active: 1 },
project: {
id: '8',
name: '演DICOM序列',
name: '演DICOM序列',
status: 'ready',
frames: 300,
fps: '30FPS',
@@ -118,11 +118,11 @@ describe('UserAdmin', () => {
frames: 0,
fps: '30FPS',
source_type: 'video',
video_path: 'uploads/7/Data_MyVideo_1.mp4',
video_path: 'uploads/7/演视LC视频序列.mp4',
},
{
id: '8',
name: '演DICOM序列',
name: '演DICOM序列',
status: 'ready',
frames: 300,
fps: '30FPS',
@@ -157,7 +157,7 @@ describe('UserAdmin', () => {
expect(await screen.findByText('演示环境已恢复出厂设置')).toBeInTheDocument();
expect(useStore.getState().projects).toEqual([
expect.objectContaining({ name: '演视LC视频序列', source_type: 'video' }),
expect.objectContaining({ name: '演DICOM序列', source_type: 'dicom' }),
expect.objectContaining({ name: '演DICOM序列', source_type: 'dicom' }),
]);
expect(useStore.getState().frames).toEqual([]);
expect(useStore.getState().masks).toEqual([]);

View File

@@ -337,7 +337,7 @@ export function UserAdmin() {
<div>
<div className="text-sm font-semibold text-red-100"></div>
<p className="mt-1 text-xs leading-relaxed text-red-200/70">
admin DICOM
adminLC视频序列DICOM序列
</p>
</div>
<button
@@ -432,7 +432,7 @@ export function UserAdmin() {
<div className="w-full max-w-lg rounded-lg border border-red-400/25 bg-[#151515] p-5 shadow-2xl">
<h2 className="text-lg font-semibold text-white"></h2>
<p className="mt-2 text-sm leading-relaxed text-red-100/80">
admin DICOM
admin LC视频序列DICOM序列
</p>
<label className="mt-4 block text-xs text-gray-400" htmlFor="factory-reset-confirm">
RESET_DEMO_FACTORY

View File

@@ -203,10 +203,10 @@ describe('api client contracts', () => {
axiosMock.client.post.mockResolvedValueOnce({
data: {
admin_user: { id: 1, username: 'admin', role: 'admin', is_active: 1 },
project: { id: 8, name: '演DICOM序列', status: 'ready', source_type: 'dicom', frame_count: 300, video_path: 'uploads/8/dicom' },
project: { id: 8, name: '演DICOM序列', status: 'ready', source_type: 'dicom', frame_count: 300, video_path: 'uploads/8/dicom' },
projects: [
{ id: 7, name: '演视LC视频序列', status: 'pending', source_type: 'video', frame_count: 0, video_path: 'uploads/7/Data_MyVideo_1.mp4' },
{ id: 8, name: '演DICOM序列', status: 'ready', source_type: 'dicom', frame_count: 300, video_path: 'uploads/8/dicom' },
{ id: 7, name: '演视LC视频序列', status: 'pending', source_type: 'video', frame_count: 0, video_path: 'uploads/7/演视LC视频序列.mp4' },
{ id: 8, name: '演DICOM序列', status: 'ready', source_type: 'dicom', frame_count: 300, video_path: 'uploads/8/dicom' },
],
deleted_counts: { users: 1 },
message: '演示环境已恢复出厂设置',
@@ -214,10 +214,10 @@ describe('api client contracts', () => {
});
await expect(resetDemoFactory('RESET_DEMO_FACTORY')).resolves.toEqual(expect.objectContaining({
admin_user: expect.objectContaining({ username: 'admin' }),
project: expect.objectContaining({ id: '8', name: '演DICOM序列', frames: 300, source_type: 'dicom' }),
project: expect.objectContaining({ id: '8', name: '演DICOM序列', frames: 300, source_type: 'dicom' }),
projects: [
expect.objectContaining({ id: '7', name: '演视LC视频序列', frames: 0, source_type: 'video' }),
expect.objectContaining({ id: '8', name: '演DICOM序列', frames: 300, source_type: 'dicom' }),
expect.objectContaining({ id: '8', name: '演DICOM序列', frames: 300, source_type: 'dicom' }),
],
}));
expect(axiosMock.client.post).toHaveBeenLastCalledWith('/api/admin/demo-factory-reset', {