一套代码多端运行:TinyVue PC + 移动端响应式开发
有人说:"做 PC 端像建别墅------空间大、功能多、细节丰富;做移动端像装修小户型------寸土寸金、每个像素都要精打细算。" 如果你要同时做两端,最痛苦的不是两端各自开发,而是"改了一个 bug,两端要改两次"------就像你有两套房子,修水管要修两次。TinyVue 的多端开发方案,目标就是"一套代码,两端运行"------你只修一次水管,两套房子同时修好。
一、TINY_MODE:多端模式的"开关钥匙"
1.1 三种运行模式
TinyVue 从 v3.17.0 开始提供三种 runtime 模式,通过 TINY_MODE 环境变量控制:
| 模式 | 值 | 说明 | 适用场景 |
|---|---|---|---|
| PC 模式 | pc |
传统桌面端组件 | 企业后台、管理系统 |
| 移动优先模式 | mobile-first |
移动端优化的组件 | 出海 App、消费级产品 |
| 简约模式 | mobile |
简化的移动端组件 | 轻量级移动页面 |
这三种模式的区别不仅仅是"尺寸适配"------每种模式对应的是完全不同的组件 runtime 实现。PC 模式的 Tree 组件有完整的勾选、拖拽、搜索功能;mobile-first 模式的 Tree 组件则针对触摸交互做了优化,手势操作更友好。
1.2 Runtime 文件说明
从 v3.17.0 开始,TinyVue 提供了多种 runtime 入口文件:
| Runtime 文件 | 对应模式 | 说明 |
|---|---|---|
tiny-vue-pc.mjs |
PC | 桌面端完整功能 |
tiny-vue-mobile-first.mjs |
Mobile-First | 移动端优先设计 |
tiny-vue-simple.mjs |
Simple | 简化版,适合轻量场景 |
每个 runtime 文件包含了对应模式下所有组件的实现。你选择哪个 runtime,就决定了你的应用运行在哪种模式下------就像你选了哪个钥匙,就打开了哪扇门。
二、Vite 配置:设定你的运行模式
2.1 基础配置
在 Vite 项目中,通过 define 选项设定 TINY_MODE:
javascript
// vite.config.js - PC 模式
export default defineConfig({
define: {
'process.env': {
TINY_MODE: 'pc'
}
}
})
javascript
// vite.config.js - Mobile-First 模式
export default defineConfig({
define: {
'process.env': {
TINY_MODE: 'mobile-first'
}
}
})
就这么一行配置,整个项目的组件渲染模式就定了。PC 模式下 <tiny-button> 渲染的是桌面端风格的按钮;mobile-first 模式下 <tiny-button> 渲染的是移动端风格的按钮------同一个组件名,不同模式下渲染不同实现。
2.2 按需加载插件配置
@opentiny/vue-vite-import 是 TinyVue 的按需加载插件,它也支持模式选择:
javascript
// vite.config.js
import importPlugin from '@opentiny/vue-vite-import'
export default defineConfig({
plugins: [
importPlugin({
// 指定模式:pc | mobile | mobile-first
mode: 'pc'
})
],
define: {
'process.env': {
TINY_MODE: 'pc'
}
}
})
mode 参数决定了按需加载时引用哪个 runtime 的组件代码。如果不指定,默认使用 PC 模式。
💡
define的TINY_MODE和插件的mode要保持一致------就像方向盘和导航仪必须指向同一个目的地,否则你就是在"左转灯亮着但车往右拐"。
三、同一套代码,两端渲染
3.1 核心思路
TinyVue 多端开发的核心思路是:业务代码一份,组件实现多份。
你的业务代码(页面结构、数据逻辑、交互流程)是相同的------订单列表页面不管是 PC 还是手机,都有"创建订单"、"查看详情"、"删除订单"这些操作。但组件的渲染实现根据 TINY_MODE 自动切换:
vue
<!-- 这段代码在 PC 和移动端完全相同 -->
<template>
<div class="order-page">
<tiny-button type="primary" @click="handleCreate">
{{ t('order.create') }}
</tiny-button>
<tiny-grid :data="orderList">
<tiny-grid-column field="name" :title="t('order.name')" />
<tiny-grid-column field="status" :title="t('order.status')" />
</tiny-grid>
</div>
</template>
- PC 模式:
<tiny-grid>渲染完整的桌面端表格------固定列、排序、筛选、分页全功能 - mobile-first 模式:
<tiny-grid>渲染移动端优化的列表卡片------触摸滑动、下拉刷新、底部加载
你不需要写两套模板 ------同一个 <tiny-grid> 在不同模式下自动变身。
3.2 实战项目结构
来一个完整的项目结构示例:
bash
order-management/
├── src/
│ ├── views/
│ │ ├── OrderList.vue # 共用的订单列表页面
│ │ ├── OrderDetail.vue # 共用的订单详情页面
│ │ └── OrderCreate.vue # 共用的创建订单页面
│ ├── components/
│ │ ├── OrderCard.vue # 共用的订单卡片组件
│ │ └── StatusTag.vue # 共用的状态标签组件
│ ├── locales/
│ │ ├── zhCN.js # 中文翻译
│ │ ├── enUS.js # 英文翻译
│ │ └── esLA.js # 西班牙语翻译
│ ├── theme/
│ │ └── brand-theme.js # 品牌主题配置
│ ├── main.js # 应用入口
│ └── App.vue # 根组件
├── vite.config.pc.js # PC 端构建配置
├── vite.config.mobile.js # 移动端构建配置
├── package.json
关键点:views 和 components 只有一份,没有 views-pc/ 和 views-mobile/ 这样的分裂目录。两端差异通过 TINY_MODE 配置解决,而不是通过文件复制。
3.3 构建配置分离
虽然代码一份,但构建配置需要分两份------因为 PC 和移动端的构建目标不同:
javascript
// vite.config.pc.js - PC 端构建
export default defineConfig({
define: {
'process.env': {
TINY_MODE: 'pc'
}
},
plugins: [
importPlugin({ mode: 'pc' })
],
build: {
outDir: 'dist-pc',
// PC 端不需要特别小的包体积
}
})
javascript
// vite.config.mobile.js - 移动端构建
export default defineConfig({
define: {
'process.env': {
TINY_MODE: 'mobile-first'
}
},
plugins: [
importPlugin({ mode: 'mobile-first' })
],
build: {
outDir: 'dist-mobile',
// 移动端需要更小的包体积
rollupOptions: {
output: {
manualChunks: {
'vendor': ['vue', 'vue-i18n']
}
}
}
}
})
然后在 package.json 中配置两个构建命令:
json
{
"scripts": {
"build:pc": "vite build --config vite.config.pc.js",
"build:mobile": "vite build --config vite.config.mobile.js",
"build:all": "npm run build:pc && npm run build:mobile"
}
}
运行 npm run build:all 就同时构建出两端的产物------PC 端在 dist-pc/,移动端在 dist-mobile/。
四、响应式适配:不只是"换组件"
4.1 CSS 响应式
虽然组件自动切换了,但你的页面布局(导航、侧边栏、内容区域的排列)还需要响应式适配。这是 CSS 层面的事,和 TinyVue 的 TINY_MODE 无关:
css
/* PC 端:左右布局 */
.order-page {
display: flex;
}
.order-page .sidebar {
width: 240px;
}
.order-page .main-content {
flex: 1;
}
/* 移动端:上下布局 */
@media (max-width: 768px) {
.order-page {
flex-direction: column;
}
.order-page .sidebar {
width: 100%;
display: none; /* 移动端隐藏侧边栏,用底部导航替代 */
}
}
💡 TINY_MODE 解决的是"组件交互方式"的差异,CSS 响应式解决的是"页面布局"的差异。两者配合才能真正做到"一套代码多端适配"------就像换车和换路:TINY_MODE 换了车(PC 大车 → 移动小车),CSS 响应式换了路(宽阔公路 → 狭窄巷道)。
4.2 条件渲染:极端差异场景
有些功能在 PC 端有意义,但在移动端完全不需要(比如高级筛选面板、批量操作按钮)。这时候用条件渲染:
vue
<template>
<div class="order-page">
<!-- PC 端才显示的高级筛选 -->
<tiny-filter-panel v-if="isPC" :fields="filterFields" />
<!-- 移动端才显示的底部操作栏 -->
<tiny-action-sheet v-if="isMobile" :actions="actions" />
<!-- 两端都显示的核心内容 -->
<tiny-grid :data="orderList">
<tiny-grid-column field="name" :title="t('order.name')" />
<!-- PC 端显示更多列 -->
<tiny-grid-column v-if="isPC" field="manager" :title="t('order.manager')" />
</tiny-grid>
</div>
</template>
<script>
export default {
computed: {
isPC() {
return process.env.TINY_MODE === 'pc'
},
isMobile() {
return process.env.TINY_MODE === 'mobile-first' || process.env.TINY_MODE === 'mobile'
}
}
}
</script>
注意:条件渲染用的是 process.env.TINY_MODE------这个值在构建时就被 Vite 的 define 替换成了字符串常量,所以不会有运行时开销。不用的代码在构建时直接被剔除,就像你没带冬季外套去热带旅行------行李箱里压根就没有这件衣服。
五、高级技巧:动态模式切换
有些产品需要"在一个应用内切换 PC/移动布局"------比如平板设备可以横屏(PC布局)竖屏(移动布局)切换。这时候需要在运行时动态切换 TINY_MODE。
5.1 基于屏幕尺寸自动切换
javascript
// utils/responsive.js
export function getDeviceMode() {
const width = window.innerWidth
return width >= 1024 ? 'pc' : 'mobile-first'
}
export function watchModeChange(callback) {
const mql = window.matchMedia('(min-width: 1024px)')
mql.addEventListener('change', (e) => {
callback(e.matches ? 'pc' : 'mobile-first')
})
}
5.2 结合路由的模式切换
javascript
// router.js
import { getDeviceMode, watchModeChange } from './utils/responsive'
const router = createRouter({
routes: [
// 同一个路由,不同模式下渲染效果不同
{ path: '/orders', component: () => import('./views/OrderList.vue') }
]
})
// 监听屏幕尺寸变化,通知应用更新模式
watchModeChange((mode) => {
// 这里可以触发全局状态更新
// 注意:TINY_MODE 是构建时常量,运行时切换需要配合条件渲染
console.log('模式切换建议:', mode)
})
⚠️
TINY_MODE是构建时的静态值,不能在运行时动态修改。运行时模式切换需要通过条件渲染 + CSS 响应式来实现,而不是修改process.env.TINY_MODE。如果你确实需要运行时动态切换组件实现,建议分别构建两端应用,通过路由分发。
六、性能优化:移动端打包策略
移动端对包体积更敏感------用户在 4G 网络下等你的页面加载,每多 100KB 就多 1 秒的等待时间。TinyVue 提供了多种优化手段:
6.1 按需加载
javascript
// vite.config.mobile.js
import importPlugin from '@opentiny/vue-vite-import'
export default defineConfig({
plugins: [
importPlugin({
mode: 'mobile-first' // 只加载移动端组件实现
})
]
})
按需加载只打包你用到的组件------就像搬家只带必需品,不带整屋家具。
6.2 选择简约模式
如果你的移动端页面功能简单(比如一个注册页、一个结果展示页),可以用 tiny-vue-simple.mjs runtime------它是 TinyVue 的精简版,只包含核心组件:
javascript
// vite.config.js - 简约模式
export default defineConfig({
define: {
'process.env': {
TINY_MODE: 'mobile'
}
}
})
简约模式就像经济舱------功能够用、体积更小、价格更低。如果你的移动端需要完整功能(复杂表格、树形控件、图表),那就选 mobile-first 模式------商务舱,功能全、体验好、体积稍大。
6.3 包体积对比参考
| Runtime | 预估体积 | 适用场景 |
|---|---|---|
| tiny-vue-pc.mjs | 较大 | 企业后台全功能 |
| tiny-vue-mobile-first.mjs | 中等 | 消费级移动应用 |
| tiny-vue-simple.mjs | 较小 | 轻量级移动页面 |
实际体积取决于你用了多少组件。按需加载 + mobile-first 模式 + Tree Shaking,最终打包体积可以压到很小------就像旅行前仔细规划行李,只带真正需要的。
七、完整实战:订单管理系统多端适配
来一个完整的入口配置,把国际化 + 主题 + 多端全串起来:
javascript
// main.js
import { createApp } from 'vue'
import App from './App.vue'
import { initI18n } from '@opentiny/vue-locale'
import { TinyThemeTool } from '@opentiny/vue'
import { applyBrandTheme } from './theme/brand-theme'
import zhCN from './locales/zhCN'
import enUS from './locales/enUS'
// 1. 国际化初始化
const savedLocale = localStorage.getItem('app-locale') || 'zhCN'
const i18n = initI18n({
locale: savedLocale,
fallbackLocale: 'enUS',
messages: { zhCN, enUS }
})
// 2. 主题初始化
applyBrandTheme()
// 3. 创建应用
const app = createApp(App)
app.use(i18n)
app.mount('#app')
vue
<!-- App.vue -->
<template>
<div :class="['app-container', modeClass]">
<!-- 语言切换器 -->
<language-switcher />
<!-- 主题切换 -->
<tiny-switch v-model="isDark" @change="toggleDark" /> 深色模式
<!-- 页面内容 -->
<router-view />
</div>
</template>
<script>
import { computed } from 'vue'
import { Switch } from '@opentiny/vue'
import LanguageSwitcher from './components/LanguageSwitcher.vue'
import { toggleDarkMode } from './theme/brand-theme'
export default {
components: {
TinySwitch: Switch,
LanguageSwitcher
},
setup() {
const modeClass = computed(() => {
return process.env.TINY_MODE === 'pc' ? 'pc-layout' : 'mobile-layout'
})
const isDark = ref(false)
const toggleDark = (val) => {
if (val) {
toggleDarkMode()
} else {
applyBrandTheme()
}
}
return { modeClass, isDark, toggleDark }
}
}
</script>
<style>
.pc-layout {
max-width: 1200px;
margin: 0 auto;
padding: 20px;
}
.mobile-layout {
padding: 12px;
}
</style>
一份代码、一套逻辑、两端适配------国际化帮你走向世界,主题帮你打造品牌,多端模式帮你覆盖所有屏幕。三个能力叠加,你的产品就是"全球通用、品牌专属、全屏适配"的满分选手。
八、多端开发 Checklist
| 检查项 | 是否完成 |
|---|---|
| Vite define 中设置了 TINY_MODE? | pc / mobile / mobile-first |
| 按需加载插件 mode 和 TINY_MODE 一致? | 同步配置 |
| 选了正确的 runtime 文件? | pc / mobile-first / simple |
| CSS 响应式处理了布局差异? | @media 断点 |
| 条件渲染处理了功能差异? | v-if="isPC" / v-if="isMobile" |
| 国际化初始化包含多语言? | initI18n |
| 构建命令分离了两端? | build:pc / build:mobile |
| 移动端做了包体积优化? | 按需加载 + simple runtime |
多端开发的终极目标不是"写两套代码",而是"写一套代码,自动适配两端"。TinyVue 的 TINY_MODE 机制做到了组件层面的自动切换------你只管写业务逻辑,组件渲染交给模式配置。再加上 CSS 响应式和条件渲染,你的应用在 PC 上是"大屏豪华版",在手机上是"小巧精致版",但底层代码只有一份。修一次 bug,两端同时修好------这才是多端开发该有的效率。
🏠 TinyVue 官网:opentiny.design 📦 GitHub 仓库:github.com/opentiny/ti...