feat(admin): embed slot management with code snippet

P4b Task 5:/system/embed-slots 管理页(Task 8/9 页面模式:useCursorList
游标累积 + 对话框治理)。列表(名称/绑定相册/启停/configVersion/更新时间,
启停开关行内 PATCH → configVersion+1 toast 提示)+ 新建/编辑对话框
(设置表单位置/尺寸/间隔/语言/默认收起,intervalMs 契约区间夹取;编辑按
整体替换语义提交完整 settings)+ 绑定相册对话框(相册标题/ID 搜索选中 →
确认框携带 A22 文案「configVersion 将递增,官网无需改动」;清空即解绑)+
嵌入代码卡({EMBED_ORIGIN}/embed/v1/loader.js + data-slot 令牌,一键复制;
EMBED_ORIGIN=VITE_EMBED_ORIGIN 缺省本机 viewer dev,.env.example 登记)。

- API 层 src/api/embedSlots.ts + types/api/embedSlot.d.ts(契约镜像)
- vitest 9 例:表单→载荷(缺省+夹取)、编辑载荷收敛、snippet 拼接、
  环境基址兜底、绑定变更分类、zh/en 键一致 + A22 短语断言
- Playwright e2e(真实 cn+ov 后端):新建→绑定(gov-photos 直种 +
  seed-fixture 新增 sync-flush 模式经 bootstrap/真实发送管线投影 photos_ov)
  →嵌入代码卡含 token→ov 公开 feed 返回相册照片→换绑 configVersion+1 +
  feed 切换(A22)→停用 feed 404;admin playwright 配置补 ov webServer
