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) => ({
|
return PROMOTION_VIDEOS_BASE.map((v) => ({
|
||||||
id: v.id,
|
id: v.id,
|
||||||
title: v.title,
|
title: v.title,
|
||||||
desc: v.desc,
|
desc: v.desc,
|
||||||
cover: promotionMediaApiUrl(siteId, v.relCover),
|
cover: promotionUrl(v.relCover),
|
||||||
src: promotionMediaApiUrl(siteId, v.relVideo)
|
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'
|
import { apiBase } from '../config'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 推广素材根路径。开发时由 Vite 插件映射到 web/promotion;
|
* 推广素材根路径。开发时由 Vite 映射到 web/promotion;
|
||||||
* 生产环境视频默认走后台上传:`/api/web/sites/{siteId}/promotion-media/...`(见 buildPromotionVideos)。
|
* 首页产品视频见 `buildPromotionVideosAsync`:同域静态存在则 `/promotion/...`,否则 `promotionMediaApiUrl`。
|
||||||
*/
|
*/
|
||||||
export function promotionUrl(relativePath) {
|
export function promotionUrl(relativePath) {
|
||||||
const parts = String(relativePath).split('/').filter(Boolean)
|
const parts = String(relativePath).split('/').filter(Boolean)
|
||||||
|
|||||||
@@ -312,7 +312,7 @@
|
|||||||
import { ref, reactive, computed, watch, onMounted, onUnmounted, nextTick } from 'vue'
|
import { ref, reactive, computed, watch, onMounted, onUnmounted, nextTick } from 'vue'
|
||||||
import { apiBase } from '../config'
|
import { apiBase } from '../config'
|
||||||
import BlockRenderer from '../components/blocks/BlockRenderer.vue'
|
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 { PROMOTION_SOCIAL_FOLLOW } from '../data/promotionSocial'
|
||||||
import { getCachedWebSiteId, fetchWebRoutes } from '../api/webPages'
|
import { getCachedWebSiteId, fetchWebRoutes } from '../api/webPages'
|
||||||
|
|
||||||
@@ -322,9 +322,18 @@ const starsEl = ref(null)
|
|||||||
let cometTimer = null
|
let cometTimer = null
|
||||||
const scrollY = ref(0)
|
const scrollY = ref(0)
|
||||||
const openFaq = ref(0)
|
const openFaq = ref(0)
|
||||||
/** 官网 site_id(bootstrap 已拉路由时此处已有值;为空则产品视频走本地 /promotion) */
|
/** 官网 site_id(bootstrap 已拉路由时此处已有值;产品视频:静态优先,否则 promotion-media API) */
|
||||||
const webSiteId = ref(getCachedWebSiteId() || '')
|
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)
|
const activeVideo = ref(null)
|
||||||
watch(
|
watch(
|
||||||
promoVideos,
|
promoVideos,
|
||||||
@@ -333,7 +342,7 @@ watch(
|
|||||||
const cur = activeVideo.value
|
const cur = activeVideo.value
|
||||||
if (!cur || !list.some((v) => v.id === cur.id)) activeVideo.value = list[0]
|
if (!cur || !list.some((v) => v.id === cur.id)) activeVideo.value = list[0]
|
||||||
},
|
},
|
||||||
{ immediate: true }
|
{ immediate: true, deep: true }
|
||||||
)
|
)
|
||||||
const videoModalOpen = ref(false)
|
const videoModalOpen = ref(false)
|
||||||
const modalVideoSrc = ref('')
|
const modalVideoSrc = ref('')
|
||||||
|
|||||||
Reference in New Issue
Block a user