fix(web): 关注我们图片多 URL 回退(public→promotion),移除部署路径提示;Nginx 增加 /social/

Made-with: Cursor
This commit is contained in:
whm
2026-03-22 01:15:20 +08:00
parent 66b873d0b0
commit 5ff300d0f7
6 changed files with 76 additions and 28 deletions

View File

@@ -63,7 +63,7 @@
</el-form-item>
<el-form-item label="保留原文件名">
<el-switch v-model="uploadPreserveFilename" />
<span class="form-hint">开启后覆盖同名文件产品视频请上传到 <code>promotion/social/</code>英文文件名见仓库 <code>web/promotion/social/README.md</code></span>
<span class="form-hint">开启后将按原文件名保存同名文件会被覆盖</span>
</el-form-item>
<el-form-item label="允许下载">
<el-switch v-model="uploadDownloadable" />

View File

@@ -27,6 +27,12 @@ server {
try_files $uri =404;
}
# web/public/social/ → 关注我们二维码等(挂载 /var/www/yh-public
location ^~ /social/ {
alias /var/www/yh-public/social/;
add_header Cache-Control "public, max-age=300";
}
# 推广素材:文件必须真实存在,禁止落到 SPA index.html否则前端误判「静态存在」、视频拿到 HTML
# 宿主机 ./web/promotion 挂载覆盖本路径,改 social 下文件无需重新 npm build
location ^~ /promotion/ {

View File

@@ -25,6 +25,11 @@ server {
try_files $uri =404;
}
location ^~ /social/ {
alias /var/www/yh-public/social/;
add_header Cache-Control "public, max-age=300";
}
location ^~ /promotion/ {
try_files $uri =404;
add_header Cache-Control "public, max-age=86400";

View File

@@ -1,15 +1,12 @@
# `public/` 静态资源目录
# `web/public/` 静态资源
[Vite 约定](https://cn.vitejs.dev/guide/assets.html#the-public-directory):此目录下的文件会**原样**复制到构建后的站点根目录`dist/`),访问路径为 **`/<文件名>`**
构建后会出现在站点根路径`/logo.png`Docker 下整个目录挂载为容器内 `/var/www/yh-public`
## 当前文件
## 可选子目录 `social/`
| 文件 | 用途 |
|------|------|
| `logo.png` | **标签页图标**`index.html``rel="icon"` / `apple-touch-icon`+ **首页与宣传册导航**`Home.vue``BrochurePage.vue` |
「关注我们」二维码可放在 **`public/social/`**,访问路径为 **`/social/文件名`**。
若此处没有对应文件,前端会自动尝试 **`/promotion/social/`**`web/promotion/social/` 挂载)。
更新品牌时替换本目录下的 `logo.png` 即可。
## 根目录文件示例
**Docker 部署**`docker-compose` 已将 **`web/public` → 容器内 `/var/www/yh-public`** 只读挂载,`deploy/web/default.conf` 中配置了「根路径下单段静态文件名优先读挂载、否则回退 `dist`」。**改宿主机 `web/public/` 内文件后无需 `npm run build`**,刷新即可(注意浏览器缓存)。
**注意**:引用时用**根路径** `/logo.png`,不要用 `src/assets` 的 import 方式写 public 资源。
- `logo.png`:导航栏与 favicon`index.html``Home.vue`

View File

@@ -1,34 +1,38 @@
import { promotionUrl } from '../utils/promotionAssets'
/**
* 「关注我们」素材统一放在 web/promotion/social/
* 线上访问:/promotion/social/<文件名>(与视频同源部署
* 「关注我们」二维码图加载顺序(前者 404 时 img @error 自动换下一项):
* 1. /social/<file> → web/public/social/Docker 挂载 /var/www/yh-public/social
* 2. /<file> → web/public/ 根目录
* 3. /promotion/social/<file> → web/promotion/social/(挂载或 dist
*/
const social = (file) => promotionUrl(`social/${file}`)
export function socialFollowImageCandidates(filename) {
return [`/social/${filename}`, `/${filename}`, promotionUrl(`social/${filename}`)]
}
export const PROMOTION_SOCIAL_FOLLOW = [
{
id: 'xiaohongshu',
label: '小红书',
image: social('xiaohongshu.png'),
imageCandidates: socialFollowImageCandidates('xiaohongshu.png'),
href: 'https://www.xiaohongshu.com/'
},
{
id: 'douyin',
label: '抖音',
image: social('douyin.png'),
imageCandidates: socialFollowImageCandidates('douyin.png'),
href: 'https://www.douyin.com/'
},
{
id: 'wechat-official',
label: '公众号',
image: social('wechat-official.png'),
imageCandidates: socialFollowImageCandidates('wechat-official.png'),
href: 'https://mp.weixin.qq.com/'
},
{
id: 'wechat-channels',
label: '视频号',
image: social('wechat-channels.jpg'),
imageCandidates: socialFollowImageCandidates('wechat-channels.jpg'),
href: 'https://channels.weixin.qq.com/'
}
]

View File

@@ -135,7 +135,6 @@
<section class="social-section" id="social-follow">
<h3 class="social-title">关注我们</h3>
<p class="social-desc">点击按钮查看二维码图片资源统一部署在 <code class="social-code">/promotion/social/</code> 即可线上访问</p>
<div class="social-links">
<button
v-for="item in socialFollowItems"
@@ -145,7 +144,15 @@
@click="openSocialFollow(item)"
>
<span class="social-pill-icon-wrap">
<img :src="item.image" :alt="item.label" class="social-pill-icon" width="32" height="32" loading="lazy" />
<img
:src="item.imageCandidates[0]"
:alt="item.label"
class="social-pill-icon"
width="32"
height="32"
loading="lazy"
@error="(e) => onSocialFollowListImgError(e, item)"
/>
</span>
<span class="social-pill-label">{{ item.label }}</span>
</button>
@@ -212,7 +219,6 @@
</div>
</section>
<!-- 产品视频web/promotion/social/ 下英文文件名 promotionVideos.js -->
<section class="video-section" id="videos">
<div class="section-title">
<h2>产品视频</h2>
@@ -297,7 +303,13 @@
<button type="button" class="close-modal" aria-label="关闭" @click="closeSocialFollow">&times;</button>
<h4 class="social-qr-title">{{ socialQrTitle }}</h4>
<div class="social-qr-frame">
<img v-show="socialQrOpen && socialQrImage" :src="socialQrImage" :alt="socialQrTitle" class="social-qr-img" />
<img
v-show="socialQrOpen && socialQrImage"
:src="socialQrImage"
:alt="socialQrTitle"
class="social-qr-img"
@error="onSocialQrImgError"
/>
</div>
<a
v-if="socialQrHref"
@@ -361,6 +373,9 @@ const socialQrOpen = ref(false)
const socialQrTitle = ref('')
const socialQrImage = ref('')
const socialQrHref = ref('')
/** 二维码大图:与列表相同的多 URL 回退 */
const socialQrCandidates = ref([])
const socialQrCandidateIdx = ref(0)
const defaultData = () => ({
logo_text: 'YUHENG ONE',
@@ -524,7 +539,9 @@ function closeVideoModal() {
function openSocialFollow(item) {
socialQrTitle.value = item.label
socialQrImage.value = item.image
socialQrCandidates.value = item.imageCandidates || []
socialQrCandidateIdx.value = 0
socialQrImage.value = item.imageCandidates?.[0] || ''
socialQrHref.value = item.href || ''
socialQrOpen.value = true
}
@@ -533,6 +550,29 @@ function closeSocialFollow() {
socialQrOpen.value = false
socialQrImage.value = ''
socialQrHref.value = ''
socialQrCandidates.value = []
socialQrCandidateIdx.value = 0
}
/** public /social、public 根路径无图时依次尝试 /promotion/social/ */
function onSocialFollowListImgError(e, item) {
const img = e.target
const list = item.imageCandidates || []
let i = Number(img.dataset.fi || 0)
if (i + 1 < list.length) {
img.dataset.fi = String(i + 1)
img.src = list[i + 1]
}
}
function onSocialQrImgError() {
const list = socialQrCandidates.value
let i = socialQrCandidateIdx.value
if (i + 1 < list.length) {
i += 1
socialQrCandidateIdx.value = i
socialQrImage.value = list[i]
}
}
function toggleFaq(i) {
@@ -949,13 +989,9 @@ onUnmounted(() => {
.social-section { padding: 72px 20px; text-align: center; position: relative; z-index: 10; }
.social-title {
font-family: 'Exo 2', sans-serif; font-size: clamp(20px, 3vw, 26px); margin-bottom: 12px;
font-family: 'Exo 2', sans-serif; font-size: clamp(20px, 3vw, 26px); margin-bottom: 28px;
color: var(--star-white); letter-spacing: 4px;
}
.social-desc {
font-size: 12px; color: rgba(255,255,255,0.4); margin-bottom: 28px; max-width: 520px; margin-left: auto; margin-right: auto;
}
.social-code { font-family: ui-monospace, Consolas, monospace; font-size: 11px; color: rgba(0, 212, 255, 0.8); }
.social-links { display: flex; justify-content: center; gap: 14px; flex-wrap: wrap; align-items: center; }
.social-pill {
display: inline-flex; align-items: center; gap: 10px;