最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • 备战2021:Vite2项目最佳实践

    正文概述 掘金(杨村长)   2021-02-03   588

    备战2021:Vite2项目最佳实践

    vite2来了

    Vite1还没用上,Vite2已经更新了,全新插件架构,丝滑的开发体验,和Vue3的完美结合。 2021年第一弹,村长打算以Vite2+Vue3为主题开启大家的前端学习之旅。

    2021先学学vite准没错

    备战2021:Vite2项目最佳实践

    本文目标

    • vite2变化分析
    • 项目中常见任务vite2+vue3实践

    创建Vite2项目

    闲言碎语不必说,下面我们表一表好汉vite2

    使用npm:

    $ npm init @vitejs/app
    

    Vite2主要变化

    对我们之前项目影响较大的我已经都标记出来了:

    • 配置选项变化:vue特有选项、创建选项、css选项、jsx选项等
    • 别名行为变化:不再要求/开头或结尾
    • Vue支持:通过 @vitejs/plugin-vue插件支持
    • React支持
    • HMR API变化
    • 清单格式变化
    • 插件API重新设计

    Vue支持

    Vue的整合也通过插件实现,和其他框架一视同仁:

    备战2021:Vite2项目最佳实践

    SFC定义默认使用setup script,语法比较激进,但更简洁,好评!

    备战2021:Vite2项目最佳实践

    别名定义

    不再需要像vite1一样在别名前后加上/,这和webpack项目配置可以保持一致便于移植,好评!

    import path from 'path'
    
    export default {
      alias: {
        "@": path.resolve(__dirname, "src"),
        "comps": path.resolve(__dirname, "src/components"),
      },
    }
    

    App.vue里面用一下试试

    <script setup>
    import HelloWorld from 'comps/HelloWorld.vue'
    </script>
    

    插件API重新设计

    Vite2主要变化在插件体系,这样更标准化、易扩展。Vite2插件API扩展自Rollup插件体系,因此能兼容现存的Rollup插件,编写的Vite插件也可以同时运行于开发和创建,好评!

    Vue3 Jsx支持

    vue3jsx支持需要引入插件:@vitejs/plugin-vue-jsx

    $ npm i @vitejs/plugin-vue-jsx -D
    

    注册插件,vite.config.js

    import vueJsx from "@vitejs/plugin-vue-jsx";
    
    export default {
      plugins: [vue(), vueJsx()],
    }
    

    用法也有要求,改造一下App.vue

    <!-- 1.标记为jsx -->
    <script setup lang="jsx">
    import { defineComponent } from "vue";
    import HelloWorld from "comps/HelloWorld.vue";
    import logo from "./assets/logo.png"
    
    // 2.用defineComponent定义组件且要导出
    export default defineComponent({
      render: () => (
        <>
          <img  src={logo} />
          <HelloWorld msg="Hello Vue 3 + Vite" />
        </>
      ),
    });
    </script>
    
    Mock插件应用

    之前给大家介绍的vite-plugin-mock已经重构支持了Vite2。

    安装插件

    npm i mockjs -S
    
    npm i vite-plugin-mock cross-env -D
    

    配置,vite.config.js

    import { viteMockServe } from 'vite-plugin-mock'
    
    export default {
      plugins: [ viteMockServe({ supportTs: false }) ]
    }
    

    设置环境变量,package.json

    {
      "scripts": {
        "dev": "cross-env NODE_ENV=development vite",
        "build": "vite build"
      },
    } 
    

    项目基础架构

    路由

    安装vue-router 4.x

    npm i vue-router@next -S
    
    备战2021:Vite2项目最佳实践

    路由配置,router/index.js

    import { createRouter, createWebHashHistory } from 'vue-router';
    
    const router = createRouter({
      history: createWebHashHistory(),
      routes: [
        { path: '/', component: () => import('views/home.vue') }
      ]
    });
    
    export default router
    

    引入,main.js

    import router from "@/router";
    createApp(App).use(router).mount("#app");
    

    状态管理

    安装vuex 4.x

    npm i vuex@next -S
    
    备战2021:Vite2项目最佳实践

    Store配置,store/index.js

    import {createStore} from 'vuex';
    
    export const store = createStore({
      state: {
        couter: 0
      }
    });
    

    引入,main.js

    import store from "@/store";
    createApp(App).use(store).mount("#app");
    

    样式组织

    安装sass

    npm i sass -D
    

    styles目录保存各种样式

    备战2021:Vite2项目最佳实践

    index.scss作为出口组织这些样式,同时编写一些全局样式

    备战2021:Vite2项目最佳实践

    最后在main.js导入

    import "styles/index.scss";
    

    UI库

    就用我们花果山团队自家的element3。

    安装

    npm i element3 -S
    

    完整引入,main.js

    import element3 from "element3";
    import "element3/lib/theme-chalk/index.css";
    
    createApp(App).use(element3)
    

    按需引入,main.js

    import "element3/lib/theme-chalk/button.css";
    import { ElButton } from "element3"
    createApp(App).use(ElButton)
    

    抽取成插件会更好,plugins/element3.js

    // 完整引入
    import element3 from "element3";
    import "element3/lib/theme-chalk/index.css";
    
    // 按需引入
    // import { ElButton } from "element3";
    // import "element3/lib/theme-chalk/button.css";
    
    export default function (app) {
      // 完整引入
      app.use(element3)
    
      // 按需引入
      // app.use(ElButton);
    }
    

    测试

    <el-button>my button</el-button>
    

    基础布局

    我们应用需要一个基本布局页,类似下图,将来每个页面以布局页为父页面即可:

    备战2021:Vite2项目最佳实践

    布局页面,layout/index.vue

    <template>
      <div class="app-wrapper">
        <!-- 侧边栏 -->
        <div class="sidebar-container"></div>
        <!-- 内容容器 -->
        <div class="main-container">
          <!-- 顶部导航栏 -->
          <navbar />
          <!-- 内容区 -->
          <app-main />
        </div>
      </div>
    </template>
    
    <script setup>
    import AppMain from "./components/AppMain.vue";
    import Navbar from "./components/Navbar.vue";
    </script>
    
    <style lang="scss" scoped>
    @import "../styles/mixin.scss";
    
    .app-wrapper {
      @include clearfix;
      position: relative;
      height: 100%;
      width: 100%;
    }
    </style>
    

    路由配置,router/index.js

    {
      path: "/",
    	component: Layout,
      children: [
        {
          path: "",
          component: () => import('views/home.vue'),
          name: "Home",
          meta: { title: "首页", icon: "el-icon-s-home" },
        },
      ],
    },
    

    动态导航

    侧边导航

    根据路由表动态生成侧边导航菜单。

    备战2021:Vite2项目最佳实践

    首先创建侧边栏组件,递归输出routes中的配置为多级菜单,layout/Sidebar/index.vue

    <template>
      <el-scrollbar wrap-class="scrollbar-wrapper">
        <el-menu
          :default-active="activeMenu"
          :background-color="variables.menuBg"
          :text-color="variables.menuText"
          :unique-opened="false"
          :active-text-color="variables.menuActiveText"
          mode="vertical"
        >
          <sidebar-item
            v-for="route in routes"
            :key="route.path"
            :item="route"
            :base-path="route.path"
          />
        </el-menu>
      </el-scrollbar>
    </template>
    
    <script setup>
    import SidebarItem from "./SidebarItem.vue";
    import { computed } from "vue";
    import { useRoute } from "vue-router";
    import { routes } from "@/router";
    import variables from "styles/variables.module.scss";
    
    const activeMenu = computed(() => {
      const route = useRoute();
      const { meta, path } = route;
      if (meta.activeMenu) {
        return meta.activeMenu;
      }
      return path;
    });
    </script>
    
    

    添加相关样式:

    • styles/variables.module.scss
    • styles/sidebar.scss
    • styles/index.scss中引入

    创建SidebarItem.vue组件,解析当前路由是导航链接还是父菜单:

    备战2021:Vite2项目最佳实践

    面包屑

    通过路由匹配数组可以动态生成面包屑。

    面包屑组件,layouts/components/Breadcrumb.vue

    <template>
      <el-breadcrumb class="app-breadcrumb" separator="/">
          <el-breadcrumb-item v-for="(item, index) in levelList" :key="item.path">
            <span
              v-if="item.redirect === 'noRedirect' || index == levelList.length - 1"
              class="no-redirect"
              >{{ item.meta.title }}</span>
            <a v-else @click.prevent="handleLink(item)">{{ item.meta.title }}</a>
          </el-breadcrumb-item>
      </el-breadcrumb>
    </template>
    
    <script setup>
    import { compile } from "path-to-regexp";
    import { reactive, ref, watch } from "vue";
    import { useRoute, useRouter } from "vue-router";
    
    const levelList = ref(null);
    const router = useRouter();
    const route = useRoute();
    
    const getBreadcrumb = () => {
      let matched = route.matched.filter((item) => item.meta && item.meta.title);
    
      const first = matched[0];
      if (first.path !== "/") {
        matched = [{ path: "/home", meta: { title: "首页" } }].concat(matched);
      }
    
      levelList.value = matched.filter(
        (item) => item.meta && item.meta.title && item.meta.breadcrumb !== false
      );
    }
    
    const pathCompile = (path) => {  
      var toPath = compile(path);
      return toPath(route.params);
    }
    
    const handleLink = (item) => {
      const { redirect, path } = item;
      if (redirect) {
        router.push(redirect);
        return;
      }
      router.push(pathCompile(path));
    }
    
    getBreadcrumb();
    watch(route, getBreadcrumb)
    
    </script>
    
    <style lang="scss" scoped>
    .app-breadcrumb.el-breadcrumb {
      display: inline-block;
      font-size: 14px;
      line-height: 50px;
      margin-left: 8px;
    
      .no-redirect {
        color: #97a8be;
        cursor: text;
      }
    }
    </style>
    

    数据封装

    统一封装数据请求服务,有利于解决一下问题:

    • 统一配置请求
    • 请求、响应统一处理

    准备工作:

    • 安装axios:

      npm i axios -S
      
    • 添加配置文件:.env.development

      VITE_BASE_API=/api
      

    请求封装,utils/request.js

    import axios from "axios";
    import { Message, Msgbox } from "element3";
    
    // 创建axios实例
    const service = axios.create({
      // 在请求地址前面加上baseURL
      baseURL: import.meta.env.VITE_BASE_API,
      // 当发送跨域请求时携带cookie
      // withCredentials: true,
      timeout: 5000,
    });
    
    // 请求拦截
    service.interceptors.request.use(
      (config) => {
        // 模拟指定请求令牌
        config.headers["X-Token"] = "my token";
        return config;
      },
      (error) => {
        // 请求错误的统一处理
        console.log(error); // for debug
        return Promise.reject(error);
      }
    );
    
    // 响应拦截器
    service.interceptors.response.use(
      /**
       * 通过判断状态码统一处理响应,根据情况修改
       * 同时也可以通过HTTP状态码判断请求结果
       */
      (response) => {
        const res = response.data;
    
        // 如果状态码不是20000则认为有错误
        if (res.code !== 20000) {
          Message.error({
            message: res.message || "Error",
            duration: 5 * 1000,
          });
    
          // 50008: 非法令牌; 50012: 其他客户端已登入; 50014: 令牌过期;
          if (res.code === 50008 || res.code === 50012 || res.code === 50014) {
            // 重新登录
            Msgbox.confirm("您已登出, 请重新登录", "确认", {
              confirmButtonText: "重新登录",
              cancelButtonText: "取消",
              type: "warning",
            }).then(() => {
              store.dispatch("user/resetToken").then(() => {
                location.reload();
              });
            });
          }
          return Promise.reject(new Error(res.message || "Error"));
        } else {
          return res;
        }
      },
      (error) => {
        console.log("err" + error); // for debug
        Message({
          message: error.message,
          type: "error",
          duration: 5 * 1000,
        });
        return Promise.reject(error);
      }
    );
    
    export default service;
    
    

    业务处理

    结构化数据展示

    使用el-table展示结构化数据,配合el-pagination做数据分页。

    备战2021:Vite2项目最佳实践

    文件组织结构如下:list.vue展示列表,edit.vuecreate.vue编辑或创建,内部复用detail.vue处理,model中负责数据业务处理。

    备战2021:Vite2项目最佳实践

    list.vue中的数据展示

    <el-table v-loading="loading" :data="list">
      <el-table-column label="ID" prop="id"></el-table-column>
      <el-table-column label="账户名" prop="name"></el-table-column>
      <el-table-column label="年龄" prop="age"></el-table-column>
    </el-table>
    

    listloading数据的获取逻辑,可以使用compsition-api提取到userModel.js

    export function useList() {
      // 列表数据
      const state = reactive({
        loading: true, // 加载状态
        list: [], // 列表数据
      });
    
      // 获取列表
      function getList() {
        state.loading = true;
        return request({
          url: "/getUsers",
          method: "get",
        }).then(({ data, total }) => {
          // 设置列表数据
          state.list = data;
        }).finally(() => {
          state.loading = false;
        });
      }
      
      // 首次获取数据
      getList();
    
      return { state, getList };
    }
    

    list.vue中使用

    import { useList } from "./model/userModel";
    
    const { state, getList } = useList();
    

    分页处理,list.vue

    <pagination
          :total="total"
          v-model:page="listQuery.page"
          v-model:limit="listQuery.limit"
          @pagination="getList"
        ></pagination>
    

    数据也在userModel中处理

    const state = reactive({
      total: 0,   // 总条数
      listQuery: {// 分页查询参数
        page: 1,  // 当前页码
        limit: 5, // 每页条数
      },
    });
    
    request({
      url: "/getUsers",
      method: "get",
      params: state.listQuery, // 在查询中加入分页参数
    })
    
    表单处理

    用户数据新增、编辑使用el-form处理

    可用一个组件detail.vue来处理,区别仅在于初始化时是否获取信息回填到表单。

    <el-form ref="form" :model="model" :rules="rules">
      <el-form-item prop="name" label="用户名">
        <el-input v-model="model.name"></el-input>
      </el-form-item>
      <el-form-item prop="age" label="用户年龄">
        <el-input v-model.number="model.age"></el-input>
      </el-form-item>
      <el-form-item>
        <el-button @click="submitForm" type="primary">提交</el-button>
      </el-form-item>
    </el-form>
    

    数据处理同样可以提取到userModel中处理。

    export function useItem(isEdit, id) {
      const model = ref(Object.assign({}, defaultData));
    
      // 初始化时,根据isEdit判定是否需要获取详情
      onMounted(() => {
        if (isEdit && id) {
          // 获取详情
          request({
            url: "/getUser",
            method: "get",
            params: { id },
          }).then(({ data }) => {
            model.value = data;
          });
        }
      });
      return { model };
    }
    

    配套视频演示

    我专门录了一套视频演示本文所做的所有操作,喜欢看视频学习的小伙伴移步: 「备战2021」Vite2 + Vue3项目最佳实践

    制作不易,求一个3连关注不过分吧!?

    配套源码讲义

    欢迎关注公众号「村长学前端」自取

    后续创作计划

    vite2最重要的更新莫过于插件系统,后续我打算搞一搞,包括不限于如下内容:

    • vite2工作机制分析
    • vite2插件机制分析
    • vite2插件编写实战

    大家点个赞,收藏一下,以便后续学习。

    支持村长

    关于vite2项目实践就说到这里,这篇内容折腾了很久,踩了不少坑,掉了几根头发,小伙伴们点个赞?鼓励一下,好不好?

    我近期的文章(感谢小伙伴们的鼓励与支持???):

    • ?备战2021:vite工程化实践,建议收藏 857赞
    • ?又是一夜,这篇Composition-API实操还觉得短吗 217赞
    • ?拿下vue3你要做好这些准备 76赞
    • ?闪电五连鞭:Composition API原理深度剖析 54赞

    起源地下载网 » 备战2021:Vite2项目最佳实践

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元