技术栈选用 Vue3 + Vite5 + Pinia + Vant4 + Sass
源码地址:
git clone https://gitee.com/gaiya001/h5-APP.git
1. 1.vite.config.js文件配置
**
import { defineConfig } from 'vite' // 导入 Vite 的配置函数
import vue from '@vitejs/plugin-vue' // 导入 Vue 插件
import path from 'path' // 导入 Node.js 的 path 模块
import Components from 'unplugin-vue-components/vite' // 导入 Vue 组件自动注册插件
import { VantResolver } from 'unplugin-vue-components/resolvers' // 导入 Vant 组件解析器
import { visualizer } from 'rollup-plugin-visualizer' // 导入 Rollup 打包分析插件
import { execSync } from 'child_process' // 导入 Node.js 的 child_process 模块export default defineConfig({base: '/', // 设置应用的基础路径server: {port: 3000, // 设置开发服务器端口open: true, // 启动开发服务器时自动打开浏览器proxy: {'/api': {target: 'https://baidu', // 设置代理的目标地址changeOrigin: true, // 改变请求的源头rewrite: (path) => path.replace(/^\/api/, ''), // 重写路径},},},plugins: [vue(), // 使用 Vue 插件Components({resolvers: [VantResolver()], // 使用 Vant 组件解析器自动注册 Vant 组件}),visualizer({ open: true }), // 使用打包分析工具并在打包后自动打开分析报告],resolve: {alias: {'@': path.resolve(__dirname, './src'), // 设置路径别名,@ 指向 src 目录},},build: {rollupOptions: {output: {manualChunks: {vendor: ['vue', 'axios', 'vant'], // 手动分割 vendor 包,包含 vue, axios, vant},},},chunkSizeWarningLimit: 500, // 设置警告阈值,单位 KBbrotliSize: false, // 关闭 Brotli 压缩大小报告},optimizeDeps: {include: ['vue', 'axios', 'vant'], // 预构建依赖,提高构建速度},// 添加打包完成后清除 log 文件的钩子buildEnd: () => {try {execSync('rimraf logs'); // 使用 rimraf 清除 logs 目录// console.log('日志文件已清除');} catch (error) {// console.error('清除日志文件失败:', error);}},
})
2.main.js文件配置详解
// 导入 Vue 的 createApp 函数,用于创建 Vue 应用实例
import { createApp } from 'vue';// 导入根组件 App.vue
import App from './App.vue';// 引入路由配置
import router from './router/index.js';// 引入路由权限配置
import '@/router/permission.js';// 引入 Vant 的样式文件
import 'vant/lib/index.css';// 引入全局样式文件(使用 SCSS)
import './assets/index.sass';// 引入 Pinia 实例
import pinia from './store/index.js';// 创建 Vue 应用实例
const app = createApp(App);// 使用路由插件
app.use(router);// 使用 Pinia 插件
app.use(pinia);// 将 Pinia 实例注册为全局属性(可选)
// app.config.globalProperties.$store = pinia;// 挂载应用到 DOM 中的 #app 元素
app.mount('#app');## ```3.axios-req.js文件详解```javascript
// 导入 axios 库,用于发送 HTTP 请求
import axios from 'axios';// 从 Vant 库中导入 Toast 和 Dialog 组件,用于显示提示信息和对话框
import { Toast, Dialog } from 'vant';// 从 Pinia 存储中导入 basic store,用于管理全局状态
import { useBasicStore } from '@/store/basic';// 创建 axios 请求实例
const service = axios.create({baseURL: import.meta.env.VITE_APP_BASE_URL, // 设置基础 URL,从环境变量中获取timeout: 12000 // 设置请求超时时间为 12000 毫秒
});let loadingInstance = null; // 用于存储 loading 提示实例
let tempReqUrlSave = ''; // 临时保存请求 URL
let authorTipDoor = true; // 控制授权提示的开关// 定义一个函数,用于处理未授权的情况
const noAuthDill = () => {authorTipDoor = false; // 关闭授权提示开关Dialog.confirm({message: '请重新登录', // 提示信息confirmButtonText: '重新登录', // 确认按钮文本closeOnClickOverlay: false, // 点击遮罩层不关闭对话框showCancelButton: false, // 不显示取消按钮showClose: false, // 不显示关闭按钮theme: 'round-button' // 对话框主题}).then(() => {useBasicStore().resetStateAndToLogin(); // 重置状态并跳转到登录页面authorTipDoor = true; // 重新打开授权提示开关});
};// 请求前拦截器
service.interceptors.request.use((req) => {const { token, axiosPromiseArr } = useBasicStore();// 收集请求地址,用于取消请求req.cancelToken = new axios.CancelToken((cancel) => {tempReqUrlSave = req.url;axiosPromiseArr.push({url: req.url,cancel});});// 设置 token 到请求头if (token) req.headers['X-Auth-Token'] = token;// 如果请求方法是 GET 并且没有 params,使用 data 作为 paramsif (req.method?.toLowerCase() === 'get' && !req.params) req.params = req.data;// 请求加载提示if (req.reqLoading ?? true) {loadingInstance = Toast.loading({forbidClick: true, // 禁止点击duration: 0, // 持续时间,0 表示持续显示message: '数据载入中...' // 提示信息});}console.log('Request:', req); // 添加请求日志return req;},(err) => {console.error('Request Error:', err); // 添加请求错误日志return Promise.reject(err);}
);// 请求后拦截器
service.interceptors.response.use((res) => {// 取消请求useBasicStore().remotePromiseArrByReqUrl(tempReqUrlSave);if (loadingInstance) {Toast.clear(); // 清除 loading 提示}// 处理文件下载if (res.data?.type?.includes("sheet")) {return res;}const { code, msg } = res.data;const successCode = [0, 200, 20000]; // 成功状态码const noAuthCode = [401, 403]; // 未授权状态码if (successCode.includes(code)) {console.log('Response:', res); // 添加响应日志return res.data;} else {// authorTipDoor 防止多个请求多次提示if (authorTipDoor) {if (noAuthCode.includes(code)) {noAuthDill(); // 处理未授权情况} else {if (!res.config?.isNotTipErrorMsg) {Toast.fail({message: msg || '请求失败', // 提示信息duration: 2 * 1000 // 持续时间 2 秒});} else {return res;}return Promise.reject(msg || '请求失败');}}}},(err) => {// 取消请求useBasicStore().remotePromiseArrByReqUrl(tempReqUrlSave);if (loadingInstance) {Toast.clear(); // 清除 loading 提示}console.error('Response Error:', err); // 添加响应错误日志Toast.fail({message: err.message || '网络请求失败', // 提示信息duration: 2 * 1000 // 持续时间 2 秒});return Promise.reject(err);}
);// 手动取消请求
export function cancelRequest(url) {const { axiosPromiseArr } = useBasicStore();const index = axiosPromiseArr.findIndex(item => item.url === url);if (index !== -1) {axiosPromiseArr[index].cancel(); // 取消请求axiosPromiseArr.splice(index, 1); // 从数组中移除已取消的请求}
}// 导出 service 实例给页面调用,config -> 页面的配置
export default function axiosReq(config) {return service(config);
}
4.路由permission.js文件配置
import router from './index'; // 引入主路由模块
import { useUserStore } from '@/store/modules/user'; // pinia持久化的信息const whiteList = ['/login']; // 不需要鉴权的路由白名单router.beforeEach(async (to, from, next) => {console.log(to,"路由前置守望");const userStore = useUserStore();// 设置页面标题document.title = to.meta?.title;// 获取用户信息const { token } = userStore.userInfo;console.log(token,'token')// 如果用户已经登录,则直接放行if (token) {next();return;}// 如果用户未登录并且目标路由不在白名单中if (!token && !whiteList.includes(to.path)) {// 重定向到登录页,并携带当前路由路径作为查询参数next(`/login?redirect=${to.path}`);} else {// 目标路由在白名单中,直接放行next();}
});
// 路由后置守卫
router.afterEach((to, from) => {// console.log('路由后置守望', to, from);
});export default router;