编制:2026-06-01
适用范围:Web 浏览器 + Electron 桌面端(xiuxiu_0.0.7_amd64.deb)+ Android APK(如远程加载)
修复难度:⭐(1 行代码)
执行时间:5 分钟(热修方式)/ 30 分钟(重构镜像方式)
风险等级:极低
影响范围:仅图片/视频/音频元素的右键菜单(从"无菜单"恢复"浏览器原生菜单")
容器内 /app/tailchat/client/web/src/App.tsx 第 113-119 行,守一 260408 自研改动:
// 守一改后的当前版本
onContextMenu={(e) => {
const target = e.target as HTMLElement;
const tagName = target.tagName.toLowerCase();
if (tagName !== "input" && tagName !== "textarea" && !target.isContentEditable) {
e.preventDefault(); // ← 这一行:非输入框元素全部阻止浏览器右键
}
}}
Bug 链条:
1. App.tsx 全局 onContextMenu 拦截
2. 白名单只允许 input / textarea / contenteditable 元素的右键
3. <img> <video> <audio> 元素的 tagName 不在白名单 → e.preventDefault() 阻止浏览器原生菜单
4. 兜底的 CustomContextMenuHost 是命令式 API,必须有代码主动调用 openContextMenu()
5. 没有任何代码为图片元素主动调 openContextMenu → 菜单根本不弹出
/static/* 图片路径反代正确,没有阻止图片访问)/static/ 路径,不是 minio 内部 URL)文件:client/web/src/App.tsx(源码)/ /app/tailchat/client/web/src/App.tsx(容器内)
@@ -113,7 +113,9 @@
onContextMenu={(e) => {
const target = e.target as HTMLElement;
const tagName = target.tagName.toLowerCase();
- if (tagName !== "input" && tagName !== "textarea" && !target.isContentEditable) {
+ if (tagName !== "input" && tagName !== "textarea"
+ && tagName !== "img" && tagName !== "video" && tagName !== "audio"
+ && !target.isContentEditable) {
e.preventDefault();
}
}}
核心改动:
- 白名单从原来的 3 个(input / textarea / contenteditable)
- 扩展为 6 个(+ img / video / audio)
- 媒体元素的右键不再被 preventDefault → 浏览器原生菜单正常弹出
适用:金祥/守一直接在狗云生产环境快速修复
# Step 1: SSH 到狗云
ssh root@45.153.129.198
# 密码:qwTQjUTy5cAbi
# Step 2: 从 service-core 容器把当前文件拷出来
docker cp tailchat-deploy-service-core-1:/app/tailchat/client/web/src/App.tsx /tmp/App.tsx
# Step 3: 备份原文件(用于回滚)
cp /tmp/App.tsx /tmp/App.tsx.bak-260601
# Step 4: 修改文件(手动 vim 或下面 sed 一行搞定)
# 推荐手动 vim 确认(防 sed 转义出错)
vim /tmp/App.tsx
# 找到这一行:
# if (tagName !== "input" && tagName !== "textarea" && !target.isContentEditable) {
# 改成:
# if (tagName !== "input" && tagName !== "textarea"
# && tagName !== "img" && tagName !== "video" && tagName !== "audio"
# && !target.isContentEditable) {
# :wq 保存退出
# Step 5: 拷回容器
docker cp /tmp/App.tsx tailchat-deploy-service-core-1:/app/tailchat/client/web/src/App.tsx
# Step 6: 进容器重新 build web bundle
docker exec tailchat-deploy-service-core-1 sh -c "cd /app/tailchat/client/web && pnpm run build 2>&1 | tail -20"
# Step 7: 重启 service-core 容器(让 nginx 重新读取新的静态文件)
docker restart tailchat-deploy-service-core-1
# Step 8: 等 30 秒容器起来
sleep 30
docker ps | grep tailchat-deploy-service-core-1
风险:跟现有 xiuxiu:v2.5.11-renderfix 镜像一样是热修方式(不固化镜像)。如果将来 docker compose down → up,会丢失这个修复(同 ima [v3 镜像欠债] 现状)。需要让金祥下次重构镜像时一并固化进去。
适用:要彻底固化进镜像、避免热修隐患
# Step 1: SSH 到镜像源母机
ssh ubuntu@81.70.146.110
# 密码:Lidazhi123(参考 /opt/tailchat-deploy/sync-xiuxiu.sh)
# Step 2: 找到 TailChat 源码目录
# 大概率在 /home/ubuntu/tailchat-source 或类似位置
# Step 3: 修改 client/web/src/App.tsx
vim tailchat/client/web/src/App.tsx
# 同 Step 4 of 方式 1,改 onContextMenu 白名单
# Step 4: 本地测试构建
cd tailchat
pnpm install
pnpm run build:web
# Step 5: 构建新镜像
docker build -t xiuxiu:v2.5.12-rightclick .
# Step 6: 导出镜像,传给狗云部署机
docker save xiuxiu:v2.5.12-rightclick | gzip > /tmp/xiuxiu-v2.5.12.tar.gz
scp /tmp/xiuxiu-v2.5.12.tar.gz root@45.153.129.198:/tmp/
# Step 7: 在狗云部署机加载镜像 + 切换
ssh root@45.153.129.198
gunzip -c /tmp/xiuxiu-v2.5.12.tar.gz | docker load
cd /opt/tailchat-deploy
# Step 8: 备份 docker-compose.yml
cp docker-compose.yml docker-compose.yml.bak-260601-pre-v2.5.12
# Step 9: 把镜像版本改成 v2.5.12-rightclick
sed -i 's|xiuxiu:v2.5.11-renderfix|xiuxiu:v2.5.12-rightclick|g' docker-compose.yml
# Step 10: 重启
docker compose up -d
⚠️ 方式 2 风险提示:如果母机上的 TailChat 源码没有同步守一所有 13 条自研改动(A1 手机回车 / A2 Windows 右键 / A3 输入草稿 / B3 多选 / B4 转发 / B5 置顶 / B6 typing / E1 联系人置顶 等),重构镜像会丢失这些热修!
先验证:母机源码里 App.tsx 是否已包含 CustomContextMenuHost 引用 + 守一其他自研改动。如果没有,必须先把所有热修补齐到源码,再构建镜像。
适用:金祥的开发机有 Git 仓库,按规范流程提交
# 在开发机上:
cd /path/to/tailchat-source
# 创建 patch
cat > /tmp/fix-right-click-image-260601.patch <<'EOF'
diff --git a/client/web/src/App.tsx b/client/web/src/App.tsx
--- a/client/web/src/App.tsx
+++ b/client/web/src/App.tsx
@@ -113,7 +113,9 @@
onContextMenu={(e) => {
const target = e.target as HTMLElement;
const tagName = target.tagName.toLowerCase();
- if (tagName !== "input" && tagName !== "textarea" && !target.isContentEditable) {
+ if (tagName !== "input" && tagName !== "textarea"
+ && tagName !== "img" && tagName !== "video" && tagName !== "audio"
+ && !target.isContentEditable) {
e.preventDefault();
}
}}
EOF
# 应用 patch
git apply /tmp/fix-right-click-image-260601.patch
# 验证
git diff client/web/src/App.tsx
# commit
git add client/web/src/App.tsx
git commit -m "fix(contextmenu): 放行 img/video/audio 元素的右键菜单 (#260601)
修复用户右键图片无法弹出'图片另存为'菜单的问题。
守一 260408 F3 自研全局 contextMenu 白名单未含媒体元素,
本 commit 在原 input/textarea/contenteditable 基础上加入 img/video/audio
让媒体元素恢复浏览器原生右键菜单。
不影响:A1 回车换行 / A2 Win 右键复制 / A3 输入草稿 / B3-B6 等其他自研
"
# 然后按规范流程:build → push → deploy
Ctrl + Shift + R(Windows)/ Cmd + Shift + R(Mac)强制刷新清缓存桌面端是 mainWindow.loadURL(config.webUrl) 远程加载 chat.lidazhiai.com 的 web 内容,因此 Web 端修好后桌面端只需:
Ctrl+R)| 项 | 修改前 | 修改后 | 是否"改变现有功能" |
|---|---|---|---|
| 图片右键菜单 | 无 | 浏览器原生菜单 | ❌ 否(从"无"变"有",不算改变) |
| 视频/音频右键菜单 | 无 | 浏览器原生菜单 | ❌ 否 |
| 输入框右键(A2 守一修复) | 浏览器原生 | 浏览器原生(不变) | ✅ 不变 |
| 消息文字右键 | 守一自定义 CustomContextMenu | 同前不变 | ✅ 不变 |
| 群组列表右键 | 守一自定义 | 同前不变 | ✅ 不变 |
| 其他 UI 元素右键 | 守一自定义 | 同前不变 | ✅ 不变 |
| 守一 A1 手机回车换行 | 已修 | 同前 | ✅ 不变 |
| 守一 A3 Windows 输入草稿 | 已修 | 同前 | ✅ 不变 |
| 守一 B3 消息多选 | 已自研 | 同前 | ✅ 不变 |
| 守一 B4 消息转发 | 已自研 | 同前 | ✅ 不变 |
| 守一 B5 消息置顶 | 已自研 | 同前 | ✅ 不变 |
| 守一 B6 typing 状态 | 已自研 | 同前 | ✅ 不变 |
| 守一 E1 联系人置顶 | 已自研 | 同前 | ✅ 不变 |
| 数据库 / 配置 / 推送 / Bot / 文件上传 | — | 完全不动 | ✅ 不变 |
| 15 个 plugin 状态 | 未启用 | 未启用 | ✅ 不变 |
→ 真正"改变"的就只有图片/视频/音频的右键菜单从"无"变"有",是新增功能而非改变现有功能。
如果修改后出现问题,立刻按以下回滚:
ssh root@45.153.129.198
docker cp /tmp/App.tsx.bak-260601 tailchat-deploy-service-core-1:/app/tailchat/client/web/src/App.tsx
docker exec tailchat-deploy-service-core-1 sh -c "cd /app/tailchat/client/web && pnpm run build 2>&1 | tail -5"
docker restart tailchat-deploy-service-core-1
sleep 20
curl -s https://chat.lidazhiai.com/ | head -3
ssh root@45.153.129.198
cd /opt/tailchat-deploy
cp docker-compose.yml.bak-260601-pre-v2.5.12 docker-compose.yml
docker compose up -d
ima 笔记里 [v3 镜像欠债] 提到守一 13 条自研修改都是 docker cp 热修,未固化进镜像。本次方案 A 沿用同一模式,不引入新的固化方式,避免"半固化半热修"的复杂状态。
建议:金祥下次重构镜像时(参考方式 2),把以下全部修改一并写入源码:
1. A1 手机端回车=换行
2. A2 Windows 右键复制(输入框)
3. A3 Windows 输入草稿不丢失
4. A4 对话宽度
5. B2 @ 功能修复
6. B3 消息多选
7. B4 消息转发
8. B5 消息置顶
9. B6 时间显示 + typing 状态
10. C1-C4 Bot 自动建群 / 文转播客 Bot
11. D1-D2 文档发布 / MP3
12. E1 联系人置顶
13. E2 界面渲染优化
14. ★ 本次方案 A:img/video/audio 右键白名单
固化后镜像版本建议命名:xiuxiu:v2.5.13-fullhotfix-260615(含日期)
如果是金祥执行本次修复,建议这样做:
[ ] 1. 看完本文档(重点:第二章 Diff + 第三章方式 1)
[ ] 2. 确认修改 App.tsx 第 113-119 行 onContextMenu
[ ] 3. 备份原文件 /tmp/App.tsx.bak-260601
[ ] 4. SSH 到狗云,按方式 1 执行
[ ] 5. 按第四章验证 Web 浏览器端
[ ] 6. 守一/测试同事用桌面 .deb 验证
[ ] 7. 把这次修复记到 ima 笔记 [v3 镜像欠债清单] 第 14 项
[ ] 8. 如有任何异常立刻回滚(第六章)
/tmp/tailchat-ima/260601-右键图片下载修复方案.mdhttps://xiuxiu3.qootc2.xyz/260601-右键图片下载修复方案.html~/xiuxiu3-backup/260531-1717/260601-右键图片下载修复方案.md本次修复纯前端,不涉及 plugin 启用。如果未来要同时启用插件(如 com.msgbyte.iam RBAC / com.msgbyte.notify 桌面通知),需另外做:
# 启用 service-all-plugins 容器(当前未启)
cd /opt/tailchat-deploy
docker compose up -d service-all-plugins service-openapi tailchat-admin
但这是另一个话题,不在本方案范围。