feat(web): 产品视频静态优先,缺失时回退 promotion-media API
Made-with: Cursor
This commit is contained in:
@@ -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<boolean>}
|
||||
*/
|
||||
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()
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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('')
|
||||
|
||||
Reference in New Issue
Block a user