本文还有配套的精品资源,点击获取 menu-r.4af5f7ec.gif

简介:本案例围绕使用uni-app框架开发短视频应用的完整实践,涵盖从项目搭建、界面设计到核心功能实现的全过程。作为一款基于Vue.js的跨平台开发框架,uni-app支持一次开发多端运行(iOS、Android、H5等),极大提升开发效率。案例以抖音类应用为参考,实现视频拍摄、编辑、上传、播放及用户交互(如点赞、评论、分享)等功能,结合.gitignore、main.js、manifest.json、pages.json等关键文件,深入讲解项目结构、路由管理、样式处理与多端兼容策略。通过本项目,开发者可系统掌握uni-app在多媒体处理、网络请求、本地存储、性能优化和安全防护等方面的核心技术,具备独立构建高性能短视频应用的能力。

uni-app 跨端开发实战:从框架原理到短视频模块深度实现

在移动应用形态日益碎片化的今天,开发者面临的最大挑战之一就是“如何用一套代码覆盖尽可能多的终端”。微信小程序、H5、iOS、Android——每个平台都有其独特的运行机制和用户习惯。而 uni-app 的出现,像是一把精准的瑞士军刀,切中了这个痛点。

它不是简单的“套壳”工具,也不是牺牲性能换取便利的妥协方案。相反,uni-app 基于 Vue.js 构建,通过编译时转换技术,将我们熟悉的组件化开发模式映射为各端原生可识别的结构(如小程序的 WXML/WXSS 或 App 的 Native 渲染指令),真正做到了“一次编写,多端运行”,且体验接近原生。

但问题来了:
👉 为什么有些团队用了 uni-app 反而更慢?
👉 为什么看似一样的代码,在 H5 上流畅,在小程序里却卡顿?
👉 如何避免陷入“写得快,维护难”的陷阱?

答案往往不在框架本身,而在对它的理解是否深入。就像一辆高性能跑车,如果你只会踩油门,那再好的引擎也发挥不出价值。接下来,我们就一起拆解这辆“跨端战车”的核心部件,并亲手打造一个高复杂度的功能模块——短视频系统,看看它是如何在真实场景中发力的。


配置即架构: manifest.json pages.json main.js 的协同艺术

很多人初学 uni-app 时,会直接跳进页面逻辑开发,结果后期被各种“莫名其妙的问题”缠住——比如某个权限始终申请不到,或者某个页面打不开。根源常常出在三个关键配置文件上: manifest.json pages.json main.js

它们就像是应用的“神经系统”——一个管全局身份与权限,一个管导航路径与外观,一个管启动流程与能力注入。忽略任何一个,都可能导致系统失调。

manifest.json:你的应用“身份证”与权限通行证 🪪

你可以把 manifest.json 看作是应用的“出生证明”。它决定了:

  • 我是谁?(名字、AppID)
  • 我能做什么?(摄像头、麦克风等权限)
  • 我在哪里运行?(H5、微信、支付宝等平台特有设置)

举个例子,你在做一个短视频 App,需要调用相机和录音功能。如果没在 manifest.json 中声明这些权限,哪怕代码里写了 uni.authorize ,真机测试时也会被系统无情拒绝!

{
  "permission": {
    "scope.camera": {
      "desc": "用于拍摄视频"
    },
    "scope.record": {
      "desc": "用于录制音频"
    }
  }
}

这段配置不只是技术要求,更是合规需求。国内《个人信息保护法》和 GDPR 都强调“明示同意”,这里的 desc 字段会在用户授权弹窗中显示,属于必要文案。

而且你知道吗? 不同平台对同一字段的处理方式可能完全不同 。例如:

"mp-weixin": {
  "subpackages": [
    {
      "root": "pages/user",
      "pages": ["profile", "settings"]
    }
  ]
},
"mp-alipay": {
  "workers": "workers/"
}

上面这段配置使用了 条件编译语法 ,告诉编译器:
- 在微信小程序中启用分包加载,减少首屏体积;
- 在支付宝小程序中注册 Worker 线程,用来做后台音视频处理。

