"""上传页面 HTML 渲染(拖拽 + 多文件 + 分片 + 断点续传)。 单文件 server-rendered,内联 CSS+JS,风格对齐 system_status_html.py: 深色模式自适应、卡片、进度条。无构建步骤、无外部依赖。 """ from __future__ import annotations # 默认分片大小 4 MiB:大于 Apache 300s 限制下单片可数秒传完,小到内存恒定。 DEFAULT_CHUNK_SIZE = 4 * 1024 * 1024 # 同一文件分片并发数 DEFAULT_CONCURRENCY = 3 # 单分片失败重试次数 MAX_RETRY = 2 def render() -> str: return f"""
拖拽文件到下方,或点击选择。支持多文件、大文件分片上传与断点续传。
把文件拖到这里,或
系统状态 · zikai file service
""" _CSS = """ :root { color-scheme: light dark; } body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; max-width: 880px; margin: 2em auto; padding: 0 1em; line-height: 1.5; } h1 { margin-bottom: 0.1em; } .sub { color: #777; margin-top: 0; font-size: 0.95em; } .drop { border: 2px dashed #bbb; border-radius: 12px; padding: 2.5em 1em; text-align: center; margin: 1.5em 0; transition: background 0.15s, border-color 0.15s; } .drop.drag { background: rgba(21,101,192,0.08); border-color: #1565c0; } .drop-hint { margin: 0 0 1em; color: #888; } .btn { display: inline-block; padding: 0.5em 1.2em; border-radius: 6px; background: #1565c0; color: #fff; cursor: pointer; font-size: 0.95em; } .btn:hover { background: #0d47a1; } .btn input { display: none; } .tasks { margin: 1em 0; } .card { border: 1px solid #ddd; border-radius: 8px; padding: 0.9em 1.1em; margin: 0.7em 0; background: rgba(0,0,0,0.02); } .task-head { display: flex; align-items: center; gap: 0.6em; margin-bottom: 0.5em; } .fname { font-weight: 600; word-break: break-all; flex: 1; } .fsize { color: #888; font-size: 0.85em; white-space: nowrap; } .fstate { font-size: 0.85em; padding: 0.1em 0.6em; border-radius: 10px; background: #eee; white-space: nowrap; } .state-running { background: #e3f2fd; color: #1565c0; } .state-hashing { background: #fff3e0; color: #e65100; } .state-done { background: #e8f5e9; color: #2e7d32; } .state-dedup { background: #f3e5f5; color: #7b1fa2; } .state-fail { background: #ffebee; color: #c62828; } .bar { position: relative; background: #e6e6e6; border-radius: 4px; height: 18px; width: 100%; overflow: hidden; } .bar .fill { height: 100%; width: 0; background: #43a047; transition: width 0.2s; } .bar .pct { position: absolute; top: 0; left: 0; width: 100%; height: 100%; text-align: center; font-size: 12px; line-height: 18px; color: #fff; mix-blend-mode: difference; } .task-meta { margin-top: 0.5em; font-size: 0.82em; color: #666; word-break: break-all; } .task-meta code { background: rgba(0,0,0,0.06); padding: 0 0.3em; border-radius: 3px; } .foot { color: #888; font-size: 0.85em; margin-top: 1.5em; text-align: center; } a.json { color: #1565c0; text-decoration: none; } @media (prefers-color-scheme: dark) {{ body {{ background: #1a1a1a; color: #e0e0e0; }} .card {{ background: rgba(255,255,255,0.04); border-color: #333; }} .drop {{ border-color: #555; }} .drop.drag {{ background: rgba(21,101,192,0.18); }} .fstate {{ background: #333; }} .bar {{ background: #333; }} .task-meta code {{ background: rgba(255,255,255,0.08); }} }} """