|
|
a08234e54b
|
fix(web): require uploaded material urls before generation
Build and Deploy / build-and-deploy (push) Successful in 9m47s
|
2026-05-25 15:47:18 +08:00 |
|
|
|
ac12c0fbd4
|
fix(generation): include missing @ mention in error
Build and Deploy / build-and-deploy (push) Successful in 6m25s
|
2026-05-25 14:09:39 +08:00 |
|
|
|
540f8ef4bb
|
fix(web): validate empty @ references before submit
Build and Deploy / build-and-deploy (push) Has been cancelled
|
2026-05-25 14:07:29 +08:00 |
|
seaislee1209
|
ab790fbe65
|
feat(team-admin): TeamAdminLayout 加消息中心铃铛 + 主题切换 — 跟超管侧栏对齐
Build and Deploy / build-and-deploy (push) Successful in 5m8s
之前团管侧栏 footer 只有头像 + 退出,缺这两个常用按钮。
观察者团管访问 /admin/assets 走 AdminLayout 是有这俩的,
团管在 /team/* 反而没有,体验不一致。
照搬 AdminLayout 同款实现:
- 消息中心铃铛:60s 轮询 + visibilitychange 立即拉,有未读右上角红点
- 主题切换:深色/浅色切换,月亮/太阳 SVG
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
2026-05-18 18:22:24 +08:00 |
|
seaislee1209
|
dccb4cb5e1
|
feat(users): 用户名旁加主管/副管 badge,观察者改绿色避免和主管撞色
超管在 /admin/users 一眼看角色:
- 主管理员 = 蓝 (info,跟 TeamMembersPage 一致)
- 副管理员 = 紫 (purple,跟 TeamMembersPage 一致)
- 观察者 = 绿 (success,从原来的蓝改过来,避免和主管同色)
成员不加 badge (默认无标识就是成员)。
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
2026-05-18 18:20:40 +08:00 |
|
seaislee1209
|
75b950849d
|
fix(team-members): 角色 select 恢复 100% 宽 + 自定义箭头不贴边
上次误把宽度改窄了,实际诉求是箭头不贴右边框。
原生 select 箭头位置浏览器定,padding 推不动。
appearance:none 关原生 + background-image 内嵌 SVG chevron + right 12px center 控位置。
padding-right 36px 给箭头留空间。
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
2026-05-18 18:18:16 +08:00 |
|
seaislee1209
|
f54bf94422
|
fix(users): 编辑 modal 观察者改 toggle switch + 说明文字下移
之前 checkbox + 一整段说明文字塞一个 label 里,文字换行 + checkbox 浮到中间,挤眼。
- 顶部一行:左标签「设为观察者」+ 右 toggle switch(复用 SettingsPage 同款样式,加到 UsersPage.module.css)
- 下方一行:小字灰色说明「可查看全部团队的内容资产...」
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
2026-05-18 18:16:20 +08:00 |
|
seaislee1209
|
690b0c00e7
|
fix(team-members): 编辑 modal 角色 select 改固定宽 200px
100% 宽时下拉箭头被推到最右、内容左对齐,中间一大片空白显眼。
角色就 2 项,200px 已经够。
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
2026-05-18 18:13:53 +08:00 |
|
seaislee1209
|
c1dbc7ac86
|
refactor(users): 改名/观察者/角色切换归并到「编辑」modal — 操作列只剩 3 个按钮
行内/独立按钮模式之前太散,actions 列挤满。本质都是「编辑用户属性」,合并到 modal:
UsersPage(超管):
- 删 cell 内联「改名」按钮 + 内联编辑 state(editingUsernameId/Value + startEditUsername/cancelEditUsername/handleSaveUsername)
- 删 actions「设为观察者/取消观察者」按钮 + handleToggleObserver
- 「编辑」modal 标题改「编辑用户」,加 [用户名] (admin 行 disabled) + [观察者复选框] (仅 team_admin 显示)
- handleSaveQuota → handleSaveUser:串调 username → observer → quota,任一失败 toast + 停留 modal
- cell 保留 observer badge 只读显示
- actions 列剩 [编辑] [重置密码] [禁用/启用]
TeamMembersPage(团管):
- 删 cell 内联「改名」按钮 + 内联编辑 state
- 删 actions「设为副管理员/取消副管理员」按钮
- 「编辑配额」改「编辑」,modal 标题「编辑成员」,加 [用户名] (按 canEditUsernameFor) + [角色 select] (canEditRoleFor 决定 select 还是 readonly 文本)
- 新 helper canEditRoleFor:仅主管可改非主管成员的角色
- handleSaveQuota → handleSaveMember:串调 username → role → quota
- actions 列剩 [编辑] [重置密码(权限矩阵)] [禁用/启用]
后端零改动,纯前端串调现有 PATCH endpoints。
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
2026-05-18 18:11:14 +08:00 |
|
seaislee1209
|
cec1e5d770
|
feat(observer): 团管观察者标记 — 可看全局内容资产(不见 ¥)
后端:
- User.is_observer BooleanField (0016 migration, default=False)
- AdminAuditLog 加 user_observer_toggle 操作类型
- UserSerializer fields 含 is_observer (/auth/me 透出)
- IsSuperAdminOrObserver permission 类:超管 + (is_team_admin && is_observer)
- 3 个 assets endpoint (overview/team_members/user_videos) 权限从 IsSuperAdmin 改为 IsSuperAdminOrObserver
- admin_user_observer_toggle_view (PATCH /admin/users/<id>/observer):
仅超管,只允许打在团管上,拒超管自己 + 拒成员
- admin_users_list_view 返回 is_team_owner/is_observer 字段(前端 row-level 判断用)
前端:
- User/AdminUser/TeamMember type 加 is_observer
- adminApi.toggleUserObserver
- ProtectedRoute 新 requireAdminOrObserver prop + requireAdmin 智能 fallback(团管被拒回 /team/dashboard)
- App.tsx /admin 父路由 requireAdminOrObserver,子路由除 assets 外仍 requireAdmin (race 防御)
- RoleAwareAdminIndexRedirect:观察者团管入 /admin 跳 /admin/assets,超管跳 /admin/dashboard
- AdminLayout sidebar 角色过滤:观察者只见「内容资产」+ 「返回首页」改「返回团队管理」+ logo「观察者」字样
- TeamAdminLayout 观察者团管加「全局资产」入口跳 /admin/assets
- AdminAssetsPage 4 处 ¥ 条件渲染 (hideMoney = role !== 'super_admin')
- UsersPage 行加「设为观察者/取消观察者」按钮(仅 is_team_admin && team_id) + 观察者 badge
- toast 提示「需该用户重新登录后生效」(JWT 不缓存 is_observer claim)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
2026-05-18 15:58:18 +08:00 |
|
seaislee1209
|
a842f87812
|
feat(users): 超管+团管可改用户名 — 内联编辑 + 5 步权限矩阵
后端:
- AdminAuditLog 新增 user_username_update 操作类型 (0015 migration)
- admin_user_username_update_view (PATCH /admin/users/<id>/username, 仅超管, admin 账号不可改)
- team_member_username_update_view (PATCH /team/members/<id>/username, 团管, 同 reset-password 5 步矩阵: 同团/拒自己/拒admin/拒主管/副管不改副管)
- 长度按 UTF-8 字节计 3-20 字节 (≈ 3-20 英文字符 或 1-6 中文字符)
前端:
- adminApi.updateUserUsername + teamApi.updateMemberUsername
- UsersPage 用户名 cell 内联「改名」按钮 (admin 行隐藏)
- TeamMembersPage 用户名 cell 内联「改名」按钮 (canEditUsernameFor 守卫)
- 按现有 TeamsPage inline edit 模板 (inline-flex + whiteSpace:nowrap)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
2026-05-18 15:43:35 +08:00 |
|
seaislee1209
|
e500c2d6a0
|
feat(assets): 3 个资产页视频也加 poster 首帧 — batch B 漏的补全
Build and Deploy / build-and-deploy (push) Successful in 6m40s
v0.20.1 batch B 只给生成页 GenerationCard + 消费记录 RecordDetailModal +
视频详情 VideoDetailModal 加了 poster,3 个资产页漏了:
- 用户资产页 (AssetsPage.tsx)
- 超管内容资产 (AdminAssetsPage.tsx)
- 团管内容资产 (TeamAssetsPage.tsx)
用户实测:消费记录详情能看到首帧海报 + 加载圈;打开内容资产页 / 生成页面
其他位置的视频卡片黑底硬等加载,没首帧 — 体验不一致。
修法:
- 后端 admin_assets_user_videos / team_assets_member_videos view 各加一行
'thumbnail_url': r.thumbnail_url or '' (batch B 的 3 个 records view 已有)
- AssetVideo 类型加 thumbnail_url?: string
- 3 个资产页 <video> 加 poster={... ? rewriteTosUrl(...) : undefined}
(跟 GenerationCard/RecordDetailModal/VideoDetailModal 写法一致)
GenerationCard.tsx 已在 batch B 加过 poster — 用户感觉"生成页面也没"是因为本地
ARK_API_KEY 未配生不了新视频,老 record thumbnail_url 字段是空。新生成的视频
会有(后端 tasks.py:_handle_completed ffmpeg 已经写入)。
测试:tsc 0 error, v0.20.1-smoke 11/11 + modal-interaction 8/8 + announcement 17/17
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
2026-05-15 17:23:56 +08:00 |
|
seaislee1209
|
08b5e66fbc
|
fix(theme): 浅色下视频上的下载/收藏按钮看不见 — 改回深半透底+白图标
v0.20.0 浅色主题切换时把 --color-bg-on-media 改成 rgba(255,255,255,0.90),
但 --color-on-overlay 没动还是白色 ⇒ 白底白图标完全看不见。
用户实测:生成页面视频卡片 hover 时下载+收藏按钮浅色下都消失。
根因:这俩 var 是给 "video 上的悬浮控件" 设计的,视频帧本身可能任何颜色
(用户上传白雪景 / 深夜景都有),控件必须用 深底+白字 才能在任意视频背景上可读。
这是行业惯例(YouTube/抖音/Bilibili 浅色主题下视频控件也是黑底白字)。
修法:浅色下也用 rgba(0,0,0,0.55) 深半透底,保持 --color-on-overlay 白色不变。
跟 dark 主题语义对称(都是深底白字,只是 alpha 不同)。
涉及:
- GenerationCard.module.css .downloadBtn (生成页卡片下载+收藏)
- VideoDetailModal.module.css 其他 video overlay 用 var 的元素也受益
未改:VideoDetailModal 内部 .floatingBtn / .timeDisplay / .controls 等
硬编码 rgba(255,...) — 那些也是视频 overlay 控件,黑底白字是对的,不动。
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
2026-05-15 17:15:42 +08:00 |
|
seaislee1209
|
aa1a70121a
|
feat(notification): 公告颜色自适应 — 算法 strip 暗色/浅色专用灰度色,彩色保留
Build and Deploy / build-and-deploy (push) Successful in 4m47s
用户反馈:公告里手写的 <div style="color:#e0e0e0"> 在浅色背景下糊;
工具栏只改了"红字""蓝字"按钮(自动适配),自由手写硬编码颜色还是会踩坑。
方案:写 adaptAnnouncementColors helper,sanitize 前预处理 HTML,用算法识别
"灰度系 + 极端亮度(>200 或 <80)"的颜色 → strip 整条声明,让继承主题色;
彩色(三通道差 ≥ 30)一律保留,因为它们通常双主题都可读。
判断细节:
- 用 canvas.fillStyle 解析任意 CSS 颜色值(支持 hex/rgb/rgba/hsl/命名色)
- 灰度判断:max(r,g,b) - min(r,g,b) < 30(允许微偏色)
- 亮度判断:(r+g+b)/3 > 200(浅) 或 < 80(深) 双向 strip
- CSS var / currentColor / inherit / transparent 一律保留(用户已经主题适配过)
- 不止 color,background-color / border-* / outline-color 都覆盖
实际验证(用户给的 HTML 例子):
- div color: #e0e0e0 (224,224,224) → 灰度+亮 → strip ✓
- h2 color: #a78bfa (167,139,250) → 紫色 → 保留 ✓
- span color: #34d399 (52,211,153) → 绿色 → 保留 ✓
- hr border #374151 (55,65,81) → 灰度+暗 → strip ✓
实现:
- 新建 web/src/lib/adaptAnnouncementColors.ts(~110 行,纯 DOMParser+canvas,无依赖)
- AnnouncementModal:sanitize 前调用 adaptAnnouncementColors
- NotificationsPage 展开公告:同上
- SSR 安全:document 不存在时原样返回
smoke 验证:
- 测试公告同时含 #e0e0e0/#374151(灰度,应 strip)+ #a78bfa/#34d399(彩,应留)
- 展开后 page.evaluate 扫 inline style 验证 4 项颜色去留 — 4/4 全过
- announcement-integration-smoke 17/17 (从 13 加 4 项颜色检查)
- v0.20.1-smoke 11/11 + modal-interaction 8/8 + v2-smoke 25/25 + vitest 71/162
UX 影响:
- 超管手写 HTML 用 #e0e0e0 类暗色专用默认色 → 自动 strip,浅深都清晰
- 超管手写 #ff5e5e/#34d399 类彩色 → 保留,两个主题都看得见
- "我自定义了颜色就保留,没自定义按系统主题"语义达成
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
2026-05-15 16:48:46 +08:00 |
|
seaislee1209
|
bd3e80fd58
|
feat(notification): 消息中心改 accordion 模式 + 跳转按钮 + 公告颜色 CSS var 自适应
用户反馈三点:
1. 公告里"红字"/"蓝字"工具按钮硬编码颜色 #ff4d4f / #00b8e6 在浅色下糊
2. 消息中心列表里公告直接渲染完整 HTML,行被撑得很大
3. 点行就自动 navigate(link_url),用户希望"只看不跳",看完主动决定要不要跳转
改动:
a) SettingsPage 公告编辑器颜色按钮(2 个):
- 红字: <span style="color:#ff4d4f"> → var(--color-danger)
- 蓝字: <span style="color:#00b8e6"> → var(--color-primary)
- 分割线 border-top 颜色 #333 → var(--color-border-card)
- 三个都改成 CSS var,自动适配浅/深主题
- title 文案加"(自适应主题)"提示超管
b) NotificationsPage accordion 模式:
- 加 expandedId: number | null state,始终最多 1 条展开
- 折叠态:chip + title + 时间 + 一行剥 HTML 后的纯文本预览(stripAndTruncate, 60 字 '…')
- 展开态:头部 + 下方完整内容(announcement 用 DOMPurify+HTML / 其他 plain) +
link_url 非空时显示【前往查看】按钮(蓝底白字,带箭头 icon)
- chevron icon 旋转 0deg/180deg 视觉指示折叠/展开
- 同 id 再点 → 收起;不同 id → 切换(前一个自动收起)
- 切页时自动重置 expandedId 为 null
c) 点击行为:
- handleRowClick(自动跳) → handleToggle(只 markRead + 切 expandedId)
- 新加 handleJump(url):用户主动点【前往查看】才触发 navigate / window.open(http url)
- 展开区域 onClick 加 stopPropagation 防误触收起
d) smoke test 更新:
- 测试公告内容做长用 EXPANDED-ONLY-MARKER 末尾标记,preview 截断后看不到
- 7.2.0 折叠态 preview 截断验证(marker 不可见)
- 7.2 展开后 marker 可见
- 7.3 再点收起 marker 不再可见
- 用 chip [公告] 文字作为稳定点击锚点(只在头部出现不在展开内容里)
验证:
- typecheck 0 error
- announcement-integration-smoke 13/13(从 10 项扩到 13,加 accordion 路径)
- v0.20.1-smoke 11/11 + v2-smoke 25/25 + modal-interaction 8/8 全过
- vitest 71 fail / 162 pass 与基线一致
GlobalAnnouncementGate 强弹 modal 行为不变(plan §一 7 — 公告强制阅读语义保留)。
重看路径走 sidebar 大铃铛 → 消息中心 → accordion 展开看全文 → 可点【前往查看】跳。
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
2026-05-15 16:24:49 +08:00 |
|
seaislee1209
|
e55a6665f2
|
test+docs(notification): announcement-integration-smoke.mjs 10/10 + plan 归档 + 完成报告
新建 web/test/announcement-integration-smoke.mjs 10 项 E2E:
- 空内容 400 / HTML 发送 200 / fan-out 数 = User 总数
- tudou 拿到未读 / 浏览器自动强弹 modal / 关闭标已读 / 再开不弹
- 消息中心 [公告] chip + HTML 渲染 / 无 console.error
跑通基线对比:
- vitest 71 fail / 162 pass (0 新增回归)
- v2-smoke 25/25 + modal-interaction 8/8 + v0.20.1-smoke 11/11
- 新 announcement-integration-smoke 10/10
归档 plan 文件 docs/todo/通知公告整合.md(v2,本次实施的源 plan)。
写完成报告 docs/todo/通知公告整合-完成报告.md(改动文件清单 + 3 commit hash + 测试结果 + 关键设计决策 + 边缘 case 处理)。
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
2026-05-15 16:04:43 +08:00 |
|
seaislee1209
|
850acf646e
|
refactor(notification): 删 AnnouncementBanner 废弃文件
AnnouncementBanner 在 v0.12.6 公告改 modal 之后就已经废弃了,
VideoGenerationPage L148 注释明确写"公告已改为弹窗,旧的横幅不再显示",
现在公告整合到 Notification 表后,无任何 import 引用,清理。
同步删 AnnouncementBanner.module.css 配套样式。
typecheck 0 error,smoke 全过。
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
2026-05-15 15:59:38 +08:00 |
|
seaislee1209
|
7a503db814
|
feat(notification): 公告整合进 Notification — fan-out + 强弹 Modal + chip + 删 announcement_enabled 概念
之前公告(QuotaConfig.announcement,v0.12.6)与 v0.20.1 消息中心 UX 重叠 —
两个铃铛 + 两套未读 + 两套入口让用户分不清。整合到统一 Notification 表:
后端:
- apps.notifications Notification.TYPE_CHOICES 加 'announcement'
- 新 endpoint POST /api/v1/admin/announcement/publish (IsSuperAdmin)
- body { content: HTML 字符串 }
- 空内容 400 "公告内容不能为空"
- User.objects.all() (含 is_active=False 封禁用户,解封后能看到历史)
- bulk_create(batch_size=500) 防大团队 OOM
- 同步把 content 存档到 QuotaConfig.announcement 作为下次编辑器初始值
- audit log: settings_update, target=announcement
- 重写 GET /announcement 内部查 Notification 表最新未读
- 重写 POST /announcement/read 标记所有未读公告已读
- endpoint 签名不变保持老前端兼容(返回结构相同)
前端:
- App.tsx 顶层挂 <GlobalAnnouncementGate /> — 任意路由有未读公告强弹 modal
必须看(关闭遮罩点击也算关闭→标已读),关闭后 60s 内不再弹
- AnnouncementModal 改成纯展示组件: props { content, onClose },不自己 fetch
HTML 内容用 DOMPurify.sanitize 防 XSS
- 删 VideoGenerationPage 右上角小喇叭 + 旧 AnnouncementModal 自动弹 + 重看路径
(用户重看走 sidebar 大铃铛 → 消息中心)
- SettingsPage 公告区:
- 删 announcement_enabled checkbox(不再有"启用/停用"概念,发了就强弹)
- "保存公告"按钮 → 改 "发送公告" 按钮
- 二次 confirm "确认发送给所有用户?发送后所有人打开页面会强制看到这条公告,无法撤回"
- 调 announcementApi.publish (POST /admin/announcement/publish)
- NotificationsPage 每条加 type chip([公告]/[安全]/[额度]/[系统]) 4 色
announcement type 用 DOMPurify + dangerouslySetInnerHTML 渲染(其他 type 纯文本)
- types/index.ts NotificationType 加 'announcement'
验证:
- 后端 curl 全过:发空 400 / 发 HTML 200 sent_to=21 / tudou GET 拿到未读 / read 后 GET 拿空
- typecheck 0 error
- v0.20.1-smoke 11/11 + modal-interaction 8/8 + v2-smoke 25/25
- vitest 71 fail / 162 pass 与基线 0 新增回归
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
2026-05-15 15:58:38 +08:00 |
|
seaislee1209
|
c54fdda0e8
|
revert(admin): 撤掉 sticky 翻页 — 用户反馈 sticky 让内容在按钮后透视,不对
Build and Deploy / build-and-deploy (push) Successful in 7m10s
sticky bottom: 0 的语义是"natural 位置在视口底之下时贴底,否则正常坐落"。
当 .content 滚动容器下方还有内容时,翻页按钮浮在视口底部,
表格行从它身后滚过 — 视觉上"翻页按钮下面又有表格内容"非常违和。
恢复成普通 .pagination + padding-bottom 兜底,
依赖批次 I 的根因三件套(100dvh + min-height: 0)解决 Safari 翻页被工具栏遮挡:
- 100dvh 保证 .layout 高度 = 用户实际可见区(不被 Safari 工具栏吃掉)
- min-height: 0 保证 .content 内部能正常 overflow-y: auto 滚动
- padding-bottom: 8px 给按钮一点缓冲
用户期望就是"滚动条能滚到最底,翻页按钮可见可点",而不是"翻页按钮固定不动"。
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
2026-05-12 21:42:36 +08:00 |
|
seaislee1209
|
f77d30a4e6
|
fix(admin): 翻页按钮改 sticky 贴底 — 笔记本小屏不用滚到底就能点
v0.20.1 §I 只修了"能滚到能点到",用户期望是"始终可见不用滚"。
4 个 admin 管理页 .pagination 改 position: sticky + bottom: 0 + 背景遮挡,
翻页按钮固定在 .content 滚动容器视口底部,内容长度无关。
z-index: 10 确保 sticky 时压在表格行之上;background var(--color-bg-page)
覆盖透视下方内容(浅/深主题各自适应)。
涉及 4 个 page CSS:RecordsPage / UsersPage / LoginRecordsPage / AuditLogsPage
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
2026-05-12 21:08:30 +08:00 |
|
seaislee1209
|
2f6d3a60cc
|
fix(admin): 超管补「升为主管理员」入口 — 不然主管撤了加不回去
之前只能撤主管不能升,撤完只能新建账号或重建团队,不合理。
后端 admin_team_member_role_view 早就支持 is_team_owner=true(L1254-1260,
设 owner=True 时自动同时 admin=True);前端 setMemberRole API 只传
is_team_admin,从没用过 is_team_owner 参数。
修法:
- lib/api.ts 加 adminApi.setMemberAsOwner(teamId, memberId)
- TeamsPage 副管/成员行 role badge 旁加小灰字 "→主管" 按钮
- 点击 confirm 提示"不会自动降级现有主管,需自己先撤旧主管再升新主管"
(后端没强制约束一团队一主管,降级逻辑交给操作员判断)
UX 流程:
- 主管 → 单击 badge = 撤销 (现有)
- 副管 → 单击 badge = 取消副管(变成员)(现有) + [→主管] 升主管
- 成员 → 单击文字 = 升副管 (现有) + [→主管] 升主管
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
2026-05-12 21:08:30 +08:00 |
|
seaislee1209
|
ed67a27399
|
feat(team): 团管重置成员密码 — 新 API + 严格权限矩阵 + 成员管理页按钮
权限矩阵(plan §G,服务端硬校验,前端按钮只是 UX 不算数):
| 操作者 | 可重置 | 不可重置 |
|----------------|------------------------|---------------------|
| 主管(owner=T) | 同团队副管 + 成员 | 其他主管 / 自己 |
| 副管(admin=T) | 同团队成员 | 副管 / 主管 / 自己 |
后端:
- 新 view team_reset_member_password_view (POST /api/v1/team/members/<id>/reset-password)
- permission IsTeamAdmin(覆盖主管+副管两种)+ 服务端逐层判断:
1. 同团队? (target.team_id != operator.team_id → 403)
2. 不能改自己? (id 相同 → 400)
3. 主管密码须超管? (target.is_team_owner → 403)
4. 副管只有主管能改? (target.is_team_admin && !operator.is_team_owner → 403)
5. 走到这里都是合法 case → 生成 8 位随机密码(secrets+string)+ must_change_password=True
- log_admin_action audit 留痕(action=user_password_reset, after.reset_by=team_admin, operator=...)
- urls.py 加路由
前端:
- lib/api.ts teamApi.resetMemberPassword(memberId) → 返回 { new_password, ... }
- TeamMembersPage.tsx:
- canResetPasswordFor(m) helper 同权限矩阵(主管→副管+成员、副管→成员、不能改自己)
- 成员行 actions 加 "重置密码" 按钮(只在 canResetPasswordFor 为 true 时显示)
- 点击 → window.confirm 二次确认 → API → 弹结果 modal 显示新密码 + 复制按钮
- 结果 modal 用 monospace font 大字 + 浅灰底显示密码,带 ⚠ 提醒"关闭后无法再次查看"
- showToast 反馈复制/失败
后端 6 项 curl 测试全通过:
- T1 主管→副管 200 ✓
- T2 主管→成员 200 ✓
- T3 主管→自己 400 "不能重置自己的密码" ✓
- T4 副管→主管 403 "主管须由超级管理员重置" ✓
- T5 副管→成员 200 ✓
- T6 副管→另一副管 403 "只有主管理员能重置副管理员密码" ✓
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
2026-05-12 18:36:16 +08:00 |
|
seaislee1209
|
c53144b2ac
|
feat(notification): 站内通知系统 — Notification 模型 + 4 个 API + Sidebar 铃铛 + 通知中心页
后端 — 新建 app apps.notifications:
- Notification model:type/title/content/link_url/is_read,索引 (recipient, is_read, -created_at)
- 4 个 endpoint:
- GET /api/v1/notifications/ (列表 + 总未读数,unread_only/page/page_size)
- GET /api/v1/notifications/unread-count (轻量,前端 60s 轮询用)
- PATCH /api/v1/notifications/<id>/read (标单条已读)
- POST /api/v1/notifications/read-all (一键全部已读)
- 严格守 user 隔离:所有查询都 filter(recipient=request.user)
- INSTALLED_APPS 注册 + urls.py include
- migration 0001_initial 应用成功
- MySQL 严格模式:所有 CharField 加 default=''(memory feedback_mysql_default)
后端 — anomaly_detector 集成:
- _RULE_LABELS / _team_admin_recipients() / _notify_user_disabled() / _notify_team_disabled() helper
- process_anomalies 里 _disable_user/_disable_team 之后调对应 notify
- 接收人 = 同团队的主管+副管(is_team_admin OR is_team_owner)
- 用 bulk_create 一次写多条
- try/except 保护:通知失败不阻断封禁主流程
前端:
- types/index.ts:AppNotification / NotificationListResponse(避开浏览器 Web API Notification 冲突)
- lib/api.ts:notificationApi (list/getUnreadCount/markRead/markAllRead)
- store/notification.ts:Zustand store 乐观更新(markRead 先动 UI 再发请求)
- pages/NotificationsPage.tsx:标题 + 全部标记已读按钮 + 未读蓝点 + 相对时间 + 点击跳 link_url + 分页
- App.tsx:/notifications 路由(ProtectedRoute 不限 role)
- Sidebar.tsx(用户 76px):铃铛 SVG + 红点 + 60s 轮询 + visibilitychange 立即刷新
- AdminLayout.tsx(超管 220px):同步加铃铛(本来 sub-agent 只加了用户侧 sidebar,我补全 admin 侧)
测试:
- 新建 web/test/v0.20.1-smoke.mjs:11 项 — 铃铛/红点/跳页/标题/100dvh/min-height:0/调试折叠/poster
- 11/11 通过 + v2-smoke 25/25 + modal-interaction 8/8 全部基线 OK
- 后端 4 endpoint 用 curl 验过:list / unread-count / PATCH read / POST read-all 都正常
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
2026-05-12 18:32:29 +08:00 |
|
seaislee1209
|
6b13cfff70
|
fix(admin): 笔记本 14寸 Safari 翻页按钮被截 — 根因三件套修法
用户报:Mac Safari + 14寸笔记本,/admin/records 翻页按钮永远在屏幕外,
拖动能看到内容超出但滚到底也看不见翻页按钮。
根因三个叠加:
1. 100vh 在 Safari 桌面端不可靠 — 算的是含工具栏/书签栏的 layout viewport,
不是用户实际能看到的 visual viewport。.layout { height: 100vh } 实际比可见高,
底部被 UI bar 盖住。
2. Flex overflow 经典 bug — .content { flex: 1; overflow-y: auto } 没加 min-height: 0,
flex 子默认 min-height: auto 跟随内容撑开,Safari/Chrome 都不让 flex 父约束高度,
导致 overflow-y: auto 形同虚设,.content 不滚动,底部按钮被外层 .layout
overflow:hidden 切掉。
3. 翻页按钮无 padding-bottom — 即使前两个修了,贴着容器边缘也不舒服。
根因修法(不打 padding 补丁):
1. AdminLayout.module.css .layout — 用 100dvh + fallback 100vh
(Dynamic Viewport Height,浏览器动态算可见区域,Safari 17+/Chrome 108+/FF 101+ 支持)
2. AdminLayout.module.css .content — 加 min-height: 0,让 flex 子元素正确 shrink
3. 4 个 admin 管理页 .pagination 加 padding-bottom: 8px(RecordsPage/UsersPage/LoginRecordsPage/AuditLogsPage)
作为视觉缓冲(三件套里最末层的 polish)
4. ProfilePage 同样 100vh 模式 → 加 100dvh fallback,防同款 bug 在用户端复现
为什么不只加 padding-bottom:
- padding 是治标 — 假设 viewport 不会再变,加缓冲就行
- 但 Safari 用户切换 zoom / 显示书签栏 / 切换 fullscreen 时实际可见区会变,
固定 padding 仍会被切
- 100dvh 是浏览器动态计算可见区域,永远准
- min-height: 0 修的是 flex 容器自身的滚动机制,根因层面解决
验收:
- 14寸笔记本 Safari → /admin/records 翻页按钮可见可点 ✓
- 桌面大屏不受影响(dvh == vh == 视口)
- Safari < 17 fallback 到 100vh,行为同改前(不变好也不变差)
- Chrome / Firefox dvh 都支持,正常
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
2026-05-12 18:26:54 +08:00 |
|
seaislee1209
|
11c1cdf8cc
|
fix(records): TeamAssetsPage 重新编辑 prompt 丢失 — VideoDetailModal fallback 补 editorHtml
根因(4 层叠加):
1. TeamAssetsPage 没传 onReEdit prop 给 VideoDetailModal → 走 modal 内部 fallback
2. fallback 只 setPrompt(task.prompt),没设 editorHtml
3. PromptInput 是 contenteditable,渲染依据是 editorHtml,不是 prompt
4. assetVideoToTask 把 editorHtml 显式置 '' → fallback 拿到的就是空串
修法:fallback 跟 store/generation.ts:reEdit 对齐,
用 useInputBarStore.setState 一次性批量灌入所有字段,
关键补 editorHtml: task.editorHtml || task.prompt || '',
让 PromptInput 渲染 + rebuildMentionSpans 走完整路径
(原文 + assetMentions 重建带缩略图的 @ 标签)。
附带顺手补:
- mode 用 switchMode 而不是 setMode(switchMode 会清 keyframe 状态)
- assetMentions: task.assetMentions || []
- references 用 setState 批量传(原代码用 setState 又用 setX 混用,不一致)
影响范围:
- 团管 /team/assets → 任意视频 → "重新编辑" → /app prompt 文本框有内容 ✓
- 超管 /admin/assets → 同上 ✓
- 用户 /user-assets → reEdit 走的是另一条路(generation.ts:reEdit),不受影响
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
2026-05-12 18:24:28 +08:00 |
|
seaislee1209
|
6ee5c8ffdb
|
feat(records): api_prompt 永久留痕 + 详情弹窗调试信息折叠区
后端:
- GenerationRecord 加 api_prompt TextField(blank, default='')
- 0021_add_api_prompt migration
- video_generate_view 计算完 _format_prompt_for_ark 后立即 save api_prompt
(即使 create_task 抛错也保留,方便事后查实际传了什么)
- admin_records / team_records view 各回传 api_prompt 字段
前端:
- AdminRecord 类型加 api_prompt?: string
- RecordDetailModal 详情弹窗右侧底部加"调试信息(开发/客服参考)"折叠区
- 默认收起,小灰字 ▸/▾ toggle
- 仅当 api_prompt && api_prompt !== prompt 才显示"实际发给火山"等宽字 box
(历史记录 api_prompt 为空则不显示这栏)
- 火山 Task ID + 复制按钮(showToast 反馈)
- 失败任务才显示原始错误(raw_error)
- 平时用户察觉不到,客服/财务复盘时点开就能看完整调试信息
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
2026-05-12 18:19:36 +08:00 |
|
seaislee1209
|
72f351d54f
|
feat(records): 视频卡片/详情弹窗用 thumbnail_url 显示首帧 poster
后端 GenerationRecord.thumbnail_url 字段早就被 tasks.py:_handle_completed (L109-111)
通过 ffmpeg 提取首帧 + 上传 TOS 填充,但只在 _serialize_task (生成页) 返回。
admin_records / team_records / profile_records 三个 view 都没回传,前端无从用。
后端:三个 records view 各加一行 'thumbnail_url': r.thumbnail_url or ''
前端:
- types/index.ts AdminRecord 加 thumbnail_url?: string
- 三处 <video> 加 poster={thumbnailUrl ? rewriteTosUrl(...) : undefined}
- RecordDetailModal.tsx (消费记录详情)
- VideoDetailModal.tsx (资产页/生成页详情)
- GenerationCard.tsx (生成页卡片)
效果:卡片首屏直接显示首帧海报(几十 KB),不再等视频 metadata 加载完
才有视觉;hover 触发 v.play() 时再真正下载视频,UX 不变。
老记录 thumbnail_url='' 时 poster=undefined,行为同改前。
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
2026-05-12 18:17:16 +08:00 |
|
seaislee1209
|
e86e3d45b1
|
fix(admin): 主管理员撤销 bug — TeamsPage 主管 badge 加 onClick
之前 L825 主管理员 badge 无 onClick,管理员之前把某成员设为主管后撤不掉,只能后台改 DB。
后端 admin_team_member_role_view 收到 is_team_admin=false 已支持同时清 is_team_owner。
前端补 onClick + confirm + 调 setMemberRole(false) 即可,后端不动。
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
2026-05-12 18:14:38 +08:00 |
|
seaislee1209
|
6d683d4e76
|
fix(records): lightbox/player overlay 关闭误触外层 modal — Portal + stopPropagation
Build and Deploy / build-and-deploy (push) Successful in 11m2s
回归测试发现的真 bug (V1 V2 历史遗留):
点击 ReferenceList 的图片 lightbox 或音视频 player 的 overlay 关闭时,
事件冒泡 + V2 modal backdrop-filter 创建的独立 stacking context 双重作用
导致 RecordDetailModal 也被一起关掉。
根因双重:
1. lightbox/player overlay 的 onClick 没 stopPropagation,
React 合成事件冒泡到外层 modal overlay 触发 onClose
2. V2 modal inner 加了 backdrop-filter: blur 创建独立 stacking context,
lightbox 视觉上 z 10002 全屏覆盖,但实际命中区域被限制在 modal inner 内;
点击 lightbox 视觉外的暗色区域 (modal 外) 直接命中 modal overlay → onClose
修复 (ReferenceList.tsx):
- 用 React.createPortal 把 lightbox + audio-video player overlay 渲染到 document.body
脱离 modal inner 的 backdrop-filter stacking context,真正全屏
- overlay onClick 加 e.stopPropagation() 作为 React 事件链兜底
- playerWrap 内部 stopPropagation 保留(防止点 player 内部冒泡关闭 player)
新增测试 (web/test/modal-interaction.mjs):
专项验证 RecordDetailModal 内部交互, 7 个用例:
1. 任务详情弹窗打开 OK
2. 参考素材区存在 OK
3. thumb 有 title 属性 (V2 加的 tooltip 真生效)
4. 点击 thumb 弹出 lightbox (DOM 验证)
4.1 关闭 lightbox 不连带关 modal (本次 fix 的关键回归)
5. 下载按钮 stopPropagation + 触发下载
6. modal 关闭 ✕ 按钮 OK
7. 全程无 console.error
全套回归通过:
- TS 编译过
- modal-interaction 8/8 (含 lightbox 冒泡修复验证)
- v2-smoke 25/25 (无新挂)
- vitest 71/162 与基线一致 (无新挂)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
2026-05-12 16:31:02 +08:00 |
|
seaislee1209
|
385e1bb49e
|
feat(records): 任务详情弹窗排版优化 — 参考素材搬左侧 + tooltip + max-height
用户反馈:右侧太挤,左侧视频下大片空白浪费,参考素材埋在右侧底部要滚才能看。
排版重组 (RecordDetailModal.tsx):
- 左侧 mediaPanel:视频 + 参考素材(纵向排列)
- 视频固定 480×270 (16:9 + object-fit contain)
- 参考素材区紧贴视频下方,有标题"参考素材(N)"
- 右侧 infoPanel:状态 / 失败原因 / 基本信息 / 提示词
- prompt 区域宽度从挤压改为可读宽度
- 不再有参考素材占下方位置
- 整体不再有"空白左下角"+ "右侧挤死"+"参考要滚"问题
参考素材区限高 (RecordDetailModal.tsx refScrollBox):
- max-height: 250px + overflow-y: auto
- Seedance 单任务最多 9 张图(memory: project_seedance_max_refs),
80px thumb × 5/行 = 1-2 行,250px 兜底有余
- 极端情况(未来扩 audio/video 参考)走内部滚动,不推视频上去
Tooltip (ReferenceList.tsx):
- refItem 加 title={label},hover thumb 弹原生 tooltip 显示完整文件名
- 解决长文件名 "公司年会现场抓拍-高分辨率-竖屏-第3版.jpg" 被省略号截断后看不全的问题
- 零成本(浏览器原生),无 JS 开销
新增 memory:
~/.claude/.../memory/project_seedance_max_refs.md
- 火山 Seedance API 单任务最多 9 张参考图片
- 防止以后又按"上百张"做 UI 假设
验证:
- TS 编译过
- modal screenshot 4 张更新 (docs/screenshots/v2/modal/)
- 视频 + 参考素材左侧贴齐,右侧 prompt 区宽松
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
2026-05-12 15:43:27 +08:00 |
|
seaislee1209
|
a1c16be1ea
|
feat(records): 任务详情弹窗加视频展示 + RGB 故障字失败态
需求: 消费记录 detail modal 加多一个视频展示框
- completed + result_url → 视频播放器
- failed → 失败图标 (RGB 故障字风格)
- processing/queued → spinner
现有信息排版整体搬到右侧,布局不动。
后端 (apps/generation/views.py):
admin_records_view + team_records_view 返回字段加 'result_url'
(model 早就有 result_url, 只是这俩 list API 没暴露)
前端类型 (web/src/types/index.ts):
AdminRecord 加 result_url?: string
RecordDetailModal 重构 (web/src/components/RecordDetailModal.tsx):
- 弹窗宽 560 → 1080 (maxWidth 95vw, maxHeight 85vh)
- body display: flex 双栏
- 左侧 480 固定宽 + 16:9 视频框 + object-fit: contain
(沿用 GenerationCard.resultArea 同一套 sizing 思路,
不同长宽比视频用 contain 居中 + 黑边补足)
- 右侧 flex 1, overflow-y auto, 现有内容整体搬过去排版不动
- 视频用 controls + preload="metadata", 不自动播放, 跟全局音量走
MediaArea 组件分支:
- completed + result_url: <video controls />
- completed - result_url: 视频 icon + "视频已生成" 占位
- failed: FailureGlitch RGB 故障字
- processing/queued: 旋转 spinner + 文字
FailureGlitch 视觉细节:
- 标题 "生成失败" 44px Space Grotesk weight 700
text-shadow: -2px 0 var(--info) cyan, 2px 0 #ff00aa magenta
+ 30px 红色 glow → 模拟 CRT RGB 信号偏移
- 副标题: 错误原因摘要 (truncate 80 char) 等宽字体
- 背景: 红色斜纹 + 顶/底彩色条纹 (red/cyan 间隔) 仿信号丢失
验证 (docs/screenshots/v2/modal/):
completed__{dark,light}.png - 视频框 + 右侧信息
failed__{dark,light}.png - RGB 故障字
TS 编译过, backend 已重启
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
2026-05-12 14:48:23 +08:00 |
|
seaislee1209
|
5f30258112
|
test+fix(theme): V2 smoke test (25/25 pass) + 修复 AdminLayout 缺主题切换按钮
新增功能回归 smoke test 覆盖 V2 改动点:
test/v2-smoke.mjs — 25 个端到端检查,涵盖:
1) LoginPage + LoginModal 浅色渲染 (4 项)
2) admin 真实登录流程 + access_token 持久化 (2 项)
3) 主题切换端到端 — 默认 dark / 点切换 → light / localStorage 持久化 /
刷新保持 / 来回切换 (6 项)
4) Admin 7 个路由侧栏导航 (7 项)
5) 团管 Sidebar + 生成页 InputBar 核心元素 (4 项 + 公告 soft-skip)
收尾: 全程无 console.error / 无 page crash (2 项)
发现并修复 1 个真 bug:
AdminLayout (super admin 后台) 缺主题切换按钮。
原因:V1 只给 components/Sidebar.tsx (76px 团管侧栏) 加了 toggle 按钮,
pages/AdminLayout.tsx (220px super admin 侧栏) 漏了。super admin 无法切主题。
修复:在 AdminLayout sidebarFooter 加月亮/太阳 SVG 切换按钮,
跟 components/Sidebar 一致;收起态只显示 icon,展开态 icon + 文字。
新增 .themeToggle class 样式 (hover bg + primary color)。
验证: tsc 过 / smoke 25/25 / 无 console.error / 无 page crash
本地 commit dev 不 push
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
2026-05-11 21:03:53 +08:00 |
|
seaislee1209
|
0c9aaa11cd
|
fix(theme): V2-fix — 清除残留 30+ 处死黑/死白硬编码 (用户反馈表格线死黑)
用户反馈: UsersPage 表格线浅色下死黑死黑。
全扫定位:V1 sub-agent 当年标注 "unmapped/near-match" 保留的硬编码,
在 V2 浅色重做下集中暴露为"死黑/死白"问题。
新增 5 个主题感知 token:
--color-bg-glass-hover DARK rgba(255,255,255,0.12) / LIGHT rgba(0,0,0,0.06)
--color-border-glass-hover DARK rgba(255,255,255,0.15) / LIGHT rgba(0,0,0,0.12)
--color-bg-thumbnail DARK rgba(0,0,0,0.30) / LIGHT rgba(0,0,0,0.05)
--color-bg-thumbnail-hover DARK rgba(0,0,0,0.15) / LIGHT rgba(0,0,0,0.04)
--color-shadow-thumb DARK rgba(0,0,0,0.30) / LIGHT rgba(0,0,0,0.10)
修复(10 个文件, 30+ 处):
表格死黑线 (用户主诉):
UsersPage / TeamsPage .table td border-bottom: rgba(42,42,56,0.5) 死紫黑
→ var(--color-border-row) (浅色 rgba(0,0,0,0.06) 浅灰)
.table tr:hover td bg: rgba(255,255,255,0.02) 浅色下消失
→ var(--color-bg-row-hover) (浅色 rgba(0,0,0,0.03))
玻璃面 hover 白透 alpha (浅色下死白看不见):
VideoDetailModal closeBtn/floatingBtn/iconBtn hover:
rgba(255,255,255,0.12-0.15) → var(--color-bg-glass-hover)
TeamsPage detailClose hover: 同上
GenerationCard moreBtn:hover border:
rgba(255,255,255,0.15) → var(--color-border-glass-hover)
视频缩略图 bg (浅色下不该死黑):
AssetsPage thumbnail bg / hover overlay: rgba(0,0,0,0.3/0.15)
→ var(--color-bg-thumbnail / -hover)
UniversalUpload thumbInner box-shadow: rgba(0,0,0,0.3)
→ var(--color-shadow-thumb)
Modal/Upload overlay & hover:
UsersPage drawerOverlay: rgba(0,0,0,0.5) → var(--color-overlay-soft)
TeamsPage detailOverlay: rgba(0,0,0,0.65) → var(--color-modal-overlay)
UniversalUpload uploadOverlay: rgba(0,0,0,0.5) → var(--color-overlay-soft)
Upload trigger 虚线边 hover:
KeyframeUpload / UniversalUpload trigger hover: #5a5a6a / #1e1e2a
→ var(--color-border-modal-hover / -bg-modal-hover) (已有 var)
trigger bg: rgba(255,255,255,0.03) → var(--color-bg-upload)
TeamsPage memberTable:
memberTableWrapper bg: rgba(255,255,255,0.03) → var(--color-bg-upload)
memberTable th bg: rgba(255,255,255,0.02) → var(--color-bg-row-hover)
AssetLibraryModal 状态 / 主色 alpha:
addAssetCard hover bg rgba(108,99,255,0.04) → var(--color-primary-bg)
dropZone hover/active: 同上 + var(--color-primary-bg-hover)
statusActive 0.12: rgba(0,184,148,0.12) → var(--color-success-bg)
statusProcessing 0.12: rgba(243,156,18,0.12) → var(--color-warning-bg)
statusFailed 0.12: rgba(231,76,60,0.12) → var(--color-danger-bg)
dropZoneWarning #ff4d4f + alpha → var(--color-danger-hover/-bg/-border)
UniversalUpload uploadError:
rgba(239,68,68,0.25) → var(--color-danger-bg)
ProfilePage warningBanner:
rgba(243,156,18,0.12/0.3) → var(--color-warning-bg / -border)
验证: tsc 通过, vitest 71 fail / 162 pass 与基线一致, 24 张 V2 截图重出
关键页对比 (docs/screenshots/v2/):
- UsersPage / TeamsPage 浅色表格线变浅灰 GitBook 风
- VideoDetailModal close 浅色下能看到 hover
- 视频缩略图浅色不死黑
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
2026-05-11 20:24:24 +08:00 |
|
seaislee1209
|
f8a39d55c7
|
feat(theme): 浅色主题 V2 — 玻璃质感重做 + LandingPage 浅色化 + 双语言系统
Phase A: index.css 完全重写 [data-theme="light"] block
- 玻璃方向修正: bg-card 从 rgba(0,0,0,0.05) 黑透明 → 双 token 拆分
--color-bg-card: #ffffff 实体白 (admin 卡)
--color-bg-glass: rgba(255,255,255,0.65) 透明白玻璃 (sidebar/modal/banner)
- Aurora 浅色不再 display:none, 改 pastel 紫蓝桃 0.20-0.32 alpha
- Inset highlight 方向反转: 浅色用 rgba(255,255,255,0.50) 白高光 (玻璃顶边标志)
- Backdrop-filter 五档标准: --bf-glass-sm/md/lg/xl (12-40px + saturate 140-180%)
- Multi-layer shadow: --shadow-card-light (2 stops) + --shadow-glass-light (3 stops + inset)
- 暖调 chip: --color-chip-warm-* GitBook 公告风格
- 文字主色: #171823 微紫 → #171717 Vercel Black
Phase B: LandingPage + AuroraCanvas 浅色化
- 移除 LandingPage 的 data-theme="dark" 强制 (V1 的回避)
- LandingPage.module.css 21 处颜色全 var 化
- AuroraCanvas: 订阅 useThemeStore, 新 LIGHT_ORBS 数组 pastel 紫蓝桃,
vignette 浅色用白色, grain opacity 减半
Phase C: 13 个玻璃面升级 (3 sub-agent 并行)
- Modal 类 (Login/ForceChange/VideoDetail.infoPanel/RecordDetail/AssetLibrary/
Announcement/Confirm/TeamsPage.detailModal): 接入 bg-modal-glass +
bf-glass-lg/xl + shadow-glass-light (含 inset highlight)
- Bar/Dropdown/Toast (AnnouncementBanner/Toast/Dropdown/Select/DatePicker):
bg-glass-strong + bf-glass-md + inset-highlight
- Sidebar + 生成页 (Sidebar/PromptInput/GenerationCard): glass + 顶边白高光
- AnnouncementBanner 写双套独立 [data-theme] 规则 (CSS gradient 内不能 var alpha)
Phase D: admin 实体卡 multi-layer shadow (13 处, 1 sub-agent)
- DashboardPage / TeamsPage / UsersPage / RecordsPage / AdminAssetsPage /
LoginRecordsPage / AuditLogsPage / ProfilePage / SettingsPage
的 .statCard / .tableWrapper / .chartWrapper / .accordionItem 等
加 var(--shadow-card-light) 双层柔阴影
AdminLayout 修复 (V1 漏的):
- .layout 改 transparent, 让 AmbientBackground pastel aurora 在主区透出
- .sidebar 加 bf-glass-md + inset highlight + 立体阴影
LoginModal / ForceChangePassword 残留 mint 清理:
- submitBtn bg/border/color 用 mint-accent var, 字重 500→600 + 字距 0.04em
- input:focus border 用 var(--color-mint-accent)
- 加 bf-glass-sm + inset highlight
验证:
- TS 编译过
- vitest 71 fail / 162 pass 与 V1 基线完全一致, 无新增回归
- 24 张 V2 截图位于 docs/screenshots/v2/ (本地, .gitignore 排除 png)
完成报告: docs/todo/亮色主题切换V2-完成报告.md
V2 plan: docs/todo/亮色主题切换V2.md
视觉对齐稿: docs/todo/showcase.html
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
2026-05-11 19:46:55 +08:00 |
|
seaislee1209
|
f0f47e8368
|
feat(theme): 亮色主题切换完整实现 — dark/light 双套 var + Sidebar 切换 + 浅色色板
Stage 1 (var 化, 350 处): 425 处硬编码颜色 → CSS var, 涉及 49 个 tsx/css module 文件,
按 hot files (DashboardPage/TeamDashboardPage/RecordDetailModal/ReferenceList) →
Modal/Asset/Profile/Login → 生成页家族/管理后台/公共 UI 三波 8 个 sub-agent 并行处理。
index.css :root 加 ~70 个新 var (modal/text 层级/状态色 bg 变体/chart/mention pill 等)。
Stage 2 (双套 var): :root 保留 DARK 默认值, [data-theme="light"] 覆盖 ~95 个 token。
浅色色板按 Vercel Geist (#fafafa / #171717 / shadow-border) + Linear Light surface 分层规范,
主色 #6c63ff → #5048cc 加深 18% 满足 WCAG AA。aurora 极光在 light 下 display:none。
Stage 3 (切换机制): 新建 store/theme.ts (Zustand + localStorage 持久化),
Sidebar 加月亮/太阳 SVG 切换按钮 (位于头像上方),
DashboardPage/TeamDashboardPage/ProfilePage 的 ECharts 配 key={theme} 强制重渲染。
Stage 4 (微调): LandingPage 强制 data-theme="dark" 保持品牌识别 (登录流程一直深色),
sidebar bg / card bg / border 在浅色下加深 0.02 提升轮廓辨识度。
Stage 5 (验证): Playwright 头无浏览器自动登录 admin + screenshot_user, 截深/浅各 12 个页面 = 24 张
到 docs/screenshots/ (本地档, .gitignore 排除 png 不入库)。
vitest 71fail/162pass 与改造前基线完全一致, 无新增回归。
完成报告: docs/todo/亮色主题切换-完成报告.md
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
2026-05-11 11:10:35 +08:00 |
|
|
|
f101878954
|
feat: 前端预览资源切换到 CDN 域名 airflow-play.airlabs.art
Build and Deploy / build-and-deploy (push) Has been cancelled
新增 rewriteTosUrl 在渲染层把 airdrama-media.tos-cn-beijing.volces.com
替换成 airflow-play.airlabs.art,覆盖 <video>/<audio> src 及 tosThumb
图片缩略;下载仍走原 TOS 直连域名以避开 CDN CORS 配置依赖。
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
2026-04-28 16:11:30 +08:00 |
|
seaislee1209
|
85aa0249b9
|
fix: v0.19.5 生成页慢速往上滚仍跳到底部 — 双层 anchor 叠加根因
Build and Deploy / build-and-deploy (push) Successful in 14m10s
v0.19.4 只修了 useEffect 的 scrollToBottom 误触, 没修 handleScroll
里的 anchor 累加。用户实测: 鼠标滚轮慢速 / 慢拖滑动条往上翻仍会
跳到最底部, 但快速拽到顶不复现。
根因(双层 anchor 叠加)
1. 浏览器自动 scroll anchoring(默认 overflow-anchor: auto): 滚动
容器头部插入内容时浏览器自动 scrollTop += diff, 保持视觉位置
2. handleScroll 里 .then 又手动 el.scrollTop += diff
=> 双倍 anchor, 总位移 = 2 * diff, 把 scrollTop 推到 max(底部)
为什么"快速拽不复现, 慢速复现":
浏览器 scroll anchoring 在用户主动 scroll 期间会暂时关闭。快速
拽到顶后立即 loadMore 完成, 浏览器把"用户刚释放"视作仍在 scroll
跳过自动 anchor, 只手动一次, 不叠加。慢速操作时 scroll event 间
有 100~300ms 静止间隔, 浏览器认为 scroll 已停, 自动 anchor 启动
+ 我们手动 anchor = 双倍。
修复(两道防线)
1. CSS: .contentArea 加 overflow-anchor: none, 彻底关掉浏览器自动
anchor, 由代码统一管 — 这是根因修复
2. handleScroll: 加 loadMoreInFlightRef 防重入 flag, 慢速操作下
多次进入 if 分支只 schedule 一次 anchor; rAF 完成后清 flag —
兜底防御, 避免极端时序下 rAF 累加
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
2026-04-25 14:17:01 +08:00 |
|
seaislee1209
|
10994df952
|
fix: v0.19.4 生成页往上翻加载历史时不再跳到底部
Build and Deploy / build-and-deploy (push) Successful in 4m15s
现象: 用户往上翻看历史, loadMore 往 tasks 数组头部 prepend 老任务后,
页面自动跳到最底, 打断浏览。
根因 VideoGenerationPage.tsx useEffect 依赖 tasks.length, 只要数量
增加就 scrollTo(scrollHeight)。这个逻辑本意是"新任务生成 push 到
末尾时滚到底", 但区分不出"头部 prepend" vs "尾部 push", 于是
loadMore 也触发了滚底。handleScroll 里的 anchor(scrollTop += diff)
本来会做视觉保位, 但 useEffect 的 scrollTo smooth 抢先/盖过它。
修复 改成比末尾 task 的 id 是否变化 (prevLastIdRef) 而非 length
- 新任务 push 到末尾 → 末尾 id 变 → 滚到底 ✅
- 头部加载历史 → 末尾 id 不变 → 保位置 ✅
- 轮询更新任务属性(如生成完成) → 数组内容变但末尾 id 不变 → 不打扰 ✅
- 删除某条任务(非末尾) → 末尾 id 不变 → 不滚 ✅
- 只有"末尾多了新东西"这一种情况才滚底, 符合用户直觉
验证 vitest 71 failed 全部为 v0.18.x 以来的 preexisting 失败
(phase2/phase3 路径解析问题), stash 前后对比完全一致, 本次改动
零新增回归。
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
2026-04-24 20:08:09 +08:00 |
|
seaislee1209
|
17fc3e5652
|
feat: 所有任务展示/列表/导出补「分辨率」字段 — 1080P 可见性完善
Build and Deploy / build-and-deploy (push) Successful in 5m18s
v0.19.0 上线 1080P 后,多处展示只有"比例"没有"分辨率",用户和财务看到
费用差异却不知道是因为分辨率不同。本次补齐:
- VideoDetailModal 视频详情信息栏:模式·模型·时长·比例·【分辨率】·tokens·费用
- RecordDetailModal 消费记录详情弹窗:基本信息加「分辨率」字段
- RecordsPage 超管消费记录 CSV 导出:比例列后加「分辨率」列
- TeamRecordsPage 团管消费记录 CSV 导出:同上
- ProfilePage 个人中心记录列表:右侧费用旁加分辨率小标签(仅当有值)
- types/index.ts: AdminRecord 加 resolution?: Resolution 字段
后端 API 之前已返回 resolution(v0.19.0 的 5 处手工序列化已覆盖 L1751
admin_records / L1815 team_records / L2704 profile_records / L2837/2919
内容资产),前端只需接住展示即可。
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
2026-04-17 22:08:06 +08:00 |
|
seaislee1209
|
27bfa689ce
|
test: 加测试服 E2E — 1080P 分辨率支持线上验证 (8/8 通过)
Build and Deploy / build-and-deploy (push) Successful in 4m29s
针对 airflow-studio.test.airlabs.art 使用团管账号 tudou 真实验证:
- Sidebar「今日剩余次数」文案 + 无钻石图标
- Toolbar 默认 720P
- AirDrama 可切 1080P
- 1080P 下 Fast Dropdown 置灰(UI 不可达 Fast+1080P)
- Fast 下 1080P Dropdown 置灰(反向)
- ProfilePage 预警文案无「今日额度」老称谓
- API 拒绝 Fast+1080P 组合(400 invalid_resolution)
- API 拒绝 adaptive ratio(400)
已跑通,附带 resolution-1080p.spec.ts (本地版,admin 账号,5/5 通过)
和 backend tests (23 unit + 5 integration 全过)。
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
2026-04-17 19:14:28 +08:00 |
|
seaislee1209
|
6b22e1fa3f
|
fix: 用户端文案从「额度」改为具体单位「次数」— 消除点数概念混淆
Build and Deploy / build-and-deploy (push) Successful in 4m5s
- Sidebar 左下角:去钻石图标(避免用户带入即梦/豆包的"点数"概念)+
数据从 daily_seconds (秒数池残留) 改为 daily_generation_limit (次数);
文案 "剩余额度"→"今日剩余次数"(必须写全,用户不猜);
数字字号放大 14→18,tabular-nums 稳定排版
- ProfilePage 预警 banner: "今日额度已使用 X%"→"今日生成次数已用 X%";
"今日额度已用完"→"今日生成次数已用完"
- generation.ts 错误映射: "额度不足,请联系管理员"→
"今日生成次数或团队余额不足,请联系管理员"(两种可能都列出)
秒数池(daily_seconds_limit)是 v0.10.0 计费改次数+金额前的遗留概念,
这次把用户端可见的"额度"全部替换为明确的"生成次数/余额"单位,避免用户
把"额度"理解成即梦/豆包的"点数"来找客服问问题。
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
2026-04-17 19:07:02 +08:00 |
|
seaislee1209
|
39667ff19c
|
feat: v0.19.0 1080P 分辨率支持 — 完整前后端 + 严格计费准确性 + 47 测试
火山 Seedance 2.0 于 2026-04-16 上线 1080P 支持。本次实现前端 UI、
后端校验/计费、数据库迁移,并严格遵守三原则:
1. 禁止兜底/静默降级 — Fast+1080P 组合在 UI/store/serializer/view/计价
五层防御,任一层穿透都 fail loud,不悄悄按 720P 扣费
2. 钱的计算绝对准确 — 前端预估公式与后端 estimate_tokens 完全一致
`(输入时长+输出时长) × 宽 × 高 × fps / 1024`;实际扣费按火山返回
total_tokens × 官方单价;预估端不维护最低 token 修正表
3. 不隐藏 bug — 无 `or '720p'` / `|| '720p'` 兜底;类型严格;异常暴露
## 后端(7 处 + 1 次迁移)
- models.py: QuotaConfig 加 base_token_price_1080p(51)/base_token_price_1080p_video(31);
GenerationRecord.resolution 加 RESOLUTION_CHOICES 约束 + default='720p'
- migrations/0020: 含 RunPython data migration 回填历史 resolution='' → '720p'
- utils/billing.py:
* RESOLUTION_MAP 加 1080P 六种宽高比(21:9 是 2206×946,不是 seedance 1.0 值)
* get_resolution 去掉 tier 默认值,非法组合 raise KeyError 不静默降级
* estimate_tokens 纯官方公式,加 input_video_duration 参数(公式完整)
- utils/airdrama_client.py: create_task 加 resolution 必填参数(无默认值)
- apps/generation/serializers.py:
* VideoGenerateSerializer 加 resolution ChoiceField
* aspect_ratio 改 ChoiceField 显式拒绝 adaptive
* SystemSettingsSerializer 加 2 个 1080P 单价
- apps/generation/views.py:
* _get_token_price 加 resolution 必填参数,Fast+1080P raise ValueError
* _sum_video_duration 累加视频参考时长
* video_generate_view 读 resolution、400 拒绝 Fast+1080P 组合、
传给 get_resolution/estimate_tokens/_get_token_price/create_task/
GenerationRecord.resolution(移除 L450 硬编码 '720p')
* _settle_payment 按 record.resolution 取单价(1080P 结算按 1080P 价)
* _serialize_task + 5 处手工序列化加 resolution 字段(无 `or '720p'`)
- apps/accounts/views.py: team 接口返回 token_price_1080p/_video
## 前端(10 处)
- types/index.ts: Resolution 类型;GenerationTask/BackendTask/Team/
QuotaConfig/AssetVideo 加字段(全部必填,无 optional)
- store/inputBar.ts: resolution state;setModel/setResolution 双向拦截
Fast+1080P 组合,toast 提示引导,不静默降级
- store/generation.ts: addTask/backendToFrontend/reEdit/regenerate 全链路
携带 resolution;mapErrorMessage 改 '今日生成次数或团队余额不足'
- components/Toolbar.tsx:
* 加分辨率选择器 Dropdown(位置:比例和时长之间)
* modelItems/resolutionItems 双向 disabled(Fast 下 1080P 灰 / 1080P 下 Fast 灰)
* estimatedTokens 对齐后端公式(含输入视频时长 + assetMentions 视频时长)
* estimatedCost 按 resolution 选单价(Fast→fast_*、1080p→1080p_*、其他→基础)
* tooltip 明示"实际费用以火山 API 返回的 token 数为准"
- components/Dropdown.tsx: 加 disabled 属性支持
- components/VideoDetailModal.tsx: 重新编辑恢复 resolution
- components/GenerationCard.tsx: 动态显示 task.resolution.toUpperCase()
- pages/SettingsPage.tsx: 加 2 个 1080P 单价输入框(独立分组)
- pages/AdminAssetsPage.tsx / TeamAssetsPage.tsx: 去 || '720p' 兜底
- lib/api.ts: videoApi.generate 参数 resolution 必填
## 测试(47 个用例)
### 后端(28 个)
- tests/test_1080p_billing.py(23): RESOLUTION_MAP 像素、estimate_tokens
公式(含/不含输入视频、不做最低 token 修正)、_get_token_price 六种
组合、Fast+1080P 抛异常、calculate_cost 对齐官方示例 4.97 / 12.39 元
- tests/test_1080p_api.py(5): video_generate_view 拒绝 Fast+1080P (400)
+ 拒绝 adaptive + 拒绝非法 resolution + 默认值兼容 + 合法组合通过
### 前端(19 个)
- test/unit/resolution1080p.test.ts(14): store 状态、双向拦截
(1080P 下切 Fast 被阻止 model 不变、反向同样)、官方像素契约测试、
价格示例对齐(720P 4.97 / 1080P 12.39)
- test/e2e/resolution-1080p.spec.ts(5): 真实浏览器验证默认 720P、
Dropdown 双向置灰、tooltip 明示以火山为准
## 与官方文档对齐
- 参数:resolution (480p/720p/1080p 小写)、ratio、duration、generate_audio
- 像素:来自 docs/API文档/创建视频生成任务API.md Seedance 2.0 & 2.0 fast 列
- 单价:来自 docs/API文档/seedance模型价格.md (46/28/51/31/37/22)
- Fast 不支持 1080P:来自 docs/API文档/Seedance 2.0 1080P.md
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
2026-04-17 19:06:45 +08:00 |
|
seaislee1209
|
dafdc8983f
|
fix: v0.18.3 版权报错友好提示 + 图片删除即梦式连续重命名
Bug 1: 版权限制错误友好提示
- ERROR_MESSAGES 加 OutputVideoSensitiveContentDetected.PolicyViolation 映射
- 漫威等知名 IP 触发的版权拦截不再显示英文 raw error
Bug 2: 图片删除后同类型引用连续重命名(即梦逻辑)
- inputBar.ts::removeReference 重写:删除后同类型剩余引用按顺序 1/2/3 连续编号
- 用 DOMParser 同步更新 editorHtml 里对应 data-ref-id 的 @mention span textContent
- 缩略图区和提示词栏同步刷新,避免"两个图片2"命名冲突
验证
- 11 个 Vitest 单元测试覆盖图片/视频/音频删除、空 editorHtml、无 @mention、
连续快速删除等边界场景
- 3 个 Playwright E2E 真实浏览器验证:上传 3 张图 → 删中间 → 再上传 → 编号不冲突
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
2026-04-17 18:03:36 +08:00 |
|
seaislee1209
|
2281c64ee8
|
fix: 音频不能作为唯一参考素材 — 前端校验 + toast 提示
Build and Deploy / build-and-deploy (push) Successful in 6m0s
Seedance API 不支持"纯音频"和"文本+音频"输入,必须搭配图片或视频。
- canSubmit() 校验同时检查 references 和 assetMentions
- Toolbar 点击禁用按钮时弹出 toast 提示原因
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
2026-04-14 14:10:39 +08:00 |
|
seaislee1209
|
0b770340c8
|
fix: 修复资产页素材库引用不可查看 + 重新编辑素材泄漏
Build and Deploy / build-and-deploy (push) Successful in 4m52s
1. AdminAssetsPage/TeamAssetsPage: asset:// 协议 URL 改用 thumb_url 显示缩略图
2. generation.ts reEdit/regenerate: 过滤 isAssetRef,素材库引用不混入 references 数组
3. PromptInput extractText: 实时同步 assetMentions store,删除 @标签后不再残留旧数据
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
2026-04-13 18:33:08 +08:00 |
|
seaislee1209
|
5972f45784
|
fix: 素材库引用缩略图烂图 + pollStatus 跨项目素材保护
Build and Deploy / build-and-deploy (push) Has been cancelled
- MentionTag: onError fallback,缩略图加载失败显示视频/图片占位图标
- createMentionSpan/VideoDetailModal: img onError 隐藏烂图
- buildReferenceSnapshots: 素材库引用用 thumb_url 做 previewUrl
- isAssetRef 标记防止视频缩略图被 <video> 渲染、重编辑防重复
- pollStatus: 已 active 的素材跳过远程查询,防止跨项目素材被误删
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
2026-04-13 11:11:12 +08:00 |
|
seaislee1209
|
4b2dd9ef5e
|
fix: 音频 ♫ 符号溢出到 prompt 文本 — 改用 CSS ::before 渲染
createMentionSpan 里音频的 ♫ 之前用 textContent 设置,
被 extractText() 的 el.textContent 读进了 prompt 纯文本,
导致 renderPromptWithMentions 匹配后留下额外的 ♫ 字符。
改用 CSS ::before content 渲染,不参与 textContent,
prompt 里不再有多余的 ♫。
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
2026-04-04 22:17:07 +08:00 |
|
seaislee1209
|
5da67435b2
|
fix: v0.18.1 用户测试 8 项 Bug 修复
Build and Deploy / build-and-deploy (push) Has been cancelled
- MutationObserver 立刻同步 editorHtml(删 @ 标签后时长/数量立即重置)
- parseAssetMentionsFromDOM 从 DOM 实时读取(不用 stale state)
- renderPromptWithMentions 支持音频 ♫ + 视频首帧 + assetType
- rebuildMentionSpans 按 label 长度降序匹配(防子串冲突)
- 删除素材后 group 缩略图优先找图片/视频(不用音频 URL)
- 素材组整组删除功能(后端 DELETE + 前端按钮)
- Celery poll 架构重构(一次性任务 + recover_stuck_tasks 统一驱动)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
2026-04-04 21:06:02 +08:00 |
|
seaislee1209
|
ba33c35dd8
|
add test
Build and Deploy / build-and-deploy (push) Successful in 4m13s
|
2026-04-04 19:38:36 +08:00 |
|
|
|
6353d2ec4f
|
feat: rename /assets route to /user-assets
Build and Deploy / build-and-deploy (push) Has been cancelled
|
2026-04-04 19:29:31 +08:00 |
|