diff --git a/admin/src/views/files/FileManage.vue b/admin/src/views/files/FileManage.vue
index ebaecfd..557492e 100644
--- a/admin/src/views/files/FileManage.vue
+++ b/admin/src/views/files/FileManage.vue
@@ -63,7 +63,7 @@
- 开启后覆盖同路径同名文件;首页「产品视频」须上传到 promotion/视频发布/… 并开启此项
+ 开启后覆盖同名文件;产品视频请上传到 promotion/social/(英文文件名,见仓库 web/promotion/social/README.md)
diff --git a/pull-and-restart.sh b/pull-and-restart.sh
index 117844a..db3dada 100755
--- a/pull-and-restart.sh
+++ b/pull-and-restart.sh
@@ -288,18 +288,18 @@ echo "构建 web 前端 -> deploy/web/dist ..."
run_sudo docker run --rm -v "$ROOT/web:/app" -v "$ROOT/deploy/web/dist:/out" -w /app \
"${REGISTRY_MIRROR}node:20-alpine" sh -c "rm -rf /out/* 2>/dev/null; (npm ci --legacy-peer-deps 2>/dev/null || npm install --legacy-peer-deps) && npm run build && cp -r dist/. /out/"
-# 官网访问的是 Nginx 根目录 deploy/web/dist,不是源码 web/;必须把 /promotion/ 静态资源拷进 dist,否则 social、logo 等一律 404
-echo "同步 web/promotion -> deploy/web/dist/promotion(排除 .mov,大视频请走后台 API)..."
+# 官网访问的是 Nginx 根目录 deploy/web/dist;产品视频已放在 social/ 英文文件名,须整目录同步(含 .mov)
+echo "同步 web/promotion -> deploy/web/dist/promotion(排除旧「视频发布」与 PPT 解压,避免重复大文件)..."
mkdir -p "$ROOT/deploy/web/dist/promotion"
if command -v rsync >/dev/null 2>&1; then
- rsync -a --exclude='*.mov' --exclude='*.MOV' --exclude='_pptx_extract/' \
+ rsync -a --exclude='_pptx_extract/' --exclude='视频发布/' \
"$ROOT/web/promotion/" "$ROOT/deploy/web/dist/promotion/"
else
mkdir -p "$ROOT/deploy/web/dist/promotion/social"
cp -a "$ROOT/web/promotion/social/." "$ROOT/deploy/web/dist/promotion/social/" 2>/dev/null || true
[ -f "$ROOT/web/promotion/logo.png" ] && cp -a "$ROOT/web/promotion/logo.png" "$ROOT/deploy/web/dist/promotion/" || true
[ -f "$ROOT/web/promotion/index.html" ] && cp -a "$ROOT/web/promotion/index.html" "$ROOT/deploy/web/dist/promotion/" || true
- echo "提示: 未检测到 rsync,仅复制了 social/logo 等;完整 promotion 请安装 rsync 后重跑本脚本。" >&2
+ echo "提示: 未检测到 rsync,仅复制了 social/logo 等;请安装 rsync 以同步完整 promotion(含视频)。" >&2
fi
echo "构建 admin 前端 -> deploy/admin/dist ..."
diff --git a/restart.sh b/restart.sh
index fc2308a..f464825 100755
--- a/restart.sh
+++ b/restart.sh
@@ -197,6 +197,18 @@ mkdir -p "$ROOT/deploy/web/dist" "$ROOT/deploy/admin/dist" "$ROOT/deploy/api"
echo "构建 web 前端 -> deploy/web/dist ..."
run_sudo docker run --rm -v "$ROOT/web:/app" -v "$ROOT/deploy/web/dist:/out" -w /app \
"${REGISTRY_MIRROR}node:20-alpine" sh -c "rm -rf /out/* 2>/dev/null; (npm ci --legacy-peer-deps 2>/dev/null || npm install --legacy-peer-deps) && npm run build && cp -r dist/. /out/"
+# 与 pull-and-restart 一致:文档根是 deploy/web/dist,须把 promotion(含 social 视频)拷入 dist
+echo "同步 web/promotion -> deploy/web/dist/promotion ..."
+mkdir -p "$ROOT/deploy/web/dist/promotion"
+if command -v rsync >/dev/null 2>&1; then
+ rsync -a --exclude='_pptx_extract/' --exclude='视频发布/' \
+ "$ROOT/web/promotion/" "$ROOT/deploy/web/dist/promotion/"
+else
+ mkdir -p "$ROOT/deploy/web/dist/promotion/social"
+ cp -a "$ROOT/web/promotion/social/." "$ROOT/deploy/web/dist/promotion/social/" 2>/dev/null || true
+ [ -f "$ROOT/web/promotion/logo.png" ] && cp -a "$ROOT/web/promotion/logo.png" "$ROOT/deploy/web/dist/promotion/" || true
+ [ -f "$ROOT/web/promotion/index.html" ] && cp -a "$ROOT/web/promotion/index.html" "$ROOT/deploy/web/dist/promotion/" || true
+fi
echo "构建 admin 前端 -> deploy/admin/dist ..."
run_sudo docker run --rm -v "$ROOT/admin:/app" -v "$ROOT/deploy/admin/dist:/out" -w /app \
"${REGISTRY_MIRROR}node:20-alpine" sh -c "rm -rf /out/* 2>/dev/null; (npm ci --legacy-peer-deps 2>/dev/null || npm install --legacy-peer-deps) && npm run build && cp -r dist/. /out/"
diff --git a/scripts/sync-video-assets-to-social.ps1 b/scripts/sync-video-assets-to-social.ps1
new file mode 100644
index 0000000..16310bc
--- /dev/null
+++ b/scripts/sync-video-assets-to-social.ps1
@@ -0,0 +1,31 @@
+# 将 web/promotion/视频发布 中文路径素材复制到 web/promotion/social(英文文件名)
+# 用法:在项目根 powershell 执行 .\scripts\sync-video-assets-to-social.ps1
+$ErrorActionPreference = "Stop"
+$Root = [System.IO.Path]::GetFullPath((Join-Path $PSScriptRoot ".."))
+$Src = Join-Path $Root "web\promotion\视频发布"
+$Dst = Join-Path $Root "web\promotion\social"
+New-Item -ItemType Directory -Force -Path $Dst | Out-Null
+
+function Copy-IfExists($fromRel, $toName) {
+ $from = Join-Path $Src $fromRel
+ $to = Join-Path $Dst $toName
+ if (Test-Path -LiteralPath $from) {
+ Copy-Item -LiteralPath $from -Destination $to -Force
+ Write-Host "OK $toName"
+ } else {
+ Write-Warning "SKIP (缺失): $from"
+ }
+}
+
+Copy-IfExists "宇恒一号操作计算软件实例(一)\宣传片-封面.jpg" "video-calc-demo-1-cover.jpg"
+Copy-IfExists "宇恒一号操作计算软件实例(一)\宣传片.mov" "video-calc-demo-1.mov"
+Copy-IfExists "宇恒一号操作计算软件实例(二)\宇恒一号操作计算软件实例(二)-封面.jpg" "video-calc-demo-2-cover.jpg"
+Copy-IfExists "宇恒一号操作计算软件实例(二)\宇恒一号操作计算软件实例(二).mov" "video-calc-demo-2.mov"
+Copy-IfExists "宇恒一号AIWord简介\宇恒一号AIWord简介-封面.jpg" "video-aiword-cover.jpg"
+Copy-IfExists "宇恒一号AIWord简介\宇恒一号AIWord简介.mov" "video-aiword.mov"
+Copy-IfExists "宇恒一号语音办公实例\宇恒一号语音办公实例-封面.jpg" "video-voice-office-cover.jpg"
+Copy-IfExists "宇恒一号语音办公实例\宇恒一号语音办公实例.mov" "video-voice-office.mov"
+Copy-IfExists "宇恒一号,AI 全自动办发票\宇恒一号,AI 全自动办发票-封面.jpg" "video-invoice-ai-cover.jpg"
+Copy-IfExists "宇恒一号,AI 全自动办发票\宇恒一号,AI 全自动办发票.mov" "video-invoice-ai.mov"
+
+Write-Host "完成。Linux 服务器上建议在 social 目录执行: chmod -R a+rX ."
diff --git a/scripts/sync-video-assets-to-social.sh b/scripts/sync-video-assets-to-social.sh
new file mode 100644
index 0000000..e867368
--- /dev/null
+++ b/scripts/sync-video-assets-to-social.sh
@@ -0,0 +1,45 @@
+#!/usr/bin/env bash
+# 将旧目录「视频发布」中含中文路径的素材复制到 web/promotion/social/,使用与 promotionVideos.js 一致的英文文件名。
+# 用法:在项目根执行 ./scripts/sync-video-assets-to-social.sh
+# 完成后可设置权限(Linux):chmod -R a+rX web/promotion/social
+set -euo pipefail
+ROOT="$(cd "$(dirname "$0")/.." && pwd)"
+SRC="$ROOT/web/promotion/视频发布"
+DST="$ROOT/web/promotion/social"
+mkdir -p "$DST"
+
+copy_if () {
+ local from="$1" to="$2"
+ if [[ -f "$from" ]]; then
+ cp -f "$from" "$to"
+ echo "OK $to"
+ else
+ echo "SKIP (缺失): $from" >&2
+ fi
+}
+
+# 操作与计算软件实例(一)
+copy_if "$SRC/宇恒一号操作计算软件实例(一)/宣传片-封面.jpg" "$DST/video-calc-demo-1-cover.jpg"
+copy_if "$SRC/宇恒一号操作计算软件实例(一)/宣传片.mov" "$DST/video-calc-demo-1.mov"
+
+# 操作与计算软件实例(二)
+copy_if "$SRC/宇恒一号操作计算软件实例(二)/宇恒一号操作计算软件实例(二)-封面.jpg" "$DST/video-calc-demo-2-cover.jpg"
+copy_if "$SRC/宇恒一号操作计算软件实例(二)/宇恒一号操作计算软件实例(二).mov" "$DST/video-calc-demo-2.mov"
+
+# AI Word
+copy_if "$SRC/宇恒一号AIWord简介/宇恒一号AIWord简介-封面.jpg" "$DST/video-aiword-cover.jpg"
+copy_if "$SRC/宇恒一号AIWord简介/宇恒一号AIWord简介.mov" "$DST/video-aiword.mov"
+
+# 语音办公
+copy_if "$SRC/宇恒一号语音办公实例/宇恒一号语音办公实例-封面.jpg" "$DST/video-voice-office-cover.jpg"
+copy_if "$SRC/宇恒一号语音办公实例/宇恒一号语音办公实例.mov" "$DST/video-voice-office.mov"
+
+# 办发票(目录名含全角逗号)
+copy_if "$SRC/宇恒一号,AI 全自动办发票/宇恒一号,AI 全自动办发票-封面.jpg" "$DST/video-invoice-ai-cover.jpg"
+copy_if "$SRC/宇恒一号,AI 全自动办发票/宇恒一号,AI 全自动办发票.mov" "$DST/video-invoice-ai.mov"
+
+if command -v chmod >/dev/null 2>&1; then
+ chmod -R a+rX "$DST" 2>/dev/null || true
+ echo "已执行 chmod -R a+rX $DST"
+fi
+echo "完成。请确认 deploy 脚本会把 web/promotion 同步到 deploy/web/dist/promotion(含 social 下 .mov)。"
diff --git a/web/promotion/README.md b/web/promotion/README.md
index 4be3470..89f0674 100644
--- a/web/promotion/README.md
+++ b/web/promotion/README.md
@@ -1,7 +1,7 @@
# 推广素材(首页与视频源)
- `index.html`:静态完整落地页参考;线上 Vue 首页已与之对齐,素材路径以本目录为准。
-- `视频发布/`:产品视频与封面。**`.mov` 等大文件默认不入 Git**;生产环境请在 **后台 → 文件管理** 上传到 `promotion/视频发布/…`,勾选 **保留原文件名**(路径与文件名见 `视频发布/README.md`)。官网解析到站点后,首页通过 `/api/web/sites/{site_id}/promotion-media/视频发布/...` 拉取。本地开发仍可将文件放在本目录,走 `/promotion/视频发布/...`。
+- `social/`:产品视频与封面已与 **二维码图** 同目录,使用 **英文文件名**(见 `social/README.md`)。线上 URL:`/promotion/social/video-*.mov`。后台上传到 `promotion/social/`,勾选 **保留原文件名**。旧「视频发布」中文路径可用 `scripts/sync-video-assets-to-social.sh` 一键复制并改名。
- `social/`:**关注我们** 统一资源包(建议只用此目录上线),首页读取:
- `social/xiaohongshu.png`、`social/douyin.png`、`social/wechat-official.png`、`social/wechat-channels.jpg`
- 若需换图,直接替换 `social/` 下对应文件(ASCII 文件名利于网关与日志)。
@@ -9,9 +9,9 @@
## 生产部署
-1. **关键**:线上 Docker/Nginx 的文档根是 **`deploy/web/dist/`**,浏览器请求 `https://域名/promotion/social/xxx.png` 实际读的是 **`deploy/web/dist/promotion/social/xxx.png`**。仅把文件放在源码 **`web/promotion/`**(例如 `/www/yh_web/web/promotion/`)**不会出现**,必须同步进 `dist`(`pull-and-restart.sh` 在 `npm run build` 后会自动 `rsync web/promotion` → `deploy/web/dist/promotion`,并排除 `.mov`)。
-2. **权限**:若手动拷贝,目录建议 `755`、文件至少 `644`,避免 `root` 拥有且 `640` 导致 Nginx(非 root 用户)读不到。
-3. **视频**:推荐 **后台上传** 到 `data/uploads/.../promotion/`,由 `/api/web/sites/.../promotion-media/` 提供;勿只放在源码目录指望自动对外访问。
+1. **关键**:线上文档根是 **`deploy/web/dist/`**,`/promotion/social/...` 对应 **`deploy/web/dist/promotion/social/...`**。`pull-and-restart.sh` 会把 **`web/promotion/`** 整体 rsync 到 dist,**包含 `social/*.mov`**;仅排除 **`视频发布/`** 与 **`_pptx_extract/`**(避免重复大文件)。改完素材后务必重新执行部署脚本或手动 rsync。
+2. **权限**:目录建议 `755`、文件 `644`(或 `chmod -R a+rX promotion/social`),避免 `640` 导致 Nginx worker 读不到(表现为 404/403)。
+3. **视频**:可静态放在 **`social/`** 英文文件名;也可用 **后台上传** `promotion/social/`,由 `/api/web/sites/.../promotion-media/` 提供,二者与 `promotionVideos.js` 的路径一致即可。
---
diff --git a/web/promotion/social/README.md b/web/promotion/social/README.md
new file mode 100644
index 0000000..ebd0433
--- /dev/null
+++ b/web/promotion/social/README.md
@@ -0,0 +1,38 @@
+# `promotion/social/`
+
+统一放 **关注我们二维码** 与 **首页产品视频** 的封面/视频,**仅用英文名、小写、连字符**,避免 URL 编码与网关问题。
+
+## 关注我们(已有)
+
+| 文件 | 说明 |
+|------|------|
+| `xiaohongshu.png` | 小红书 |
+| `douyin.png` | 抖音 |
+| `wechat-official.png` | 公众号 |
+| `wechat-channels.jpg` | 视频号 |
+
+## 产品视频(与 `promotionVideos.js` 一致)
+
+| 文件 | 说明 |
+|------|------|
+| `video-calc-demo-1-cover.jpg` / `video-calc-demo-1.mov` | 操作与计算(一) |
+| `video-calc-demo-2-cover.jpg` / `video-calc-demo-2.mov` | 操作与计算(二) |
+| `video-aiword-cover.jpg` / `video-aiword.mov` | AI Word |
+| `video-voice-office-cover.jpg` / `video-voice-office.mov` | 语音办公 |
+| `video-invoice-ai-cover.jpg` / `video-invoice-ai.mov` | 办发票 |
+
+线上访问示例:`https://你的域名/promotion/social/douyin.png`
+(须将 `web/promotion` 同步到 **`deploy/web/dist/promotion`**,见 `pull-and-restart.sh`。)
+
+## 从旧「视频发布」目录迁移
+
+若本地仍有中文子目录下的素材,在项目根执行:
+
+```bash
+./scripts/sync-video-assets-to-social.sh
+```
+
+## 后台上传
+
+目录:`promotion/social/`,上传上表文件名,勾选 **保留原文件名**。API 路径为
+`/api/web/sites//promotion-media/social/<文件名>`。
diff --git a/web/promotion/视频发布/README.md b/web/promotion/视频发布/README.md
index c51b992..ae5900c 100644
--- a/web/promotion/视频发布/README.md
+++ b/web/promotion/视频发布/README.md
@@ -1,26 +1,11 @@
-# 产品视频目录
+# 视频发布(旧结构,可选)
-大体积 **`.mov` 视频不入 Git**。线上由官网 **后台 → 文件管理** 上传到站点下的 `promotion/视频发布/...`,与首页「产品视频」使用的路径一致。
+产品视频已迁移到 **`../social/`** 下 **英文文件名**,与 `web/src/data/promotionVideos.js` 一致。
-## 上传步骤
+迁移命令(在项目根):
-1. 打开 **后台**,进入 **文件管理 → 功能模块**。
-2. 选择 **官网站点**(与系统设置中的「官网」站点一致)。
-3. 新建或使用目录:`promotion/视频发布/<子目录名>/`(与下列清单一致)。
-4. 上传对应 **封面 `.jpg`** 与 **视频 `.mov`** 时,请勾选 **「保留原文件名」**,文件名必须与下列清单完全一致(否则首页无法匹配)。
+```bash
+./scripts/sync-video-assets-to-social.sh
+```
-## 文件清单(相对 `promotion/`)
-
-| 子目录 | 封面文件 | 视频文件 |
-|--------|----------|----------|
-| `视频发布/宇恒一号操作计算软件实例(一)` | `宣传片-封面.jpg` | `宣传片.mov` |
-| `视频发布/宇恒一号操作计算软件实例(二)` | `宇恒一号操作计算软件实例(二)-封面.jpg` | `宇恒一号操作计算软件实例(二).mov` |
-| `视频发布/宇恒一号AIWord简介` | `宇恒一号AIWord简介-封面.jpg` | `宇恒一号AIWord简介.mov` |
-| `视频发布/宇恒一号语音办公实例` | `宇恒一号语音办公实例-封面.jpg` | `宇恒一号语音办公实例.mov` |
-| `视频发布/宇恒一号,AI 全自动办发票` | `宇恒一号,AI 全自动办发票-封面.jpg` | `宇恒一号,AI 全自动办发票.mov` |
-
-(若表内「封面」文件名与代码中 `web/src/data/promotionVideos.js` 不一致,以代码中的 `relCover` / `relVideo` 为准。)
-
-## 本地开发
-
-若仅在本地调试,仍可将视频放在本目录,Vite 开发服务器会通过 `/promotion/...` 直接读本地文件。
+之后请使用 **`promotion/social/`** 维护素材;本目录可仅作本地备份或留空。
diff --git a/web/src/data/promotionVideos.js b/web/src/data/promotionVideos.js
index bf0526c..aa9bfc8 100644
--- a/web/src/data/promotionVideos.js
+++ b/web/src/data/promotionVideos.js
@@ -1,43 +1,46 @@
import { promotionUrl, promotionMediaApiUrl } from '../utils/promotionAssets'
-const ROOT = '视频发布'
+const SOCIAL = 'social'
-/** 相对 `promotion/` 的路径;与后台上传目录 promotion/视频发布/… +「保留原文件名」一致 */
+/**
+ * 产品视频与封面统一使用 promotion/social/ 下英文文件名(无空格、无中文路径),便于线上 URL 与 Nginx。
+ * 静态:/promotion/social/xxx.mov;后台上传路径:promotion/social/ + 下列文件名,勾选「保留原文件名」。
+ */
export const PROMOTION_VIDEOS_BASE = [
{
id: 'calc-demo-1',
title: '操作与计算软件实例(一)',
desc: '宇恒一号宣传片',
- relCover: `${ROOT}/宇恒一号操作计算软件实例(一)/宣传片-封面.jpg`,
- relVideo: `${ROOT}/宇恒一号操作计算软件实例(一)/宣传片.mov`
+ relCover: `${SOCIAL}/video-calc-demo-1-cover.jpg`,
+ relVideo: `${SOCIAL}/video-calc-demo-1.mov`
},
{
id: 'calc-demo-2',
title: '操作与计算软件实例(二)',
desc: '进阶操作与计算演示',
- relCover: `${ROOT}/宇恒一号操作计算软件实例(二)/宇恒一号操作计算软件实例(二)-封面.jpg`,
- relVideo: `${ROOT}/宇恒一号操作计算软件实例(二)/宇恒一号操作计算软件实例(二).mov`
+ relCover: `${SOCIAL}/video-calc-demo-2-cover.jpg`,
+ relVideo: `${SOCIAL}/video-calc-demo-2.mov`
},
{
id: 'aiword',
title: '宇恒一号 AI Word 简介',
desc: 'AI Word 能力介绍',
- relCover: `${ROOT}/宇恒一号AIWord简介/宇恒一号AIWord简介-封面.jpg`,
- relVideo: `${ROOT}/宇恒一号AIWord简介/宇恒一号AIWord简介.mov`
+ relCover: `${SOCIAL}/video-aiword-cover.jpg`,
+ relVideo: `${SOCIAL}/video-aiword.mov`
},
{
id: 'voice',
title: '语音办公实例',
desc: '语音驱动办公流程',
- relCover: `${ROOT}/宇恒一号语音办公实例/宇恒一号语音办公实例-封面.jpg`,
- relVideo: `${ROOT}/宇恒一号语音办公实例/宇恒一号语音办公实例.mov`
+ relCover: `${SOCIAL}/video-voice-office-cover.jpg`,
+ relVideo: `${SOCIAL}/video-voice-office.mov`
},
{
id: 'invoice',
title: 'AI 全自动办发票',
desc: '发票场景自动化演示',
- relCover: `${ROOT}/宇恒一号,AI 全自动办发票/宇恒一号,AI 全自动办发票-封面.jpg`,
- relVideo: `${ROOT}/宇恒一号,AI 全自动办发票/宇恒一号,AI 全自动办发票.mov`
+ relCover: `${SOCIAL}/video-invoice-ai-cover.jpg`,
+ relVideo: `${SOCIAL}/video-invoice-ai.mov`
}
]
diff --git a/web/src/utils/promotionAssets.js b/web/src/utils/promotionAssets.js
index 546d0ff..4db7269 100644
--- a/web/src/utils/promotionAssets.js
+++ b/web/src/utils/promotionAssets.js
@@ -12,7 +12,7 @@ export function promotionUrl(relativePath) {
/**
* 官网产品视频/封面:读取站点 uploads 下 `promotion/` 目录(与后台上传路径一致)
* @param {string} siteId Mongo 站点 id(与 /api/web/routes 返回的 site_id 一致)
- * @param {string} relativePath 相对 promotion/ 的路径,如 `视频发布/xxx/yyy.mov`
+ * @param {string} relativePath 相对 promotion/ 的路径,如 `social/video-aiword.mov`
*/
export function promotionMediaApiUrl(siteId, relativePath) {
if (!siteId) return promotionUrl(relativePath)
diff --git a/web/src/views/BrochurePage.vue b/web/src/views/BrochurePage.vue
index d42897c..299accc 100644
--- a/web/src/views/BrochurePage.vue
+++ b/web/src/views/BrochurePage.vue
@@ -29,21 +29,15 @@
@@ -532,6 +526,7 @@ watch(
text-decoration: none;
font-size: 13px;
transition: background 0.2s, color 0.2s;
+ cursor: pointer;
}
.toc-item:hover {
background: rgba(0, 212, 255, 0.1);
diff --git a/web/src/views/Home.vue b/web/src/views/Home.vue
index 0f0c25c..292e70a 100644
--- a/web/src/views/Home.vue
+++ b/web/src/views/Home.vue
@@ -209,7 +209,7 @@
-
+