Vue 门户网站 PC 与移动端适配方案
本文梳理了 Vue 项目中实现 PC 与移动端适配的两套主流方案——CSS 媒体查询响应式和双视图动态切换,涵盖设备检测、状态管理、路由分发、UI 组件库选择及构建工具配置,适用于需要同时支持桌面端和移动端的门户网站项目。
一、核心技术方案概述
针对Vue门户网站PC和移动端适配,目前主流有两种方案:
| 方案 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|
| 一套代码 + CSS媒体查询 | 两端布局差异不大,仅样式/位置/显隐不同 | 维护成本低,部署简单 | 交互逻辑差异大时代码臃肿 |
| 两套视图 + 动态切换 | 两端UI/交互差异巨大,如导航结构、功能模块完全不同 | 可针对性深度优化,代码隔离清晰 | 开发量相对较大 |
对于"移动端和PC端的导航目录展示不一样"这一典型需求,当两者导航结构差异不大时,推荐媒体查询;当差异巨大时,推荐动态切换方案。
二、方案一:一套代码 + CSS媒体查询
2.1 核心实现:媒体查询 + 弹性布局
通过CSS媒体查询,根据屏幕宽度改变导航栏的布局和展示方式,实现差异化效果。
<template>
<div id="app">
<!-- 顶部导航栏(仅桌面端显示) -->
<header class="pc-header" v-if="!isMobile">
<nav class="top-nav">
<ul class="menu-list">
<li v-for="item in menuItems" :key="item.id">
<a :href="item.link">{{ item.title }}</a>
</li>
</ul>
</nav>
</header>
<!-- 菜单按钮(仅移动端显示) -->
<header class="mobile-header" v-if="isMobile">
<button class="menu-button" @click="toggleDrawer">☰</button>
</header>
<!-- 移动端抽屉式菜单 -->
<div class="drawer" :class="{ open: isDrawerOpen }">
<div class="drawer-header">
<span>导航菜单</span>
<button class="close-button" @click="toggleDrawer">×</button>
</div>
<ul class="mobile-menu-list">
<li v-for="item in menuItems" :key="item.id">
<a :href="item.link">{{ item.title }}</a>
</li>
</ul>
</div>
<!-- 遮罩层 -->
<div
v-if="isMobile && isDrawerOpen"
class="overlay"
@click="closeDrawer"
></div>
</div>
</template>
<script setup>
import { ref } from "vue";
import { useMediaQuery } from "@vueuse/core";
const isMobile = useMediaQuery("(max-width: 768px)");
const isDrawerOpen = ref(false);
const toggleDrawer = () => {
isDrawerOpen.value = !isDrawerOpen.value;
};
const closeDrawer = () => {
isDrawerOpen.value = false;
};
</script>2.2 CSS媒体查询实现布局切换
<style scoped>
/* 移动端优先:默认移动端样式 */
.menu-button {
display: block;
background: none;
border: none;
font-size: 24px;
cursor: pointer;
}
.drawer {
position: fixed;
left: 0;
top: 0;
bottom: 0;
width: 250px;
background: #fff;
transform: translateX(-100%);
transition: transform 0.3s ease;
z-index: 1000;
}
.drawer.open {
transform: translateX(0);
}
.overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.5);
z-index: 999;
}
/* PC端调整:通过媒体查询覆盖移动端样式 */
@media (min-width: 769px) {
.menu-button,
.drawer,
.overlay {
display: none; /* 移动端专属元素全部隐藏 */
}
.pc-header {
display: block;
}
.top-nav .menu-list {
display: flex;
gap: 24px;
}
}
</style>核心思路:移动端优先策略,小屏幕默认垂直单列布局,通过min-width媒体查询重构成PC端多列/水平布局。
三、方案二:两套视图 + 动态切换
适用场景
当PC端和移动端的UI设计差异较大时,可以采用"一套代码库,两套独立视图"的模式,在根组件或路由入口根据设备类型动态选择渲染哪套组件。
想象一下这个场景:产品经理给你看两个设计稿,一个是 PC 端的后台管理系统,布局复杂,侧边导航、顶部工具栏、数据表格、各种图表卡片一应俱全,信息密度极高。另一个是移动端的 H5 页面,设计简洁,以卡片流和列表为主,交互方式也完全不同,比如有大量的滑动操作和底部弹出层。这两个页面的信息架构、交互逻辑和视觉风格天差地别。这时候如果你硬要用一套 HTML 结构和 CSS 媒体查询去 “响应”,代码会变得极其臃肿和难以维护。你可能需要写大量的 display: none 来隐藏 PC 端的复杂元素,又得用 JavaScript 去动态改变交互行为,最后搞出来一个 “四不像”,在 PC 上看着简陋,在手机上又显得笨重。 所以,当 PC 端和移动端的页面功能、交互和视觉设计差异巨大时,强行使用响应式就是给自己挖坑。更合理的做法是:在同一个 Vue 项目里,为 PC 端和移动端分别开发两套独立的页面组件。用户访问同一个 URL(比如 /search),我们根据他使用的设备(是电脑还是手机),动态地加载并渲染对应的那套组件。这样做的好处非常明显:两套代码逻辑清晰,互不干扰,可以针对各自平台的特性进行深度优化。PC 端你可以放心使用 Element Plus 这类 桌面端组件库,构建复杂的交互;移动端则可以引入 Vant 这类轻量级移动端 UI 库,专注于触屏体验。 这种模式特别适合一些 “重 PC、轻移动” 或者 “两端功能侧重点不同” 的项目。比如一个电商后台,PC 端是功能完整的运营台,而移动端可能只是一个简单的订单查询或数据概览页面。它们的核心业务虽然相通,但展现形式和操作路径完全可以独立设计。接下来,我们就聊聊怎么在 Vue 项目里优雅地实现这个方案。
3.1 推荐的项目目录结构
src/
├── views/
│ ├── pc/ # PC端所有页面组件
│ │ ├── Home/
│ │ ├── About/
│ │ └── index.vue # PC端根布局
│ ├── mobile/ # 移动端所有页面组件
│ │ ├── Home/
│ │ ├── About/
│ │ └── index.vue # 移动端根布局
│ └── index.vue # 入口:设备判断+分发
├── router/
│ ├── pc-routes.ts
│ └── mobile-routes.ts
├── stores/
│ └── device.ts # Pinia设备状态管理
└── components/
├── common/ # 跨端复用组件
├── pc/
└── mobile/3.2 Pinia设备状态管理
// stores/device.ts
import { defineStore } from "pinia";
import { computed } from "vue";
import { useMediaQuery } from "@vueuse/core";
export type DeviceType = "pc" | "mobile";
export const useDeviceStore = defineStore("device", () => {
const isMobile = useMediaQuery("(max-width: 768px)");
const deviceType = computed<DeviceType>(() =>
isMobile.value ? "mobile" : "pc",
);
return { deviceType };
});3.3 智能分发入口
<!-- views/index.vue -->
<script setup lang="ts">
import { useDeviceStore } from "@/stores/device";
import PcLayout from "./pc/index.vue";
import MobileLayout from "./mobile/index.vue";
const deviceStore = useDeviceStore();
</script>
<template>
<div>
<PcLayout v-if="deviceStore.deviceType === 'pc'" />
<MobileLayout v-else />
</div>
</template>3.4 路由分发(进阶)
// router/index.ts
import { createRouter, createWebHistory } from "vue-router";
import { pcRoutes } from "./pc-routes";
import { mobileRoutes } from "./mobile-routes";
const router = createRouter({
history: createWebHistory(),
routes: [],
});
router.beforeEach(async (to, from, next) => {
const deviceStore = useDeviceStore();
const isMobile = deviceStore.deviceType === "mobile";
// 根据设备动态添加对应路由
const targetRoutes = isMobile ? mobileRoutes : pcRoutes;
targetRoutes.forEach((route) => router.addRoute(route));
next();
});四、辅助适配工具与UI组件库选择
4.1 视口单位转换工具
推荐使用 postcss-px-to-viewport 插件,在Vite配置中直接使用px进行开发,构建时自动转换为vw单位:
// vite.config.ts
import { defineConfig } from "vite";
import postcssPxToViewport from "postcss-px-to-viewport";
export default defineConfig({
css: {
postcss: {
plugins: [
postcssPxToViewport({
unitToConvert: "px",
viewportWidth: 1920, // PC设计稿宽度
unitPrecision: 6,
propList: ["*"],
viewportUnit: "vw",
minPixelValue: 1,
}),
],
},
},
});4.2 UI组件库搭配建议
| 设备端 | 推荐组件库 | 说明 |
|---|---|---|
| PC端 | Element Plus / Ant Design Vue | 成熟的桌面端组件,支持键盘/鼠标事件 |
| 移动端 | Vant | 轻量级移动端组件库,支持touch手势 |
若移动端导航需使用Vant且在PC端测试,需引入触摸模拟器:
import "@vant/touch-emulator"; // 将PC mouse事件转为touch事件4.3 断点阈值参考
| 设备类型 | 宽度阈值 | 说明 |
|---|---|---|
| PC端 | > 768px | 桌面端完整布局 |
| 平板端 | 768px - 1024px | 响应式二级断点可选适配 |
| 移动端 | ≤ 768px | 移动端精简布局 |
五、综合建议与架构参考
5.1 技术选型对比
| 考量维度 | 媒体查询方案 | 动态切换方案 |
|---|---|---|
| 两端差异程度 | 仅样式/显隐/位置 | UI/交互/功能完全不同 |
| 维护成本 | 低 | 中等 |
| 开发效率 | 高 | 中高 |
| 代码复用率 | 高 | 中(业务逻辑可抽离复用) |
| 典型场景 | 响应式门户展示页 | 电商后台+H5官网 |
5.2 双视图动态切换项目完整流程
- 设备检测层:使用
useMediaQuery获得deviceType - 路由分发层:根组件
v-if或路由守卫动态加载端特定组件 - UI组件层:PC端Element Plus,移动端Vant,common抽离共享逻辑
- 适配工具层:postcss-px-to-viewport + flex/Grid弹性布局
- 样式层:PC和移动端独立样式文件,基础全局样式跨端共享
5.3 注意事项
- SSR闪屏处理:设备检测需在根组件
beforeMount或setup阶段执行,避免先渲染错误端再切换 - resize节流:若使用
addEventListener手动监听 resize,需加入防抖处理,避免频繁触发;若使用 VueUse 的useMediaQuery,已内置优化,无需额外处理。 - 移动端viewport设置:
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">