上一篇中介绍了MPA(Multi-Page Application)与SPA(Single-Page Application)的定义、工作原理、优缺点、适用场景以及特点,如果没有看过的小伙伴,点击下方链接查看 > juejin.cn/post/756424…
下面进入正题
1、项目实战
1.1 MPA项目实战
技术栈选择
// package.json - 传统MPA配置
{
"name": "mpa-project",
"scripts": {
"dev": "nodemon server.js",
"build:css": "postcss src/css/*.css -d public/css",
"build:js": "webpack --mode=production"
},
"dependencies": {
"express": "^4.18.0",
"ejs": "^3.1.8"
},
"devDependencies": {
"webpack": "^5.0.0",
"postcss": "^8.0.0"
}
}
服务端配置
// server.js
const express = require('express');
const app = express();
// 设置模板引擎
app.set('view engine', 'ejs');
app.set('views', './views');
// 静态资源
app.use(express.static('public'));
// 路由配置
app.use('/', require('./routes/index'));
app.use('/products', require('./routes/products'));
app.use('/cart', require('./routes/cart'));
// 启动服务器
app.listen(3000, () => {
console.log('MPA服务器运行在 http://localhost:3000');
});
// routes/products.js
const express = require('express');
const router = express.Router();
const Product = require('../models/product');
router.get('/', async (req, res) => {
try {
const products = await Product.findAll();
res.render('products', {
title: '产品列表',
products: products,
user: req.session.user
});
} catch (error) {
res.status(500).render('error', { error });
}
});
module.exports = router;
1.2 SPA项目实战
技术栈选择
// package.json - 现代SPA配置
{
"name": "spa-project",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
},
"dependencies": {
"vue": "^3.3.0",
"vue-router": "^4.2.0",
"pinia": "^2.1.0",
"axios": "^1.4.0"
},
"devDependencies": {
"vite": "^4.3.0",
"@vitejs/plugin-vue": "^4.2.0"
}
}
核心代码实现
// src/main.js
import { createApp } from 'vue';
import { createPinia } from 'pinia';
import App from './App.vue';
import router from './router';
const app = createApp(App);
app.use(createPinia());
app.use(router);
app.mount('#app');
// src/router/index.js
import { createRouter, createWebHistory } from 'vue-router';
const routes = [
{
path: '/',
name: 'Home',
component: () => import('../views/Home.vue'),
meta: { title: '首页' }
},
{
path: '/products',
name: 'Products',
component: () => import('../views/Products.vue'),
meta: { title: '产品列表', requiresAuth: true }
},
{
path: '/products/:id',
name: 'ProductDetail',
component: () => import('../views/ProductDetail.vue'),
props: true
},
{
path: '/cart',
name: 'Cart',
component: () => import('../views/Cart.vue'),
meta: { requiresAuth: true }
}
];
const router = createRouter({
history: createWebHistory(),
routes,
scrollBehavior(to, from, savedPosition) {
if (savedPosition) {
return savedPosition;
} else {
return { top: 0 };
}
}
});
// 路由守卫
router.beforeEach((to, from, next) => {
const authStore = useAuthStore();
// 设置页面标题
if (to.meta.title) {
document.title = to.meta.title;
}
// 验证身份
if (to.meta.requiresAuth && !authStore.isAuthenticated) {
next('/login');
} else {
next();
}
});
export default router;
// src/stores/auth.js
import { defineStore } from 'pinia';
export const useAuthStore = defineStore('auth', {
state: () => ({
user: null,
token: localStorage.getItem('token')
}),
getters: {
isAuthenticated: (state) => !!state.token
},
actions: {
async login(credentials) {
try {
const response = await api.post('/auth/login', credentials);
this.token = response.data.token;
this.user = response.data.user;
localStorage.setItem('token', this.token);
} catch (error) {
throw new Error('登录失败');
}
},
logout() {
this.token = null;
this.user = null;
localStorage.removeItem('token');
router.push('/');
}
}
});
// src/composables/useApi.js
import { ref } from 'vue';
export function useApi() {
const loading = ref(false);
const error = ref(null);
const request = async (url, options = {}) => {
loading.value = true;
error.value = null;
try {
const response = await fetch(url, {
headers: {
'Content-Type': 'application/json',
...options.headers
},
...options
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return await response.json();
} catch (err) {
error.value = err.message;
throw err;
} finally {
loading.value = false;
}
};
return { loading, error, request };
}