fix: docs 分组与 summary 统一 + 记事本页加「复制链接」按钮
docs 乱:
- main.py 页面路由(/upload /files /wb-admin /wb/{id} /docs /redoc / /health)
加 tags(pages/docs/meta)+ summary/description,Swagger UI 按 tag 分组显示。
- whiteboard_controller 残留「白板/笔画/修改次数」改为「记事本/文本/编辑次数」。
复制链接:
- 记事本页加「复制链接」按钮,复制 {origin}/wb/{id} 分享链接(保留「复制文本」)。
This commit is contained in:
@@ -1,10 +1,10 @@
|
||||
"""白板接口:REST(访问/管理)+ WebSocket(实时同步)。
|
||||
|
||||
路由:
|
||||
GET /api/wb/{board_id} 公开:访问白板元数据,不存在则新建(前端 init 用)
|
||||
GET /api/wb/{board_id} 公开:访问记事本元数据,不存在则新建(前端 init 用)
|
||||
WS /ws/wb/{board_id} 公开:实时协作 + 心跳
|
||||
GET /api/admin/wb Basic Auth:管理页列表
|
||||
DELETE /api/admin/wb/{board_id} Basic Auth:删除白板
|
||||
DELETE /api/admin/wb/{board_id} Basic Auth:删除记事本
|
||||
|
||||
HTML 页面 /wb/{id} 与管理页 /wb-admin 由 main.py 直接返回静态文件,
|
||||
不在此 controller 注册,避免与 REST 同路径冲突。
|
||||
@@ -41,8 +41,8 @@ def _service(db: Session = Depends(get_db)) -> WhiteboardService:
|
||||
@router.get(
|
||||
"/api/wb/{board_id}",
|
||||
response_model=WhiteboardOut,
|
||||
summary="访问白板元数据(不存在则新建)",
|
||||
description="前端打开 /wb/{id} 页面后调本接口拉取初始笔画;不存在时自动创建空板。",
|
||||
summary="访问记事本元数据(不存在则新建)",
|
||||
description="前端打开 /wb/{id} 页面后调本接口拉取初始文本;不存在时自动创建空板。",
|
||||
)
|
||||
def get_whiteboard(board_id: str, service: WhiteboardService = Depends(_service)) -> WhiteboardOut:
|
||||
return service.get_or_create(board_id)
|
||||
@@ -53,8 +53,8 @@ def get_whiteboard(board_id: str, service: WhiteboardService = Depends(_service)
|
||||
@router.get(
|
||||
"/api/admin/wb",
|
||||
response_model=WhiteboardListResponse,
|
||||
summary="列出所有白板(需鉴权)",
|
||||
description="供白板管理页使用:board_id / 创建时间 / 修改次数 / 上次修改时间。",
|
||||
summary="列出所有记事本(需鉴权)",
|
||||
description="供记事本管理页使用:board_id / 创建时间 / 编辑次数 / 上次修改时间。",
|
||||
)
|
||||
def list_whiteboards(
|
||||
limit: int = Query(100, ge=1, le=500),
|
||||
@@ -68,7 +68,7 @@ def list_whiteboards(
|
||||
|
||||
@router.delete(
|
||||
"/api/admin/wb/{board_id}",
|
||||
summary="删除白板(需鉴权)",
|
||||
summary="删除记事本(需鉴权)",
|
||||
description="删 DB 行,并关闭该 board 的所有在线 WebSocket 连接。",
|
||||
)
|
||||
def delete_whiteboard(
|
||||
|
||||
46
app/main.py
46
app/main.py
@@ -155,48 +155,68 @@ def create_app() -> FastAPI:
|
||||
app.mount("/static", StaticFiles(directory=str(_STATIC_DIR)), name="static")
|
||||
|
||||
# 受 Basic Auth 保护的文档接口
|
||||
@app.get("/openapi.json")
|
||||
@app.get("/openapi.json", tags=["docs"], summary="OpenAPI 文档(需鉴权)")
|
||||
def protected_openapi(_: str = Depends(require_docs_auth)) -> JSONResponse:
|
||||
return JSONResponse(app.openapi())
|
||||
|
||||
@app.get("/docs")
|
||||
@app.get("/docs", tags=["docs"], summary="Swagger UI(需鉴权)")
|
||||
def protected_docs(_: str = Depends(require_docs_auth)):
|
||||
return get_swagger_ui_html(
|
||||
openapi_url="/openapi.json", title="zikai docs", swagger_favicon_url=""
|
||||
)
|
||||
|
||||
@app.get("/redoc")
|
||||
@app.get("/redoc", tags=["docs"], summary="ReDoc(需鉴权)")
|
||||
def protected_redoc(_: str = Depends(require_docs_auth)):
|
||||
return get_redoc_html(
|
||||
openapi_url="/openapi.json", title="zikai docs", redoc_favicon_url=""
|
||||
)
|
||||
|
||||
@app.get("/", response_class=PlainTextResponse)
|
||||
@app.get("/", tags=["meta"], summary="版本号")
|
||||
def root() -> PlainTextResponse:
|
||||
return PlainTextResponse(f"zikai {app.version}\n")
|
||||
|
||||
@app.get("/health")
|
||||
@app.get("/health", tags=["meta"], summary="存活探针")
|
||||
def health() -> dict:
|
||||
return {"status": "ok"}
|
||||
|
||||
@app.get("/upload", response_class=HTMLResponse)
|
||||
@app.get(
|
||||
"/upload",
|
||||
response_class=HTMLResponse,
|
||||
tags=["pages"],
|
||||
summary="上传页面",
|
||||
description="拖拽 / 多文件 / 分片(4 MiB) / 断点续传上传页面(公开)。",
|
||||
)
|
||||
def upload_page() -> HTMLResponse:
|
||||
"""拖拽 / 多文件 / 分片上传页面(公开,对齐 /api/files/upload)。"""
|
||||
return HTMLResponse(render_upload_html())
|
||||
|
||||
@app.get("/files", response_class=HTMLResponse)
|
||||
@app.get(
|
||||
"/files",
|
||||
response_class=HTMLResponse,
|
||||
tags=["pages"],
|
||||
summary="文件浏览页(需鉴权)",
|
||||
description="列出 / 下载 / 删除已上传文件;支持多选、批量下载删除与分页。Basic Auth 同 docs。",
|
||||
)
|
||||
def files_page(_: str = Depends(require_docs_auth)) -> HTMLResponse:
|
||||
"""文件浏览页(Basic Auth,同 docs):列出/下载/删除已上传文件。"""
|
||||
return _serve_static_html("file_browser.html")
|
||||
|
||||
@app.get("/wb-admin", response_class=HTMLResponse)
|
||||
@app.get(
|
||||
"/wb-admin",
|
||||
response_class=HTMLResponse,
|
||||
tags=["pages"],
|
||||
summary="记事本管理页(需鉴权)",
|
||||
description="查看所有记事本的创建时间 / 编辑次数 / 上次修改时间,并可删除。Basic Auth 同 docs。",
|
||||
)
|
||||
def whiteboard_admin_page(_: str = Depends(require_docs_auth)) -> HTMLResponse:
|
||||
"""白板管理页(Basic Auth,同 docs):查看/删除白板。"""
|
||||
return _serve_static_html("whiteboard_admin.html")
|
||||
|
||||
@app.get("/wb/{board_id}", response_class=HTMLResponse)
|
||||
@app.get(
|
||||
"/wb/{board_id}",
|
||||
response_class=HTMLResponse,
|
||||
tags=["pages"],
|
||||
summary="记事本页面",
|
||||
description="公开访问的共享文本记事本,不存在则自动新建;实时协作走 WS /ws/wb/{id}。",
|
||||
)
|
||||
def whiteboard_page(board_id: str) -> HTMLResponse:
|
||||
"""白板页面(公开):访问即协作,不存在则前端拉取时自动新建。"""
|
||||
return _serve_static_html("whiteboard.html")
|
||||
|
||||
return app
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
<span class="wb-online" id="online" title="在线人数">●</span>
|
||||
</div>
|
||||
<div class="wb-bar-right">
|
||||
<button class="btn" id="copyLinkBtn" title="复制分享链接">复制链接</button>
|
||||
<button class="btn" id="copyBtn" title="复制全部文本">复制文本</button>
|
||||
<button class="btn danger" id="clearBtn" title="清空全部内容(所有人)">清空</button>
|
||||
</div>
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
const editor = document.getElementById("editor");
|
||||
const clearBtn = document.getElementById("clearBtn");
|
||||
const copyBtn = document.getElementById("copyBtn");
|
||||
const copyLinkBtn = document.getElementById("copyLinkBtn");
|
||||
const statusEl = document.getElementById("status");
|
||||
const onlineEl = document.getElementById("online");
|
||||
|
||||
@@ -76,6 +77,12 @@
|
||||
toast(ok ? "已复制全部文本" : "复制失败");
|
||||
});
|
||||
|
||||
copyLinkBtn.addEventListener("click", async () => {
|
||||
const url = `${location.origin}/wb/${boardId}`;
|
||||
const ok = await copyText(url);
|
||||
toast(ok ? "链接已复制" : "复制失败");
|
||||
});
|
||||
|
||||
// ---------- 应用远端更新(保留光标) ----------
|
||||
// 策略:用最长公共前后缀算出变更区间,仅替换该区间,光标按相对位置调整。
|
||||
function applyRemoteUpdate(newText) {
|
||||
|
||||
Reference in New Issue
Block a user