这就体现了 uni-app 的高阶玩法: 不改业务逻辑,也能优化性能表现

⚠️ 小贴士:很多新手以为只要代码调用了 API 就行,其实像位置、蓝牙这类敏感权限,必须提前在 manifest.json 中注册才能生效!否则上线审核直接被打回。

graph TD
    A[manifest.json] --> B[基本信息]
    A --> C[权限声明]
    A --> D[网络超时]
    A --> E[H5 特性]
    A --> F[小程序分包]
    A --> G[App 原生配置]
    B --> H[名称/版本/AppID]
    C --> I[相机/麦克风/位置]
    E --> J[History路由/标题栏]
    F --> K[减少首屏体积]
    G --> L[NVue渲染引擎]
    style A fill:#f9f,stroke:#333

这张图是不是有点像“配置决策树”?每一个分支都对应着不同的构建行为。当你开始意识到“配置也是代码”的时候,你就离高手不远了。


pages.json:不只是路由表,更是用户体验设计图 🧭

如果说 manifest.json 是身份证,那 pages.json 就是整张应用的地图。

但它比传统前端的路由配置强大得多,因为它不仅能定义“去哪”,还能控制“怎么去”、“看到什么”。

来看一段典型的配置:

{
  "pages": [
    {
      "path": "pages/index/index",
      "style": {
        "navigationBarTitleText": "首页",
        "enablePullDownRefresh": true,
        "backgroundColor": "#f8f8f8"
      }
    }
  ],
  "subPackages": [
    {
      "root": "pages/user",
      "pages": [
        {
          "path": "profile",
          "style": {
            "navigationBarTitleText": "个人中心"
          }
        }
      ],
      "independent": true
    }
  ],
  "tabBar": {
    "list": [
      {
        "pagePath": "pages/home/index",
        "text": "首页",
        "iconPath": "static/tabbar/home.png",
        "selectedIconPath": "static/tabbar/home-active.png"
      }
    ],
    "color": "#7A7E83",
    "selectedColor": "#007AFF"
  },
  "globalStyle": {
    "navigationBarTextStyle": "black",
    "backgroundColor": "#FFFFFF"
  }
}

这里有几个关键点值得深挖:

✅ 分包加载 ≠ 拆文件夹那么简单

你有没有遇到过这样的情况:微信小程序主包超过 2MB 提交失败?

解决方案当然是分包。但光拆目录还不够, independent: true 这个参数才是精髓所在。它表示该分包可以独立运行,不会重复加载主包资源,极大节省内存。

这对于短视频类 App 尤其重要——用户进入“发布页”或“消息中心”这类重型页面时,不能因为加载缓慢而流失。

✅ tabBar 不只是图标切换

底部标签栏不仅是 UI 元素,更是用户心智模型的一部分。颜色、选中态、背景色都要符合品牌规范。而且要注意:

  • borderStyle : 设置边框样式,黑色显得更沉稳;
  • selectedColor : 强调当前活跃入口,建议使用主色调。

更重要的是, tabBar 页面必须放在主包中 ,不能放分包!这是小程序硬性规定,否则运行时报错。

✅ 全局样式 vs 页面级样式:别让重复劳动拖累效率

globalStyle 定义默认风格,比如导航栏文字颜色、背景色等。这样就不必每个页面都写一遍。

但如果某个页面想个性化呢?完全没问题,直接在 pages 数组中的具体页面下写 style 即可覆盖全局设置。

而且支持跨平台差异化配置!比如:

"style": {
  "navigationBarTitleText": "详情页",
  "app-plus": {
    "titleNView": {
      "buttons": [{
        "text": "分享",
        "fontSize": "14px"
      }]
    }
  },
  "mp-weixin": {
    "customNavigation": true
  }
}

意思是:
- 在 App 端添加原生按钮,点击直接触发分享;
- 在微信小程序中使用自定义导航栏,规避平台限制。

