• 不依赖框架用vue3空白项目从头打造一个过得去的前端


    通过全程参与,可以加深对VUE项目的理解。

    近期做的一个项目,前端除了UI外,没有使用什么框架。不使用现成的框架是无奈之举,因为找不到合适的。之前用的框架,比较老旧,还是vue2的;新的吧,有学习成本,怕耽误时间,也不知道效果怎么样,存在一定的风险。利用最基本的“空白”项目,按需添加基础功能,代码可控,进度也较有保证,同时还能够消除无框架不会工作恐惧症。现在记录一下心得,以后可以反复使用。

    记录重点有:

    0、整体结构
    1、路由
    2、导航条及子菜单
    3、ajax请求封装
    4、vue.config.js及系统配置
    5、登录及退出

    一、整体结构

    1、创建项目
    首先是新创建一个vue3项目。方法是

    vue create 项目名称
    
    • 1

    然后选择合适的选项。具体可参考拙作
    vue3多个项目共享开发和单个项目独立打包的解决方案

    1)按默认方案创建
    在这里插入图片描述
    项目结构:
    在这里插入图片描述
    2)创建时增加路由及store支持
    在这里插入图片描述
    在这里插入图片描述
    多了router、store以及一些页面。

    3)我们项目的整体结构
    实际项目中,当然还会夹带一些私货,额外增加一些东东。
    在这里插入图片描述

    2、项目入口main.js
    注意项目的入口不是App.vue,而是main.js。

    import { createApp } from 'vue'
    import App from './App.vue'
    import router from './router'
    import store from './store'
    
    createApp(App).use(store).use(router).mount('#app')
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    main.js是系统约定好的名称,正如一般程序的入口是函数void main()一样。整个项目的入口是main.js,然后每个模块的入口可以是index.js。都是js。本质上,vue是一个大的js语法糖。它有这样那样的结构,让人只把杭州作汴州,但归根到底,它最终是要编译成原始的js,才能被浏览器识别、运行。

    从上述代码可以知道,main.js的作用是加载App.vue,引入store、路由,然后绑定到页面id="app"的div。这样该div就是整个项目的活动区域,即展示页面内容的区域了:
    在这里插入图片描述
    vue项目是单页面应用,只有一张html页面。我们看到的所有内容,都展示在id="app"的这个div上!完全由js控制。

    3、我们项目里的main.js
    main.js这里的引用,都是全局性的。除了路由,store,还可以引入ui框架,css,全局性组件等等。比如我们项目里的main.js是这样写:

    import { createApp } from "vue";
    import App from "./App.vue";
    import router from "./router";
    
    import PerfectScrollbar from "vue3-perfect-scrollbar";//滚动条美化
    import "vue3-perfect-scrollbar/dist/vue3-perfect-scrollbar.css";
    import Antd from "ant-design-vue";//ant design vue,UI框架
    import "ant-design-vue/dist/antd.css";
    
    import "@/assets/css/default.css"; //自定义的全局css
    
    createApp(App).use(Antd).use(PerfectScrollbar).use(router).mount("#app");
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    二、路由

    简单来说,路由就是菜单配置,将菜单项的id,路径,都集中写在了一个配置文件里。

    1、系统自动生成的路由

    import { createRouter, createWebHashHistory } from 'vue-router'
    import HomeView from '../views/HomeView.vue'
    
    const routes = [
      {
        path: '/',
        name: 'home',
        component: HomeView
      },
      {
        path: '/about',
        name: 'about',
        // route level code-splitting
        // this generates a separate chunk (about.[hash].js) for this route
        // which is lazy-loaded when the route is visited.
        component: () => import(/* webpackChunkName: "about" */ '../views/AboutView.vue')
      }
    ]
    
    const router = createRouter({
      history: createWebHashHistory(),
      routes
    })
    
    export default router
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    以上完全是系统自动生成的代码。看上去也不难理解。其中路由表routes对应的是导航条
    在这里插入图片描述
    2、实际项目中的路由
    见本文第六章第一条

    3、代码中使用路由
    如果想多加几个导航条,可以依葫芦画瓢,很容易就能实现。每个路由,name是唯一的ID,在代码里控制跳转的话,引用name就可以,可以不再重复写这个path。比如在某个vue里,可以这样使用路由:

    import { defineComponent } from "vue";
    import { useRouter } from "vue-router"; //引入useRouter
    
    export default defineComponent({
      setup() {
    	const router = useRouter();
        const browseIt = (fd) => {
          const to = router.resolve({
            name: "about", //这里是跳转页面的name,要与路由设置保持一致
            params: { id: fd.id },
          });
          window.open(to.href, "_blank");//新开一个页面,打开about
        };
    
        return {
          browseIt,
        };
      },
    });
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    三、导航条及子菜单

    1、导航条
    系统生成的代码,已经做了很好的示范:

    <template>
      
      <nav>
        <router-link to="/">Homerouter-link> |
        <router-link to="/about">Aboutrouter-link>
      nav>
      
      
      <router-view/>
    template>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    通常,我们的菜单可以从服务器端返回,然后利用循环语句输出。

    2、子菜单
    导航条是一级菜单,二级或以下是子菜单。子菜单,可以利用UI框架来完成。比如我们利用ant design vue的menu组件来完成
    在这里插入图片描述

    四、ajax请求封装

    我们做的项目,总免不了要从服务器端请求数据。前后端分离,理论上前端的数据都来自于服务器端。

    目前一般是结合第三方组件axios对ajax请求进行封装。理由主要有2个:

    1)ajax一般有超时、返回代码区别对待等共性操作,封装一个ajax处理方法,统一调用,方便维护和修改;除此之外,axios还可以对ajax请求进行拦截,使得请求前、请求后、结果返回做相应处理。

    2)解决跨域问题。axios本身似乎并不解决跨域问题,但由于第一点,它对ajax请求进行了封装,我们可以利用一个统一方法,结合api路径前缀做转发,使得浏览器以为api所在路径与前端是同一台服务器,因而不存在跨域。注意所谓跨域问题,是浏览器的一个安全设置,是一种保护措施。只要它不觉得跨域,那跨域就不存在。

    1、统一的ajax封装方法

    1)ajax封装代码(src/request/index.js)

    import axios from "axios";
    
    // 创建一个 axios 实例
    const service = axios.create({
      baseURL: "/api", // 所有的请求地址前缀部分
      timeout: 60000, // 请求超时时间毫秒
      withCredentials: true, // 异步请求携带cookie
      headers: {
        // 设置后端需要的传参类型
        "Content-Type": "application/json",
        //'token': 'your token',
        "X-Requested-With": "XMLHttpRequest",
      },
    });
    
    // 添加请求拦截器
    service.interceptors.request.use(
      function (config) {
        // 在发送请求之前做些什么
        return config;
      },
      function (error) {
        // 对请求错误做些什么
        console.log(error);
        return Promise.reject(error);
      }
    );
    
    // 添加响应拦截器
    service.interceptors.response.use(
      function (response) {
        console.log(response);
        // 2xx 范围内的状态码都会触发该函数。
        // 对响应数据做点什么
        // dataAxios 是 axios 返回数据中的 data
        const dataAxios = response.data;
        // 这个状态码是和后端约定的
        //const code = dataAxios.reset;
        return dataAxios;
      },
      function (error) {
        // 超出 2xx 范围的状态码都会触发该函数。
        // 对响应错误做点什么
        console.log(error);
        return Promise.reject(error);
      }
    );
    
    export default service;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49

    2)使用ajax统一封装方法

    import request from "@/request";
    
    export const userLogin = (params) => {
      return request({ //request就是统一封装好的方法
        url: "/sys/login",//注意request方法会在前面加上前缀“/api”,变成实质上是请求 “/api/sys/login”
        params,
        method: 'post'
      })
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    2、开发环境中,利用vue.config.js做api路径转发
    如上所属,ajax的封装方法request,会在api请求路径前加上前缀“/api”,可以利用这一点来设置转发。转发是为了避免跨域。其原理,表面上,我们请求的是当前服务器的路径“/api/a/b/c”,但我们设置凡“/api”开头的路径,都转发到另一台服务器(即后端所在服务器)。浏览器蒙在鼓里,并没有察觉,因此不会触发所谓跨域警告。

      devServer: {
        //devServer 只是一个webpack插件 只能用于开发环境
        proxy: {
          "/api": {
            target: "192.168.0.22",
            pathRewrite: {
              "^/api": "",
            },
          },
        },
      },
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    3、生产环境中,利用nginx做api路径转发

    location /api/ {
    	proxy_pass http://192.168.0.22:8090/;#必须斜杠/结尾
    	proxy_set_header   X-Forwarded-Proto $scheme;
    	proxy_set_header   Host              $http_host;
    	proxy_set_header   X-Real-IP         $remote_addr;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    五、vue.config.js及系统配置

    有关vue.config.js里面的配置,上面约略提到了一些,这里给出完整代码:

    1、系统生成的vue.config.js代码

    const { defineConfig } = require('@vue/cli-service')
    module.exports = defineConfig({
      transpileDependencies: true
    })
    
    • 1
    • 2
    • 3
    • 4

    2、按项目需要修改后的代码

    const { defineConfig } = require("@vue/cli-service");
    const path = require("path");
    const appConfig = require("./public/config");
    
    const resolve = (dir) => {
      return path.join(__dirname, dir);
    };
    
    module.exports = defineConfig({
      transpileDependencies: true,
    
      devServer: {
        //devServer 只是一个webpack插件 只能用于开发环境
        proxy: {
          "/api": {
            target: appConfig.server,
            pathRewrite: {
              "^/api": "",
            },
          },
        },
      },
    
      // 项目部署基础
      // 默认情况下,我们假设你的应用将被部署在域的根目录下,
      // 例如:https://www.my-app.com/
      // 默认:'/'
      // 如果您的应用程序部署在子路径中,则需要在这指定子路径
      // 例如:https://www.foobar.com/my-app/
      // 需要将它改为'/my-app/'
      publicPath: "/",
      chainWebpack: (config) => {
        config.resolve.alias
          .set("@", resolve("src")) // key,value自行定义,比如.set('@@', resolve('src/components'))
          .set("_c", resolve("src/components"));
    
        config.plugin("html").tap((args) => {
          args[0].title = appConfig.app.name;
          return args;
        });
      },
    });
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43

    值得一提的是,里面的devServer元素设置,针对的是开发环境。详见拙作:vue.config.js中的devServer

    3、真正的项目配置
    按我的理解,vue.config.js只在开发阶段和发布时有用,之后就像被消费了的耗材,没啥用处了。同一项目中,真正的项目配置,是/public/config/index.js,即使是发布、部署到生产环境,仍然可以修改,是真正意义上的配置文件。

    /public/config/index.js

    exports.app = {
      name: "订餐拿饭抓阄系统",
      owner: "蓬蓬养猪场",
      developer: "一群饭桶",
    };
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    在这里插入图片描述

    详见拙作:vue项目读取全局配置

    六、登录及退出

    主要是路由的应用。原理是:
    将页面分为无须登录可浏览和必须登录方可浏览2种。在路由表中做过滤。当转向必须登录页面时,检查登录状态,如果已登录,放行;未登录,转向登录页。注意登录页要设为无须登录可浏览。流程很简单,大家都明白,就不画流程图了。

    1、路由表(/src/router/index.js)
    完整的路由表。有关登录控制部分,见所谓“路由守卫”。

    import { createRouter, createWebHashHistory } from "vue-router";
    import Home from "../views/home/PageIndex.vue";
    
    const routes = [
      {
        path: "/login",
        name: "login",
        component: () => import("../views/login/PageIndex.vue"),
        meta: {
          noLogin: true, //无须登录即可浏览。自定义属性
        },
      },
      {
        path: "/",
        name: "Home",
        component: Home,
      },
      {
        path: "/map",
        name: "Map",
        component: () => import("../views/map/PageIndex.vue"),
      },
      {
        path: "/resource",
        name: "Resource",
        component: () => import("../views/resource/PageIndex.vue"),
      },
      {
        path: "/resource/detail/:id",
        name: "ResourceDetail",
        component: () => import("../views/resource/PageDetail.vue"),
      },
      {
        path: "/sys",
        name: "Sys",
        component: () => import("../views/sys/PageIndex.vue"),
      },
    ];
    
    const router = createRouter({
      history: createWebHashHistory(),
      routes,
    });
    
    // 路由守卫
    router.beforeEach((to, from, next) => {
      const isLogin = localStorage.isLogin ? true : false;
      if (isLogin) {
        //已经登录的情况下,不能再打开登录页
        to.path === "/login" ? next("home") : next();
      } else {
        //如果无须登录则直接打开,否则转向登录页面
        to.meta.noLogin || to.path === "/login" ? next() : next("/login");
      }
    });
    
    export default router;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57

    2、登录及登出
    注意要使用router本身的方法,不可用原始的js,如window.location.href = *** 这种方法,否则部署到nginx会报错。
    1)登录

    const router = useRouter();
    localStorage.setItem("isLogin", true);
    document.cookie = "token=" + res.token;
    router.replace({ path: "/" });//转向首页。使用replace,避免登录后回退问题
    
    • 1
    • 2
    • 3
    • 4

    2)登出

    const router = useRouter();
    localStorage.removeItem("isLogin");
    router.replace({ path: "/login" });
    
    • 1
    • 2
    • 3

    七、store

    暂无

  • 相关阅读:
    电脑硬件——显卡
    基于热交换优化的BP神经网络(分类应用) - 附代码
    20220608-SCRFD-样本和计算重分配
    贼简单的Android计时工具,老铁,还不试用起来。
    TypeScript_基本类型
    算法笔记-第十章-图的存储
    如何迈向IPv6之IPv6过渡技术-尚文网络奎哥
    MySQL的介绍
    《深入理解java虚拟机》第七章读书笔记——虚拟机类加载机制
    微信小程序启动报错 app.js错误: ReferenceError: App is not defined at app.js;渲染层错误
  • 原文地址:https://blog.csdn.net/leftfist/article/details/125844055