- i18n zh/en(embedSlotsManage.* + menus.system.embedSlots)

Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
浅念
2026-09-20 23:25:59 +08:00
parent 8f2c21d25a
commit 0238f505c4
17 changed files with 1452 additions and 8 deletions
+83 -1
View File
@@ -234,7 +234,8 @@
"title": "Accounts & Sync",
"users": "Account Management",
"userSync": "User Sync",
"usage": "Usage"
"usage": "Usage",
"embedSlots": "Embed Slots"
},
"albums": {
"title": "Albums",
@@ -700,6 +701,87 @@
"colCreatedAt": "Recorded At"
}
},
"embedSlotsManage": {
"list": {
"title": "Embed Slots (floating carousel mount points)",
"create": "New Slot",
"loadMore": "Load More",
"noMore": "All loaded",
"empty": "No embed slots yet; click \"New Slot\" to create one",
"loadFailed": "Failed to load embed slots",
"retry": "Retry",
"colName": "Name",
"colAlbum": "Bound Album",
"colEnabled": "Enabled",
"colConfigVersion": "Config Version",
"colUpdatedAt": "Updated At",
"colActions": "Actions",
"unbound": "Unbound",
"actionEdit": "Edit",
"actionBind": "Bind Album",
"actionCode": "Embed Code"
},
"form": {
"nameLabel": "Name",
"namePlaceholder": "e.g. Homepage floating slot",
"nameRequired": "Please enter a slot name",
"submit": "Save",
"positionLabel": "Position",
"positionLeft": "Left",
"positionRight": "Right",
"sizeLabel": "Size",
"sizeSm": "Small (narrow-screen tier)",
"sizeMd": "Standard",
"intervalLabel": "Interval",
"intervalUnit": "ms (3000-30000)",
"langLabel": "Language",
"langAuto": "Follow album",
"langZh": "Chinese",
"langEn": "English",
"collapsedLabel": "Collapsed by default",
"collapsedHint": "Show a small handle first; click to expand"
},
"create": {
"title": "New Embed Slot",
"created": "Slot created (embed code available via \"Embed Code\")"
},
"edit": {
"title": "Edit Embed Slot",
"noChanges": "No changes",
"saved": "Saved (configVersion increased to {version})"
},
"bind": {
"title": "Bind Album: {name}",
"currentLabel": "Current",
"targetLabel": "Target album",
"searchPlaceholder": "Search by album title or ID",
"searching": "Searching…",
"noMatch": "No matching album",
"clearHint": "Clear the selection and submit to unbind (public feed returns 404)",
"versionHint": "Binding changes only increase configVersion; no change is needed on the host site (the widget only knows the token)",
"confirmTitle": "Confirm binding change",
"confirmBind": "After binding, configVersion will increase and no change is needed on the host site; the widget will show the album's recent photos.",
"confirmRebind": "After rebinding, configVersion will increase and no change is needed on the host site; the widget will switch to the new album's recent photos.",
"confirmUnbind": "After unbinding, configVersion will increase and no change is needed on the host site; the public feed will go offline immediately (404).",
"actionBind": "Bind",
"actionRebind": "Rebind",
"actionUnbind": "Unbind",
"saved": "Binding updated (configVersion increased to {version})"
},
"code": {
"title": "Embed Code: {name}",
"tokenLabel": "Slot token (data-slot)",
"snippetLabel": "Embed code (paste into the host site page)",
"copy": "Copy",
"copied": "Embed code copied",
"hint": "Paste the code into the host page to mount the floating carousel; later rebinding or toggling never requires changing this code.",
"close": "Close"
},
"toast": {
"enabledOn": "Enabled (configVersion increased to {version})",
"enabledOff": "Disabled (configVersion increased to {version}; public feed returns 404)"
}
},
"apiMsg": {
"AUTH_INVALID_CREDENTIALS": "Invalid account or password",
"AUTH_RATE_LIMITED": "Too many failed login attempts; try again later",
+83 -1
View File
@@ -234,7 +234,8 @@
"title": "账号与同步",
"users": "账号管理",
"userSync": "用户同步",
"usage": "用量统计"
"usage": "用量统计",
"embedSlots": "展示位管理"
},
"albums": {
"title": "相册管理",
@@ -700,6 +701,87 @@
"colCreatedAt": "记录时间"
}
},
"embedSlotsManage": {
"list": {
"title": "展示位管理(悬浮轮播组件挂载点)",
"create": "新建展示位",
"loadMore": "加载更多",
"noMore": "已全部加载",
"empty": "暂无展示位,点击「新建展示位」创建",
"loadFailed": "展示位列表加载失败",
"retry": "重试",
"colName": "名称",
"colAlbum": "绑定相册",
"colEnabled": "启用",
"colConfigVersion": "配置版本",
"colUpdatedAt": "更新时间",
"colActions": "操作",
"unbound": "未绑定",
"actionEdit": "编辑",
"actionBind": "绑定相册",
"actionCode": "嵌入代码"
},
"form": {
"nameLabel": "名称",
"namePlaceholder": "如:官网首页悬浮位",
"nameRequired": "请输入展示位名称",
"submit": "保存",
"positionLabel": "悬浮位置",
"positionLeft": "左侧",
"positionRight": "右侧",
"sizeLabel": "组件尺寸",
"sizeSm": "小(窄屏降级档)",
"sizeMd": "标准",
"intervalLabel": "轮播间隔",
"intervalUnit": "毫秒(3000-30000)",
"langLabel": "内容语言",
"langAuto": "跟随相册",
"langZh": "中文",
"langEn": "英文",
"collapsedLabel": "默认收起",
"collapsedHint": "初始仅展示小把手,点击展开"
},
"create": {
"title": "新建展示位",
"created": "展示位已创建(嵌入代码可在「嵌入代码」查看)"
},
"edit": {
"title": "编辑展示位",
"noChanges": "未修改任何内容",
"saved": "已保存(configVersion 已递增至 {version})"
},
"bind": {
"title": "绑定相册:{name}",
"currentLabel": "当前绑定",
"targetLabel": "目标相册",
"searchPlaceholder": "输入相册标题或 ID 搜索",
"searching": "搜索中…",
"noMatch": "无匹配相册",
"clearHint": "清空选择并提交即解绑(公开端随即 404)",
"versionHint": "绑定变更仅递增 configVersion,已嵌入的官网无需改动(组件只认令牌)",
"confirmTitle": "确认绑定变更",
"confirmBind": "绑定相册后 configVersion 将递增,官网无需改动;组件将展示该相册的近期照片。",
"confirmRebind": "换绑相册后 configVersion 将递增,官网无需改动;组件将切换展示新相册的近期照片。",
"confirmUnbind": "解绑后 configVersion 将递增,官网无需改动;组件公开端将立即下线(404)。",
"actionBind": "绑定",
"actionRebind": "换绑",
"actionUnbind": "解绑",
"saved": "绑定已更新(configVersion 已递增至 {version})"
},
"code": {
"title": "嵌入代码:{name}",
"tokenLabel": "展示位令牌(data-slot)",
"snippetLabel": "嵌入代码(粘贴到官网宿主页)",
"copy": "复制",
"copied": "已复制嵌入代码",
"hint": "将代码粘贴到官网页面即可挂载悬浮轮播;后续换绑相册、启停均无需改动该代码。",
"close": "关闭"
},
"toast": {
"enabledOn": "已启用(configVersion 已递增至 {version})",
"enabledOff": "已停用(configVersion 已递增至 {version},公开端随即 404)"
}
},
"apiMsg": {
"AUTH_INVALID_CREDENTIALS": "账号或密码错误",
"AUTH_RATE_LIMITED": "登录失败次数过多,请稍后再试",