fix(web): 关注我们图片多 URL 回退(public→promotion),移除部署路径提示;Nginx 增加 /social/
Made-with: Cursor
This commit is contained in:
@@ -63,7 +63,7 @@
|
|||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="保留原文件名">
|
<el-form-item label="保留原文件名">
|
||||||
<el-switch v-model="uploadPreserveFilename" />
|
<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>
|
||||||
<el-form-item label="允许下载">
|
<el-form-item label="允许下载">
|
||||||
<el-switch v-model="uploadDownloadable" />
|
<el-switch v-model="uploadDownloadable" />
|
||||||
|
|||||||
@@ -27,6 +27,12 @@ server {
|
|||||||
try_files $uri =404;
|
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)
|
# 推广素材:文件必须真实存在,禁止落到 SPA index.html(否则前端误判「静态存在」、视频拿到 HTML)
|
||||||
# 宿主机 ./web/promotion 挂载覆盖本路径,改 social 下文件无需重新 npm build
|
# 宿主机 ./web/promotion 挂载覆盖本路径,改 social 下文件无需重新 npm build
|
||||||
location ^~ /promotion/ {
|
location ^~ /promotion/ {
|
||||||
|
|||||||
@@ -25,6 +25,11 @@ server {
|
|||||||
try_files $uri =404;
|
try_files $uri =404;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
location ^~ /social/ {
|
||||||
|
alias /var/www/yh-public/social/;
|
||||||
|
add_header Cache-Control "public, max-age=300";
|
||||||
|
}
|
||||||
|
|
||||||
location ^~ /promotion/ {
|
location ^~ /promotion/ {
|
||||||
try_files $uri =404;
|
try_files $uri =404;
|
||||||
add_header Cache-Control "public, max-age=86400";
|
add_header Cache-Control "public, max-age=86400";
|
||||||
|
|||||||
@@ -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/`
|
||||||
|
|
||||||
| 文件 | 用途 |
|
「关注我们」二维码可放在 **`public/social/`**,访问路径为 **`/social/文件名`**。
|
||||||
|------|------|
|
若此处没有对应文件,前端会自动尝试 **`/promotion/social/`**(`web/promotion/social/` 挂载)。
|
||||||
| `logo.png` | **标签页图标**(`index.html` 的 `rel="icon"` / `apple-touch-icon`)+ **首页与宣传册导航**(`Home.vue`、`BrochurePage.vue`) |
|
|
||||||
|
|
||||||
更新品牌时替换本目录下的 `logo.png` 即可。
|
## 根目录文件示例
|
||||||
|
|
||||||
**Docker 部署**:`docker-compose` 已将 **`web/public` → 容器内 `/var/www/yh-public`** 只读挂载,`deploy/web/default.conf` 中配置了「根路径下单段静态文件名优先读挂载、否则回退 `dist`」。**改宿主机 `web/public/` 内文件后无需 `npm run build`**,刷新即可(注意浏览器缓存)。
|
- `logo.png`:导航栏与 favicon(见 `index.html`、`Home.vue`)
|
||||||
|
|
||||||
**注意**:引用时用**根路径** `/logo.png`,不要用 `src/assets` 的 import 方式写 public 资源。
|
|
||||||
|
|||||||
@@ -1,34 +1,38 @@
|
|||||||
import { promotionUrl } from '../utils/promotionAssets'
|
import { promotionUrl } from '../utils/promotionAssets'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 「关注我们」素材统一放在 web/promotion/social/
|
* 「关注我们」二维码图加载顺序(前者 404 时 img @error 自动换下一项):
|
||||||
* 线上访问:/promotion/social/<文件名>(与视频同源部署)
|
* 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 = [
|
export const PROMOTION_SOCIAL_FOLLOW = [
|
||||||
{
|
{
|
||||||
id: 'xiaohongshu',
|
id: 'xiaohongshu',
|
||||||
label: '小红书',
|
label: '小红书',
|
||||||
image: social('xiaohongshu.png'),
|
imageCandidates: socialFollowImageCandidates('xiaohongshu.png'),
|
||||||
href: 'https://www.xiaohongshu.com/'
|
href: 'https://www.xiaohongshu.com/'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'douyin',
|
id: 'douyin',
|
||||||
label: '抖音',
|
label: '抖音',
|
||||||
image: social('douyin.png'),
|
imageCandidates: socialFollowImageCandidates('douyin.png'),
|
||||||
href: 'https://www.douyin.com/'
|
href: 'https://www.douyin.com/'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'wechat-official',
|
id: 'wechat-official',
|
||||||
label: '公众号',
|
label: '公众号',
|
||||||
image: social('wechat-official.png'),
|
imageCandidates: socialFollowImageCandidates('wechat-official.png'),
|
||||||
href: 'https://mp.weixin.qq.com/'
|
href: 'https://mp.weixin.qq.com/'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'wechat-channels',
|
id: 'wechat-channels',
|
||||||
label: '视频号',
|
label: '视频号',
|
||||||
image: social('wechat-channels.jpg'),
|
imageCandidates: socialFollowImageCandidates('wechat-channels.jpg'),
|
||||||
href: 'https://channels.weixin.qq.com/'
|
href: 'https://channels.weixin.qq.com/'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -135,7 +135,6 @@
|
|||||||
|
|
||||||
<section class="social-section" id="social-follow">
|
<section class="social-section" id="social-follow">
|
||||||
<h3 class="social-title">关注我们</h3>
|
<h3 class="social-title">关注我们</h3>
|
||||||
<p class="social-desc">点击按钮查看二维码;图片资源统一部署在 <code class="social-code">/promotion/social/</code> 即可线上访问</p>
|
|
||||||
<div class="social-links">
|
<div class="social-links">
|
||||||
<button
|
<button
|
||||||
v-for="item in socialFollowItems"
|
v-for="item in socialFollowItems"
|
||||||
@@ -145,7 +144,15 @@
|
|||||||
@click="openSocialFollow(item)"
|
@click="openSocialFollow(item)"
|
||||||
>
|
>
|
||||||
<span class="social-pill-icon-wrap">
|
<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>
|
||||||
<span class="social-pill-label">{{ item.label }}</span>
|
<span class="social-pill-label">{{ item.label }}</span>
|
||||||
</button>
|
</button>
|
||||||
@@ -212,7 +219,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<!-- 产品视频:web/promotion/social/ 下英文文件名,见 promotionVideos.js -->
|
|
||||||
<section class="video-section" id="videos">
|
<section class="video-section" id="videos">
|
||||||
<div class="section-title">
|
<div class="section-title">
|
||||||
<h2>产品视频</h2>
|
<h2>产品视频</h2>
|
||||||
@@ -297,7 +303,13 @@
|
|||||||
<button type="button" class="close-modal" aria-label="关闭" @click="closeSocialFollow">×</button>
|
<button type="button" class="close-modal" aria-label="关闭" @click="closeSocialFollow">×</button>
|
||||||
<h4 class="social-qr-title">{{ socialQrTitle }}</h4>
|
<h4 class="social-qr-title">{{ socialQrTitle }}</h4>
|
||||||
<div class="social-qr-frame">
|
<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>
|
</div>
|
||||||
<a
|
<a
|
||||||
v-if="socialQrHref"
|
v-if="socialQrHref"
|
||||||
@@ -361,6 +373,9 @@ const socialQrOpen = ref(false)
|
|||||||
const socialQrTitle = ref('')
|
const socialQrTitle = ref('')
|
||||||
const socialQrImage = ref('')
|
const socialQrImage = ref('')
|
||||||
const socialQrHref = ref('')
|
const socialQrHref = ref('')
|
||||||
|
/** 二维码大图:与列表相同的多 URL 回退 */
|
||||||
|
const socialQrCandidates = ref([])
|
||||||
|
const socialQrCandidateIdx = ref(0)
|
||||||
|
|
||||||
const defaultData = () => ({
|
const defaultData = () => ({
|
||||||
logo_text: 'YUHENG ONE',
|
logo_text: 'YUHENG ONE',
|
||||||
@@ -524,7 +539,9 @@ function closeVideoModal() {
|
|||||||
|
|
||||||
function openSocialFollow(item) {
|
function openSocialFollow(item) {
|
||||||
socialQrTitle.value = item.label
|
socialQrTitle.value = item.label
|
||||||
socialQrImage.value = item.image
|
socialQrCandidates.value = item.imageCandidates || []
|
||||||
|
socialQrCandidateIdx.value = 0
|
||||||
|
socialQrImage.value = item.imageCandidates?.[0] || ''
|
||||||
socialQrHref.value = item.href || ''
|
socialQrHref.value = item.href || ''
|
||||||
socialQrOpen.value = true
|
socialQrOpen.value = true
|
||||||
}
|
}
|
||||||
@@ -533,6 +550,29 @@ function closeSocialFollow() {
|
|||||||
socialQrOpen.value = false
|
socialQrOpen.value = false
|
||||||
socialQrImage.value = ''
|
socialQrImage.value = ''
|
||||||
socialQrHref.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) {
|
function toggleFaq(i) {
|
||||||
@@ -949,13 +989,9 @@ onUnmounted(() => {
|
|||||||
|
|
||||||
.social-section { padding: 72px 20px; text-align: center; position: relative; z-index: 10; }
|
.social-section { padding: 72px 20px; text-align: center; position: relative; z-index: 10; }
|
||||||
.social-title {
|
.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;
|
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-links { display: flex; justify-content: center; gap: 14px; flex-wrap: wrap; align-items: center; }
|
||||||
.social-pill {
|
.social-pill {
|
||||||
display: inline-flex; align-items: center; gap: 10px;
|
display: inline-flex; align-items: center; gap: 10px;
|
||||||
|
|||||||
Reference in New Issue
Block a user