diff --git a/web/src/data/promotionVideos.js b/web/src/data/promotionVideos.js index aa9bfc8..537f0aa 100644 --- a/web/src/data/promotionVideos.js +++ b/web/src/data/promotionVideos.js @@ -44,16 +44,79 @@ export const PROMOTION_VIDEOS_BASE = [ } ] -/** @param {string} siteId 空串时走本地/静态 /promotion/… */ -export function buildPromotionVideos(siteId) { +/** + * 检测同域静态 /promotion/ 文件是否可访问(优先 HEAD,405 时用 Range GET) + * @param {string} url + * @returns {Promise} + */ +export async function promotionStaticUrlExists(url) { + try { + const head = await fetch(url, { + method: 'HEAD', + mode: 'same-origin', + credentials: 'same-origin', + cache: 'default' + }) + if (head.ok) return true + if (head.status === 405) { + const r = await fetch(url, { + method: 'GET', + headers: { Range: 'bytes=0-0' }, + mode: 'same-origin', + credentials: 'same-origin', + cache: 'default' + }) + return r.ok || r.status === 206 + } + return false + } catch { + return false + } +} + +/** + * 有静态则静态,否则(需 siteId)走 promotion-media API + * @param {string} siteId + * @param {string} relPath promotion 下相对路径,如 social/xxx.mov + */ +export async function pickPromotionAssetUrl(siteId, relPath) { + const staticUrl = promotionUrl(relPath) + const hasStatic = await promotionStaticUrlExists(staticUrl) + if (hasStatic) return staticUrl + if (siteId) return promotionMediaApiUrl(siteId, relPath) + return staticUrl +} + +/** + * 同步列表:全部静态 URL(首屏占位、无 siteId 时与异步结果一致场景) + * @param {string} [_siteId] 保留兼容,当前忽略 + */ +export function buildPromotionVideos(_siteId) { return PROMOTION_VIDEOS_BASE.map((v) => ({ id: v.id, title: v.title, desc: v.desc, - cover: promotionMediaApiUrl(siteId, v.relCover), - src: promotionMediaApiUrl(siteId, v.relVideo) + cover: promotionUrl(v.relCover), + src: promotionUrl(v.relVideo) })) } -/** @deprecated 请用 buildPromotionVideos(siteId);保留兼容旧引用 */ -export const PROMOTION_VIDEOS = buildPromotionVideos('') +/** + * 静态优先:存在 /promotion/... 则用静态,否则用 uploads API(需 siteId) + * @param {string} siteId + */ +export async function buildPromotionVideosAsync(siteId) { + const id = siteId || '' + return Promise.all( + PROMOTION_VIDEOS_BASE.map(async (v) => { + const [cover, src] = await Promise.all([ + pickPromotionAssetUrl(id, v.relCover), + pickPromotionAssetUrl(id, v.relVideo) + ]) + return { id: v.id, title: v.title, desc: v.desc, cover, src } + }) + ) +} + +/** @deprecated 请用 buildPromotionVideos() 或 buildPromotionVideosAsync */ +export const PROMOTION_VIDEOS = buildPromotionVideos() diff --git a/web/src/utils/promotionAssets.js b/web/src/utils/promotionAssets.js index 4db7269..b586e47 100644 --- a/web/src/utils/promotionAssets.js +++ b/web/src/utils/promotionAssets.js @@ -1,8 +1,8 @@ import { apiBase } from '../config' /** - * 推广素材根路径。开发时由 Vite 插件映射到 web/promotion; - * 生产环境视频默认走后台上传:`/api/web/sites/{siteId}/promotion-media/...`(见 buildPromotionVideos)。 + * 推广素材根路径。开发时由 Vite 映射到 web/promotion; + * 首页产品视频见 `buildPromotionVideosAsync`:同域静态存在则 `/promotion/...`,否则 `promotionMediaApiUrl`。 */ export function promotionUrl(relativePath) { const parts = String(relativePath).split('/').filter(Boolean) diff --git a/web/src/views/Home.vue b/web/src/views/Home.vue index 292e70a..6db8f31 100644 --- a/web/src/views/Home.vue +++ b/web/src/views/Home.vue @@ -312,7 +312,7 @@ import { ref, reactive, computed, watch, onMounted, onUnmounted, nextTick } from 'vue' import { apiBase } from '../config' import BlockRenderer from '../components/blocks/BlockRenderer.vue' -import { buildPromotionVideos } from '../data/promotionVideos' +import { buildPromotionVideos, buildPromotionVideosAsync } from '../data/promotionVideos' import { PROMOTION_SOCIAL_FOLLOW } from '../data/promotionSocial' import { getCachedWebSiteId, fetchWebRoutes } from '../api/webPages' @@ -322,9 +322,18 @@ const starsEl = ref(null) let cometTimer = null const scrollY = ref(0) const openFaq = ref(0) -/** 官网 site_id(bootstrap 已拉路由时此处已有值;为空则产品视频走本地 /promotion) */ +/** 官网 site_id(bootstrap 已拉路由时此处已有值;产品视频:静态优先,否则 promotion-media API) */ const webSiteId = ref(getCachedWebSiteId() || '') -const promoVideos = computed(() => buildPromotionVideos(webSiteId.value)) +const promoVideos = ref(buildPromotionVideos(webSiteId.value)) +async function refreshPromoVideos() { + const id = webSiteId.value || '' + try { + promoVideos.value = await buildPromotionVideosAsync(id) + } catch { + promoVideos.value = buildPromotionVideos(id) + } +} +watch(webSiteId, refreshPromoVideos, { immediate: true }) const activeVideo = ref(null) watch( promoVideos, @@ -333,7 +342,7 @@ watch( const cur = activeVideo.value if (!cur || !list.some((v) => v.id === cur.id)) activeVideo.value = list[0] }, - { immediate: true } + { immediate: true, deep: true } ) const videoModalOpen = ref(false) const modalVideoSrc = ref('')