这种灵活性,让你既能统一设计语言,又能适配平台特性。


main.js:不仅仅是入口,更是全局能力中枢 🧠

你以为 main.js 只是用来挂载 Vue 实例?Too young too simple 😏

实际上,它是整个应用的“大脑皮层”——负责初始化状态、注入工具、捕获异常、集成第三方库。

标准模板长这样:

import { createApp } from 'vue'
import App from './App.vue'

const app = createApp(App)

app.config.globalProperties.$utils = {
  formatTime(time) {
    return new Date(time).toLocaleString()
  },
  showToast(msg) {
    uni.showToast({ title: msg, icon: 'none' })
  }
}

app.component('CustomButton', CustomButton)

app.config.errorHandler = (err, instance, info) => {
  console.error('[Global Error]', err, info)
  uni.showModal({
    title: '异常',
    content: '程序出现错误,请重启',
    showCancel: false
  })
}

app.mount()

几个容易被忽视的关键细节:

🔧 $utils 注入:告别重复造轮子

你肯定写过无数次格式化时间、提示消息的函数吧?与其到处复制粘贴,不如一次性挂到全局属性上。

以后任何组件都可以通过 this.$utils.formatTime() 直接调用,清爽又高效。

🛡️ 全局错误捕获:防止白屏崩溃的最后一道防线

移动端最怕什么?白屏!尤其是冷启动时发生 JS 错误,用户根本不知道发生了什么。

errorHandler 就是为此而生。一旦捕获未处理异常,立即弹出友好提示,并上报日志,帮助定位问题。

🔌 第三方库整合:Pinia + 请求封装 = 开发幸福感爆棚

更进一步,我们可以在这里接入状态管理器 Pinia 和统一请求层:

import pinia from './store'
import api from '@/api/request'

app.use(pinia)
app.config.globalProperties.$api = api

这样一来,所有组件都能通过 this.$api.get('/user') 发起请求,同时状态变更也能集中管理,彻底告别“数据散落各处”的混乱局面。

💡 小技巧:虽然 uni-app 编译器会自动调用 mount() ,但显式写出能让代码意图更清晰,尤其适合团队协作项目。


组件化设计:不只是复用,而是工程化的起点 🧱

uni-app 完全继承了 Vue 3 的 Composition API 和 <script setup> 语法糖,这让组件开发变得极其简洁。但真正的高手,不会只满足于“能用”,而是追求“好用、易维护”。

生命周期钩子:别再混淆页面级和组件级!

这是一个高频误区:很多人把 onLoad 当成组件生命周期来用,结果发现根本不会触发。

真相是:

钩子 类型 触发时机
onLoad , onShow , onReady 页面级 页面专属
onMounted , onUnmounted 组件级 所有组件通用

所以如果你想监听“页面显示”,应该这样做:

onMounted(() => {
  if (typeof __wxConfig !== 'undefined') {
    // 微信小程序环境需手动监听
    uni.$on('onShow', onShow)
    uni.$on('onHide', onHide)
  } else {
    // 其他平台可直接执行
    onShow()
  }
})

onUnmounted(() => {
  uni.$off('onShow', onShow)
  uni.$off('onHide', onHide)
})

看到了吗? 平台差异仍然存在 ,但我们可以通过抽象封装屏蔽掉这些细节,让用户无感。


自定义组件封装:点赞按钮的“高内聚低耦合”实践 ❤️

下面这个点赞按钮组件,是我见过最实用的小零件之一:

<template>
  <button 
    class="like-btn" 
    :class="{ active: liked }"
    @click="toggleLike"
    :disabled="loading"
  >
    <text class="icon">{{ liked ? '❤️' : '🤍' }}</text>
    <text class="count">{{ displayCount }}</text>
  </button>
</template>

<script setup>
import { ref, computed } from 'vue'

const props = defineProps({
  initialCount: Number,
  hasLiked: Boolean,
  disabled: Boolean
})

const emit = defineEmits(['change'])

