业务背景
领导想使用同一个链接,实现pc打开是web应用,手机打开是一个H5应用
解决方案
以下是基于Vue和Node.js的混合策略详细实现方案,结合服务端设备识别与前端动态适配:
架构设计
bash
项目结构
├── server/ # Node.js服务端
│ ├── middleware/
│ │ └── deviceDetect.js # 设备检测中间件
│ └── server.js # 主服务
├── src/ # Vue前端
│ ├── layouts/
│ │ ├── Mobile.vue # 移动端布局
│ │ └── Desktop.vue # PC端布局
│ ├── router/
│ │ └── dynamicRoutes.js # 动态路由
│ ├── App.vue
│ └── main.js
└── 其他标准Vue项目结构
分步实现
一、服务端设备检测(Node.js)
ini
// server/middleware/deviceDetect.js
const mobileDetect = (req, res, next) => {
const ua = req.headers['user-agent'];
// 使用正则检测常见移动设备
const isMobile = /Mobile|Android|iP(hone|od)|IEMobile|BlackBerry/.test(ua);
// 将设备类型注入请求对象
req.deviceType = isMobile ? 'mobile' : 'desktop';
next();
};
module.exports = mobileDetect;
二、Express服务配置
ini
// server/server.js
const express = require('express');
const path = require('path');
const deviceDetect = require('./middleware/deviceDetect');
const app = express();
// 使用设备检测中间件
app.use(deviceDetect);
// 注入设备类型到HTML模板
app.get('/', (req, res) => {
const template = `
<!DOCTYPE html>
<html>
<head>
<title>混合适配示例</title>
<script>
window.__DEVICE_TYPE__ = '${req.deviceType}'; // 注入设备类型
</script>
</head>
<body>
<div id="app"></div>
<script src="/dist/build.js"></script>
</body>
</html>
`;
res.send(template);
});
// 静态资源托管
app.use(express.static(path.join(__dirname, '../dist')));
app.listen(3000, () => {
console.log('Server running on port 3000');
});
三、Vue前端动态适配
xml
<!-- src/App.vue -->
<template>
<div id="app">
<component :is="currentLayout">
<router-view/>
</component>
</div>
</template>
<script>
export default {
computed: {
currentLayout() {
return this.$store.state.deviceType === 'mobile'
? 'MobileLayout'
: 'DesktopLayout';
}
},
created() {
// 初始化设备类型
this.$store.commit('SET_DEVICE_TYPE', window.__DEVICE_TYPE__);
// 添加窗口监听实现响应式切换
window.addEventListener('resize', this.handleResize);
},
methods: {
handleResize() {
const isMobile = window.innerWidth <= 768;
this.$store.commit('SET_DEVICE_TYPE', isMobile ? 'mobile' : 'desktop');
}
}
}
</script>
四、Vuex状态管理
typescript
// src/store/index.js
import Vue from 'vue';
import Vuex from 'vuex';
Vue.use(Vuex);
export default new Vuex.Store({
state: {
deviceType: 'desktop' // 默认值
},
mutations: {
SET_DEVICE_TYPE(state, type) {
state.deviceType = type;
}
}
});
五、动态路由配置
ini
// src/router/dynamicRoutes.js
export const createRoutes = (deviceType) => {
const baseRoutes = [
{
path: '/',
component: () => deviceType === 'mobile'
? import('@/views/MobileHome.vue')
: import('@/views/DesktopHome.vue')
}
];
return deviceType === 'mobile'
? [...baseRoutes, ...mobileSpecificRoutes]
: [...baseRoutes, ...desktopSpecificRoutes];
};
六、响应式布局组件
xml
<!-- src/layouts/Mobile.vue -->
<template>
<div class="mobile-layout">
<nav-bar position="bottom"/>
<transition name="slide">
<keep-alive>
<router-view/>
</keep-alive>
</transition>
</div>
</template>
<style scoped>
.mobile-layout {
max-width: 768px;
margin: 0 auto;
}
</style>
xml
<!-- src/layouts/Desktop.vue -->
<template>
<div class="desktop-layout">
<side-bar/>
<main-content>
<router-view/>
</main-content>
</div>
</template>
增强功能实现
1. 设备类型同步策略
scss
// 在路由跳转时保持设备标识
router.beforeEach((to, from, next) => {
if (to.query.deviceType) {
store.commit('SET_DEVICE_TYPE', to.query.deviceType);
}
next();
});
2. 混合检测策略
javascript
// 综合检测方法
function getDeviceType() {
// 服务端注入的优先级最高
if (window.__DEVICE_TYPE__) return window.__DEVICE_TYPE__;
// 前端补充检测
return window.innerWidth <= 768 || 'ontouchstart' in window
? 'mobile'
: 'desktop';
}
3. 按需加载优化
less
// 动态加载组件优化
Vue.component('AsyncComponent', () => ({
component: import(`@/components/${store.state.deviceType}/Component.vue`),
loading: LoadingComponent,
delay: 200
}));
部署配置
Nginx层设备识别(可选)
bash
server {
listen 80;
location / {
# 设备识别前置处理
if ($http_user_agent ~* '(mobile|android|iphone)') {
set $device_type 'mobile';
}
proxy_set_header X-Device-Type $device_type;
proxy_pass http://localhost:3000;
}
}
测试方案
- 设备模拟测试:
scss
// 使用Jest测试不同设备类型
describe('设备适配测试', () => {
test('移动端布局渲染', () => {
store.commit('SET_DEVICE_TYPE', 'mobile');
const wrapper = mount(App);
expect(wrapper.find('.mobile-layout').exists()).toBe(true);
});
});
- 端到端测试:
dart
// 使用Cypress进行真实设备测试
describe('端到端测试', () => {
it('在移动设备显示正确布局', () => {
cy.viewport('iphone-x')
.visit('/')
.get('.mobile-nav').should('be.visible');
});
});
优化建议
- 缓存策略:
arduino
// 服务端添加Vary头
res.setHeader('Vary', 'User-Agent');
- 渐进增强:
xml
<!-- 核心内容直出 -->
<noscript>
<div class="core-content">
<!-- 基础HTML内容 -->
</div>
</noscript>
- 性能监控:
javascript
// 添加设备类型到性能统计
window.performance.mark('deviceType', store.state.deviceType);
这种混合方案实现了:
- 首屏服务端精准设备识别
- 前端动态响应式适配
- 代码逻辑与UI的分离维护
- SEO友好性
- 平滑的过渡动画
- 设备类型状态同步
可根据实际需求选择在服务端渲染(SSR)或客户端渲染(CSR)模式下使用,建议搭配Vue CLI的构建配置实现最优打包策略。