vue

197 阅读15分钟

vue搭建项目

//安装vue
npm install vue -g
//安装vue脚手架
npm install -g @vue/cli
//查看是否安装成功
vue -V

搭建vue项目

1. 创建项目:vue create xxx(项目名),以下步骤是选择 vue2移动端
2. 安装lessnpm install less -snpm install less-loader -s
3. 安装vue-router:npm install vue-router@3.5.2 -s (安装最新版本,可能会出现兼容性问题)

3.1 新建 router 文件夹,在里面新建 index.js文件

import Vue from "vue";
import VueRouter from "vue-router";
Vue.use(VueRouter);

//非懒加载
import Home from "@/view/home.vue";
const routes = [
  {
    path: "/",
    name: "Home",
    component: Home,
  },
];

const router = new VueRouter({
  routes,
});

export default router;

    3.2 在main.js页面引用即可

import router from './router/index';

 new Vue ({
  router,
  render:(h)=>h(App),
)}.$mount("#app');
4. 执行项目npm run serve

如果运行项目代码报错如下:

image.png

需要关闭每次保存都进行检测

在vue.config.js文件上面,配置 lintOnSave:false

image.png

5. 初始化css样式
* {
  margin: 0;
  padding: 0;
}
/*常用基本标签重设*/
body {
  color: #000;
  -webkit-text-size-adjust: none;
  -webkit-overflow-scrolling: touch;
  outline: none;
  background: #fff;
  -webkit-user-select: none;
  -moz-user-select: none;
  -o-user-select: none;
  user-select: none;
  -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
}
ol,
ul,
li {
  list-style: none;
}
a {
  text-decoration: none;
  color: #4a4a4a;
}
a:hover {
  text-decoration: none;
}

img {
  border: 0;
  vertical-align: middle;
}
/*禁止长按链接与图片弹出菜单*/
a,
img {
  -webkit-touch-callout: none;
}
input {
  vertical-align: middle;
  outline: none;
}
textarea {
  resize: none;
}
form {
  display: inline-block;
  width: 100%;
}
button {
  border: none;
  background: none;
  text-align: center;
  cursor: pointer;
  display: block;
}
table {
  border-spacing: 0;
  border-collapse: collapse;
}
textarea {
  overflow-y: auto;
  overflow-x: hidden;
  outline-style: none;
  outline-width: initial;
  outline-color: initial;
  resize: none;
}
textarea:focus {
  border: none;
}

.weight {
  font-weight: bold;
}
/*显隐*/
.show {
  display: block;
}
.hide {
  display: none;
}

/*浮动、位置*/
.fleft {
  float: left;
}
.fright {
  float: right;
}
.tcenter {
  text-align: center;
}
.tleft {
  text-align: left;
}
.tright {
  text-align: right;
}

/*clear clearfix清浮动*/
.clear {
  clear: both;
}
.clearfix:after {
  content: "\0020";
  display: block;
  height: 0;
  clear: both;
  visibility: hidden;
}
.clearfix {
  zoom: 1;
}

/*flex布局*/
.flex {
  display: flex;
  display: -webkit-box;
  display: -ms-flexbox;
}
.flex1 {
  flex: 1;
  -webkit-box-flex: 1;
  -ms-flex: 1;
}
.flex2 {
  flex: 2;
  -webkit-box-flex: 2;
  -ms-flex: 2;
}
.flex3 {
  flex: 3;
  -webkit-box-flex: 3;
  -ms-flex: 3;
}
.flex-cloum {
  flex-direction: column;
}
.flex-item {
  display: flex;
  align-items: center;
}
.flex-wrap {
  flex-wrap: wrap;
}
6. 配置模式和环境变量

6.1 在根目录下新建 .env.development(测试) 与 .evn.production(生产)两个文件配置测试与生产的不同地址 image.png
       6.2 为不同的地址在 package.json 文件配置打包环境

image.png

7. 配置vue.config.js

简单一点的配置就是,增加一个publicPath,打包的静态资源就不是空白页面,渲染的是配置的首页 image.png

8. 移动端适配(rem和viewport)

rem手写版

在src文件目录下,新建utils文件夹,再新建rem.js文件

//默认调用一次设置
setHtmlFontSize();

//详情说明setHtmlFontSize
function setHtmlFontSize() {
  // 1. 获取当前屏幕的宽度
  var windowWidth = document.documentElement.offsetWidth;
  // 2. 定义标准屏幕宽度
  var standardWidth = 375;
  // 3. 定义标准屏幕的根元素字体大小 假设100px  1rem=100px  10px = 0.1rem  1px 0.01rem
  var standardFontSize = 100;
  // 4. 计算当前屏幕对应的根元素字体大小
  var nowFontSize = (windowWidth / standardWidth) * standardFontSize + "px";
  // console.log(nowFontSize);
  // 5. 把当前计算的根元素的字体大小设置到html上
  document.querySelector("html").style.fontSize = nowFontSize;
}

//简写setHtmlFontSize,两个setHtmlFontSize二先一即可
function setHtmlFontSize() {
  // 1. 获取当前屏幕的宽度
  var windowWidth = document.documentElement.offsetWidth;
  // 2. 把当前计算的根元素的字体大小设置到html上
  document.querySelector("html").style.fontSize = (windowWidth / 375) *100 + 'px';
}

// 6. 添加一个屏幕宽度变化的事件  屏幕变化就触发变化根元素字体大小计算的js
window.addEventListener("resize", setHtmlFontSize);


------------------------以下是需要兼容pc端可配置的----------------------

// 如果需要适配PC端
function setRemPc(){
  var windowWidth=document.documentElement.offsetWidth;
  //1092设置图宽度
  document.querySelector("html").style.fontSize=(windowWidth / 1092) * 100 + 'px';
};

 if((navigator.userAgent.match(/(phone|pad|pod|iPhone|iPod|ios|iPad|Android|Mobile|BlackBerry|IEMobile|MQQBrowser|JUC|Fennec|wOSBrowser|BrowserNG|WebOS|Symbian|Windows Phone)/i))) {
  //移动端使用
  setHtmlFontSize();
  window.addEventListener('resize', setHtmlFontSize);//浏览器窗口大小改变时调用rem换算方法
}else{
    //pc端使用
    setRemPc()  
    window.addEventListener('resize', setRemPc);//浏览器窗口大小改变时调用rem换算方法
 }

在main.js里面引用 import '@/utils/rem'

viewport

安装插件 npm i postcss-px-to-viewport --save
在根目录下新建 postcss.config.js 文件,配置如下:

module.exports = {
  plugins: {
    "postcss-px-to-viewport": {
      unitToConvert: "px", //需要转换的单位,默认为"px"viewportWidth: 375, //设计稿的视口宽度
      unitPrecision: 5, //单位转换后保留的小数位数
      propList: ["*"], //要进行转换的属性列表,*表示匹配所有,!表示不转换
      viewportUnit: "vw", //转换后的视口单位
      fontViewportUnit: "vw", //转换后字体使用的视口单位
      selectorBlackList: ["jti"], //不进行转换的css选择器,继续使用原有单位
      minPixelValue: 1, //设置最小的转换数值
      mediaQuery: false, //设置媒体查询里的单位是否需要转换单位
      replace: true, //是否直接更换属性值,而不添加备用属性
      exclude: [/node_modules/], //忽略某些文件夹下的文件
    },
    autoprefixer: {
      overrideBrowserslist: [
        "Android 4.1",
        "iOS 7.1",
        "Chrome > 31",
        "ff > 31",
        "ie >= 8",
        //'last 2 versions', // 所有主流浏览器最近2个版本
      ],
      grid: true,
    },
  },
};
9.安装ui框架

比如:vant
安装插件
npm i vant@latest-v2
按需引用
npm i babel-plugin-import -D
在babel.config.js文件中配置如下:

module.exports = {
  presets: ["@vue/cli-plugin-babel/preset"],
  plugins: [
    [
      "import",
      {
        libraryName: "vant",
        libraryDirectory: "es",
        style: true,
      },
      "vant",
    ],
  ],
};
10.安装axios

10.1 安装axios

npm install axios

10.2在主入口文件main.js中引用

import axios from 'axios'

Vue.use(axios);

10.3 封装axios
(qs与lodash插件请参考文档)

import axios from "axios";
import qs from "qs";
import { toast } from "vant";
import { cloneDeep } from "lodash";
import store from "@/store";

let baseURL = process.env.VUE_APP_URL;
//接口白名单
const whiteList = ["/xxxxx/getAuthCodeUri2login"];

class Http {
  static defaultOpts = {
    baseURL,
    headers: { "content-type": "application/json" },
    timeout: 30000,
    withCredentials: true,  //携带cookie(根据要求添加,默认是不携带)
  };
  _http;

  constructor(opts = { enableReqInterceptor: true }) {
    this._http = axios.create(Object.assign(cloneDeep(Http.defaultOpts), opts));

    if (opts.enableReqInterceptor) {
      this._http.interceptors.request.use(
        reqInterceptorHandler,
        reqInterceptorError
      );
    }
    this._http.interceptors.response.use(
      resInterceptorHandler,
      resInterceptorError
    );
  }

  post(reqUrl, params, config) {
    return this._http.post(reqUrl, params, config);
  }

  get(reqUrl, params) {
    return this._http.get(reqUrl, { params });
  }
}

function reqInterceptorHandler(config) {
  return new Promise((resolve, reject) => {
    setReqConfg(config);
    config.data = config.data || {};
    // 附件上传
    if (config.url == "/file/uploadImg") {
      config.headers["content-type"] = "multipart/form-data";
      return resolve(config);
    }
    //XXX详情特殊拼参请求
    if (config.url == "/XXXX/info") {
      config.url = "/XXXX/info" + `/${store.state.applyId}`;
    }
    // 一般请求
    if (config.headers["content-type"] === "application/json") {
      config.data = qs.stringify(config.data);
    }

    return resolve(config);
  });
}
function reqInterceptorError(error) {
  console.error("req error:", error);
  return Promise.reject(error);
}

function resInterceptorHandler(res) {
  return res.data;
}

function resInterceptorError(err) {
  console.error("res error:", JSON.stringify(err));
  if (err && err.response.status === 401) {
    console.log("登陆过期");
  }
  toast.error(err.data || err.response.data.errMsg || err)
  return Promise.reject(err);
}

// 数组是否包含部分字符串
function isStr(arr, str) {
  let n = arr.length;
  for (let i = 0; i < n; i++) {
    if (str.indexOf(arr[i]) != -1) {
      return true;
    }
  }
  return false;
}

//设置请求配置参数
function setReqConfg(config) {
  //白名单接口处理
  if (isStr(whiteList, config.url)) {
    config.baseURL = process.env.VUE_APP_Q_URL;
  } else {
    let token = localStorage.getItem("token");
    config.headers["accessToken"] = token;
  }
}

export default new Http();

11.babel.config.js配置

工程创建完成后,需要配置兼容浏览器

module.exports = {
  presets: [
    "@vue/cli-plugin-babel/preset",
    //兼容处理代码
    [
      "@babel/preset-env",
      {
        useBuiltIns: "entry",
        corejs: 3,
        targets: {
          ios: "8",
          android: "4",
          chrome: "58",
        },
      },
    ],
  ],
  plugins: [
    [
      "import",
      {
        libraryName: "vant",
        libraryDirectory: "es",
        style: true,
      },
      "vant",
    ],
  ],
};

vue.config.js配置文件

Vue Cli 3生成的项目结构,没有build、config目录,而是使用vue.config.js来进行配置

查看Vue Cli 版本 vue --vuesion

vue create xx 项目,会自动生成 vue.config.js文件,如下图所示,这里使用的是 @vue/cli-service 提供的 defineConfig 帮手函数

image.png

1. publicPath
  • Type: string
  • Default: '/'
    1.baseUrl 从Vue CLI 3.3起已弃用,请使用publicPath
    2.默认情况下,VUE CLI会假设你的应用是被部署在一个域名的要路径上,例如 htts://www.my-app.com/ 。如果应该被部署在一个子路径上,你就需要用到这个选项指定这个子路径。例如,部署子路径为:htts://www.my-app.com/my-app/,则设置 publicPath 为 /my-app/.这个值也可以被设置为空字符串('')或是相对路径('./'),这样所有的资源都会被链接为相对路径,这样打出来的包可以被部署在任意路径。
const { defineConfig } = require("@vue/cli-service");
module.exports = defineConfig({
 transpileDependencies: true,
 publicPath:'',
})
2.outputDir
  • Type: string
  • Default: 'dist'
  • 当构建生产环境包,注意目标目录的内容在构建之前会被清除(构建时传入 --no-clean可关闭该行为
  • 注:outputDir不能配置为空字符串
const { defineConfig } = require("@vue/cli-service");
module.exports = defineConfig({
 transpileDependencies: true,
 publicPath:'',
 outputDir:'dist'|| "my-app" || '...'
})
3.assetsDir
  • Type: string

  • Default: ''

  • 放置生成的静态资源 (js、css、img、fonts) 的 (相对于 outputDir 的) 目录。

const { defineConfig } = require("@vue/cli-service");
module.exports = defineConfig({
 transpileDependencies: true,
 publicPath:'',
 assetsdir:''|| "assets"
})
4.indexPath
  • Type: string

  • Default: 'index.html'

  • 指定生成的 index.html 的输出路径 (相对于 outputDir)。也可以是一个绝对路径。

const { defineConfig } = require("@vue/cli-service");
module.exports = defineConfig({
 transpileDependencies: true,
 publicPath:'',
 indexPath:'myIndex.html'|| "index.html" || '...'
})
5.filenameHashing
  • Type: boolean

  • Default: true

  • 默认情况下,生成的静态资源在它们的文件名中包含了 hash 以便更好的控制缓存。然而,这也要求 index 的 HTML 是被 Vue CLI 自动生成的。如果你无法使用 Vue CLI 生成的 index HTML,你可以通过将这个选项设为 false 来关闭文件名哈希。

const { defineConfig } = require("@vue/cli-service");
module.exports = defineConfig({
 transpileDependencies: true,
 publicPath:'',
 filenameHashing:false || true
})
6.lintOnSave
  • Type: boolean | 'warning' | 'default' | 'error'

  • Default: 'default'

  • 是否在开发环境下通过 eslint-loader 在每次保存时 lint 代码。这个值会在 @vue/cli-plugin-eslint被安装之后生效。

const { defineConfig } = require("@vue/cli-service");
module.exports = defineConfig({
transpileDependencies: true,
publicPath:'',
lintOnSave:false || true ||process.env.NODE_ENV !== "production"

})
7.pages
  • Type: Object

  • Default: undefined

  • 在 multi-page 模式下构建应用。每个“page”应该有一个对应的 JavaScript 入口文件。其值应该是一个对象,对象的 key 是入口的名字,value 是:

    • 一个指定了 entrytemplatefilenametitle 和 chunks 的对象 (除了 entry 之外都是可选的);
    • 或一个指定其 entry 的字符串。
const { defineConfig } = require("@vue/cli-service");
module.exports = defineConfig({
transpileDependencies: true,
publicPath:'',
pages: {
   index: {
     // page 的入口
     entry: 'src/index/main.js',
     // 模板来源
     template: 'public/index.html',
     // 在 dist/index.html 的输出
     filename: 'index.html',
     // 当使用 title 选项时,
     // template 中的 title 标签需要是 <title><%= htmlWebpackPlugin.options.title %></title>
     title: 'Index Page',
     // 在这个页面中包含的块,默认情况下会包含
     // 提取出来的通用 chunk 和 vendor chunk。
     chunks: ['chunk-vendors', 'chunk-common', 'index']
   },
   // 当使用只有入口的字符串格式时,
   // 模板会被推导为 `public/subpage.html`
   // 并且如果找不到的话,就回退到 `public/index.html`。
   // 输出文件名会被推导为 `subpage.html`。
   subpage: 'src/subpage/main.js'
 }

总:
const { defineConfig } = require("@vue/cli-service");
module.exports = defineConfig({
  // 默认:'/',部署应用包时的基本 URL,Vue CLI 会假设你的应用是被部署在一个域名的根路径上,如果是部署在一个子路径上,比如在https://www.my-app.com/my-app/,则设置publicPath: /my-app/。这个值也可以被设置为空字符串 ('') 或是相对路径 ('./'),这样所有的资源都会被链接为相对路径,方便迁移。
  publicPath: process.env.NODE_ENV === "production" ? "/" : "/",
  // 默认:'dist', 当运行vue-cli-service build时生成的生产环境构建文件的目录。注意目标目录在构建之前会被清除 (构建时传入--no-clean 可关闭该行为)。
  outputDir: "dist",
  // 默认:'', 放置生成的静态资源 (js、css、img、fonts)的 (相对于 outputDir 的) 目录
  assetsDir: "",
  // 默认:'index.html', 指定生成的index.html 的输出路径 (相对于 outputDir)。也可以是一个绝对路径。
  indexPath: "index.html",
  // 默认:true, 默认情况下,生成的静态资源在它们的文件名中包含了hash 以便更好的控制缓存。然而,这也要求index的 HTML 是被 Vue CLI自动生成的。如果你无法使用 Vue CLI 生成的 index HTML,你可以通过将这个选项设为false 来关闭文件名哈希。
  filenameHashing: true,
  // 默认:undefined, 在 multi-page 模式下构建应用。每个“page”应该有一个对应的 JavaScript 入口文件。其值应该是一个对象,对象的 key 是入口的名字,value 可以是对象或字符串,类似:
  pages: {
    index: {
      // page 的入口
      entry: "src/index/main.js",
      // 模板来源
      template: "public/index.html",
      // 在 dist/index.html 的输出
      filename: "index.html",
      // 当使用 title 选项时,
      // template 中的 title 标签需要是 <title><%= htmlWebpackPlugin.options.title %></title>
      title: "Index Page",
      // 在这个页面中包含的块,默认情况下会包含
      // 提取出来的通用 chunk 和 vendor chunk。
      chunks: ["chunk-vendors", "chunk-common", "index"],
    },
    // 当使用只有入口的字符串格式时,
    // 模板会被推导为 `public/subpage.html`
    // 并且如果找不到的话,就回退到 `public/index.html`。
    // 输出文件名会被推导为 `subpage.html`。
    subpage: "src/subpage/main.js",
  },
  // 默认:default (可选值:‘warning’ | ‘default’ | ‘error’), 是否在开发环境下通过eslint-loader在每次保存时lint代码。这个值会在 @vue/cli-plugin-eslint被安装之后生效。
  lintOnSave: default,
  // 默认:false, 是否使用包含运行时编译器的 Vue 构建版本。设置为 true 后你就可以在 Vue 组件中使用 template选项了,但是这会让你的应用额外增加 10kb 左右。
  runtimeCompiler: false,
  // 默认:[], 默认情况下babel-loader会忽略所有 node_modules中的文件。如果你想要通过 Babel 显式转译一个依赖,可以在这个选项中列出来。
  transpileDependencies: [],
  // 默认:true, 如果你不需要生产环境的 source map,可以将其设置为false 以加速生产环境构建。
  productionSourceMap: true,
  // 默认:undefined, 设置生成的 HTML 中 <link rel="stylesheet"> 和 <script> 标签的 crossorigin 属性。
  crossorigin: undefined,
  // 默认:false, 在生成的 HTML 中的<link rel="stylesheet"> 和<script>标签上启用Subresource Integrity (SRI)。如果你构建后的文件是部署在 CDN 上的,启用该选项可以提供额外的安全性。
  integrity: false,
  // 如果这个值是一个对象,则会通过 webpack-merge合并到最终的配置中。
  // 如果这个值是一个函数,则会接收被解析的配置作为参数。该函数既可以修改配置并不返回任何东西,也可以返回一个被克隆或合并过的配置版本。
  // 调整 webpack 配置最简单的方式就是在 vue.config.js 中的 configureWebpack 选项提供一个对象
  // 该对象将会被 webpack-merge 合并入最终的 webpack 配置。
  // 有些 webpack 选项是基于 vue.config.js 中的值设置的,所以不能直接修改。例如你应该修改 vue.config.js 中的 outputDir 选项而不是修改 output.path;你应该修改 vue.config.js 中的 publicPath 选项而不是修改 output.publicPath。这样做是因为 vue.config.js 中的值会被用在配置里的多个地方,以确保所有的部分都能正常工作在一起。
  configureWebpack: {
    plugins: [
      new MyAwesomeWebpackPlugin()
    ]
  },
  // 如果你需要基于环境有条件地配置行为,或者想要直接修改配置,那就换成一个函数 (该函数会在环境变量被设置之后懒执行)。该方法的第一个参数会收到已经解析好的配置。在函数内,你可以直接修改配置,或者返回一个将会被合并的对象:
  //   configureWebpack: config => {
  //     if (process.env.NODE_ENV === 'production') {
  //       // 为生产环境修改配置...
  //     } else {
  //       // 为开发环境修改配置...
  //     }
  //   }
  // });
  // 是一个函数,会接收一个基于 webpack-chain 的 ChainableConfig实例。允许对内部的webpack配置进行更细粒度的修改。
  // Vue CLI 内部的 webpack 配置是通过 webpack-chain 维护的。这个库提供了一个 webpack 原始配置的上层抽象,使其可以定义具名的 loader 规则和具名插件,并有机会在后期进入这些规则并对它们的选项进行修改。
  // 它允许我们更细粒度的控制其内部配置。接下来有一些常见的在 vue.config.js 中的 chainWebpack 修改的例子。
  // 当你打算链式访问特定的 loader 时,vue inspect 会非常有帮助。
  // 对于 CSS 相关 loader 来说,我们推荐使用 css.loaderOptions 而不是直接链式指定 loader。这是因为每种 CSS 文件类型都有多个规则,而 css.loaderOptions 可以确保你通过一个地方影响所有的规则。
  chainWebpack: config  => {
    config.module
      .rule('graphql')
      .test(/\.graphql$/)
      .use('graphql-tag/loader')
        .loader('graphql-tag/loader')
        .end()
      // 你还可以再添加一个 loader
      .use('other-loader')
        .loader('other-loader')
        .end()
  },
  // 如果你想要替换一个已有的基础 loader,例如为内联的 SVG 文件使用 vue-svg-loader 而不是加载这个文件:
  // chainWebpack: config => {
  //   const svgRule = config.module.rule('svg')
 
  //   // 清除已有的所有 loader。
  //   // 如果你不这样做,接下来的 loader 会附加在该规则现有的 loader 之后。
  //   svgRule.uses.clear()
 
  //   // 添加要替换的 loader
  //   svgRule
  //     .use('vue-svg-loader')
  //       .loader('vue-svg-loader')
  // }
 
  css: {
    // 默认:true, 默认情况下,只有 *.module.[ext]结尾的文件才会被视作CSS Modules 模块。设置为 false后你就可以去掉文件名中的.module并将所有的 *.(css|scss|sass|less|styl(us)?)文件视为 CSS Modules模块。
    // 如果你在 css.loaderOptions.css里配置了自定义的 CSS Module选项,则 css.requireModuleExtension必须被显式地指定为true或者false,否则我们无法确定你是否希望将这些自定义配置应用到所有 CSS文件中。
    requireModuleExtension: true,
    // 默认:生产环境下是 true,开发环境下是 false, 是否将组件中的 CSS 提取至一个独立的 CSS 文件中 (而不是动态注入到 JavaScript中的inline代码)。同样当构建 Web Components组件时它总是会被禁用 (样式是 inline 的并注入到了 shadowRoot 中)。当作为一个库构建时,你也可以将其设置为false免得用户自己导入 CSS。提取 CSS 在开发环境模式下是默认不开启的,因为它和 CSS 热重载不兼容。然而,你仍然可以将这个值显性地设置为 true 在所有情况下都强制提取。
    extract: true,
    // 默认:false, 是否为 CSS 开启 source map。设置为true之后可能会影响构建的性能。
    sourceMap: false,
    // 支持的 loader 有, css-loader,postcss-loader,sass-loader,less-loader,stylus-loader
    loaderOptions: {
      css: {
        // 这里的选项会传递给 css-loader
      },
      postcss: {
        // 这里的选项会传递给 postcss-loader
      }
    },
    devServer: {
      host: "0.0.0.0" || 'localhost',
      port: 8888 || 8080 || ...,
      open: true,
      https: fase,
      // 如果你的前端应用和后端 API 服务器没有运行在同一个主机上,你需要在开发环境下将 API 请求代理到 API 服务器。这个问题可以通过vue.config.js 中的 devServer.proxy 选项来配置。
      proxy: "http://localhost:6666",
      //配置多个代理
      proxy: {
        "/dev-api": {
          target: "http://localhost:6666",
          changeOrigin: true,
          ws: true,
          pathRewrite: {
            ["^/dev-api"]: "",
          },
        },
       "/foo": {
        target: "http://localhost:8080", // 本地模拟数据服务器
        changeOrigin: true,
        pathRewrite: {
          "^/foo": "" // 去掉接口地址中的foo字符串
        }
      },
     }
    },
     // 是否为 Babel 或 TypeScript 使用 thread-loader
    parallel: require('os').cpus().length > 1,
    // 向 PWA 插件传递选项
    pwa: {},
    // 这是一个不进行任何 schema 验证的对象,因此它可以用来传递任何第三方插件选项。例如
    pluginOptions: {
      foo: {
      // 插件可以作为 `options.pluginOptions.foo` 访问这些选项。
      }
    }
  }

router-view不显示

场景:新建项目时写好路由,router-view无效不起作用,也不报错,仔细核对发现routers的问题
const routers = [  //应该是routes routes routes
  {
    path: "/home",
    name: "home",
    component: home,
  },
  {
    path: "/login",
    name: "login",
    component: login,
  },
];

const router = new VueRouter({
  mode: "hash",
  routers,
});

routes,routes,routes,routes,routes,routes 大意了,多写几遍

Vuex

vuex是专门为vue开发的状态管理工具,类似一个仓库,仓库中存储着应用的状态。

  • vuex的状态存储是响应式的
  • 改变状态的唯一方法就是提交commit(mutation),符合vue单向数据流思想
  • vuex模块
    • state:保存公共数据,数据变化的时候, 都会重新求取计算属性,并且触发更新相关联的 DOM
    • mutation:修改数据的方法(同步)
    • getters:类似于computed(计算属性,对现有的状态进行计算得到新的数据;当 Store 数据源发生变化时,Getter 的返回值会自动更新。)
    • action:用于提交mutation,不是直接改变数据,而是异步操作,或者一些比较复杂的逻辑都可以使用action提交dispatch,dispatch就是commit
    • module:保存特定的模块,例如用户模块,允许将单一的store拆分为多个并且各自保存

1.安装

npm install vuex@3 --save ,在vue2中,要用vuex 3的版本,不能下载最新的

2.新建store文件夹

在src目录下,新建store文件夹,并且在下面新建index.js文件

image.png

index.js文件夹

import Vue form 'vue';
import Vuex form 'vuex;
Vue.use(Vuex)

export const store = new Vuex.Store({
  //公共数据
  state:{
  name:'王一一',
  age:'',
  token:'',
  register:'',
  },
  //修改state数据状态
  mutations:{
   setName(state,name){
     state.name=name;
   },
   setRegister(state,register){
     state.register=register
   },
   setToken(state,token){
     state.token=token
   },
 },
 //计算属性
 getters:{
 name:state => state.name,
 age:stage => state.age,
 },
 //异步处理方法
 actions:{
 //获取用户token
   getUserToken({commit},isRegister=false){
     return new Promise(async (resolve,reject)=>{
       let register= await SureRegister(isRegister);
       if(register){
         commit('setRegister',true);
         SureLogin().then((data)=>{
           commit('setToken',data.token)
         }).catch(()=>{});
         resolve(resolve);
       }else{
         reject('请注册')
       }
   },
 },
})

3.挂载到main.js

  import {store} from '@/store';

  new Vue({
   router,
   store,
   render: (h) => h(App),
  }).$mount("#app");

4.调用mutations数据

方法一:

this.$store.commit('mutation名', 实参)

eg.this.$store.commit('setName','刘一一')

方法二:mapMutations

import {mapMutations} from 'vuex'

methods:{
//将this.setName 映射为 this.$store.commit('setName')
...mapMutations(['setName','setToken'])
//将this.add() 映射为 this.$store.commit('setName')
...mapMutations({
  add:'setName'
)}

5.调用actions

方法一:

this.$store.dispatch('actions名',参数) eg:this.$store.dispatch('getUserToken')

方法二:mapActions

import {mapActions} from 'vuex';

methods:{
//将this.getUserToken映射为this.$store.dispatch('getUserToken')
  ...mapActions(['getUserToken','xxxx'];
//将this.add映射为this.$store.dispatch('getUserToken'
  ...mapActions({
    add:'getUserToken'
  })
}

6.调用state、getters

方法一:

this.$store.state.name

ths.$store.getters.name

方法二:mapState

import {mapState} from 'vuex'
import {mapGetters} from 'vuex'

export default{
  computed:{
    ...mapState(['name','age']),
    ...mapGetters(['name','age'])
    
  }

路由懒加载

当打包构建应用时,JavaScript包会变得非常大,影响页面加载。路由懒加载能把不同路由对应的组件分割成不同的代码块,当路由被访问时才加载对应的组件,这样会更加高效。

1.非懒加载
import Home from '@/view/Home'
const routes=[
  {
    path:'/',
    name:'home',
    component:Home
  }
]  
2.使用import
//const 组件名 =()=>import('组件路径')
//下面两行代码,没有指定 webpackChunkName,每个组件打包成一个js文件
const Home =()=> import('@/view/Home');
const Index =()=> import('@/view/Index');

//下面2行代码,指定了相同的webpackChunkName,会合并打包成一个js文件
const Home =()=> import(/* webpackChunkName:'index' */ '@/view/Home');
const Index =()=>import(/* webpackChunkName:'index' */ '@/view/Index');

const routes=[
  {
    path:'/',
    name:'home',
    component:Home
  },
    {
    path:'/index',
    name:'index',
    component:Index
  },
]
3.webpack提供的require.ensure()

require.ensure(dependencies:String [],callback:function(require),errorCallback:function(error),chunkName:String)

require.ensure()接受三个参数:

第一个参数的依赖关系是一个数组,代表了当前需要进来的模块的一些依赖;
第二个参数回调就是一个回调函数其中需要注意的是,这个回调函数有一个参数要求,通过这个要求就可以在回调函数内动态引入其他模块值得注意的是,虽然这个要求是回调函数的参数,理论上可以换其他名称,但是实际上是不能换的,否则的的的的WebPack就无法静态分析的时候处理它;
第三个参数errorCallback比较好理解,就是处理错误的回调;
第四个参数chunkName则是指定打包的组块名称。
此方法也可指定相同的chunkName,合并打包成一个js文件

//生成demo.js
{
  path:'/',
  name:'home',
  component:r => require.ensure([],()=>r(require('@/view/home')),'demo')
 },
  {
   path:'/index',
   name:'index',
   component:r=>require.ensure([],()=>r(require('@/view/index')),'demo')
 },
 
 
 //传入空字符串 则每个component会单独生成一个js文件
 {
   path:'/index',
   name:'index',
   component:r=>require.ensure([],()=>r(require('@/view/index')),'')
 }
4.vue异步组件

vue-router配置路由,使用Vue的异步组件技术,可以实现按需加载。
但是,这种情况下每一个组件就会生成一个js文件,不能分类指定chunkName

//vue异步组件
const routes=[
  {
    path:'/',
    name:'home',
    component:resolve=>require(['@/view/home'],resolve)
  }
]

filter(过滤器)的调用

filter是一个数据格式经过了过滤器后出来另一种数据格式,常见的场景比如:日期,数字

html中调用调用过滤器

1.全局过滤器

全局过滤器就是在main.js内直接通过 Vue.filter(‘过滤器名称’,函数) 来定义,后续在所有组件内都可以使用

//引入公共全局filters    --main.js 页面--
import filters from '@/filters/index';
Object.keys(filters).forEach(k=> Vue.filter(k,filters[k]));

<!--------组件内调用----------------->
<span>{{ currentTime | formatTime }}</span>

<!-- 在 `v-bind` 中 -->
  `<div v-bind:id="rawId | formatId"></div>`

2.局部过滤器

定义在组件内部filters属性上,它只能在此组件内部使用。

import {formatTime,dealNumber} from 'lib/utils'
filters:{
  formatTime(value){
    return formatTime(value)
    },
   formatMoney(val) {
      return dealNumber(val)
    }
  },  
  <span>{{ currentTime | formatTime }}</span> 
  <span>{{ businessInfo.regAmt | formatMoney }}</span>

js中调用过滤器

1.使用this.$options

this.formatTime=this.$options.filters.formatTime;
this.curTime=this.formatTime(new Date().getTime());

2.使用Vue.filter('xx')

//需要引入Vue
import Vue from 'vue';
export default{
  data(){
    return{
      formatTime:'',
    };
  },
  created(){
  this.formatTime=Vue.filter('formatTime');
  this.curTime=this.formatTime(new Date().getTime());

3.使用this.constructor

this.formatTime=this.constructor.filter('formatTime');
this.curTime=this.formatTime(new Date().getTime());

路由钩子

路由钩子分为三种:

 全局钩子:beforeEach、afterEach、beforeResolve
 单个路由里面的钩子:beforeEnter
 组件路由:beforeRouteEnter、beforeRouteUpdate、beforeRouteLeave

全局钩子

1.beforeEach:全局前置守卫,进入路由之前

import router from './router'; //引入路由
import store from './store';
router.beforeEach(async(to,from,next)=>{
  if(!store.getters.isRegister){  //没有注册标注
    try{
      await store.dispatch('getUserToken')
      next();
      }catch(err){
      console.log(err)
      next();
      }
    }else{
    next();
    }
  });
  ~~~ or~~~
  router.beforeEach((to, from, next) => {
  if (to.meta.title) {
    document.title = to.meta.title;
  }
  next();
});
  

2.beforeResolve:全局解析守卫(2.5.0+)在beforeRouteEnter调用之后调用

1.  router.beforeResolve((to, from, next) => {
1.  next();
1.  });

3.afterEach:全局后置钩子,进入路由之后

import router from './router'; //引入路由
  router.afterEach((to, from) => {
    // 重置滚动距离
    window.scrollTo(0, 0)
  })

路由的三个参数
to:将要进入的路由对象;
from:将要离开的路由对象;
next:这个参数是函数,且必须调用,否则不能进入路由(卡在空白页面)

单个路由钩子

为某些路由单独配置守卫

const router =new VueRouter({
  routes:[
    {
      path:'/test',
      component:test,
      beforeEnter:(to,from,next) =>{
      //参数用法与全局的钩子一样,调用顺序在全局前置守卫后面,所以不会被全局守卫覆盖
        document.title='测试'
        next()
        }
       }
     ]
  })
      

路由组件内的守卫

1.beforeRouteEnter 进入路由前

image.png 2.beforeRouterUpdate(2.2) 路由复用同一个组件时
详情:当前页面跳转同一个路由地址且参数不同时,此时这个组件会被复用。在这种情况下,传递过来的参数不会发生变化,因为组件复用了不会再重新创建,生命周期只创建一次

//第一种:使用beforeRouteUpdate
 beforeRouteUpdate(to,form,next){
      this.id = to.query.id
          next()
    }
//第二种:通过监听路由的变化
 watch:{
      $route(to){
         this.id = to.query.id
      }
 //第三种:通过计算属性
  computed: {
     comPath(){
       return this.id = this.$route.query.id
     }


3.beforeRouteLeave 离开当前路由时

image.png

触发钩子的顺序

将路由导航、keep-alive、和组件生命周期钩子结合起来的,触发顺序,假设是从a组件离开,第一次进入b组件:

  1. beforeRouteLeave:路由组件的组件离开路由前钩子,可取消路由离开。

  2. beforeEach: 路由全局前置守卫,可用于登录验证、全局路由loading等。

  3. beforeEnter: 路由独享守卫

  4. beforeRouteEnter: 路由组件的组件进入路由前钩子。

  5. beforeResolve: 路由全局解析守卫

  6. afterEach:路由全局后置钩子

  7. beforeCreate:组件生命周期,不能访问this。

  8. created:组件生命周期,可以访问this,不能访问dom。

  9. beforeMount:组件生命周期

  10. deactivated: 离开缓存组件a,或者触发a的beforeDestroy和destroyed组件销毁钩子。

  11. mounted:访问/操作dom。

  12. activated:进入缓存组件,进入a的嵌套子组件(如果有的话)。

  13. 执行beforeRouteEnter回调函数next。

生命周期

生命周期是指vue实例对象从创建之初到销毁的过程,vue所有功能的实现都是围绕其生命周期进行的,在生命周期的不同阶段调用对应的钩子函数可以实现组件数据管理和DOM渲染两大重要功能。

vue生命周期可以分为八个阶段,分别是:
beforeCreate(创建前)、created(创建后)、beforeMount(载入前)、mounted(载入后)、beforeUpdate(更新前)、updated(更新后)、beforeDestroy(销毁前)、destroyed(销毁后)

beforeCreate(创建前)

beforeCreate(初始化页面前)

详情:在组件实例初始化完成之后立即调用。会在实例初始化完成、props 解析之后、data() 和 computed 等选项处理之前立即调用(这时候 el,data,message 都是 underfined)。

注意:组合式 API 中的 setup() 钩子会在所有选项式 API 钩子之前调用,beforeCreate() 也不例外。

场景:可以加入 loading 事件;在服务器端的应用场景中,这个时候发送数据请求比较多一些


created(创建后)

created(初始化界面后)

详情:当这个钩子被调用时,以下内容已经设置完成:响应式数据、计算属性、方法和侦听器。然而,此时挂载阶段还未开始,因此 $el 属性仍不可用。(实例创建完成后,data、methods 被初始化)

场景:推荐这个时候发送请求数据,尤其是返回的数据与绑定事件有关时


beforeMount(载入前)

beforeMount(渲染dom前)

详情:当这个钩子被调用时,组件已经完成了其响应式状态的设置,但还没有创建 DOM 节点。它即将首次执行 DOM 渲染过程。(完成el和data初始化,但还没有创建 DOM 节点,在挂载开始之前被调用)

场景:可以发送数据请求

注意:在服务器端渲染期间不会被调用


mounted(载入后)

mounted(渲染dom后)

详情:所有同步子组件(vue实例)都已经被挂载。(不包含异步组件或 <Suspense> 树内的组件)

场景:获取 el 中 DOM 元素,进行 DOM 操作;如果返回的数据操作依赖 DOM 完成,推荐这个时候发送数据请求

注意:在服务器端渲染期间不会被调用


beforeUpdate(更新前)

beforeUpadated(更新数据前)

详情:这个钩子可以用来在 Vue 更新 DOM 之前访问 DOM 状态。在这个钩子中更改状态也是安全的(数据更新时调用)

场景:挂载完成之前访问现有DOM,比如手动移除已添加的事件监听器;也可以进一步修改数据

    watch: {
        // 监听下拉筛选框改变,并路由更新
        queryData: {
            handler(newVal) {
                let path = this.$route.path
                this.$router.replace({
                    path,
                    query: newVal
                })
            },
            immediate: false,
            deep: true
        }
    },
    // 当筛选值改变时自动存储并且改变当前url携带的参数(跟watch配合使用生效)
    beforeUpdate() {
        this.$set(this.queryData, 'prDeptNo', this.menuList.Allstaff.value)
        this.$set(this.queryData, 'prDeptNoTitle', this.menuList.Allstaff.title)
        this.$set(this.queryData, 'prcurPage', this.menuList.tabList.value)
    }
    

注意:在服务器渲染期间不会被调用,只有初次渲染会在服务端调用


updated(更新后)

updated(更新数据后)

详情:父组件的更新钩子将在其子组件的更新钩子之后调用。

这个钩子会在组件的任意 DOM 更新后被调用,这些更新可能是由不同的状态变更导致的。如果你需要在某个特定的状态更改后访问更新后的 DOM,请使用 [nextTick()]作为替代。(由于数据更改,重新渲染时调用)

场景:可执行依赖与DOM的操作

  updated(){
     if (this.$route.query.innerTab) {
      this.tabIndex = Number(this.$route.query.innerTab);
      this.getTabList();
    }
  }
 或者
updated(e) {
  this.$nextTick(() => {
    this.init(true)
  })
},

注意:服务器端渲染期间不会被调用


beforeDestroy(销毁前)

beforeDestroy(卸载组件前)

详情:当这个钩子被调用时,组件实例依然还保有全部的功能(实例销毁之前调用)

场景:实例销毁之前,执行清理任务,比如:清除定时器、保存搜索记录等

image.png

注意:服务器端渲染期间不会被调用


destroyed(销毁后)

destroyed(卸载组件后)

详情:

  • 其所有子组件都已经被卸载。
  • 所有相关的响应式作用 (渲染作用以及 setup() 时创建的计算属性和侦听器) 都已经停止。

场景:手动清理一些副作用,例如计时器、DOM 事件监听器或者与服务器的连接。

image.png 注意:服务器端渲染期间不会被调用

注:生命周期beforeDestroy和destroyed调用 

 情境一 :离开当前路由,会直接调用  

当前路由不使用缓存时,离开当前路由会直接调用beforeDestroy和destroyed销毁

 情况二:由路由钩子函数主动调用

当前路由使用缓存,离开当前路由不会直接调用beforeDestroy和destroyed销毁,需要使用路由钩子函数主动去调用 

beforeRouteLeave(to,from,next){  
this.$destroy();  
next  
}  

路由

路由router跳转页面

方式一

this.$router.push({path:'/login', query:{name:'Tom', id: 123} })

这里url的路径为: /login?name=Tom&id=123
在跳转的目标页面中,可以通过this.$route.query来获取查询参数

const id = this.$route.query.id;  //123
const name = this.$route.query.name;  //Tom

方式二

this.$router.push({name:'login', params:{id:123, name:'Tom'} });

这里url的路径为: /login/123
路由配置示例(带参数)

const routes = [
  path: '/login/:id/:name',  //可定义多个参数
  name: 'login',
  component: login
]

在跳转的目标页面中,可以通过 this.$route.params获取路由参数

const id = this.$route.params.id; //123
const name = this.$route.params.name;  //Tom

路由对象属性

  • $route.path
    类型:string
    字符串,对应当前路由的路径,总是解析为绝对路径,如“/login"。

  • $route.params
    类型:Object
    一个key/value对象,包含了动态片段和全匹配片段,如果没有路由参数,就是一个空对象。

  • $route.query
    类型:Object
    一个key/value对象,表示URL查询参数。如果没有查询参数,则是一个空对象。

  • $route.hash
    类型: string
    当前路由的 hash 值 (带 #) ,如果没有 hash 值,则为空字符串。

  • $route.fullPath
    类型: string
    完成解析后的URL,包含查询参数和hash的完整路径。

  • $route.matched
    类型:Array<RouteRecord>
    一个数组,包含当前路由的所有嵌套路径片段的路由记录。路由记录就是routes配置数组中的对象副本(还有在children数组)

const router = new VueRouter({
 routes: [
   // 下面的对象就是路由记录
   {
     path: '/foo',
     component: Foo,
     children: [
       // 这也是个路由记录
       { path: 'bar', component: Bar }
     ]
   }
 ]
})
  • $route.name
    当前路由的名称,如果有的话。

  • $route.redirectedFrom
    如果存在重定向,即为重定向来源的路由的名字。