Vue 3 + Element Plus 构建视频播放详情页:工程化实践与性能优化 1. 项目概述与核心价值最近在重构一个视频展示类的前端项目核心需求是模仿腾讯视频电影网站的风格并实现一个功能完备的视频播放详情页。这不仅仅是“画个页面”那么简单它涉及到前端工程化、组件化设计、状态管理、多媒体处理以及用户体验优化等多个维度的综合实践。对于正在学习Vue生态或希望提升前端工程能力的开发者来说这是一个绝佳的练手项目。它能让你从零开始理解一个商业级视频网站前端是如何将设计稿转化为可交互、高性能、易维护的代码的。这个项目的核心价值在于它模拟了一个真实、高频的业务场景。你不仅会用到Vue 3的Composition API、Vue Router、Pinia等现代前端技术栈还会深入处理视频播放、海报墙、分页加载、路由传参等具体业务逻辑。通过Element Plus组件库我们可以快速搭建出符合设计规范且美观的界面从而将更多精力投入到业务逻辑和性能优化上。最终产出的详情页应该具备视频播放、选集切换、影片信息展示、相关推荐、评论互动等核心功能并且播放体验要流畅页面切换要顺滑。2. 技术栈选型与项目架构设计2.1 为什么是Vue 3 Element Plus Vite在技术选型上我们选择了当前最主流、最具前瞻性的组合Vue 3、Element Plus和Vite。这背后有充分的考量。首先Vue 3的Composition API提供了比Options API更灵活、更利于逻辑复用的代码组织方式。在处理视频详情页这种逻辑复杂的页面时我们可以将“播放器控制”、“选集管理”、“数据获取”等逻辑抽离成独立的组合式函数composables使得代码结构清晰易于测试和维护。例如播放器的播放/暂停、音量控制、全屏切换等逻辑完全可以封装成一个useVideoPlayer的hook。其次Element Plus作为基于Vue 3的组件库完美继承了Vue 3的性能优势并且组件丰富、设计成熟。对于需要快速搭建中后台或内容展示型页面的项目来说它能极大提升开发效率。比如影片信息的展示可以用el-descriptions组件分页加载评论可以用el-pagination而视频选集列表用el-menu或自定义列表渲染都非常方便。它的按需引入特性也能有效控制最终打包体积。最后Vite作为新一代前端构建工具其基于ES Module的快速冷启动和热更新能力能为开发体验带来质的飞跃。在开发视频详情页这种可能需要频繁调整样式和逻辑的页面时Vite几乎秒级的更新反馈能显著提升开发效率。同时它对Vue 3的一流支持使得整个开发流程非常顺畅。2.2 项目目录结构规划一个清晰的目录结构是项目可维护性的基石。我们采用功能导向的模块化结构而非传统的“按文件类型分文件夹”的方式。src/ ├── api/ # 所有接口请求模块 │ ├── video.js # 视频相关接口 │ └── comment.js # 评论相关接口 ├── assets/ # 静态资源 │ ├── styles/ # 全局样式、变量 │ └── images/ # 图片资源 ├── components/ # 公共组件 │ ├── VideoPlayer/ # 视频播放器组件核心 │ ├── CommentList/ # 评论列表组件 │ └── RecommendCard/ # 推荐卡片组件 ├── composables/ # 组合式函数 │ ├── useVideoPlayer.js # 播放器逻辑 │ ├── useVideoDetail.js # 详情页数据逻辑 │ └── usePagination.js # 分页逻辑 ├── router/ # 路由配置 │ └── index.js ├── stores/ # Pinia状态管理 │ ├── video.js # 视频相关状态如播放历史 │ └── user.js # 用户相关状态 ├── views/ # 页面组件 │ ├── Home.vue # 首页 │ └── VideoDetail.vue # 视频播放详情页核心页面 └── utils/ # 工具函数 └── request.js # 封装axios注意composables文件夹是Vue 3项目的最佳实践之一。将可复用的业务逻辑如数据获取、播放器控制封装于此能极大提升代码的复用性和可测试性。避免在VideoDetail.vue中写超过300行的代码把逻辑合理地拆分出去。2.3 路由设计与状态管理策略路由设计上详情页需要一个动态路由来承载不同的视频ID。在router/index.js中我们这样配置import { createRouter, createWebHistory } from vue-router; import Home from /views/Home.vue; import VideoDetail from /views/VideoDetail.vue; const routes [ { path: /, name: Home, component: Home }, { path: /video/:id, // 使用动态段 :id name: VideoDetail, component: VideoDetail, props: true // 重要将路由参数 id 作为props传递给组件 } ]; const router createRouter({ history: createWebHistory(), routes }); export default router;使用props: true可以将路由参数this.$route.params.id直接作为组件的props接收使得组件逻辑更纯粹不依赖$route对象便于测试。状态管理方面我们使用Pinia。对于视频详情页有些状态是页面局部的如当前播放时间、弹幕开关适合用组件内的ref/reactive管理。而有些状态是需要跨组件或跨页面共享的比如用户的播放历史、收藏列表、登录状态等这些就适合放在Pinia的store中。例如一个简单的videoStore可以这样定义// stores/video.js import { defineStore } from pinia; import { ref } from vue; export const useVideoStore defineStore(video, () { const playHistory ref([]); // 播放历史记录 const addToHistory (videoItem) { // 去重逻辑 const index playHistory.value.findIndex(item item.id videoItem.id); if (index -1) { playHistory.value.splice(index, 1); } playHistory.value.unshift(videoItem); // 最新观看的放在最前面 // 可以限制历史记录长度比如只保留最近50条 if (playHistory.value.length 50) { playHistory.value.pop(); } }; return { playHistory, addToHistory }; });在详情页组件中当视频开始播放时就可以调用addToHistory方法将当前视频信息存入全局状态这样在首页或其他页面就能展示用户的观看足迹了。3. 视频播放详情页核心功能实现3.1 页面布局与组件拆分详情页的UI布局可以参照腾讯视频通常分为几个主要区域顶部导航区包含网站Logo、搜索框、用户中心入口等。主内容区左侧视频播放器核心、视频标题、操作栏点赞、收藏、分享、选集列表。右侧影片详细信息导演、演员、简介、相关推荐视频列表。底部内容区用户评论列表、发表评论框。基于此我们在VideoDetail.vue中可以进行如下组件拆分!-- VideoDetail.vue 模板结构示例 -- template div classvideo-detail-container !-- 顶部导航可抽成公共组件 -- AppHeader / div classmain-content !-- 左侧区域 -- div classleft-panel !-- 1. 视频播放器组件 -- VideoPlayer :video-urlcurrentVideoUrl :postervideoInfo.poster timeupdatehandleTimeUpdate endedhandleVideoEnded / !-- 2. 视频标题与操作栏 -- VideoActionBar :titlevideoInfo.title :is-likedvideoInfo.isLiked likehandleLike collecthandleCollect / !-- 3. 视频选集列表 -- VideoEpisodeList :episodesvideoInfo.episodes :current-episode-idcurrentEpisodeId selectswitchEpisode / /div !-- 右侧区域 -- div classright-panel !-- 4. 影片信息展示 -- VideoMetaInfo :infovideoInfo / !-- 5. 相关推荐 -- VideoRecommendList :listrecommendList / /div /div !-- 底部评论区域 -- div classcomment-section !-- 6. 评论列表 -- CommentList :video-idvideoId / !-- 7. 发表评论框 (需要登录) -- CommentEditor v-ifuserStore.isLogin submitsubmitComment / /div /div /template通过组件化拆分VideoDetail.vue文件主要承担数据获取、状态管理和事件分发的职责每个子组件各司其职逻辑清晰便于独立开发和维护。3.2 视频播放器组件的深度封装播放器是详情页的灵魂。我们不直接使用原生video标签而是选择封装一个功能更强大的VideoPlayer组件。这里以流行的video.js或plyr库为例它们对HLS/m3u8流媒体支持更好但核心思路一致。首先安装并引入一个播放器库。以plyr为例npm install plyr然后创建components/VideoPlayer/index.vuetemplate div classvideo-player-wrapper refplayerContainer !-- 播放器容器 -- /div /template script setup import { ref, onMounted, onUnmounted, watch } from vue; import Plyr from plyr; import plyr/dist/plyr.css; // 引入样式 const props defineProps({ videoUrl: { type: String, required: true }, poster: { type: String, default: }, options: { type: Object, default: () ({}) } }); const emit defineEmits([timeupdate, play, pause, ended, error]); const playerContainer ref(null); let player null; // 初始化播放器 const initPlayer () { if (!playerContainer.value) return; // 销毁旧的播放器实例防止内存泄漏 if (player) { player.destroy(); } const defaultOptions { controls: [ play-large, // 中央播放按钮 rewind, play, fast-forward, progress, current-time, duration, mute, volume, captions, settings, pip, airplay, fullscreen ], settings: [captions, quality, speed], autoplay: false, poster: props.poster, // 针对HLS流的配置 ...(props.videoUrl.endsWith(.m3u8) ? { type: hls, hls: { // 可配置HLS.js的选项如自适应码率 enableWorker: true, lowLatencyMode: true, } } : {}), ...props.options // 合并外部传入的配置 }; player new Plyr(playerContainer.value, defaultOptions); // 监听播放器事件并向上抛出 player.on(timeupdate, (event) { emit(timeupdate, player.currentTime); }); player.on(play, () emit(play)); player.on(pause, () emit(pause)); player.on(ended, () emit(ended)); player.on(error, (event) { console.error(播放器错误:, event.detail); emit(error, event.detail); }); // 设置源 player.source { type: video, title: 播放中, sources: [{ src: props.videoUrl, type: getVideoType(props.videoUrl) }] }; }; // 根据URL后缀判断视频类型 const getVideoType (url) { if (url.includes(.m3u8)) return application/x-mpegURL; if (url.includes(.mp4)) return video/mp4; return video/mp4; // 默认 }; // 监听videoUrl变化切换视频源 watch(() props.videoUrl, (newUrl) { if (player newUrl) { player.source { type: video, sources: [{ src: newUrl, type: getVideoType(newUrl) }] }; } }); onMounted(() { initPlayer(); }); onUnmounted(() { if (player) { player.destroy(); player null; } }); // 暴露一些方法给父组件可选 defineExpose({ play: () player?.play(), pause: () player?.pause(), setCurrentTime: (time) { if(player) player.currentTime time; } }); /script style scoped .video-player-wrapper { width: 100%; background-color: #000; border-radius: 8px; overflow: hidden; } /* 覆盖plyr默认样式以适配设计 */ :deep(.plyr) { height: 100%; } /style实操心得播放器库的初始化一定要放在onMounted生命周期中确保DOM已挂载。在组件销毁时onUnmounted必须调用player.destroy()来释放资源避免内存泄漏。对于HLS.m3u8流媒体plyr内部会使用HLS.js库需要确保已正确引入其类型配置。3.3 动态数据获取与状态管理详情页的数据通常来自后端API。我们在composables/useVideoDetail.js中封装数据获取逻辑。// composables/useVideoDetail.js import { ref, computed } from vue; import { useRoute } from vue-router; import { getVideoDetailApi, getRecommendListApi } from /api/video; export function useVideoDetail() { const route useRoute(); const videoId computed(() route.params.id); // 从路由获取ID // 响应式数据 const videoInfo ref({}); const recommendList ref([]); const currentEpisodeId ref(0); // 当前播放的集数ID const loading ref(false); const error ref(null); // 当前播放的视频URL根据选集ID计算 const currentVideoUrl computed(() { const episode videoInfo.value.episodes?.find(ep ep.id currentEpisodeId.value); return episode?.url || videoInfo.value.mainUrl || ; }); // 获取视频详情 const fetchVideoDetail async () { loading.value true; error.value null; try { const res await getVideoDetailApi(videoId.value); videoInfo.value res.data; // 默认播放第一个选集或正片 if (videoInfo.value.episodes?.length 0) { currentEpisodeId.value videoInfo.value.episodes[0].id; } } catch (err) { error.value err.message || 获取视频详情失败; console.error(fetchVideoDetail error:, err); } finally { loading.value false; } }; // 获取相关推荐 const fetchRecommendList async () { try { const res await getRecommendListApi(videoId.value); recommendList.value res.data.list; } catch (err) { console.error(fetchRecommendList error:, err); } }; // 切换选集 const switchEpisode (episodeId) { if (currentEpisodeId.value episodeId) return; currentEpisodeId.value episodeId; // 这里可以触发播放器重新加载新源播放器组件通过watch videoUrl已自动处理 // 如果需要记录播放位置可以在这里保存当前集的播放进度 }; // 初始化加载 const init () { fetchVideoDetail(); fetchRecommendList(); }; return { videoId, videoInfo, recommendList, currentEpisodeId, currentVideoUrl, loading, error, switchEpisode, init }; }在VideoDetail.vue的setup中我们可以这样使用script setup import { onMounted } from vue; import { useVideoDetail } from /composables/useVideoDetail; import { useVideoStore } from /stores/video; const { videoInfo, recommendList, currentEpisodeId, currentVideoUrl, loading, error, switchEpisode, init } useVideoDetail(); const videoStore useVideoStore(); // 视频开始播放时记录历史 const handlePlay () { if (videoInfo.value.id) { videoStore.addToHistory({ id: videoInfo.value.id, title: videoInfo.value.title, poster: videoInfo.value.poster, episode: currentEpisodeId.value }); } }; onMounted(() { init(); }); /script这种设计将数据逻辑、业务逻辑与UI组件彻底分离VideoDetail.vue变得非常简洁只负责组合和渲染。4. 关键交互与用户体验优化4.1 选集列表的交互与状态同步选集列表VideoEpisodeList需要高亮当前选中项并处理点击切换。我们可以使用el-menu或自己用div渲染。关键在于状态的同步当用户点击选集时不仅要切换currentEpisodeId最好还能给用户一个反馈比如在切换时显示一个短暂的加载状态。!-- components/VideoEpisodeList.vue -- template div classepisode-list div classlist-header span选集/span span v-ifloadingEpisode切换中.../span /div div classepisode-grid div v-forep in episodes :keyep.id classepisode-item :class{ is-active: ep.id currentEpisodeId } clickhandleSelect(ep.id) span{{ ep.name }}/span !-- 可以加上播放图标或时长 -- /div /div /div /template script setup import { ref } from vue; const props defineProps({ episodes: { type: Array, default: () [] }, currentEpisodeId: { type: [Number, String], default: 0 } }); const emit defineEmits([select]); const loadingEpisode ref(false); const handleSelect async (episodeId) { if (episodeId props.currentEpisodeId || loadingEpisode.value) return; loadingEpisode.value true; // 模拟一个短暂的切换延迟让用户感知到操作反馈 await new Promise(resolve setTimeout(resolve, 150)); emit(select, episodeId); loadingEpisode.value false; }; /script style scoped .episode-grid { display: flex; flex-wrap: wrap; gap: 10px; } .episode-item { padding: 8px 16px; border: 1px solid #e0e0e0; border-radius: 4px; cursor: pointer; text-align: center; transition: all 0.2s; } .episode-item:hover { border-color: #409eff; color: #409eff; } .episode-item.is-active { border-color: #409eff; background-color: #ecf5ff; color: #409eff; font-weight: bold; } /style4.2 播放进度记忆与续播功能这是一个提升用户体验的重要功能。当用户退出详情页再回来时如果能从上次观看的位置继续播放会非常友好。实现思路是在用户离开页面或切换选集时将播放进度保存到本地存储LocalStorage或Pinia store中。我们可以在之前封装的useVideoPlayercomposable 或VideoPlayer组件内部实现这个逻辑。// 在 useVideoPlayer.js 或 VideoPlayer 组件脚本部分补充 import { useStorage } from vueuse/core; // 推荐使用vueuse的useStorage更便捷 // 为每个视频生成一个唯一的存储key const getStorageKey (videoId, episodeId) video_progress_${videoId}_${episodeId}; // 使用vueuse的useStorage它提供响应式接口 const progressStorage useStorage(getStorageKey(props.videoId, props.episodeId), 0); // 监听播放时间更新节流保存 let saveTimer null; const handleTimeUpdate (currentTime) { // 每5秒保存一次进度避免频繁写入Storage if (!saveTimer) { saveTimer setTimeout(() { progressStorage.value Math.floor(currentTime); // 保存整数秒 saveTimer null; }, 5000); } }; // 播放器初始化后尝试恢复进度 onMounted(() { if (player progressStorage.value 0) { const savedTime progressStorage.value; // 可以询问用户是否跳转到上次播放位置 // 这里我们自动跳转 player.currentTime savedTime; } }); // 切换选集或离开页面时保存当前进度 onBeforeUnmount(() { if (player) { progressStorage.value Math.floor(player.currentTime); } });注意事项自动续播功能需要谨慎使用。对于短视频如几分钟的可能不需要。最好能提供一个UI提示比如“检测到您上次观看到XX分XX秒是否跳转”让用户自己选择。同时保存的进度应该有有效期或定期清理机制避免存储过多无用数据。4.3 图片懒加载与骨架屏详情页通常有大量图片海报、演员头像、推荐列表图使用懒加载可以显著提升页面初始加载性能。我们可以使用vue-lazyload库或浏览器原生的loadinglazy属性兼容性需注意。对于Element Plus的el-image组件它内置了懒加载和占位功能是很好的选择。!-- 使用el-image展示海报 -- el-image :srcvideoInfo.poster :preview-src-list[videoInfo.poster] !-- 点击可预览大图 -- fitcover lazy !-- 开启懒加载 -- classposter-img template #placeholder !-- 加载中的占位图 -- div classimage-slot el-iconPicture //el-icon /div /template template #error !-- 加载失败的占位图 -- div classimage-slot el-iconPicture //el-icon /div /template /el-image在数据加载完成前使用骨架屏Skeleton能有效缓解用户的等待焦虑。Element Plus提供了el-skeleton组件。template div classvideo-meta el-skeleton :loadingloading animated :throttle500 template #template !-- 骨架屏结构模拟真实布局 -- div styledisplay: flex; el-skeleton-item variantimage stylewidth: 200px; height: 300px; / div styleflex: 1; margin-left: 20px; el-skeleton-item varianth1 stylewidth: 50%; / el-skeleton-item varianttext stylewidth: 80%; margin-top: 16px; / el-skeleton-item varianttext stylewidth: 60%; / !-- ... 更多骨架项 -- /div /div /template template #default !-- 真实内容 -- div classreal-content img :srcvideoInfo.poster alt海报 classposter div classinfo h1{{ videoInfo.title }}/h1 p{{ videoInfo.description }}/p !-- ... -- /div /div /template /el-skeleton /div /template5. 性能优化与部署实践5.1 路由懒加载与组件异步加载对于单页面应用首屏加载速度至关重要。Vue Router支持路由懒加载可以将不同路由对应的组件分割成不同的代码块当路由被访问时才加载对应组件。// router/index.js const routes [ // ... 其他路由 { path: /video/:id, name: VideoDetail, component: () import(/views/VideoDetail.vue) // 懒加载 } ];对于详情页内部的大型子组件如评论列表、推荐列表也可以使用Vue 3的defineAsyncComponent进行异步加载。script setup import { defineAsyncComponent } from vue; // 异步加载评论组件只在需要时加载 const CommentList defineAsyncComponent(() import(/components/CommentList.vue) ); /script5.2 接口请求的缓存与防抖视频详情页可能涉及多个接口详情、推荐、评论。对于不常变化的数据如视频基础信息可以考虑使用缓存策略减少不必要的请求。简单的内存缓存可以这样实现// utils/request.js 或在 composable 中 const cache new Map(); export async function cachedRequest(key, fetchFn) { if (cache.has(key)) { return Promise.resolve(cache.get(key)); } const data await fetchFn(); cache.set(key, data); return data; } // 使用 const videoDetail await cachedRequest(video_detail_${videoId}, () getVideoDetailApi(videoId));对于搜索框或实时保存评论这类高频触发的事件必须使用防抖debounce或节流throttle。可以使用lodash的相关函数或自己实现。import { debounce } from lodash-es; // 在搜索输入框上使用防抖 const handleSearchInput debounce((keyword) { searchVideos(keyword); }, 500);5.3 构建优化与部署使用Vite构建默认已经做了很多优化。我们还可以通过以下配置进一步提升依赖分包ManualChunks在vite.config.js中将较大的、不常变的第三方库如vue,element-plus,plyr单独打包利用浏览器缓存。// vite.config.js import { defineConfig } from vite; import vue from vitejs/plugin-vue; export default defineConfig({ plugins: [vue()], build: { rollupOptions: { output: { manualChunks: { vue-vendor: [vue, vue-router, pinia], ui-vendor: [element-plus], player-vendor: [plyr] } } } } });CDN部署静态资源将构建后的dist目录中的静态文件js, css, images上传到CDN并在Vite配置中设置base为CDN地址加速资源加载。Docker容器化部署对于需要独立部署前端项目的场景可以编写Dockerfile。# Dockerfile FROM nginx:alpine COPY dist /usr/share/nginx/html COPY nginx.conf /etc/nginx/nginx.conf EXPOSE 80 CMD [nginx, -g, daemon off;]对应的nginx.conf需要配置SPA的路由回退server { listen 80; server_name localhost; root /usr/share/nginx/html; index index.html; location / { try_files $uri $uri/ /index.html; # 关键支持Vue Router的history模式 } # 缓存静态资源 location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg)$ { expires 1y; add_header Cache-Control public, immutable; } }构建并运行容器docker build -t vue-video-website . docker run -p 8080:80 vue-video-website6. 常见问题排查与调试技巧在开发过程中你肯定会遇到各种问题。这里记录几个典型问题的排查思路。6.1 播放器相关错误问题控制台报错bfsvc error: failed to set element application device. status [c00000bb]或类似。排查这类错误通常与浏览器底层媒体播放或DRM相关在前端层面难以直接解决。首先检查视频源URL是否有效、格式是否被浏览器支持如.mp4, .m3u8。其次尝试更换不同的视频源或播放器库如从plyr换到video.js。最后在无插件模式下测试排查浏览器插件冲突。问题HLS.m3u8视频无法播放或卡顿。排查确保服务器正确配置了CORS允许你的前端域名访问视频流。检查网络控制台Network tab看.m3u8文件和.ts分片请求是否成功状态码是否为200。确认使用的播放器库正确引入了HLS支持如plyr需确保HLS.js被加载。对于跨域问题如果后端无法修改在开发环境下可以配置Vite代理。// vite.config.js export default defineConfig({ server: { proxy: { /api: { target: http://your-backend-api.com, changeOrigin: true, }, /video-stream: { // 代理视频流请求 target: http://your-video-server.com, changeOrigin: true, rewrite: (path) path.replace(/^\/video-stream/, ) } } } });6.2 Element Plus样式与自定义问题问题Element Plus组件样式覆盖不生效。排查Vue单文件组件中style scoped内的样式默认无法影响子组件的根元素。如果需要覆盖Element Plus组件内部深层元素的样式需要使用:deep()选择器。/* 错误无法生效 */ .my-form .el-input__inner { border-color: red; } /* 正确使用深度选择器 */ .my-form :deep(.el-input__inner) { border-color: red; }问题按需引入后某些组件样式丢失。排查确保按需引入的插件如unplugin-vue-components配置正确并引入了对应的样式文件。在main.js或插件配置中需要导入样式// main.js import element-plus/dist/index.css; // 或者使用按需导入的插件如 unplugin-vue-components它会自动处理样式6.3 Vue开发与构建问题问题npm install -g vue/cli报错权限或网络问题。解决使用nvm管理Node版本避免全局安装权限问题。使用npm install -g vue/cli --registryhttps://registry.npmmirror.com切换淘宝镜像。更推荐使用Vite创建项目npm create vuelatest这是Vue官方的现代构建工具链。问题Vue项目打包后资源路径错误CSS、JS、图片404。排查检查vite.config.js中的base配置。如果项目部署在非根路径如https://domain.com/my-app/需要设置base: /my-app/。同时确保路由的history模式与后端配置匹配或者改用hash模式。问题vue-devtools不显示或无法使用。排查确保浏览器安装的是最新版Vue Devtools。检查是否在生产构建模式下Devtools默认在生产模式禁用。在开发时确保NODE_ENV不是production。尝试在应用中手动启用在main.js中加入app.config.devtools trueVue 3。6.4 跨域与接口联调问题这是前后端分离项目最常见的坑。前端运行在localhost:5173后端API在localhost:3000浏览器会因同源策略阻止请求。开发环境使用Vite的server.proxy配置代理如上文所示。生产环境需要后端配置CORS跨域资源共享头部或者通过Nginx等反向代理将前后端请求统一到一个域名下。Nginx反向代理配置示例server { listen 80; server_name your-domain.com; location / { root /path/to/your/vue/dist; index index.html; try_files $uri $uri/ /index.html; } location /api/ { proxy_pass http://backend-server:3000/; # 代理到后端服务 proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } location /video/ { proxy_pass http://video-server:4000/; # 代理到视频流服务 # 可能需要设置特殊的代理头部以支持视频流 proxy_set_header Host $host; proxy_buffering off; # 对视频流很重要 proxy_cache off; } }这个配置将所有/api开头的请求转发给后端API所有/video开头的请求转发给视频流服务器而其他请求如/,/assets/则服务于前端静态资源完美解决了跨域问题。