const liked = ref(props.hasLiked)
const count = ref(props.initialCount)
const loading = ref(false)

const displayCount = computed(() => {
  return count.value > 999 ? `${Math.floor(count.value / 1000)}k` : count.value
})

async function toggleLike() {
  if (props.disabled || loading.value) return

  loading.value = true
  try {
    const res = await uni.request({
      method: liked.value ? 'DELETE' : 'POST',
      url: `/api/like/${postId}`
    })

    if (res.statusCode === 200) {
      liked.value = !liked.value
      count.value += liked.value ? 1 : -1
      emit('change', { liked: liked.value, count: count.value })
    }
  } catch (err) {
    uni.showToast({ title: '操作失败', icon: 'none' })
  } finally {
    loading.value = false
  }
}
</script>

亮点在哪?

  • ✅ 支持千位缩写(999+ → 1k),视觉更友好;
  • ✅ 内部处理 loading 态,外部无需关心;
  • ✅ 通过 @change 事件向外通知状态变化,保持解耦;
  • ✅ 接收 disabled 控制是否禁用,适用于未登录场景。

这样一个小按钮,可以在评论区、视频卡片、动态列表等多个地方复用,极大提升开发效率。


App.vue:根组件的多重身份——全局样式 + 状态初始化 👑

App.vue 表面上只是一个外壳,实则肩负重任:

<script setup>
import { onLaunch, onShow } from '@dcloudio/uni-app'
import store from './store'

onLaunch(() => {
  const token = uni.getStorageSync('token')
  if (token) {
    store.dispatch('user/fetchProfile')
  }
})

onShow((options) => {
  console.log('App显示,来源:', options.scene)
})
</script>

<style>
@import url('//at.alicdn.com/t/font_2345678_xxxxxx.css');
body {
  font-family: 'PingFang SC', sans-serif;
}
</style>

<style src="./styles/theme.scss"></style>

它做了三件事:

  1. 启动时检查登录态 :自动拉取用户信息,避免每次打开都要重新登录;
  2. 监听页面前后台切换 :可用于统计停留时长、恢复播放等;
  3. 注入全局样式与字体 :确保全站风格统一。
graph LR
    A[App.vue] --> B[onLaunch]
    A --> C[onShow]
    A --> D[全局样式]
    A --> E[全局Store]
    B --> F[初始化Token]
    C --> G[处理分享跳转]
    D --> H[字体/颜色/间距]
    E --> I[用户信息/主题模式]
    style A fill:#4CAF50,color:white

这张图完美诠释了 App.vue 的中枢地位。它既是启动器,又是协调者,还是风格制定者。


样式系统构建:SCSS 让跨端样式不再头疼 🎨

CSS 写多了就会发现:颜色值满天飞、间距大小不一、响应式断点混乱……到最后连自己都看不懂。

解决办法?上 SCSS!

uni-app 默认支持 SCSS/LESS,强烈推荐使用 uni.scss 作为全局样式入口。

设计系统先行:变量与混合宏的力量

先建两个基础文件:

// variables.scss
$primary-color: #007AFF;
$text-dark: #1C1C1E;
$spacing-md: 16px;
$border-radius: 12px;
// mixins.scss
@mixin flex-center {
  display: flex;
  justify-content: center;
  align-items: center;
}

@mixin mobile-only {
  @media (max-width: 768px) { @content; }
}

然后在组件中轻松调用:

.video-card {
  padding: $spacing-md;
  border-radius: $border-radius;
  @include flex-center;
}

好处是什么?

  • 修改主色只需改 $primary-color ,全站同步更新;
  • 响应式布局通过 @include mobile-only 快速隔离;
  • 团队协作时有一致的设计语言,新人也能快速上手。

多端兼容调试:rpx + 条件编译 = 精准控制

移动端最难搞的就是适配。iPhone、安卓机、折叠屏……屏幕尺寸千奇百怪。

uni-app 提供了 rpx 单位,类似于微信小程序的 rpx ,基于 750 设计稿自动缩放,简直是设计师的好朋友!

