前端H5移动端基础框架模板 :Vue3 + Vite5 + Pinia + Vant4 + Sass + 附源码

技术栈选用 Vue3 + Vite5 + Pinia + Vant4 + Sass

源码地址:

javascript 复制代码
git clone https://gitee.com/gaiya001/h5-APP.git


1. 1.vite.config.js文件配置

**

javascript 复制代码
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, // 设置警告阈值,单位 KB
    brotliSize: 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文件配置详解

javascript 复制代码
// 导入 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 作为 params
        if (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文件配置

javascript 复制代码
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;
相关推荐
前端熊猫1 分钟前
React 生命周期
前端·react.js·前端框架
anyup_前端梦工厂1 分钟前
详解 ES6 Reflect
前端·javascript·es6
m0_748238781 小时前
今日推荐库:@microsoftfetch-event-source 前端发送SSE请求实现GPT流式输出
前端·gpt
叫我菜菜就好1 小时前
【Flutter_Web】Flutter编译Web第一篇(插件篇):Flutter_web实现上传TOS上传资源,编写web插件
前端·javascript·flutter·dart
布兰妮甜1 小时前
React组件最佳实践
前端·javascript·react.js·组件
@阿猫阿狗~1 小时前
创建一个 React 项目
前端·react.js·前端框架
Amo 67291 小时前
react中使用echarts
前端·react.js·echarts
m0_748240542 小时前
华为数通产品-交换机--配置管理方式 (Console口登录、telnet ssh、WEB )
前端·华为·ssh
苟非2 小时前
暂停一下,给Next.js项目配置一下ESLint(Next+tailwind项目)
开发语言·前端·javascript·next
Irises`2 小时前
前端页面导出word
前端·word·bug