但某些 CSS 属性在不同平台表现不一致怎么办?

答案是: 条件编译

.banner {
  width: 750rpx;

  /* #ifdef H5 */
  position: sticky;
  top: 0;
  /* #endif */

  /* #ifdef MP-WEIXIN */
  height: 250rpx;
  /* #endif */
}

编译时,只有对应平台才会包含这部分样式,干净利落。

配合 Chrome DevTools 和微信开发者工具双端联调,定位样式问题事半功倍。


短视频功能实战:从拍摄到上传的完整链路 📹

现在进入重头戏——实现一个完整的短视频采集与处理流程。

这类功能对性能、兼容性、用户体验的要求极高,稍有不慎就会导致卡顿、闪退、上传失败。

视频采集:chooseVideo 还是 camera?选择决定体验

方案一: uni.chooseVideo —— 快速上手

适合简单场景,比如让用户拍个小视频发评论。

uni.chooseVideo({
  sourceType: ['camera'],
  compressed: true,
  maxDuration: 60,
  success: (res) => {
    console.log('临时路径:', res.tempFilePath);
  }
});

优点:API 简单,跨平台支持好;
缺点:无法自定义界面、不能实时预览、控制粒度粗。

方案二: <camera> 组件 —— 高级定制

想要美颜、倒计时、滤镜、暂停录制?必须上 <camera>

<camera device-position="back" flash="off"></camera>
<button @click="startRecord">开始</button>
<button @click="stopRecord">结束</button>
const ctx = uni.createCameraContext()

function startRecord() {
  ctx.startRecord()
}

function stopRecord() {
  ctx.stopRecord({
    success: (res) => {
      console.log('视频保存至:', res.tempVideoPath)
    }
  })
}

⚠️ 注意: <camera> 在支付宝小程序等平台不支持!必须降级处理:

<!-- #ifdef APP-VUE || H5 -->
<camera />
<!-- #endif -->
<!-- #ifndef APP-VUE || H5 -->
<view>当前平台不支持实时摄像头</view>
<!-- #endif -->
graph TD
    A[用户点击拍摄按钮] --> B{判断平台是否支持<camera>}
    B -->|支持| C[初始化摄像头组件]
    B -->|不支持| D[使用chooseVideo替代]
    C --> E[显示实时预览画面]
    E --> F[用户触发开始录制]
    F --> G[调用startRecord()]
    G --> H[持续录制视频流]
    H --> I[用户点击停止]
    I --> J[调用stopRecord()获取tempVideoPath]
    J --> K[进入剪辑或上传流程]

这条链路清晰展示了“用户动作 → 平台适配 → 结果产出”的全过程。


视频处理:剪辑、压缩、转码三位一体 🔧

原始视频太大?画质太差?格式不兼容?统统要处理!

剪辑:截取精彩片段

使用原生插件进行裁剪:

const videoEditor = uni.requireNativePlugin('DCloud-VideoEditor')

videoEditor.cut({
  srcPath: '/storage/emulated/0/test.mp4',
  startTime: 5,
  endTime: 15,
  destPath: '/cache/cut.mp4',
  success: (res) => {
    console.log('剪辑成功:', res.path)
  }
})

注意:此功能仅限 App 端,需申请存储权限。

压缩:平衡画质与体积
uni.compressVideo({
  src: tempFilePath,
  quality: 'medium',
  bitrate: 2000 * 1000,
  fps: 24,
  success: (res) => {
    console.log('压缩后大小:', res.size / 1024 / 1024 + 'MB')
  }
})

根据网络状态动态调整:

uni.getNetworkType({
  success: function (res) {
    let bitrate = 1500 * 1000
    if (res.networkType === 'wifi') bitrate = 4000 * 1000
    // 应用到 compressVideo
  }
})
转码:统一格式保兼容

推荐输出: H.264 + AAC + MP4

平台 是否支持 H.265 推荐编码
微信小程序 H.264
Android 部分老旧机型不支持 H.264
iOS H.264(兼容优先)
H5 Edge 支持有限 H.264

前端可用 ffmpeg.wasm ,但性能开销大;生产环境建议在服务端转码。


文件校验:上传前的最后一道关卡 🔍

别忘了加一道保险:

function validateVideo(file) {
  return new Promise((resolve, reject) => {
    uni.getVideoInfo({
      src: file.path,
      success: (info) => {
        if (info.width > 1280 || info.size > 50 * 1024 * 1024) {
          reject(new Error('视频超标'))
        } else {
          resolve(info)
        }
      }
    })
  })
}

提前拦截超规文件,减轻服务器压力,也提升用户体验。


系统整合:网络层封装 + 持久化策略 = 稳定基石 🛠️

最后,让我们把零散的能力整合成坚固的基础设施。

统一请求层:告别重复配置

封装 http.js ,实现:

  • 自动拼接 baseURL;
  • 统一 header 设置;
  • 错误提示集中处理;
  • Token 自动注入。
class HttpRequest {
  request(options) {
    const token = uni.getStorageSync('token')
    if (token) {
      options.header.Authorization = `Bearer ${token}`
    }

    return new Promise((resolve, reject) => {
      uni.request({
        ...options,
        success: (res) => {
          if (res.statusCode === 401) {
            uni.redirectTo({ url: '/login' })
          }
          resolve(res.data)
        },
        fail: () => {
          uni.showToast({ title: '网络异常' })
          reject()
        }
      })
    })
  }
}

从此,所有 API 调用都走这一层,安全又省心。

缓存策略:内存 + 存储 = 双保险

类型 特点 使用建议
uni.setStorage 持久化,~10MB 用户信息、Token
内存变量 快速访问,易丢失 临时数据
Pinia 状态集中管理 登录态、主题模式

最佳实践:内存为主,定期同步到本地。

actions: {
  saveToStorage() {
    uni.setStorage({
      key: 'user_info',
      data: JSON.stringify(this.info)
    })
  }
}

写在最后:uni-app 的真正价值是什么?✨

经过这一番深入剖析,你会发现:

uni-app 的核心竞争力,从来都不是“跨端”本身,而是 如何让你在复杂的多端环境中,依然保持高效的工程化节奏

它提供了一套完整的解决方案:
- 配置驱动架构,
- 组件化开发范式,
- SCSS 样式管理体系,
- 插件扩展能力,
- 成熟的生态支持。

当你掌握了这些底层逻辑,无论是做电商、社交、教育还是医疗类 App,都能游刃有余。

所以,下次有人问你:“uni-app 到底好不好用?”
你可以笑着回答:

“它不一定是最强的,但一定是最适合快速验证想法、高效交付产品的那一款。” 🚀

本文还有配套的精品资源,点击获取 menu-r.4af5f7ec.gif

简介:本案例围绕使用uni-app框架开发短视频应用的完整实践,涵盖从项目搭建、界面设计到核心功能实现的全过程。作为一款基于Vue.js的跨平台开发框架,uni-app支持一次开发多端运行(iOS、Android、H5等),极大提升开发效率。案例以抖音类应用为参考,实现视频拍摄、编辑、上传、播放及用户交互(如点赞、评论、分享)等功能,结合.gitignore、main.js、manifest.json、pages.json等关键文件,深入讲解项目结构、路由管理、样式处理与多端兼容策略。通过本项目,开发者可系统掌握uni-app在多媒体处理、网络请求、本地存储、性能优化和安全防护等方面的核心技术,具备独立构建高性能短视频应用的能力。


本文还有配套的精品资源,点击获取
menu-r.4af5f7ec.gif

Logo

火山引擎开发者社区是火山引擎打造的AI技术生态平台,聚焦Agent与大模型开发,提供豆包系列模型(图像/视频/视觉)、智能分析与会话工具,并配套评测集、动手实验室及行业案例库。社区通过技术沙龙、挑战赛等活动促进开发者成长,新用户可领50万Tokens权益,助力构建智能应用。

更多推荐