vue

vue 首页加载空白页

  1. 服务器使用gzip,前端进行配置
  2. 使用空骨架(放一张图片/使用html写/插件)onload加载成功后切换
  3. 把静态资源放到cdn上
  4. 压缩图片
  5. 路由懒加载
  6. 图片懒加载
  7. 按需引入第三方库

使用vue骨架屏

原理:生成的vNode,通过 $mount 方法,会直接替换挂载的 DOM 元素
缺点每个都一样

//在index.html文件中
<div id="app">
    <div>
    		写入骨架屏
    	    <img src="./img/icons/android-chrome-512x512.png">
	</div>
</div>

使用骨架屏插件

vue-skeleton-webpack-plugin 这个插件跟自己手写差不多,虽然可以不同路由匹配不同skeleton,但是只能是初始页面刷新生效。

vue 大数据卡顿

  1. 懒加载(触底加载)
  2. 分页
  3. document.creatDocumentFargment 虚拟DOM
  4. 压缩图片
  5. Object.freeze 冻结取消响应式
  6. 组件库vue-virtual-scroller (必须设置高度)

vue 样式初始化normalize.css

在这里插入图片描述

//mian.ts
import "@/styles/normalize.css";
normalize.css
/*! normalize.css v5.0.0 | MIT License | github.com/necolas/normalize.css */

/**
 * 1. Change the default font family in all browsers (opinionated).
 * 2. Correct the line height in all browsers.
 * 3. Prevent adjustments of font size after orientation changes in
 *    IE on Windows Phone and in iOS.
 */

/* Document
   ========================================================================== */

html {
 
  font-family: sans-serif;
  /* 1 */
  line-height: 1.15;
  /* 2 */
  -ms-text-size-adjust: 100%;
  /* 3 */
  -webkit-text-size-adjust: 100%;
  /* 3 */
}
a{
  display: block;
}
/* Sections
   ========================================================================== */

/**
 * Remove the margin in all browsers (opinionated).
 */

body {
  margin: 0;
}

/**
 * Add the correct display in IE 9-.
 */

article,
aside,
footer,
header,
nav,
section {
  display: block;
}

/**
 * Correct the font size and margin on `h1` elements within `section` and
 * `article` contexts in Chrome, Firefox, and Safari.
 */

h1 {
  font-size: 2em;
  margin: 0.67em 0;
}

/* Grouping content
   ========================================================================== */

/**
 * Add the correct display in IE 9-.
 * 1. Add the correct display in IE.
 */

figcaption,
figure,
main {
  /* 1 */
  display: block;
}

/**
 * Add the correct margin in IE 8.
 */

figure {
  margin: 1em 40px;
}

/**
 * 1. Add the correct box sizing in Firefox.
 * 2. Show the overflow in Edge and IE.
 */

hr {
  box-sizing: content-box;
  /* 1 */
  height: 0;
  /* 1 */
  overflow: visible;
  /* 2 */
}

/**
 * 1. Correct the inheritance and scaling of font size in all browsers.
 * 2. Correct the odd `em` font sizing in all browsers.
 */

pre {
  font-family: monospace, monospace;
  /* 1 */
  font-size: 1em;
  /* 2 */
}

/* Text-level semantics
   ========================================================================== */

/**
 * 1. Remove the gray background on active links in IE 10.
 * 2. Remove gaps in links underline in iOS 8+ and Safari 8+.
 */

a {
  background-color: transparent;
  /* 1 */
  -webkit-text-decoration-skip: objects;
  /* 2 */
}

/**
 * Remove the outline on focused links when they are also active or hovered
 * in all browsers (opinionated).
 */

a:active,
a:hover {
  outline-width: 0;
}

/**
 * 1. Remove the bottom border in Firefox 39-.
 * 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari.
 */

abbr[title] {
  border-bottom: none;
  /* 1 */
  text-decoration: underline;
  /* 2 */
  text-decoration: underline dotted;
  /* 2 */
}

/**
 * Prevent the duplicate application of `bolder` by the next rule in Safari 6.
 */

b,
strong {
  font-weight: inherit;
}

/**
 * Add the correct font weight in Chrome, Edge, and Safari.
 */

b,
strong {
  font-weight: bolder;
}

/**
 * 1. Correct the inheritance and scaling of font size in all browsers.
 * 2. Correct the odd `em` font sizing in all browsers.
 */

code,
kbd,
samp {
  font-family: monospace, monospace;
  /* 1 */
  font-size: 1em;
  /* 2 */
}

/**
 * Add the correct font style in Android 4.3-.
 */

dfn {
  font-style: italic;
}

/**
 * Add the correct background and color in IE 9-.
 */

mark {
  background-color: #ff0;
  color: #000;
}

/**
 * Add the correct font size in all browsers.
 */

small {
  font-size: 80%;
}

/**
 * Prevent `sub` and `sup` elements from affecting the line height in
 * all browsers.
 */

sub,
sup {
  font-size: 75%;
  line-height: 0;
  position: relative;
  vertical-align: baseline;
}

sub {
  bottom: -0.25em;
}

sup {
  top: -0.5em;
}

/* Embedded content
   ========================================================================== */

/**
 * Add the correct display in IE 9-.
 */

audio,
video {
  display: inline-block;
}

/**
 * Add the correct display in iOS 4-7.
 */

audio:not([controls]) {
  display: none;
  height: 0;
}

/**
 * Remove the border on images inside links in IE 10-.
 */

img {
  border-style: none;
}

/**
 * Hide the overflow in IE.
 */

svg:not(:root) {
  overflow: hidden;
}

/* Forms
   ========================================================================== */

/**
 * 1. Change the font styles in all browsers (opinionated).
 * 2. Remove the margin in Firefox and Safari.
 */

button,
input,
optgroup,
select,
textarea {
  font-family: sans-serif;
  /* 1 */
  font-size: 100%;
  /* 1 */
  line-height: 1.15;
  /* 1 */
  margin: 0;
  /* 2 */
}

/**
 * Show the overflow in IE.
 * 1. Show the overflow in Edge.
 */

button,
input {
  /* 1 */
  overflow: visible;
}

/**
 * Remove the inheritance of text transform in Edge, Firefox, and IE.
 * 1. Remove the inheritance of text transform in Firefox.
 */

button,
select {
  /* 1 */
  text-transform: none;
}

/**
 * 1. Prevent a WebKit bug where (2) destroys native `audio` and `video`
 *    controls in Android 4.
 * 2. Correct the inability to style clickable types in iOS and Safari.
 */

button,
html [type="button"],
/* 1 */
[type="reset"],
[type="submit"] {
  -webkit-appearance: button;
  /* 2 */
}

/**
 * Remove the inner border and padding in Firefox.
 */

button::-moz-focus-inner,
[type="button"]::-moz-focus-inner,
[type="reset"]::-moz-focus-inner,
[type="submit"]::-moz-focus-inner {
  border-style: none;
  padding: 0;
}

/**
 * Restore the focus styles unset by the previous rule.
 */

button:-moz-focusring,
[type="button"]:-moz-focusring,
[type="reset"]:-moz-focusring,
[type="submit"]:-moz-focusring {
  outline: 1px dotted ButtonText;
}

/**
 * Change the border, margin, and padding in all browsers (opinionated).
 */

fieldset {
  border: 1px solid #c0c0c0;
  margin: 0 2px;
  padding: 0.35em 0.625em 0.75em;
}

/**
 * 1. Correct the text wrapping in Edge and IE.
 * 2. Correct the color inheritance from `fieldset` elements in IE.
 * 3. Remove the padding so developers are not caught out when they zero out
 *    `fieldset` elements in all browsers.
 */

legend {
  box-sizing: border-box;
  /* 1 */
  color: inherit;
  /* 2 */
  display: table;
  /* 1 */
  max-width: 100%;
  /* 1 */
  padding: 0;
  /* 3 */
  white-space: normal;
  /* 1 */
}

/**
 * 1. Add the correct display in IE 9-.
 * 2. Add the correct vertical alignment in Chrome, Firefox, and Opera.
 */

progress {
  display: inline-block;
  /* 1 */
  vertical-align: baseline;
  /* 2 */
}

/**
 * Remove the default vertical scrollbar in IE.
 */

textarea {
  overflow: auto;
}

/**
 * 1. Add the correct box sizing in IE 10-.
 * 2. Remove the padding in IE 10-.
 */

[type="checkbox"],
[type="radio"] {
  box-sizing: border-box;
  /* 1 */
  padding: 0;
  /* 2 */
}

/**
 * Correct the cursor style of increment and decrement buttons in Chrome.
 */

[type="number"]::-webkit-inner-spin-button,
[type="number"]::-webkit-outer-spin-button {
  height: auto;
}

/**
 * 1. Correct the odd appearance in Chrome and Safari.
 * 2. Correct the outline style in Safari.
 */

[type="search"] {
  -webkit-appearance: textfield;
  /* 1 */
  outline-offset: -2px;
  /* 2 */
}

/**
 * Remove the inner padding and cancel buttons in Chrome and Safari on macOS.
 */

[type="search"]::-webkit-search-cancel-button,
[type="search"]::-webkit-search-decoration {
  -webkit-appearance: none;
}

/**
 * 1. Correct the inability to style clickable types in iOS and Safari.
 * 2. Change font properties to `inherit` in Safari.
 */

::-webkit-file-upload-button {
  -webkit-appearance: button;
  /* 1 */
  font: inherit;
  /* 2 */
}

/* Interactive
   ========================================================================== */

/*
 * Add the correct display in IE 9-.
 * 1. Add the correct display in Edge, IE, and Firefox.
 */

details,
/* 1 */
menu {
  display: block;
}

/*
 * Add the correct display in all browsers.
 */

summary {
  display: list-item;
}

/* Scripting
   ========================================================================== */

/**
 * Add the correct display in IE 9-.
 */

canvas {
  display: inline-block;
}

/**
 * Add the correct display in IE.
 */

template {
  display: none;
}

/* Hidden
   ========================================================================== */

/**
 * Add the correct display in IE 10-.
 */

[hidden] {
  display: none;
}

a {
  text-decoration: none;
  color: #333;
}

html {
  color: #000;
  background: #fff;
  overflow-y: scroll;
  -webkit-text-size-adjust: 100%;
  -ms-text-size-adjust: 100%
}

html * {
  outline: 0;
  -webkit-text-size-adjust: none;
  -webkit-tap-highlight-color: rgba(0, 0, 0, 0)
}

html,
body {
  font-family: sans-serif
}

body,
div,
dl,
dt,
dd,
ul,
ol,
li,
h1,
h2,
h3,
h4,
h5,
h6,
pre,
code,
form,
fieldset,
legend,
input,
textarea,
p,
blockquote,
th,
td,
hr,
button,
article,
aside,
details,
figcaption,
figure,
footer,
header,
hgroup,
menu,
nav,
section {
  margin: 0;
  padding: 0
}

input,
select,
textarea {
  font-size: 100%
}

table {
  border-collapse: collapse;
  border-spacing: 0
}

fieldset,
img {
  border: 0
}

abbr,
acronym {
  border: 0;
  font-variant: normal
}

del {
  text-decoration: line-through
}

address,
caption,
cite,
code,
dfn,
em,
th,
var {
  font-style: normal;
  font-weight: 500
}

ol,
ul {
  list-style: none
}

caption,
th {
  text-align: left
}

h1,
h2,
h3,
h4,
h5,
h6 {
  font-size: 100%;
  font-weight: 500
}
input{
	display:block;
	border: none;
  outline: none;
}

token过期,无感知刷新

原文连接1
原文连接2

1.登录,从后台获取到token(鉴权令牌),refresh_token(刷新token的令牌),expire_time(token的时效)
2.用户操作中,向后台发送请求,每次请求时,将当前请求时间与expire_time对比,使用refresh_token去重新获取token。(refresh_token也是有时效的,但是比token长)
3,使用refresh_token去重新获取token ,并覆盖上一次存储信息

token过期后,Token 刷新并发处理解决方案

由后端设置redis
前端设置axios

首先token过期就会重新获取token,如果并发多条数据那就先搞一个闸门,一旦有一个去刷新接口就 把闸门关闭。什么时候刷新完成 什么时候关闭,并把后面的以函数的形式都存到数组里面,等token 刷新完成之后 还得重新执行下 这些请求

let isFreshToken = true // 默认是打开
let casheRequests = [];   // 请求队列
// 响应拦截
request.interceptors.response.use(res => {
	const { data, status, config } = res;
	if(status == 200) { // 请求状态成功
		if(data.Code == 4441) { // token过期
			if(isFreshToken) {//token可以刷新 一次并发只能有一个刷新
			    isFreshToken = false; // 把开关关闭
			    // 刷新token 是一个 返回promise的请求接口的方法 这个是自定义的 看你们自己的项目来定,要用原生的axios
			    reFreshToken().then(result => {
					isFreshToken = true // 方法重新打开
					let token = result.token; // 拿到新的token
					// 重新赋值token
					localStorage.setItem('token', token);
					// 重新执行 之前缓存的方法数组 使用最新的token
					casheRequests.forEach(cb => cb(token))
					// 重置为空 降缓存的请求方法
					casheRequests = []
					// 然后重新执行本次的方法
					config.headers['token']= token // 使用最新的token
					return request(config); 
					
				})
				//
			} else {
               // token 正在刷新中 其他的请求先放在队列中
               return new Promise(resolve => {
					// 将resolve放进队列,用一个函数形式来保存,等token刷新后直接执行
		              casheRequests.push((token) => {
		                config.headers['token']= token
		                resolve(request(config))
		              })
				})
			}
		}
		return data
	}
}

登录鉴权

用于控制那些页面是需要登录后查看的
给路由添加meta 属性在beforeEach中判断,是否用于meta属性,有就代表需要鉴权,然后对登录状态进行查看,查看是否有token

扫码支付

1.前端讲订单商品传给后端
2.后端调用微信服务器发起统一支付
3.得到微信返回二维码,返给前端
4.手机扫码
5.微信客户端讲扫码链接提交微信支付系统
6.微信支付系统验证链接有效返回支付授权
7.支付成功后,微信支付系统,异步通知商家支付结果

手机号短信验证码登录

1、携带手机号向后端发送请求,后端会给手机发送验证码
2、携带手机号和验证码发送请求,后端进行判断

腾讯云短信验证,创建正文模板,创建密匙

扫码登录

网页

1.pc请求二维码
2.服务器返回二维码id,过期时间
3.pc获取二维码id生成二维码
4.手机扫码获得二维码id
5.手机将手机token+二维码id发送到服务器
6.服务器校验手机端token,根据手机端token+二维码id生成PCtoken
7.PC通过轮询更新二维码状态,返回PCtoken

微信授权登录(pc)

微信授权登录(H5)

手机号登录

1.先进行手机号的正则校验,
2.调用接口,后端返回一个码,和用户输入的码进行对比

生成二维码 qrcode插件

qrcode插件

npm install --save qrcode

import QRCode from 'qrcode'

// With promises
QRCode.toDataURL('I am a pony!')//生成的图片路径
  .then(url => {
    console.log(url)
  })
  .catch(err => {
    console.error(err)
  })

// 或async/await
const generateQR = async text => {
  try {
    console.log(await QRCode.toDataURL(text))
  } catch (err) {
    console.error(err)
  }
}

vue 样式格式化

vscode插件,在项目根目录右键最底下有个选项
在这里插入图片描述

# EditorConfig is awesome: https://EditorConfig.org

# top-most EditorConfig file
root = true

[*]
indent_style = space
indent_size = 4
end_of_line = crlf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true

两个日期相差多少天

function diffDay(lastDate,earlyDate){
    return (Date.parse(lastDate) - Date.parse(earlyDate))/1000/60/60/24;
}

diffDay('2021/04/22','2021/04/20'); //return 2

v-if和else button一直高亮问题

//添加key
<template v-if="show">
	<input>
	<button key="1">点我</button>
</template>
<template v-else>
	<input>
	<button key="2">点我</button>
</template>

post传值问题

修改headers[‘Content-Type’] = ‘application/json’
或者headers[‘Content-Type’] = ‘application/x-www-form-urlencoded;charset=UTF-8’
先用axios试一试能不能发,如果不能就改header,如果还不能就qs

1.下载 cnpm install qs
import qs from 'qs'
post传值后台收不到 需要用qs转换一下数据
 post('http://localhost:8080/api', qs.stringify(this.addCategoryName))
 axios({
        method:'post',//post,get
        url:'/api/song-search',//地址
        data:{keyword: keyword1},//数据
        headers: {'X-Requested-With': 'XMLHttpRequest'},//请求头
        timeout: 1000,//超过多少秒终止
        
     }).then((res) => {
        // 将请求的结果赋值给personData全局变量,用于展示搜索结果
        if (res.data.code === '0') {
            this.personData = res.data.data || [];
        } else {
            this.personData = [];
        }
    })

关闭语法检查 lintOnSave:false,

vue.config.js

vue-router路由页面不刷新的解决办法

当我们在一个页面上,页面上有不同的分类,点击不同的分类需要传不同的参数来请求接口,当接口返回数据后,需要将页面的数据重新渲染,而不是之前的数据。

1.created改为activated

activated() {
    this.getList();
  },

2.watch监听

watch: {
	'$route' (to, from) {
    // 路由发生变化页面刷新
	this.$router.go(0);
		}
},

3.使用VUE的v-if控制DOM

4.在router-view上添加 :key=“$route.fullPath”

  1. this.$router.push 的query 添加new Date().getTime()
	 this.$router.push({
        path: "/homePage/searchResult",
        query: {
          keywords: this.input,
          type: this.type,
          date:new Date().getTime()
        }
      }) 

fullPath获取完整路由,重新渲染

当我们在一个页面上,页面上有不同的分类,点击不同的分类需要传不同的参数来请求接口,当接口返回数据后,需要将页面的数据重新渲染,而不是之前的数据。

比如:
path只是路径部分,fullPath是完整地址
fullPath: “/movie/2?name=zs%20age%3D28”
path: “/movie/2”
query: {name: ‘zs age=28’}

我们每次只改变url后面的id,同时将页面重新渲染

<router-view :key='$route.fullPath'>
或者
this.$router.push(`/login?redirect=${this.$route.fullPath}`);

通过绑定一个fullPath,可以识别当前页面路由的完整地址,当地址发生改变(包括参数改变)则重新渲染页面(例如动态路由参数的变化)

组件刷新

使用v-if【一般的方式】

<template>
  <my-component v-if="renderComponent"/>
</template>
<script>
  export default {
    data() {
      return {
        renderComponent: true,
      };
    },
    methods: {
      forceRerender() {
        // Remove my-component from the DOM
        this.renderComponent = false;

        this.$nextTick(() => {
          // Add the component back in
          this.renderComponent = true;
        });
      }
    }
  };
</script>

forceUpdate【强制刷新】

 this.$forceUpdate()

:key=“”【key属性】

 <component-to-re-render :key="componentKey" />
 export default {
  data() {
    return {
      componentKey: 0,
    };
  },
  methods: {
    forceRerender() {
      this.componentKey += 1;
    }
  }
}

页面刷新

也可以刷新 缺点出现空白页
1.this.$router.go(0)
2.location.reload()
3.if+provide+inject

vue3 页面组件刷新使用if+provide+inject

//优点没有白屏

<!-- App.vue -->
<template>
  <router-view v-if="isRouterAlive"></router-view>
  <!-- 在router-view使用isRouterAlive或者是下面这种在组件中使用 -->
  <!-- <BLank v-if="isRouterAlive"></BLank> -->
</template>

<script>
    // 局部组件刷新
    const isRouterAlive = ref(true);
    const reload = () => {
      isRouterAlive.value = false;
      nextTick(() => {
        isRouterAlive.value = true;
      });
    };
    provide("reload", reload);
</script>
//组件使用test.vue
<template>
  <input type="text"> 
  <button @click="ceshi">测试按钮</button>
</template>
<script>
import {inject} from "vue";
const ceshi =  inject('reload', Function, true)
</script> 

V2 vant 全局使用Toast

 this.$toast("提示内容");

V3 vant 全局使用Toast

ts+setup
import { Toast } from "vant";
import "vant/es/toast/style";
 Toast("结束时间不能比开始时间大");

静态资源路径问题

https://blog.csdn.net/weixin_45768768/article/details/122199323

https://blog.csdn.net/weixin_53072519/article/details/120658248

img动态路径

解决办法:数组里面图片的路径要写成如下:

image:require('../assets/img/login.png')
渲染的时候要写

<img :src="item.image" />
具体事例

<a-menu
        v-for="(v, i) in navData"
        :key="i"
        :default-selected-keys="['/account/home']"
        :selected-keys="[v.key]"
        mode="inline"
        type="inner"
        class="accout_menu"
        @openChange="onOpenChange"
      >
        <a-menu-item key="v.key">
          <router-link :to="{ name: v.name }">
            <img :src="v.src" class="menu_account_image" />
            <span class="menu_account_title">{{ v.title }}</span>
          </router-link>
        </a-menu-item

:src=" "
8080端口下找的是public下的文件,所以找不到
方法一

v-for
 <img
          style="width:100%;height:100%;"
          :src="require('../'+item.imgSrc)"
        />


图片存放在assets/banner/banner.jpg
  imgs: [{
        id: '01',
        imgSrc: 'assets/banner/banner1.jpg',
      }, {
        id: '02',
        imgSrc: 'assets/banner/banner2.png'
      }, {
        id: '03',
        imgSrc: 'assets/banner/banner3.jpg'
      }]

方法2

<img
          style="width:100%;height:100%;"
          :src="item.imgSrc"
        />
import img1 from '../assets/banner/banner1.jpg'
 imgs: [{
        id: '01',
        imgSrc: img1,
      },] 

普通js文件引入vue实例,调用vue上的$message等方法

import Vue from 'vue';
let vm = new Vue();
 vm.$message({
          showClose: true,
          message: data.msg,
          type: 'warning'
        });

简单上下按钮分页效果

在这里插入图片描述

<body>
    <div id="app">
        <ul>
            <li v-for="d in currentList" :key="d">{{d}}</li>
        </ul>
        <button :disabled="page===1" @click="page--">上一页</button>//如果第一页再往前就按钮禁用
        <button :disabled="page===Math.ceil(mapList.length/pageSize)" @click="page++">下一页</button>//判断最后一页禁用按钮
    </div>
</body>
<script src="https://cdn.jsdelivr.net/npm/vue@2.7.10/dist/vue.js"></script>
<script>
    new Vue({
        el: '#app',
        data() {
            return {
                mapList: [1, 2, 3, 4, 5, 6, 7, 8, 9],
                page: 1, //页数,最大页数:数组长度/2向上取整
                pageSize: 2 //每页2页
            }
        },
        computed: {
            currentList() {
                return this.mapList.slice((this.page - 1) * this.pageSize, this.page * this.pageSize)//截取对应数据
            }
        },

    })
</script>

vue更新数据却不渲染页面解决方案

Vue不能检测通过数组索引直接修改一个数组项,由于JavaScript的限制,Vue不能检测数组和对象的变化

1.使用set设置数组

this.$set(arr,index,newVal)

2. this.$forceUpdate()

checkClick (item) {
    item.check =! item.check;
    this.$forceUpdate() 
},

3.路由参数变化时,页面不更新,本质上就是数据没有更新

在这里插入图片描述
通过watch监听$route的变化

watch: {
    '$route': function() {
 
     }
}

4.在异步更新执行之前操作DOM数据不会变化

原因:Vue在更新DOM时是异步执行。只要侦听到数据变化,Vue将开启一个队列,并缓冲在同一个事件循环中发生的所有数据变更。如果同一个 watcher 被多次触发,只会被推入到队列中一次。这种在缓冲时去除重复数据对于避免不必要的计算和 DOM 操作是非常重要的。然后,在下一个的事件循环“nextTick”中,Vue 刷新队列并执行实际 (已去重的) 工作。
nextTick:在下一次dom更新循环结束之后,执行回调函数
解决办法:

this.$nextTick(function(){ })

在这里插入图片描述

5.获取后台返回的数组进行排序处理了,页面内容却不排序

原因:显示的元素不会动

解决办法:

使用v-if先隐藏元素,更新的数组排序处理好了,才显示元素

6.vue修改数据后dom的更新异步的,解决setTimeout

v3 reactive 请求回来的数据进行赋值

const arr=reactive([])
arr=resault.data//X这样不会响应式
const arrNew=reactive({arr1:[]})
arrNew.arr1=resault.data//这样可以响应式

vue同时给一个元素绑定单机和双击事件

<div id="app">
	<button @click="a" @dblclick="b">点击</button>
</div>
<script>
    let vm = new Vue({
        el: "#app",
        data: {
            name: "清欢"
        },
        methods: {
           a() {
               this.flag = true
               setTimeout(() => {
                   if (this.flag) {
                       console.log(1);
                   }
               }, 300);
            },
            b() {
                this.flag = false
                console.log(5);
            }
        }
    })
</script>

input默认获取焦点 二次点击获取不到焦点

 <input v-show="!show"  :ref="value.id" />
this.$nextTick(() => {
             this.$refs[this.value.id].focus();
                    })

只用获取一次焦点使用autofocus

autofocus="true" 

拖拽元素换位置tabel不用插件

<table>
	<thead>
		<tr>
			<th>标题1</th>
			<th>标题2</th>
			<th>标题3</th>
		</tr>
	</thead>
	<tbody>
		<tr v-for="(items,index) in dataList" :key="index"
			draggable="true"
            @dragstart="handleDragStart($event, items)"
            @dragover.prevent="handleDragOver($event, items)"
            @dragenter="handleDragEnter($event, items)"
            @dragend="handleDragEnd($event, items)"
        >
			<td>{{items.content}}</td>
		</tr>
	</tbody>
</table>

<script>
	var VM = new Vue({
		el:'#app',
		data:function(){
			return {
				dataList:[{
					content:'内容'
				},{
					content:'内容'
				},{
					content:'内容'
				}],
				dragging: null
			}
		},
		methods:{
			handleDragStart(e,items){
                this.dragging = items;//开始拖动时,暂时保存当前拖动的数据。
            },
            handleDragEnd(e,items){
                this.dragging = null;//拖动结束后,清除数据
            },
            handleDragOver(e) {
                e.dataTransfer.dropEffect = 'move';//在dragenter中针对放置目标来设置!
            },
            handleDragEnter(e,items){
                e.dataTransfer.effectAllowed = "move";//为需要移动的元素设置dragstart事件
                if(items == this.dragging) return;
                var newItems = [...this.dataList];//拷贝一份数据进行交换操作。
                var src = newItems.indexOf(this.dragging);//获取数组下标
                var dst = newItems.indexOf(items);
                newItems.splice(dst, 0, ...newItems.splice(src, 1));//交换位置
                this.dataList = newItems;
            }
		}
	})
</script>

vue使用vue.draggable

官网

1.下载

npm i -S vuedraggable

2.使用

里面必须是循环,v-for element-ui的列表就不可以,需要自己创个表格

<draggable v-model="myArray" >
   <div v-for="element in myArray" :key="element.id">{{element.name}}</div>
</draggable>
  import draggable from 'vuedraggable'
  ...
  export default {
		 data() {
		    return {
		    myArray:[]
		    };
		  },
        components: {
            draggable,
        },
  ...

3.配合vuex使用

会存在一个问题,如果直接v-model去绑定vuex的数据,会直接修改数据,这里不允许直接修改vuex和props的数据

//方法一:v-model的另一种写法
<draggable :value="$store.state.vuexArr" @input="update($event)">   </draggable>

update($event){
//向后端发送请求,提交修改的数据
//axios.post('updata',{}).then((res)=>{commit修改数据})
this.$store.commit('updateVuexArr',$event)
}

//方法二:使用官方使用的计算属性
<draggable v-model="vuexArr"></draggable>
computed: {
    vuexArr: {
        get() {
            return this.$store.state.vuexArr
        },
        set(value) {
            this.$store.commit('updateVuexArr', value)
        }
    }
}

vscode vue2的一些扩展

在这里插入图片描述
在这里插入图片描述

vscode vue3的扩展

在这里插入图片描述
在这里插入图片描述

vue报错:“TypeScript intellisense is disabled on template.” 解决

因为用来v3的插件 禁用就行或者
在jsconfig.json里添加"jsx":“preserve”

vue里面使用swiper

1.下载

注意版本,太高的版本会报错

 npm install swiper@5 vue-awesome-swiper@3 -S

2.引入

import Swiper from "swiper";

import 'swiper/css/swiper.min.css';
 <div class="swiper-container">//最外层包裹的不可以变
     	<div class="swiper-wrapper">
	        <div class="swiper-slide">Slide 1</div>
	        <div class="swiper-slide">Slide 2</div>
	        <div class="swiper-slide">Slide 3</div>
      </div>
      <div class="swiper-button-next"></div>
      <div class="swiper-button-prev"></div>
</div>
//使用
 mounted(){
    new Swiper ('.swiper-container', {
		loop: true,
		navigation: {
          nextEl: ".swiper-button-next",
          prevEl: ".swiper-button-prev",
        	},
    	autoplay: {
              delay: 1500,
              disableOnInteraction: false
            }
  })        
  },

vue swiper 封装slot插槽

另一种引入

英文官网

2.1下载

npm install swiper

2.2 注册引入

import Swiper from 'swiper/js/swiper';//这个要根据自己的情况找路径
import 'swiper/css/swiper.css';

swiper:null,  // 存放swiper实例
  mounted(){
        new Swiper('.swiper',{
            loop:true,
            direction:"horizontal"
        });
    },
    beforeDestroy(){//卸载,防止内存泄漏
        this.swiper.destroy();
    }

2.3 页面结构

 <div class="swiper">
            <div class="swiper-wrapper">
                <div class="swiper-slide" v-for="d in swiperList" :key="d.image">
                    <a :href="d.link">
                        <img :src="d.image">
                    </a>
                </div>
            </div>
        </div>

swiper刷新后不起效果

在这里插入图片描述
或者

在这里插入图片描述

异步加载数据,存在延时的问题,使用watch来监听,并且this.$nextTick()

watch: {
    cities: function () {
      this.$nextTick(() => {
        this.scroll.refresh()
      })
    }
  }

vue使用element-ui

1.按需引入

npm i element-ui -S
npm install babel-plugin-component -D

2.babel.config.js

module.exports = {
  presets: [
    '@vue/cli-plugin-babel/preset',
    [
      "@babel/preset-env", { modules: false }
    ]
  ],
  "plugins": [
    [
      "component",
      {
        "libraryName": "element-ui",
        "styleLibraryName": "theme-chalk"
      }
    ]
  ]
}

3.main.js

import { Button } from 'element-ui';
Vue.component(Button.name, Button);

4.使用

    <el-button type="primary">按钮</el-button>

使用message

main。js

import { Button,Message } from 'element-ui';
Vue.prototype.$message = Message
 <el-button type="primary" @click=" $message('这是一条消息提示');">按钮</el-button>

使用loading

import {Loading} from 'element-ui';
Vue.use(Loading);
//局部使用
loading: true
<div class="box" v-loading="loading">123</div>

在这里插入图片描述
在这里插入图片描述

element 表单校验

prop+对象+校验

  <el-form-item   prop="name">//prop
      <el-input v-model="ruleForm.name" />
    </el-form-item>
const ruleForm = reactive({ name: 'Hello',})//名字

input 表单验证联动效果

实现:下一个表单验证取决于上一个表单输入,提交按钮的防止多次点击
重要点:
this. r e f s . r u l e F o r m . v a l i d a t e F i e l d ( " n a m e " ) ; / / 用自己的校验规则的时候,也制定另外一个校验规则进行校验 t h i s . refs.ruleForm.validateField("name");//用自己的校验规则的时候,也制定另外一个校验规则进行校验 this. refs.ruleForm.validateField("name");//用自己的校验规则的时候,也制定另外一个校验规则进行校验this.refs.form.validate((pass) => {})//表单整体验证,通过后发送请求

在这里插入图片描述

//使用自定义表单验证
<el-form
      v-loading="loading"
      :model="formData"//
      :rules="rules"//
      :ref="ruleForm"//
    >
      <el-form-item
        label="设备名称"
        prop="name"//
      >
        <el-input v-model="formData.name"></el-input>
      </el-form-item>
      <el-form-item
        label="设备分类"
        prop="category"//
      >
        <el-select v-model="formData.category">
          <el-option
            v-for="d in $store.state.statusList1"
            :value="d.id"
            :key="d.id"
            :label="d.name"
          >
          </el-option>
        </el-select>
      </el-form-item>
      <el-form-item>
        <el-button
          @click="submit"
          v-loading="submiting"
        >提交</el-button>
      </el-form-item>
    </el-form>


export default {
  data() {
    const nameValidator = (rule, value, callback) => {//需要在data下面写
      if (value !=='1') {
        callback(new Error('value不能为1'));
      } else {
        callback();
      }
    };
    const categoryValidator = (rule, value, callback) => {
      this.$refs.ruleForm.validateField("name");//这个校验的时候,使用指定校验来校验
      callback();
    };
    return {
      submiting: false,
      formData: {
        name: "",
        category: "", },
      loading: true,
      rules: {//定义规则
        name: [
          { required: true, message: "请输入活动名称", trigger: "blur" },
          { validator: nameValidator, trigger: "blur" },//validator使用自定义的规则
        ],
        category: [
          { required: true, message: "请输入活动名称", trigger: "change" },
          { validator: categoryValidator, trigger: "blur" },
        ]
      },
    };
  },
  methods: {
    submit() {
      this.$refs.form.validate((pass) => {//全部校验成功后提交
        if (!pass) return;
        this.submiting = true;//按钮的loading效果,实现防抖,防止多次点击
        axios.post("/pre-edit", this.formData).then((res) => {//发送请求
          if (res.code) {
            this.submiting = false;
            this.$router.back();
          }
        });
      });
    },
  },
};
</script>

vue 日历插件

new Date(year,month,0) 0 代表最后一天,1代表第一天

1.先获取当前年:let year=new Date().getFullYear()
		当前月:let month=new Date().getMonth()+1,
2.获取这个月有多少天 :new Date(year,month,0).getDate(),利用push 添加到数组里面
3.获取到这个月第一天是周几:new Date(year,month-1,1).getDay() 
	再获取上个月最后一天:new Date(year,month-1,0).getDate(),利用unshife补全前面的天数
4.数组长度%7看余数 ,获取下月是那月new Date(year,month,1).getMonth(),push补全

在这里插入图片描述

<template>
<div class="week-wrapper">
  <div>
    <button @click="onChange(-1)">上个月</button>
    <p>{{ new Date(year, month - 1, 1).getFullYear() }} - {{ new Date(year, month - 1, 1).getMonth() + 1 }}</p>
    <button @click="onChange(1)">下个月</button>
  </div>
  <ul>
    <li>周一</li>
    <li>周二</li>
    <li>周三</li>
    <li>周四</li>
    <li>周五</li>
    <li>周六</li>
    <li>周日</li>
  </ul>
  <ul>
    <li
      v-for="d in list"
      :key="d.date"
    >
      {{ d.label }}
      <span v-if="arr.includes(d.date)"></span>
    </li>
  </ul>
</div>
</template>

<script>
export default {
  data() {
    return {
      list: [],
      year: new Date().getFullYear(),
      month: new Date().getMonth() + 1,
      arr: ['8-5','8-6','8-7','8-10','9-2','9-5']
    };
  },
  methods: {
    setList(year, month) {
      this.list = [];
      // 这个月总共有多少天
      const lastDay = new Date(year, month, 0);
      const total = lastDay.getDate();
      for (let i = 1; i <= total; i++) {
        this.list.push({
          label: i,
          date: `${lastDay.getMonth() + 1}-${i}`
        });
      }
      /* 0周日,1-6周1-周6
       year年month月的第一天是周几
       第一天是周1,week === 1,不用补
       第一天是周2,week === 2,补1天
       第一天是周3,week === 3,补2天
       第一天是周4,week === 4,补3天
       第一天是周5,week === 5,补4天
       第一天是周6,week === 6,补5天
       第一天是周日,week === 0,补6天
      */
      const firstDay = new Date(year, month - 1, 1);
      const week = firstDay.getDay();
      const fillLeft = !week ? 6 : week - 1;

      // 上个月的最后一天
      const lastMonthLastDay = new Date(year, month - 1, 0);
      let lastMonthLastDayCount = lastMonthLastDay.getDate();

      for (let i = 0; i < fillLeft; i++) {
        this.list.unshift({
          label: lastMonthLastDayCount - i,
          date: `${lastMonthLastDay.getMonth() + 1}-${lastMonthLastDayCount - i}`
        });
      }

      // 下个月需要补几天
      const n = this.list.length % 7;
      if (n > 0) {
        // 下个月是几月?
        const nextMonth = new Date(year, month, 1);
        for (let i = 0; i < 7 - n; i++) {
          this.list.push({
            label: i + 1,
            date: `${nextMonth.getMonth() + 1}-${i + 1}`
          });
        }
      }
    },

    onChange(n) {
      this.month += n;
      this.setList(this.year, this.month);
    }
  },

  created() {
    this.setList(this.year, this.month);
  }
}
</script>

<style>
.week-wrapper ul {
  display: flex;
  flex-wrap: wrap;
  width: 500px;
  margin: 0 auto;
  list-style: none;
}
.week-wrapper li {
  width: 14.285%; /*七分之一*/
}
</style>

vue 购物车逻辑

//bookshop页面
<template>
    <div class="book-shop">
        <ul>           
                <li v-for="(shop,index) in list" :key="shop.name">
                    <h2> 
                        <div :class="{'checkbox':true,'checked':shop.books.every(i=>i.checked)}" @click="toggleShop(index)"></div>
                        {{shop.name}}
                    </h2>
                    <ul>
                        <li v-for="book in shop.books" :key="book.name">
                            <div :class="{'checkbox':true,'checked':book.checked}" @click="book.checked=!book.checked"> </div>
                            {{book.name}}-{{book.price}}
                            <step-input v-model="book.num"></step-input>
                        </li>
                    </ul>
                </li>
            
        </ul>
        <div :class="{'checkbox':true,'checked':isAllChecked}" @click="toggleAll"></div>全选
        <div>总数:{{total.num}}总价:{{total.price}}</div>
    </div>
</template>

<script>
//@代表src
import StepInput from '@/components/StepInput.vue';
    export default {
  components: { StepInput },
        data() {
            return {
                list: [
                    {
                        name:'书店1',
                        books:[
                            {
                                name:'小王子和白玫瑰',
                                price:10,
                                num:1,
                                checked:false
                            },
                            {
                                name:'十宗罪',
                                price:20,
                                num:1,
                                checked:false
                            },
                        ]
                    },
                    {
                        name:'书店2',
                        books:[
                            {
                                name:'小公主',
                                price:50,
                                num:1,
                                checked:false
                            },
                            {
                                name:'白雪公主',
                                price:20,
                                num:1,
                                checked:false
                            },
                        ]
                    },
                ]
            }
        },
        methods: {
            toggleShop(index) {
                const checked1=this.list[index].books.every(i=>i.checked)//判断该书店下是不是都勾上了,也就是书店的勾选状态
                
                this.list[index].books.forEach(item => {//当前书店状态取反
                    item.checked=!checked1
                });
            },
            toggleAll(){
                const checked=this.isAllChecked
                this.list.forEach(shop=>shop.books.forEach(book=>book.checked=!checked))
            }
        },
        computed: {
            isAllChecked() {              
                return   this.list.every(shop=>shop.books.every(book=>book.checked))
            },
            total(){
                let num=0;let price=0;
                this.list.forEach(shop=>{
                    shop.books.filter(book=>book.checked).forEach(book=>{
                        num+=book.num
                        price+=book.num*book.price
                    })
                })
                return {num,price}
            }
        },
    }
</script>

<style lang="scss" scoped>
.book-shop{
    .checkbox{
        display: inline-block;
        width:20px;
        height: 20px;
        border-radius: 50%;
        border: 2px solid black;
    }
    & .checked{
       background: red;
       position: relative;
    }
    & .checked::after{
       position: absolute;
       content: '>';
       left: -3px;
       top: -2px;
       width: 20px;
       font-size: 16px;
       color: white;
       height: 20px;
       transform: rotate(90deg);
    }
}
    
</style>

//组件
//- 10 + 效果
<template>
    <div class="step-input">
        <button @click="$emit('input',value-1)" :disabled="value==0">-</button>
        {{value}}
        <button @click="$emit('input',value+1)">+</button>
    </div>
</template>

<script>
    export default {
        props: {
            value: {
                type: Number,
                default:0, 
            },
        },
    }
</script>

<style lang="scss" scoped>

</style>

vue 使用节流防抖

utils下放文件

function throttling(fun,delay =300){//节流throttle
 let canRun = true; // 通过闭包保存一个标记
  return function () {
    if (!canRun) return;//在delay时间内,直接返回,不执行fn
    canRun = false;
    setTimeout(() => {
      fn.apply(this, arguments);
      canRun = true;//直到执行完fn,也就是delay时间后,打开开关,可以执行下一个fn
    }, delay );
  };
}
function debounce(func, delay = 300, immediate = false) {//防抖debounce
    let timer = null
    return function() {
        if (timer) {
            clearTimeout(timer)
        }
        if (immediate && !timer) {
            func.apply(this, arguments)
        }
        timer = setTimeout(() => {
         func.apply(this, arguments)
        }, delay)
    }
}
export {debounce,throttling}

页面使用

<div @click="appSearch">
import {debounce,throttling} from 'assets/utils/debThro.js'
methods: {
    appSearch:debounce(function(value){
        this.handleSearch(value)
    }, 1000),
    handleSearch(value) {
        console.log(value)
    }
    //或者
    appSearch:throttling(function(){
		let itemlength=this.value.list.length
		this.$emit('nextItem',[itemlength,this.value.id])
	}),
}

input输入提示词防抖

<input placeholder="请输入" @input="inputChange($event.target.value)"/>

 timer:null,//用于防抖
 personData:[],//请求结果

inputChange:debounce(function(val){
                this.remoteSearch(val)
            },300)        
remoteSearch(keyword1) {
    if(keyword1==''){
        return
    }
     axios({
        method:'post',
        url:'/api/song-search',
        data:{keyword: keyword1},
     }).then((res) => {
        // 将请求的结果赋值给personData全局变量,用于展示搜索结果
        if (res.data.code === '0') {
            this.personData = res.data.data || [];
        } else {
            this.personData = [];
        }
    })

vue 配置移动端

//html 页面上配置script 动态给html赋值
(function(doc,win){
   //获取html
   var docEl=doc.documentElement,
   //用于获取事件
   //in window 用于查询window的一些事件和方法是否存在orientationchange屏幕翻转
   resizeEvt="orientationchange" in window?"orientationchange":"resize";
   
   function fun(){
    var w=docEl.clientWidth;
    if(!w) return
    if(w>=750){
        docEl.style.fontSize='100px'
    }else{
        docEl.style.fontSize=100 /750* w + 'px'
    }
   }
   //如果文档没有addEventListener这方法,就停止执行
   if(!doc.addEventListener)return
   //通过二级事件绑定为window添加resize事件
   win.addEventListener(resizeEvt,fun,false)
   //页面初始化加载的时候也需要获取当前设备宽度,改变字体大小
   //DOMContentLoaded w3c支持的一个事件,因为load事件会在页面所以元素渲染结束后才执行,DOMContentLoaded是在HTML 文档被完全加载和解析完成之后,不用等待图片
   doc.addEventListener('DOMContentLoaded',fun,false)

})(document,window)

npm install postcss-pxtorem --save-dev
npm install amfe-flexible --save-dev

1、在mian.js中 引入amfe-flexible

import ‘amfe-flexible’

vue -version看版本
2.在vite.config.js中配置postcss-pxtorem

在这里插入图片描述

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import postCssPxToRem from "postcss-pxtorem"
 
// https://vitejs.dev/config/
export default defineConfig({
  plugins: [vue()],
  css: {
    postcss: {
      plugins: [
        postCssPxToRem({
          rootValue: 112.5, // 设计图最大宽度除以10  //比如750的宽就写成75  我这边是1125的宽
          propList: ['*'], // 需要转换的属性,这里选择全部都进行转换
        })
      ]
    },
  }
})

2.(vue2)在根目录创建.postcssrc.js文件夹中进行配置

module.exports = {
    plugins: {
    
      // to edit target browsers: use "browserslist" field in package.json
      autoprefixer: {
        browsers: ['Android >= 4.0', 'iOS >= 7']
      },
      'postcss-pxtorem': {
        rootValue: 37.5,
        propList: ['*', '!border*']
      }
    }
  }

我还是想使用px来表达的话,那么我们可以把1px写成 1Px 或 1PX来解决

vue 使用better-scroll

https://better-scroll.github.io/docs/zh-CN/plugins/slide.html#%E4%BB%8B%E7%BB%8D

1.下载

npm install better-scroll --save //具备所有插件的

npm install @better-scroll/core --save//基本滚动
//按需下载插件
//使用插件
import BScroll from '@better-scroll/core'
//import Pullup from '@better-scroll/pull-up'

// 注册插件
//BScroll.use(Pullup)

let bs = new BScroll('.wrapper', {
  probeType: 3,
  pullUpLoad: true,
})

2.使用

import BScroll from '@better-scroll/core'
//.wrapper设置宽高,里面设置一个盒子,在盒子里面再写结构
created () {
            this.getSingerList()//获取数据
        },
 mounted () {
            this.$nextTick(() => {//设置scroll 
                this.SetScroll();
            });
        },
methods: {
SetScroll(){
 this.scroll = new BScroll('.wrapper',{
            scrollY: true,
            click: true,
            probeType: 3
        })
 }}
 beforeDestroy(){//卸载,防止内存泄漏
        this.scroll.destroy()
    }

ajax 动态获取数据

当数据发生变化的时候,就调用函数里面的$nextTick,然后进行刷新滚动的方法(scroll.refresh())

watch: {
    cities: function () {
      this.$nextTick(() => {
        this.scroll.refresh()
      })
    }
  }
把Ajax响应过来的数据进行监听(如上:cities)
当数据发生变化的时候,就调用函数里面的$nextTick,然后进行刷新滚动的方法(scroll.refresh()

左右联动

在这里插入图片描述

 mounted () {
            this.$nextTick(() => {
                this.singerScroll();//把获得的数据存到newSingerList里面
            });
        },
watch(){
newSingerList(){//监听newSingerList的改变
                 this.$nextTick(()=>{
                    this.$refs.scrollItem.forEach(i=>{//获取左边每块到顶部的距离
                    this.heigtList.push(i.offsetTop)
                	})
                 this.scroll1.refresh()//数据发生变化,从新渲染
                 })
            
            }
}
methods: {
singerScroll(){
         this.scroll1 = new BScroll('.swiper',{
                                    scrollY: true,
                                    click: true,
                                    probeType: 3
                                    })
                
           this.scroll1.on('scroll', ({ y }) => {//获得滚动y轴高度
           this.heigtList.forEach((item,index)=>{     //判断在那个区间,高亮                                       
            if(-y>=item&&-y<this.heigtList[index+1]){
                 this.currentIndex=index
            }else if(-y>=this.heigtList[this.heigtList.length-1]){//判断到最后一个高亮
                     this.currentIndex=this.heigtList.length-1
            }
                    })                    
                })
            },
  onShortcutStart(index){ //点击右边导航,滚动到指定位置
                this.currentIndex=index
                this.scroll1.scrollTo(0,-this.heigtList[index],300)
            },
}

起一个node服务器

//根目录创建test.js
const axios=require('axios')
axios.post('http://jxsjs.com/equipment/login').then(({data})=>{console.log(data)})

在这里插入图片描述

proxy 跨域

vue.config.js里面

const { defineConfig } = require('@vue/cli-service')
module.exports = defineConfig({
  transpileDependencies: true,
  // 开发服务器
  devServer: {
    // 服务器接口代理
    proxy: {
      // 请求的路径中包含/api就转发
      '/api': {
        // 代理的不同域远程服务器
        target: 'http://m.jxsjs.com'
      }
    }
  }
})

axios的封装1

1.创建文件夹request

在这里插入图片描述

api.js

import { get, post } from './http'
export const apiAddress = p => post('/hot-music', p);//这里就不用写/api

http.js

/**axios封装
 * 请求拦截、相应拦截、错误统一处理
 */
import axios from 'axios';
import QS from 'qs';
import { Toast } from 'vant';
import store from '../store/index'
import router from '../router/index'
 
// 环境的切换
if (process.env.NODE_ENV == 'development') {    
    axios.defaults.baseURL = '/api';
} else if (process.env.NODE_ENV == 'debug') {    
    axios.defaults.baseURL = '';
} else if (process.env.NODE_ENV == 'production') {    
    axios.defaults.baseURL = 'http://api.123dailu.com/';
}

// 请求超时时间
axios.defaults.timeout = 10000;

// post请求头
axios.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded;charset=UTF-8';

// 请求拦截器
axios.interceptors.request.use(    
    config => {
        // 每次发送请求之前判断是否存在token,如果存在,则统一在http请求的header都加上token,不用每次请求都手动添加了
        // 即使本地存在token,也有可能token是过期的,所以在响应拦截器中要对返回状态进行判断
        const token = store.state.token;        
        token && (config.headers.Authorization = token);        
        return config;    
    },    
    error => {        
        return Promise.error(error);    
    })

// 响应拦截器
axios.interceptors.response.use(    
    response => {        
        if (response.status === 200) {            
            return Promise.resolve(response);        
        } else {            
            return Promise.reject(response);        
        }    
    },
    // 服务器状态码不是200的情况    
    error => {        
        if (error.response.status) {            
            switch (error.response.status) {                
                // 401: 未登录                
                // 未登录则跳转登录页面,并携带当前页面的路径                
                // 在登录成功后返回当前页面,这一步需要在登录页操作。                
                case 401:                    
                    router.replace({                        
                        path: '/login',                        
                        query: { redirect: router.currentRoute.fullPath } 
                    });
                    break;
                // 403 token过期                
                // 登录过期对用户进行提示                
                // 清除本地token和清空vuex中token对象                
                // 跳转登录页面                
                case 403:                     
                    Toast({                        
                        message: '登录过期,请重新登录',                        
                        duration: 1000,                        
                        forbidClick: true                    
                    });                    
                    // 清除token                    
                    localStorage.removeItem('token');                    
                    store.commit('loginSuccess', null);                    
                    // 跳转登录页面,并将要浏览的页面fullPath传过去,登录成功后跳转需要访问的页面
                    setTimeout(() => {                        
                        router.replace({                            
                            path: '/login',                            
                            query: { 
                                redirect: router.currentRoute.fullPath 
                            }                        
                        });                    
                    }, 1000);                    
                    break; 
                // 404请求不存在                
                case 404:                    
                    Toast({                        
                        message: '网络请求不存在',                        
                        duration: 1500,                        
                        forbidClick: true                    
                    });                    
                break;                
                // 其他错误,直接抛出错误提示                
                default:                    
                    Toast({                        
                        message: error.response.data.message,                        
                        duration: 1500,                        
                        forbidClick: true                    
                    });            
            }            
            return Promise.reject(error.response);        
        }       
    }
);
/** 
 * get方法,对应get请求 
 * @param {String} url [请求的url地址] 
 * @param {Object} params [请求时携带的参数] 
 */
export function get(url, params){    
    return new Promise((resolve, reject) =>{        
        axios.get(url, {            
            params: params        
        })        
        .then(res => {            
            resolve(res.data);        
        })        
        .catch(err => {            
            reject(err.data)        
        })    
    });
}
/** 
 * post方法,对应post请求 
 * @param {String} url [请求的url地址] 
 * @param {Object} params [请求时携带的参数] 
 */
export function post(url, params) {    
    return new Promise((resolve, reject) => {         
        axios.post(url, QS.stringify(params))        
        .then(res => {            
            resolve(res.data);        
        })        
        .catch(err => {            
            reject(err.data)        
        })    
    });
}

页面使用

import { apiAddress } from '@/request/api';// 导入我们的api接口
export default {        
    name: 'Address',    
    created () {
        this.onLoad();
    },
    methods: {            
        // 获取数据            
        async onLoad() {
            // 调用api接口,并且提供了两个参数                
           const list=await apiAddress({type: 0})  
            this.singList=list.data     
        }        
    }
}

axios的封装2

import axios from 'axios'
import {
    Message,
    // Loading
} from "element-ui";
// 此时需要自行下载一下qs
// import qs from 'qs'
//判断是否是生产环境
var isPro = process.env.NODE_ENV === "production" //process.env.NODE_ENV用于区分是生产环境还是开发环境
//配置不同的baseURL
// 原理:
// 在生产环境(给客户部署项目)下使用的是"/weixin-api"(后端给的路径域名后边的部分),此时会自动拿到ip地址 + /weixin-api(路径拼接),在本地跑项目时拿到的时/api 也就是vue.config.js配置跨域下的路径;"/weixin-api" : "/api"

let baseURL = isPro ? "/api" : "/api"
const service = axios.create({
    baseURL: baseURL,
    timeout: 30000 // 请求超时时间
})
let loading = "";
// 请求拦截器
service.interceptors.request.use(
    (config) => {
    if(store.state.user.token){//判断vuex里面是否有token,有就每次请求携带token
            config.headers.token=store.state.user.token
        }
        // console.log(config)
        // 在请求发送之前做一些处理
        if (!(config.headers['Content-Type'])) {
            // loading = Loading.service({//全局加载
            //     lock: true,
            //     text: "加载中...",
            //     spinner: "el-icon-loading",
            //     background: "rgba(255,255,255,0.7)",
            //     customClass: "request-loading",
            // });
            if (config.method == 'post') {
                config.headers['Content-Type'] =
                    'application/x-www-form-urlencoded;charset=UTF-8'
                for (var key in config.data) {
                    if (config.data[key] === '') {
                        delete config.data[key]
                    }
                }
                // qs用于序列化data传输的数据 不然后端拿到的话会出现data数据套了一层拿不到数据的问题
                // config.data = qs.stringify(config.data)
            } else {
                config.headers['Content-Type'] =
                    'application/x-www-form-urlencoded;charset=UTF-8'
                // config.params
            }
        }
        const token = localStorage.getItem("token");
        // 让每个请求携带token-- ['X-Token']为自定义key 请根据实际情况自行修改
        if (token) {
            config.headers['Authorization'] = token
        }
        return config
    },
    (error) => {
        // loading.close();

        // 发送失败
        console.log('发送失败', error)
        return Promise.reject(error)
    }
)

// 响应拦截器
service.interceptors.response.use(
    (response) => {
        // loading.close();
        const dataAxios = response.data
        // 这个状态码是和后端约定的
        return dataAxios
    },
    (error) => {
        Message({
            message: error,
            type: 'error',
            duration: 3 * 1000
        })
        // 如果请求接口失败,取消loading,否则中间有一个接口错误就一直白屏loading转圈;
        loading.close();
        return Promise.reject(error)
    }
)

export default service
  import axios from "@/assets/request";
axios.get('product/getBaseCategoryList', {})

axios 连续多次请求同一接口,取消上一次请求

v3 封装axios

添加链接描述

axios 中的一个属性‘cancelToken’

//yemian.vue
import axios from 'axios';
const CancelToken = axios.CancelToken;
let cancel;
getSongCont(){//请求函数
    if(cancel){ cancel()}//如果有请求就取消
         axios({
        method:'post',
        data:{keyword:this.value},
        url:'/api/song-search',//地址
         cancelToken: new CancelToken(function executor(c) {//设置CancelToken
         cancel = c;
    }),})
        
      },

在这里插入图片描述

路由/->/home默认打开首页

export default new Router({
  routes: [
    {
      path: '/',
      redirect:'/home'
    },
    {
      path: '/home',
      name: 'home',
      component: home
    } 
  ],
  linkActiveClass:'router-active'//覆盖默认的路由高亮的类
})

路由嵌套一级路由高亮显示错误

//一级路由用这个
.router-link-active{
        color: yellow;
    }
//二级路由用这个
.router-link-exact-active{
        color: yellow;
    }

v2路由页面左边出来平移跳转

    .a {
        background-color: red;
    }
    .b {
        background-color: green;
    }
    .left-enter {
        transform: translateX(100%);
    }
    .left-enter-to {
        transform: translateX(0);
    }
    .left-enter-active {
        transition: transform 1s;
    }
    .left-leave {
        transform: translateX(0);
    }
    .left-leave-to {
        transform: translateX(-100%);
    }
    .left-leave-active {
        transition: transform 1s;
    }
 
    .right-enter {
        transform: translateX(-100%);
    }
    .right-enter-to {
        transform: translateX(0);
    }
    .right-enter-active {
        transition: transform 1s;
    }
    .right-leave {
        transform: translateX(0);
    }
    .right-leave-to {
        transform: translateX(100%);
    }
    .right-leave-active {
        transition: transform 1s;
    }
</style>
<body>
    <div id="app">
        <transition :name="transitionName">
            <router-view></router-view>
        </transition>
    </div>
    <script>
        new Vue({
            data: {
                transitionName: 'left'//通过transitionName变化left/right改变效果
            },
            watch: {
                $route(to, from) {//判断路由父子关系
                 const toPath=to.path.split('/').length
                 const fromPath=from.path.split('/').length
                    this.transitionName=toPath<fromPath ? 'right':'left'
                }
            }
        })
    </script>
</body>
</html>

v3路由页面左边出来平移跳转

.left-enter-from {
  transform: translateX(100%);
}
.left-enter-to {
  transform: translateX(0);
}
.left-enter-active {
  transition: transform 1s;
}
.left-leave-from {
  transform: translateX(0);
}
.left-leave-to {
  transform: translateX(-100%);
}
.left-leave-active {
  transition: transform 1s;
}

.right-enter-from {
  transform: translateX(-100%);
}
.right-enter-to {
  transform: translateX(0);
}
.right-enter-active {
  transition: transform 1s;
}
.right-leave-from {
  transform: translateX(0);
}
.right-leave-to {
  transform: translateX(100%);
}
.right-leave-active {
  transition: transform 1s;
}

 <router-view v-slot="{ Component }">
      <transition :name="transitionName">
        <component :is="Component" />
      </transition>
    </router-view>


<script setup lang="ts">
import { ref } from "vue";
import { onBeforeRouteUpdate } from "vue-router";

let transitionName = ref(" ");

onBeforeRouteUpdate((to) => {//监听路由的改变
  if (to.path == "/daka2/addpeople") {
    transitionName.value = "right";
  } else {
    transitionName.value = "left";
  }
});
</script>

svg 圆形进度条

在这里插入图片描述

根据虚线的间距
stroke-dasharray用于创建虚线,之所以后面跟的是array的,是因为值其实是数组,一个参数时: 其实是表示虚线长度和每段虚线之间的间距,两个参数或者多个参数时:一个表示长度,一个表示间距

  svg {
            vertical-align: middle;
            width: 150px;
            height: 150px;
        }

        .pie-bg {
            stroke: #bacd0c;
            opacity: .3;
        }

        .pie-bar {
            stroke: #f2ff00eb;
        }

        circle {
            stroke-dashoffset: 0;
            transform: rotate(-90deg);
            transform-origin: center;
            transition: all .2s;
            stroke: currentColor;
            z-index: 2;
        }
<svg xmlns="http://www.w3.org/2000/svg" width="50" height="50" viewBox="0 0 50 50">
        <circle fill="transparent" class="pie-bg" stroke-width="1" cx="25" cy="25" r="20"></circle>
        <circle fill="transparent" class="pie-bar" stroke-width="1" cx="25" cy="25" r="20"
            style="stroke-dasharray: 23.654, 160.664;"></circle>//第一个值代表长度
</svg>
 var pieBar = document.querySelector('.pie-bar');
        var pathLen = 40 * Math.PI;//圆的周长
        var percent = 45;//占百分比,在这设置
        pieBar.style.strokeDasharray = pathLen * percent / 100 + " " + pathLen;

cookie登录

//浏览器请求报文=响应头+响应体(post参数)
//服务器返回响应报文=响应头+响应体(服务器返回的数据)
//cookie,响应头包含set-cookie告诉浏览器把这个给我保存起来,之后的请求浏览器都会携带cookie

在这里插入图片描述
在这里插入图片描述

npm run serve之后自动打开

--package.json
serve:" vue-cli-service serve --open"

关闭eslint校验

//在根目录下vue.config.js
//关闭eslint
  lintOnSave:false

忽略,不检查
修改eslint,最简单的一种,就是忽略…也就是不检查,一了百了,在项目的根目录下新建一个文件,文件名:.eslintignore(就是这个,不要加多余的东西),然后文件里可以添加待忽略的文件或文件夹

# 忽略目录
build/
tests/
node_modules/
src/micpo

# 忽略文件
**/*-min.js
**/*.min.js

iconfont.js

根据路由是否呈现footer

//方法一:v-show

 <!-- 底部位置判断路由路径显示footer -->
 //home,search有其他没有
    <footer-index v-show="$route.path=='/home'||$route.path=='/search'"></footer-index>

//方法二:元 信息

<footer-index v-show="$route.meta.show"></footer-index>
//路由配置meta
 {
  path:'/home',
  component:Home,
  meta:{show:true}
 },
 {
  path:'/login',
  component:Login,
  meta:{show:false}
 },

编程式导航路由参数不变,多次点击会报错

声明式导航不会有这样的错误
为什么会这样是因为编程式导航会返回一个promise,拥有成功和失败的回调

在这里插入图片描述

//解决方法
Vue.use(VueRouter)
//先把vueRouter原型对象的push,replace,先保存一份
let originPush=VueRouter.prototype.push;
let originReplace=VueRouter.prototype.replace;
//重写push和replace方法
//第一个参数:往那跳(传递什么参数)
//第二个参数:成功回调
//第三个参数:失败回调
VueRouter.prototype.push=function(location,resolve,reject){
  if(resolve&& reject){
    originPush.call(this,location,resolve,reject)
  }else{
    originPush.call(this,location,()=>{},()=>{})
  }
}
VueRouter.prototype.replace=function(location,resolve,reject){
  if(resolve&& reject){
    originReplace.call(this,location,resolve,reject)
  }else{
    originReplace.call(this,location,()=>{},()=>{})
  }
}

当前页面跳转,去掉query/pramas参数

//只需要跳转自己就可以了
this.$router.push("/search");

在这里插入图片描述

在这里插入图片描述

一分钟倒计时验证码

在这里插入图片描述

				<el-input
                  placeholder="请输入内容"
                  v-model="phoneCode"
                >
                  <template slot="append">
                    <el-button
                      @click="getCode"
                      :disabled="isdisable||inputPhone.length==0"//判断是否禁用,一开始输入框为空禁用,点击获取验证验证手机号是否正确
                    >{{codeBtnDisabled}}</el-button>
                  </template>
                </el-input>
                
countNum :10//初始倒计时时间
isdisable :false//禁用设置

  computed: {
    codeBtnDisabled() {
      if (this.isdisable) {//如果已经被禁用就显示倒计时
        return "倒计时" + this.countNum;
      } else {//没有就显示获取验证码
        return "获取验证码";
      }
    },
  },
 this.isdisable = true;
        this.t = setInterval(() => {
          this.countNum = this.countNum -= 1;
          if (this.countNum == 0) {
            clearInterval(this.t);
            this.countNum = 10;
            this.isdisable = false;
          }
        }, 1000);

(v2)增删改查 列表使用input修改

在这里插入图片描述

//todolist.vue
<template> 
  <div>
    <input
      type="text"
      v-model="inp"
    ><button @click="add">添加</button>
    <ul>
      <list-item
        v-for="item in list"
        :key="item.id"
        :value="item"
        @del="deleItem"
        @xg="xg1"
      ></list-item>
    </ul>
  </div>
</template>

<script>
import ListItem from "./ListItem.vue";
export default {
  components: { ListItem },
  data() {
    return {
      list: [
        { id: 1, name: "AAA" },
        { id: 2, name: "BBB" },
        { id: 3, name: "CCC" },
      ],
      inp: "",
    };
  },
  methods: {
    xg1(name) {
      this.list.splice(
        this.list.findIndex((i) => i.id == name.id),
        1,
        name
      );
    },
    add() {
      this.list.push({ id: new Date().getTime(), name: this.inp });
      this.inp = "";
    },
    deleItem(id) {
      this.list.splice(
        this.list.findIndex((i) => {
          return i.id == id;
        }),
        1
      );
    },
  },
};
</script>

<style lang="scss" scoped>
</style>
//ListItem
<template>
  <div>
    <li>
      <input
        v-if="isShow"
        type="text"
        placeholder="请输入"
        v-model="inp"
      >
      <span v-else>{{value.name}}</span>
      <button @click="del">删除</button>
      <button @click="xiugaiAndChange">{{change}}</button>
    </li>
  </div>
</template>

<script>
export default {
  props: {
    value: {
      type: Object,
    },
  },
  data() {
    return {
      isShow: false,
      inp: "",
    };
  },
  computed: {
    change() {
      if (this.isShow) {
        return "保存";
      } else {
        return "修改";
      }
    },
  },
  methods: {
    del() {
      this.$emit("del", this.value.id);
    },
    xiugaiAndChange() {
      if (this.isShow) {
        this.isShow = !this.isShow;
        this.$emit("xg", { id: this.value.id, name: this.inp });
      } else {
        this.isShow = !this.isShow;
      }
    },
  },
};
</script>

<style lang="scss" scoped>
</style>

(v2)使用v-model循环增删改查

在这里插入图片描述

//a.vue
<template>
    <div>
        <input type="text" v-model="inp1" /><button @click="add">添加</button>
        <item-git
            v-for="(item, index) in list"
            :key="item.id"
            v-model="list[index]"
            :index="index + ''"
            @del=" (index) => {  list.splice(index, 1)} " ></item-git>
    </div>
</template>

<script>
import itemGit from "./itemGit.vue";
export default {
    components: { itemGit },
    data() {
        return {
            inp1: "",
            list: [
                { id: 1, name: "111" },
                { id: 2, name: "222" },
            ],
        };
    },
    methods: {
        add() {
            this.list.push({ id: new Date().getTime(), name: this.inp1 });
            this.$nextTick(() => {});
        },
    },
};
</script>
//b.vue
<template>
    <div>
        <span v-show="show">{{ value.name }}</span>
        <input v-show="!show" type="text" v-model="inp" :ref="value.id" />
        <button @click="chang">{{ changeTitle }}</button>
        <button @click="$emit('del', index)">删除</button>
    </div>
</template>

<script>
export default {
    props: {
        value: {
            type: Object,
            default: () => {},
        },
        index: { type: String },
    },
    computed: {
        changeTitle() {
            if (this.show) {
                return "修改";
            } else {
                return "保存";
            }
        },
    },
    data() {
        return {
            show: true,
            inp: JSON.parse(JSON.stringify(this.value.name)),
        };
    },
    methods: {
        chang() {
            this.show = !this.show;
            if (this.changeTitle == "保存") {
                this.$nextTick(() => {
                    this.$refs[this.value.id].focus();
                });
            } else {
                this.$emit("input", { id: this.value.id, name: this.inp });
            }
        },
    },
};
</script>

v2 table span和input输入切换

在这里插入图片描述

<el-table-column min-width="300px" label="标题">
  <template scope="scope">
    <el-input v-show="scope.row.edit" size="small" v-model="scope.row.title"></el-input>
    <span v-show="!scope.row.edit">{{ scope.row.title }}</span>
  </template>
</el-table-column>
<el-table-column align="center" label="编辑" width="120">
  <template scope="scope">
    <el-button v-show='!scope.row.edit' type="primary" @click='scope.row.edit=true' size="small" icon="edit">编辑</el-button>
    <el-button v-show='scope.row.edit' type="success" @click='scope.row.edit=false' size="small" icon="check">完成</el-button>
  </template>
</el-table-column>
 

(v3)增删改查 input+span

//a.vue
<script setup lang="ts">
import HelloWorld from "./components/HelloWorld.vue";
import { ref, reactive } from "vue";
interface Obj {
  id?: Number;
  name?: String;
}
let list: Obj[] = reactive([
  { id: 1, name: "123" },
  { id: 21, name: "123" },
]);
let inp = ref("");
const add = () => {
  list.push({ id: 4, name: inp.value });
};
</script>

<template>
  <input type="text" v-model="inp" /><button @click="add">添加</button>
  <HelloWorld
    v-for="(item, index) in list"
    v-model="list[index]"
    :index="index"
    @del=" (i:number) => {  list.splice(i, 1);  } " ></HelloWorld>
</template>
//b.vue
<template>
  <div>
    <p v-show="isShow">{{ modelValue.name }}</p>
    <input v-show="!isShow" type="text" v-model="inpp" />
    <button @click="update">{{ changStr }}</button>
    <button @click="$emit('del', index)">删除</button>
  </div>
</template>

<script setup lang="ts">
import { ref, computed } from "vue";
const props = defineProps(["modelValue", "index"]);
const emit = defineEmits(["update:modelValue", "del"]);
let isShow = ref(true);
let inpp = ref(props.modelValue.name);
let changStr = computed(() => {
  if (!isShow.value) {
    return "保存";
  } else {
    return "修改";
  }
});
const update = () => {
  isShow.value = !isShow.value;
  if (isShow.value) {
    emit("update:modelValue", { id: props.modelValue.id, name: inpp.value });
  }
};
</script>

vue使用nprogress进度条

npm install --save nprogress
//这里在封装的request.js里面引入,请求的时候呈现
import nprogress from 'nprogress';//进度条效果
//引入进度条样式
import "nprogress/nprogress.css"
//请求拦截器
    nprogress.start()
// 响应拦截器
    nprogress.done()

vue使用mockjs拦截请求

简单写法
1.下载

npm install mockjs

2.创建文件夹
在这里插入图片描述

3.引入

//main.js
import '@/mock' //引入mock

4.index.js注册所有的mock服务

// 首先引入Mock
import Mock from "mockjs";
// 设置拦截ajax请求的相应时间
Mock.setup({
  timeout: '200-600'
});

let configArray = [];

// 使用webpack的require.context()遍历所有mock文件
const files = require.context('.', true, /\.js$/);
files.keys().forEach((key) => {
  if (key === './index.js') return;
  configArray = configArray.concat(files(key).default);
});

// 注册所有的mock服务
configArray.forEach((item) => {
  for (let [path, target] of Object.entries(item)) {
    let protocol = path.split('|');
    Mock.mock(new RegExp('^' + protocol[1]), protocol[0], target);
  }
});

5.get,post请求获取数据

//demoList .js
let demoList = [{
    id: 1,
    name: 'zs',
    age: '23',
    job: '前端工程师'
},{
    id: 2,
    name: 'ww',
    age: '24',
    job: '后端工程师'
}]
//axios.get请求
export default {
'get|/parameter/query':  () => {//get(设置请求方式post、get)|(option) 参数option传递的数据
return {
  status: 200,
  message: 'success',
  data: demoList
};
}
}

6.Mock.mock拦截请求

Mock.mock( rurl, rtype, template|function( options ) )
rurl
可选。
表示需要拦截的 URL,可以是 URL 字符串或 URL 正则。例如 '/domian/list.json'。

rtype
可选。
表示需要拦截的 Ajax 请求类型。例如 GETPOSTPUTDELETE 等。

template
可选。
表示数据模板,可以是对象或字符串。
数据模板中的每个属性由 3 部分构成:属性名、生成规则、属性值:
// 属性名   name
// 生成规则 rule
// 属性值   value
'name|rule': value
例如:'name|1-10':1 会产生一个1-10之间的整数,详细规则参见官方文档

function(options)
可选。
表示用于生成响应数据的函数。
options
指向本次请求的 Ajax 选项集,含有 url、type 和 body 三个属性

7.登录数据拦截,如果密码用户名正确就返回

import Mock from "mockjs";
import vue from 'vue'
let vm=new vue()

// mock
Mock.mock(/\/api\/login/, "post", (option) => {
    console.log(option);
    // 获取post请求传过来的参数
    let params = JSON.parse(option.body);

    let response = {
      code: 0,
      data: [],
    };
//判断用户名和密码是否是admin和123
    if (params.username === "admin" && params.password === "123") {

      response.data = {
        token: window.btoa("token"),
      };
      vm.$message({
        message: '登录成功',
        type: 'success'
      });
    } else {
      response = {
        code: 401,
        data: null,
      };
      vm.$message({
        message: '密码或者用户错误',
        type: 'warning'
      });
    }

    return response;
  });

  export default {
    Mock
}

mock用到的图片需要放到poblic文件夹下

因为以后要打包,打包需要放到poblic文件夹下,因为打包会生成dist文件夹

vue使用json-server

添加链接描述

起服务

在项目跟文件下的data数据文件夹里
cmd 输入 json-server cost.json
在这里插入图片描述

vuex刷新数据会初始化

1.保存在本地存储

页面刷新后,原有的 vuex 中的 state 会发生改变,如果在页面刷新之前,可以将 state 信息保存到localstorage,页面重新加载时,从localstorage中取出,再将该值赋给 state,那么该问题即可解决。

【在某组件中添加如下钩子函数。比如 App.vue中】
created() {
    //在页面加载时读取localStorage里的状态信息
    if (localStorage.getItem("store") ) {
        this.$store.replaceState(Object.assign({}, this.$store.state,JSON.parse(localStorage.getItem("store"))))
    }

    //在页面刷新时将vuex里的信息保存到localStorage里
    window.addEventListener("beforeunload",()=>{
        localStorage.setItem("store",JSON.stringify(this.$store.state))
    })
}


注:
    this.$store.replaceState()  用于替换 store 的信息(状态合并)。
    Object.assign(target, ...source)  将source的值 赋给 target,若有重复的数据,则覆盖。其中...表示可以多个source。
    JSON.stringify()  用于将对象转为 JSON
    JSON.parse()   用于将 JSON 转为对象

2.从新请求数据

//从新发起请求获取数据

vue ssr服务器端渲染

ssr服务器端渲染,浏览器直接呈现服务器返回的htm利用node搭建页面渲染。浏览器加载html文件—>服务端填好内容—>返回浏览器渲染
csr浏览器端渲染:浏览器加载html文件—>下载js—>运行vue—>渲染页面

优势:更好的seo,搜索引擎优化,提高页面页面加载速度

实现

1.vue官网 vue ssr
https://vuejs.org/guide/scaling-up/ssr.html#rendering-an-app

2.简易ssr
需要和vue版本一样

//下载这些
 "dependencies": {
    "express": "^4.17.3",
    "vue": "2.6.14",
    "vue-router": "^4.0.12",
    "vue-server-renderer": "2.6.14"
  }

打包空白页问题

1.修改vue.config.js
publicPath:“./”
2.如果使用histor打包需要和后端商量配置
3.把histor更改为hash路由模式

三级路由跳转优化

//放到父元素上,没有经历循环,所以只产生一个回调函数,用事件委托
//1.根标签<div
          class="all-sort-list2"
          @click="goSearch($event)"
        >
//2.给子节点添加指定    <a
                :data-categoryName="c1.categoryName"
                :data-category1Id="c1.categoryId"
              >{{c1.categoryName}}</a>
goSearch(event) {
      //路由跳转的方法
      let element = event.target;//获得标签上的属性
      let { categoryname, category1id, category2id, category3id } =
        element.dataset;
      if (categoryname) {//如果有就表示是a标签
        let location = { name: "search" };
        let query = { categoryName: categoryname };
        if (category1id) {//判断一二三级分类
          query.category1Id = category1id;
        } else if (category2id) {
          query.category2Id = category2id;
        } else if (category3id) {
          query.category3Id = category3id;
        }

        location.query = query;
        this.$router.push(location);//路由跳转,对象模式
      }
    },

切换路由列表重复加载问题

有的列表信息,只需要加载一次,每次切换组件都会从新加载,这时候就在app.vue里面写加载一次就行,根组件的mounted只执行一次

合并路由跳转的params,query参数

goSearch() {
      //点击搜索跳转search路由,自己是params参数//携带三级分类的query参数
      if (this.$route.query) {
        let location = {
          name: "search",
          params: {
            keyword: this.keyword || undefined,
          },
        };
        location.query = this.$route.query;
        this.$router.push(location);
      }
    },

 {
  path:'/search/:keyword?',
  component:Search,
  name:'search',
  meta:{show:true}
 },
//点击三级分类,自己是query,携带pramas
	  let element = event.target;//data-categoryname=“c2.categoryName”
      let { categoryname, category1id, category2id, category3id } =
        element.dataset;//获得data- 属性
		let location = { name: "search" };
        let query = { categoryName: categoryname };
       
        //判断路由跳转,是否有params参数,有就一起带过去
        if (this.$route.params) {
          location.params = this.$route.params;
          location.query = query;
          this.$router.push(location);
        }
      }

在这里插入图片描述
点击1,2都要跳转search页面,需要合并分别传递的参数

路由登录跳转之前点击的页面

//beforeEach
//未登录不能去哪里
    let toPath = to.path
    if (toPath.indexOf('/trade') == -1 || toPath.indexOf('/pay') != -1 || toPath.indexOf('/center') !== -1) {
      //都没登录进入login
      next('/login?redirect=' + toPath) //登录后跳转之前点的页面
      //把未登录时候的要去成的信息,存储在地址栏中,使用query参数
页面中使用
 //看是否包含query参数,如果有,跳到query参数,指定的路由,没有就去首页
          let toPath=this.$route.query.redirect||'/home'
          this.$router.push(toPath);

判断是否是从指定页面过来,否则停留在当前页面

router
 {
        path: '/trade',
        component: TradeIndex,
        name: 'trade',
        meta: {
            show: true
        },
        beforeEnter: (to, from, next) => {
            if (from.path == '/shopcart') {
                next()
            } else {
                next(false)
                //终端当前导航,如果浏览器的url改变了,那么url的地址会重置到from路由对应的地址
            }
        }
    },

vue使用懒加载lazyload

//package.json 如果报错就修改版本
 # 先写在原有的安装
npm uninstall vue-lazyload --save

# 再安装低版本的
npm install vue-lazyload@1.3.3 --save

npm网站

//动态数据的时候需要设置一个key值
<ul>  
    <li v-for="img in list">
        <img v-lazy="img.src" :key="img.src" >
    </li>
</ul>

数字增加效果组件requestAnimationFrame

<template>
  <div>
    <p> {{n}}</p>
  </div>
</template>

<script>
export default {
  data() {
    return {
      n: 0,//页面显示数字
      step: 0,//刷新率
    };
  },
  props: {
    value: {
      type: Number,
      default: 0,
    },
  },
  created() {
    requestAnimationFrame(this.render);
  },
  methods: {
    render() {
      if (this.value === 0) {
        return;
      }
      this.n += this.step; 
      if (this.n < this.value) {
        //在动画没有结束前,递归渲染
        requestAnimationFrame(this.render);
      } else {
        this.n = this.value;
      }
    },
  },
  watch: {
    //因为props传过来是异步的所以,需要watch来监听数据变化
    value() {
      this.step = Math.floor(this.value / 70);
      this.n = 0;
      requestAnimationFrame(this.render);
    },
  },
};
</script>

使用vue-animate-number实现数据动态改变

npm install vue-animate-number 
//main.js
import VueAnimateNumber from 'vue-animate-number'
Vue.use(VueAnimateNumber)
//组件中使用
<animate-number from="0" :to="item.num" duration="2000"></animate-number>
//动态获取的数据
// 添加一个key值 等于item.num

<animate-number from="0" :to="item.num" :key="item.num" duration="2000"></animate-number>

在这里插入图片描述

父组件异步数据通过props方式传递给子组件,子组件接收不到的问题

1. 使用v-if控制子组件渲染的时机

缺点:有一段空白期

 <child :msg="msg" v-if="isGetData"></child>

2.子组件使用watch监听父组件传递过来的数据

例如swiper动态获取数据

//子组件
 props: {
    msg: {
      type: String,
      default: "",
    },
  },
 watch: {
    // 监听到父组件传递过来的数据后,加工一下,
    // 存到data中去,然后在页面上使用
    msg(newnew, oldold) {
     
    },
  },

兄弟之间组件传递对象数据,深拷贝浅拷贝问题

//ab兄弟组件,a传给b,传递一个对象,如果要修改对象的一个属性,
obj={name:'张三',age:18}
this.$emit('b',obj)
//b.vue 使用watch来监听这个值,如果是浅拷贝,就只有一开始的时候执行一次watch
watch:{
obj(){}
}
//解决办法:1.deep:true
//			2.this.$emit('b',{...obj})//深拷贝对象

vuex 修改对象数据,深浅拷贝监听问题

//a.vue 传
this.$store.commit('setFormData', JSON.parse(JSON.stringify(this.formData)));//需要转化成深拷贝
// store/index.js
setFormData(state, formData) {
      state.formData = formData;
    }
    
//b.vue 取
 watch: {
    '$store.state.formData'() {//只有对象是深拷贝后,才会监听到变化
      this.pageNo = 1;
      this.getList();
    }
  },

keep-alive路由切换,点击过的数据就不从新加载,每点击过再从新获取


<nav>
	<router-link to="/home"></router-link>
	<router-link to="/about"></router-link>
</nav>
<keep-alive :include="['AboutView','HomeView']"(组件名)>//如果不配置include 会把router-view展示区域的所有东西都缓存下来,10个切换项就要保存10个浪费性能
	<router-view></router-view>
</keep-alive>


//点击列表vue
数据一
数据二
数据三
//详情页vue
要求第一次点击进去发送请求,再一次点击相同的进去不发送请求,点击不一样的从新发送请求
1.保存id值
id=""
created(){
	this.id=this.$route.query.id
	this.getList()//先发一次请求
}
methods:{
	getList(){
		id:this.id
	}//发送请求
}
activeted(){
	//this.id//之前记录下来的id
	//this.$route.query.id//最新的id
	//判断俩是否相等
	if(this.id!==this.$route.query.id){
	this.id=this.$route.query.id
		this.getList()//发送请求
	}
}

当前页面下,监听路由的变化,再次发送请求

不用beforeRouteUpdate因为获取不到路由信息
 watch: {
      $route(to,from){
    console.log(to.path);
  }
  },

项目登录流程

登录接口完成后 会得到token ,vuex保存token,本地存储保存token,然后跳转到home页面,home页面一加载,mounted就调用接口获取用户信息,携带token
store 里面
token: localStorage.getItem(“TOKEN”),

//本地存储token
async userLogin({commit},user){ //登录
            const result=await $axios.post("/user/passport/login/",user)
            if(result.code==200){
                //服务器下发token ,vuex保存token,
               commit("USERLOGIN",result.data.token)
localStorage.setItem("TOKEN",result.data.token)

                return 'ok'
            }else{
                return Promise.reject(new Error('faile'))
            }
        }, 

路由前置守卫

router.beforeEach(async (to,from,next)=>{
  let token=store.state.user.token//token
  let name=store.state.user.userInfo.name//用户信息
  if(token){
   if(to.path=='/login'||to.path=='/register'){//如果登录了,就不能去login界面
      next('/home')
   }else{  
      if(name){//如果有用户信息就直接放行
        next()
      }else{//没有就去获取
         try {
          await store.dispatch('user/userInfo')//获取用户信息
          // async userInfo({commit}){ //获取用户信息
          //  const result= await $axios.get("/user/passport/auth/getUserInfo")
        //    if(result.code==200){
         //       commit('GETUSERINFO',result.data)
         //       //持久化存储
         //       return 'ok'
          //  }else{
          //      return Promise.reject(new Error('faile'))
//}
       // }, 
          next()
         } catch (error) {
          //如果token已经失效 就登出 并且回到login
          store.dispatch('user/userLogout')
          // async userLogout({commit}){ //获取用户信息
         //   const result= await $axios.get( '/user/passport/logout')
         //   if(result.code==200){
                //持久化存储
         //       commit('CLEAR')(state.token='',  state.userInfo={} localStorage.removeItem("TOKEN"))
          //      return 'ok'
         //   }else{
          //      return Promise.reject(new Error('faile'))
           // }
      //  }, 
          next('/login')
         }
     }
  next()
   }
  }else{
    next()
  }
  })

退出登录流程

1.发送请求告诉服务器,提交登陆
2.清除浏览器的所以缓存,token,vuex保存的用户信息,token

登录token 刷新问题

token会有过期时间,使用refresh_token进行token刷新,保持登录状态

添加链接描述

路由设置是否登录,登录就跳转,没有就跳转登录,登录鉴权

//router/index.js
{
            path: '/mine',
            name: 'mine',
            component: Mine,
            meta:{
                auth:false//判断是否需要登录判断
            }
        },
router.beforeEach((to,from,next)=>{
    let token=localStorage.getItem('userName')
    // 判断该页面是否需要登录
    if(to.meta.auth){
        // 如果token存在直接跳转
        if(token){
            next()
        }else{
            // 否则跳转到login登录页面
            next({
                path:'/login',
                // 跳转时传递参数到登录页面,以便登录后可以跳转到对应页面
                query:{
                    redirect:to.fullPath
                }
            })
        }
    }else{
        // 如果不需要登录,则直接跳转到对应页面
        next()
    }
})
//登录.vue
 methods: {
    submit() {
      //登录成功后存储用户信息
      localStorage.setItem("userName", this.name);
      // 接收参数,如果存在redirect参数,登录后重定向到指定页面
      if (this.$route.query.redirect) {
        this.$router.push({ path: this.$route.query.redirect });
      // 如不存在,直接跳转到首页
      } else {
        this.$router.push({ path: "/home" });
      }
    }
  }

后台路由鉴权,权限

1.登录后会返回用户信息,路由权限数组,用户信息,按钮权限,使用vuex保存起来。
2.拆分现有的路由,分为固定路由(登录,首页,404等),暴露动态路由(需要权限的路由)
3.vuex里面根据暴露的动态路由和接口获得的权限,使用filter+indexOf!==-1判断是否有这个权限,同时需要注意2,3级路由,利用递归传递子路由进行对比。
4.concat合并固定路由动态路由.
5.在beforEach中addRouter添加路由
6.左侧菜单权限,遍历路由信息,利用递归组件,判断是否用于children属性

vuex中的方法,用来计算出有权限的路由
在这里插入图片描述

路由点击页面权限校验

{
    path: '/about',
    name: 'about',
    component: AboutView,
    meta:{isAuth:true}//需要权限校验就写这个
  },
{
    path: '/home',
    name: 'home',
    component: HomeView,
  },
router.beforeEach((to,from,next)=>{//全局前置路由守卫,在切换之前进行调用+初始化的时候调用
  //to:到去哪 from:从哪来 next:next() 放行
  if(to.meta.isAuth){//根据meta是否有isAuth属性判断需不需鉴定权限
    if(localStorage.getItem('username')==='张三'){//切换路径之前判断一下,本地存储里面用户名是否是张三,是就放行
      next()
    }else{
      alert('权限不够')
    }
  }else{
    next()
  }
})

按钮鉴权自定义指令

<el-button v-btnlimit="'edit'">修改</el-button>
//首先登陆后会获得按钮权限,保存在vuex/session里面
//使用自定义指令,在inseted(插入到节点的时候判断)
//然后判断edit是否在数组里面显示,如果有就显示,没有就 el.parentNode.removeChild(el)
import Vue from 'vue';
Vue.directive('btnlimit', {
    // 当被绑定的元素插入到 DOM 中时……
    inserted: (el, binding) => {
        // el 当前绑定的元素 binding.value指令的绑定值
        let permissionList = sessionStorage.getItem('permission_button');
        // 判断一下是否包含这个元素,如果不包含的话,那就让他爸爸元素把子元素扔进垃圾堆
        if (!permissionList.includes(binding.value)) {
            el.parentNode.removeChild(el)
        }
    }
})
// 大家可以把自己定义的指令写在一个directive.js文件中,在main.js总入口引入下就可以了,简单而不失优雅

在子组件标签上添加click事件不生效.native

某个组件的根元素上绑定事件,直接使用 @click=‘‘function’ 是不生效的,我们可以添加.native修饰符 @click.native=’‘function’'。

<div id="app">
    <div class="box">
      <Son @click='handlerFun'></Son>
    </div>
  </div>
  <script>
    const Son = Vue.component('Son', {
      template: '<button class="box1">son</button>',
      methods: {}
    })
    new Vue({
      el: "#app",
      components: {
        Son
      },
      methods: {
        handlerFun() {
          console.log('父级')
        }
      }
    })
  </script>

v2封装分页器

在这里插入图片描述

//PaginationView.vue分页器组件
<template>
  <div class="pagination">
    {{startNumAndEndnum}}
    <button
      :disabled="pageNo==1"
      @click="$emit('getPageNo',pageNo-1)"
    >上一页</button>
    <button
      v-if="startNumAndEndnum.start>1"
      @click="$emit('getPageNo',1)"
      :class="{active:pageNo==1}"
    >1</button>
    <button v-if="startNumAndEndnum.start>2">...</button>
    <template v-for="(page,index) in startNumAndEndnum.end">
      <button
        :key="index"
        @click="$emit('getPageNo',page)"
        v-if="page>=startNumAndEndnum.start"
        :class="{active:pageNo==page}"
      >
        {{page}}</button>
    </template>
    <template v-if="startNumAndEndnum.end< totalPage - 1">
      <button>...</button>
    </template>
    <template v-if="startNumAndEndnum.end<totalPage">
      <button
        @click="$emit('getPageNo',totalPage)"
        :class="{active:pageNo==totalPage}"
      >{{totalPage}}</button>
    </template>
    <button
      :disabled="pageNo==totalPage"
      @click="$emit('getPageNo',pageNo+1)"
    >下一页</button>
    <button>{{total}}</button>
  </div>
</template>

<script>
// :pageNo="1" :total="91" :pageSize="3" (每页显示) :continues="5"(连着的)
export default {
  data() {
    return {};
  },
  props: ["pageNo", "total", "pageSize", "continues"],
  computed: {
    totalPage() {
      //总共多少页
      if (!Math.ceil(this.total ?? 0 / this.pageSize ?? 0)) {
        return 0;
      } else {
        return Math.ceil(this.total / this.pageSize);
      }
    },
    startNumAndEndnum() {
      const { continues, totalPage, pageNo } = this;
      let start = 0,
        end = 0;
      //连续页面是5,当不满5页
      if (continues > totalPage) {
        start = 1;
        end = totalPage;
      } else {
        start = pageNo - parseInt(continues / 2);
        end = pageNo + parseInt(continues / 2);
        if (start < 1) {
          start = 1;
          end = continues;
        }
        if (end > totalPage) {
          end = totalPage;
          start = totalPage - continues + 1;
        }
      }
      return { start, end };
    },
  },
};
</script>
<style lang="scss" scoped>
.pagination {
  width: 100%;
  height: 50px;
  justify-content: center;
  display: flex;
  button {
    width: 50px;
    height: 50px;
    border: 1px solid red;
    margin: 0 10px;
  }
}
.active {
  background: pink;
}
</style>
//使用页面 
<PaginationView
	 :pageNo="searchParams.pageNo"//当前页面
	  :total="searchList.total"//总数
	  :pageSize="searchParams.pageSize"//一页几个
	  :continues="5"//连续数字5个
	  @getPageNo="getPageNo"//点击接收的方法
></PaginationView>
methods:{
 getPageNo(pageNo) {
      //获取当前第几页
      //整理参数
      this.searchParams.pageNo = pageNo;
      //再次发请求
      this.getData();
    },
}

路由拆分到一个单独js文件

//index.js
import routes from './routes'

//routes.js
import Home from '@/views/Home/HomeView.vue'
import Login from '@/views/Login/LoginView.vue'

export default [{
         path:'/',
         redirect:'/home'
        },
        {
         path:'/home',
         component:Home,
         meta:{show:true},
        },
        {
         path:'/detail/:id?',
         component:Detail,
         name:'detail',
         meta:{show:true},
        }]

无法找到模块“…”的声明文件。“…”隐式拥有 “any” 类型。

在src目录下新建一个types目录,然后在types 目录下新建一个 index.d.ts文件然后在文件中添加代码 declare module “第三方类库名”。

declare module '@antv/g6-editor';

要关闭any类型的警告

在这里插入图片描述
在 .eslintrc.js文件中找到rules 添加一行代码即可

"@typescript-eslint/no-explicit-any": ["off"]

vue使用Day.js库

官网

1 .下载

npm install dayjs

2.引入

const dayjs = require('dayjs')
//import dayjs from 'dayjs' // ES 2015
dayjs().format()

3.使用

//1.修改格式
	dayjs().format() 
	// 默认返回的是 ISO8601 格式字符串 '2020-04-02T08:02:17-05:00'
	dayjs('2019-01-25').format('DD/MM/YYYY') // '25/01/2019'

//2.时间差
	const date1 = dayjs('2019-01-25')
	const date2 = dayjs('2018-06-05')
	date1.diff(date2) // 20214000000
	date1.diff(date2, 'month') // 7
	date1.diff(date2, 'month', true) // 7.645161290322581
	date1.diff(date2, 'day') // 233

//3.一个时间在另一个时间之前
	dayjs().isBefore(Dayjs, unit? : String);
	dayjs().isBefore(dayjs()); // false
	dayjs().isBefore(dayjs(), 'year'); // false

//4、一个时间是否等于另一个时间
	dayjs().isSame(Dayjs, unit? : String);
	dayjs().isSame(dayjs()); // true
	dayjs().isSame(dayjs(), 'year'); // true

//5、一个时间在另一个时间之后。
	dayjs().isAfter(Dayjs, unit? : String);
	dayjs().isAfter(dayjs()); // false
	dayjs().isAfter(dayjs(), 'year'); // false

vue ajax 组件,Vue.js中ajax请求代码应该写在组件的methods中还是vuex的actions中?

1.如果一个axios请求要多组件复用,就放到actions中
2.如果actions请求回来的数据要放到vuex中就放到actions中
3.如果只执行一次,或者不返回什么数据就放到页面中
或者
1.定义一个api来管理全部请求
2.在actions中发送请求
3.用promise来处理返回数据

页面try,catch接收 vuex 返回的请求状态

//store
//页面触发按钮,请求vuex接口,想要获取一个状态,如页面详情加入购物车,向后台发送请求,如果返回成功再跳转到购物车页面
 actions:{
        async addOrUpdateShopCart({commit},{skuId,skuNum}){
            const result=await $axios.post("/cart/addToCart/"+skuId+"/"+skuNum)//会返回一个code:200的状态,并没有返回值
            if(result.code==200){
               return 'ok'
            }else{
               return Promise.reject(new Error('faile'))//如果不等于200可以抛出一个错误
            }
         },
    }
//组件
  <a @click="addShopcar">加入购物车</a>
  
 methods: {
    ...mapActions("detail", ["addOrUpdateShopCart"]),
async addShopcar() {//使用try,catch来捕获状态,actions返回一个promise
      try {
        await this.addOrUpdateShopCart({//发送请求
          skuId: this.$route.params.id,
          skuNum: this.skuNum,
        });
        //在这做页面跳转+携带参数
        //参数过多使用sessionStorage
      } catch (error) {//捕获错误
        alert(error.meaasge);
      }
    },}

vue3中reactive定义的引用类型直接赋值导致数据失去响应式

//这样直接赋值就会失去响应式
let data = reactive(['小猫', '小狗'])
    data = reactive(['大猫', '大狗'])
//解决方法1 再套一个对象
let data = reactive({  animals: ['小猫', '小狗'] })
  data.animals = ['大猫', '大狗']
//解决方法2 用ref定义
let data = ref(['小猫', '小狗'])
  data.value = ['大猫', '大狗']

如何修改props传进来的值

方法一:v-model的另一种写法

<draggable :value="$store.state.vuexArr" @input="update($event)">   </draggable>

update($event){
//向后端发送请求,提交修改的数据
//axios.post('updata',{}).then((res)=>{commit修改数据})
this.$store.commit('updateVuexArr',$event)
}

方法二:使用官方使用的计算属性

<draggable v-model="vuexArr"></draggable>
computed: {
    vuexArr: {
        get() {
            return this.$store.state.vuexArr
        },
        set(value) {
            this.$store.commit('updateVuexArr', value)
        }
    }
}

方法三 使用emit

方法四 v-model

单例 生成全局唯一uuid,存到store里面

//uuid_token.js
import { v4 as uuidv4 } from 'uuid';
//生成的随机字符串不能发生变化,游客身份持久存储
export const getUUID=()=>{
//先从本地存储获取uuid
let uuid_token=localStorage.getItem('UUIDTOKEN')
//如果没有,就生成一个,只存储一次
    if(!uuid_token){
        uuid_token=uuidv4()
        localStorage.setItem('UUIDTOKEN',uuid_token)
    }
    return uuid_token
}
import {getUUID} from '@/utils/uuid_token'//游客身份模块(生成随机uuid字符串)
export default{
    namespaced:true,
    state:{
        goodInfo:{},
        uuid_token:getUUID()//获取
    },

v3使用@路径符(不全)

看链接

npm install @types/node --save-dev

1、tsconfig.json
在这里插入图片描述
2.vite.config.js

  resolve: {
    alias: {
      '@':resolve(__dirname,'./src')
    	}
	},

vue3 封装axios ts+api.js+vite

1.在src下新建http 文件夹 ,再新建index.ts

import axios from 'axios'

// http/index.ts
    import axios from 'axios'
    //创建axios的一个实例 
    var instance = axios.create({
        // baseURL: import.meta.env.VITE_RES_URL, //接口统一域名
        timeout: 6000, //设置超时
        headers: {
            'Content-Type': 'application/json;charset=UTF-8;',
        }
    })
    //请求拦截器 
    instance.interceptors.request.use((config: any) => {
        // 每次发送请求之前判断是否存在token,如果存在,则统一在http请求的header都加上token,不用每次请求都手动添加了
        const token = window.localStorage.getItem('token');
        token && (config.headers.Authorization = token)
        //若请求方式为post,则将data参数转为JSON字符串
        if (config.method === 'POST') {
            config.data = JSON.stringify(config.data);
        }
        return config;
    }, (error) =>
        // 对请求错误做些什么
        Promise.reject(error));

    //响应拦截器
    instance.interceptors.response.use((response) => {
        //响应成功
        console.log('响应成功');
        return response.data;
    }, (error) => {
        console.log(error)
        //响应错误
        if (error.response && error.response.status) {
            const status = error.response.status
            console.log(status);
            return Promise.reject(error);
        }
        return Promise.reject(error);
    });
    export default instance;

2.再http下新建axios.ts

    import instance from "./index"
    /**
     * @param {String} method  请求的方法:get、post、delete、put
     * @param {String} url     请求的url:
     * @param {Object} data    请求的参数
     * @param {Object} config  请求的配置
     * @returns {Promise}     返回一个promise对象,其实就相当于axios请求数据的返回值
     */
    const axios = async ({
        method,
        url,
        data,
        config
    }: any): Promise<any> => {
        method = method.toLowerCase();
        if (method == 'post') {
            return instance.post(url, data, { ...config })
        } else if (method == 'get') {
            return instance.get(url, {
                params: data,
                ...config
            })
        } else if (method == 'delete') {
            return instance.delete(url, {
                params: data,
                ...config
            })
        } else if (method == 'put') {
            return instance.put(url, data, { ...config })
        } else {
            console.error('未知的method' + method)
            return false
        }
    }
    export {
        axios
    }

3.在src 下新建api 文件夹 ,在新建api.ts

import { axios } from "../http/axios"
//敏感词校验
export const getUser = (data: any) => {
    return axios({
        url: "/getUser",
        data,
        method: "get",
        config: {
            // headers: {
            //     'Request-Type': 'wechat'
            // },
            timeout: 10000
        }
    })
} 

4.页面使用

  <script setup lang="ts">
  import { getUser } from "../api/index";
  const s = await getUser({
    text: "里斯",
  });
  console.log(s);
  </script>

配置跨域vite+ts+v3

vite.config.js

server: {
       proxy: {
        '/api': {
          target: 'http://seec.xinyuefei.com',
          changeOrigin: true,//v3里面必须设置
           rewrite: (path) => path.replace(/^\/api/, '')//让路径没有api
        },
      } 
    }

过渡效果列表增加删除换位置,有过渡效果

在这里插入图片描述


<script setup>
import { shuffle as _shuffle } from 'lodash-es'
import { ref } from 'vue'

const getInitialItems = () => [1, 2, 3, 4, 5]
const items = ref(getInitialItems())
let id = items.value.length + 1

function insert() {
  const i = Math.round(Math.random() * items.value.length)
  items.value.splice(i, 0, id++)
}

function reset() {
  items.value = getInitialItems()
}

function shuffle() {
  items.value = _shuffle(items.value)
}

function remove(item) {
  const i = items.value.indexOf(item)
  if (i > -1) {
    items.value.splice(i, 1)
  }
}
</script>

<template>
  <button @click="insert">insert at random index</button>
  <button @click="reset">reset</button>
  <button @click="shuffle">shuffle</button>

  <TransitionGroup tag="ul" name="fade" class="container">
    <div v-for="item in items" class="item" :key="item">
      {{ item }}
      <button @click="remove(item)">x</button>
    </div>
  </TransitionGroup>
</template>

<style>
.container {
  position: relative;
  padding: 0;
}

.item {
  width: 100%;
  height: 30px;
  background-color: #f3f3f3;
  border: 1px solid #666;
  box-sizing: border-box;
}

/* 1. 声明过渡效果 */
.fade-move,
.fade-enter-active,
.fade-leave-active {
  transition: all 0.5s cubic-bezier(0.55, 0, 0.1, 1);
}

/* 2. 声明进入和离开的状态 */
.fade-enter-from,
.fade-leave-to {
  opacity: 0;
  transform: scaleY(0.01) translate(30px, 0);
}

/* 3. 确保离开的项目被移除出了布局流
      以便正确地计算移动时的动画效果。 */
.fade-leave-active {
  position: absolute;
}
</style>

v3 element plus 使用messagebox

//main.js
import "element-plus/theme-chalk/index.css";
全局挂载

v3 element plus 引入icon vite

npm i @iconify-json/ep -D
npm i unplugin-vue-components unplugin-icons unplugin-auto-import -D

github

<i-ep-edit />
//vite.config.js
import path from 'path'
import { defineConfig } from 'vite'
import Vue from '@vitejs/plugin-vue'
import Icons from 'unplugin-icons/vite'
import IconsResolver from 'unplugin-icons/resolver'
import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite'
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'
import Inspect from 'vite-plugin-inspect'

const pathSrc = path.resolve(__dirname, 'src')

export default defineConfig({
  resolve: {
    alias: {
      '@': pathSrc,
    },
  },
  plugins: [
    Vue(),
    AutoImport({
      // Auto import functions from Vue, e.g. ref, reactive, toRef...
      // 自动导入 Vue 相关函数,如:ref, reactive, toRef 等
      imports: ['vue'],

      // Auto import functions from Element Plus, e.g. ElMessage, ElMessageBox... (with style)
      // 自动导入 Element Plus 相关函数,如:ElMessage, ElMessageBox... (带样式)
      resolvers: [
        ElementPlusResolver(),

        // Auto import icon components
        // 自动导入图标组件
        IconsResolver({
          prefix: 'Icon',
        }),
      ],

      dts: path.resolve(pathSrc, 'auto-imports.d.ts'),
    }),

    Components({
      resolvers: [
        // Auto register icon components
        // 自动注册图标组件
        IconsResolver({
          enabledCollections: ['ep'],
        }),
        // Auto register Element Plus components
        // 自动导入 Element Plus 组件
        ElementPlusResolver(),
      ],

      dts: path.resolve(pathSrc, 'components.d.ts'),
    }),

    Icons({
      autoInstall: true,
    }),

    Inspect(),
  ],
})

promise.all 循环请求多个次

//store
 deleteCartListBySkuId(){}//删除单个
 //实现删除多个,需要多次调用删除单个的函数
 deleteAllCheckedCart({dispatch,getters}){//删除全部勾选的
           let PromiseAll=[]
           getters.cartList.cartInfoList.forEach((item)=>{          
           let promise= item.isChecked==1? dispatch('deleteCartListBySkuId',item.skuId):''
           PromiseAll.push(promise)
          })
          return Promise.all(PromiseAll)
          //有一个错就全错,都对才对
        }
//页面使用
  //删除全部选中的产品
    async deleteAllChecked() {
      try {
        await this.deleteAllCheckedCart();
        this.getData();
      } catch (error) {
        alert(error);
      }
    },

后台管理系统路由面包屑+tags删除添加

在这里插入图片描述

使用element面包屑+tag实现,在vuex中配置一个数组用来存放路径设置。
1.首页是不可以删除的
2.添加:点击路由如果数组没有就添加,有就切换到那个页面
3.删除:用splice删除点击判断是否是最后一个选项,如果是最后一个,就展示前一个。不是最后一个就展示当前删除的后一个
4.页面使用watch监听route变化。使用router.push()实现跳转

v3+ts代码.

//store
 routerArr: ["/home"],
 //vue
<template>
    <ul>
        <li v-for="item,i in routerArr" @click="app(item,i)" :class="{active:i==show}">
            {{item}}<span @click.stop="del(i)">X</span></li>
    </ul>
</template>

<script setup lang="ts">
import { useRoute, useRouter } from 'vue-router';
import { watch, ref } from 'vue';
import { useMainStore } from '../store'
import { storeToRefs } from 'pinia';
const mainStoreI = useMainStore()
const { routerArr } = storeToRefs(mainStoreI)
const router = useRouter()
const route = useRoute()
let show = ref(0)
watch(() => router.currentRoute.value.path, (newValue, oldValue) => {
    show.value =routerArr.value.findIndex(i=>i==newValue)
    if (routerArr.value.indexOf(newValue) == -1 && newValue !== '/') {
        routerArr.value.push(newValue)
        show.value = routerArr.value.length - 1
    }
}, { immediate: true })
const del = (i: number) => {
    if (i == show.value) {
        if (i === routerArr.value.length - 1) {
            routerArr.value.splice(i, 1)
            router.push(routerArr.value[routerArr.value.length-1])
            show.value = routerArr.value.length-1
        } else {
            routerArr.value.splice(i, 1)
            router.push(routerArr.value[i])
        }

    } else if (i < show.value) {
        routerArr.value.splice(i, 1)
        show.value = i
    } else if (i > show.value) {
        routerArr.value.splice(i, 1)
    }

}
const app = (item: string, i: number) => {
    router.push(item)
    show.value = i
}
</script>

<style scoped>
.active {
    color: red;
}
</style>

vue3文件上传下载

<template>
<p v-if="info.file_name">
  <a
    :href="info.file_url"
    :download="info.file_name"
  >{{ info.file_name }}</a>
  <el-button type="primary">删除</el-button>
</p>
<p v-else>
  <el-button type="primary" @click="onClick">上传文件</el-button>
  <input
    ref="file"
    v-show="false"
    type="file"
    @change="onChange"
  />
</p>
</template>

<script setup lang="ts">
import { ref } from 'vue';
import type { Info } from '@/assets/api';
import { ElMessage } from 'element-plus';
import { uploadFile } from '@/assets/api';

const props = defineProps<{
  info: Info
}>();
const emit = defineEmits(['update']);

const check = (file: File) => {
  const { name, size } = file;
  let msg = '';
  if (size / 1024 / 1024 > 10) {
    msg = '文件大小不能大于10M';
  } else {
    const type = name.split('.').pop();
    if (type && !['js', 'png', 'jpg'].includes(type)) {
      msg = '文件类型不正确';
    }
  }
  if (msg) ElMessage.error(msg);
  return !msg;
};

const upload = (file: File) => {
  const formData = new FormData();
  formData.append('file', file);
  formData.append('id', props.info.id + '');
  uploadFile(formData).then(() => emit('update'));
};

const file = ref<HTMLInputElement | null>(null);
const onChange = (e: Event) => {
  const { files } = e.target as HTMLInputElement;
  // const file = (files as FileList)[0];
  if (files) {
    const file = files[0];
    check(file) && upload(file);
  }
};
const onClick = () => file.value?.click();
</script>

使用vee-validate vue表单验证

npm i vee-validate@2 --save
使用2版本,3版本有一些困难

1.创建文件

//src/plugins/validate.js
//npm i vee-validate@2 --save
import Vue from 'vue'
import VeeValidate from 'vee-validate';
//中文提示信息
import zh_CN from 'vee-validate/dist/locale/zh_CN';
Vue.use(VeeValidate)
//表单验证规则
VeeValidate.Validator.localize('zh_CN', {
    messages: {
        ...zh_CN.messages,
        is: (field) => `${field}必须和密码相同`
    },
    attributes: { //给校验的field属性名映射中文名称
        phone: '手机号',
        code1: '验证码',
        password: '密码',
        password1: '确认密码',
        agree: '协议'
    }
})
//自定义校验规则,比如协议勾选项
//tongyi校验规则的名字
VeeValidate.Validator.extend('tongyi', {
    validate: value => {
        return value
    },
    getMessage: field => field + '必须同意'
})

main.js中引入

import ‘@/plugins/validate.js’; //表单验证

使用页面

//输入框
		<input
          type="text"
          placeholder="请输入你的手机号"
          v-model="phone"
          name="phone"
          v-validate="{required:true,regex:/^1\d{10}$/}"
        >
        <span class="error-msg">{{errors.first('phone')}}</span>
//密码框
		<input
          type="text"
          placeholder="请输入你的密码"
          v-model="password"
          name="password"
          v-validate="{required:true,regex:/^[0-9A-Za-z]{8,20}$/}"
        >
        <span class="error-msg">{{errors.first('password')}}</span>
//密码再次确认
		 <input
          type="text"
          placeholder="请输入确认你的密码"
          v-model="password1"
          name="password1"
          v-validate="{required:true,is:password}"
          :class="{invalid:errors.has('password1')}"
        >
        <!-- is:password 判断两次密码相同-->
        <span class="error-msg">{{errors.first('password1')}}</span>
//确认勾选框(需要自定义校验)
		 <input
          type="checkbox"
          v-model="agree"
          name="agree"
          v-validate="{required:true,'tongyi':true}"
          :class="{invalid:errors.has('agree')}"
        >
        <span>同意协议并注册《尚品汇用户协议》</span>
        <span class="error-msg">{{errors.first('agree')}}</span>

//都满足条件再发送请求
async userRegiser1() {
      const success = await this.$validator.validateAll(); //判断是否都满足条件
      if (success) {
        //都满足再发送请求
        try {
          this.phone &&
            this.code1 &&
            this.password == this.password1 &&
            (await this.userRegister({
              phone: this.phone,
              code: this.code1,
              password: this.password,
            }));
          this.$router.push("/login");
        } catch (error) {
          alert(error);
        }
      }
    },

后台管理系统模板github

简单版

复杂版

在这里插入代码片

v3+ts+scss 修改主题颜色

1.创建scss
在这里插入图片描述
2.编写scss

//theme.scss
//.el-header,.el-footer 的背景色
$background-main-color1:#3b69d6;    //背景色
$background-main-color2:#209F5C;    //背景色
$background-main-color3:#283444;    //背景色
$background-main-color4:#c73c27;    //背景色
//style.scss
//可以写颜色由于多个页面避免重复的样式编写
@import "./theme.scss";    //引入声明的皮肤文件

//初次进入调用
@mixin background-main-color($color){ //@mixin 后面的函数名称为自定义。
  background-color: $color;   //背景色默认为参数
  [background-main-color="background-main-color2"] & {    //如果条件成立,背景色则用$background-main-color2
    background-color: $background-main-color2;    //这个$background-main-color2已经在theme.scss中定义过了。
  }
  [background-main-color="background-main-color3"] & { 
    background-color: $background-main-color3;   
  }
  [background-main-color="background-main-color4"] & { 
    background-color: $background-main-color4;   
  }
} 

3.页面使用

<style scoped  lang="scss">
    .el-header,.el-footer{
        @include background-main-color($background-main-color1);
    }
    </style>

4.切换背景

defaultTheme(command){
if(command == "blue") {window.document.documentElement.setAttribute("background-main-color","background-main-color1");} 
else if(command == "green") {window.document.documentElement.setAttribute("background-main-color","background-main-color2");}
else if(command == "gray") {window.document.documentElement.setAttribute("background-main-color","background-main-color3");}
else { window.document.documentElement.setAttribute("background-main-color","background-main-color4"); }} 

重置路由方法

//router/index.js

const createRouter = () => new Router({
  scrollBehavior: () => ({ y: 0 }),
  routes: constantRoutes
})

const router = createRouter()//重置路由
export function resetRouter() {
  const newRouter = createRouter()
  router.matcher = newRouter.matcher // reset router
}

export default router

addRoute 动态添加路由

添加链接描述

router.beforeEach里面先判断是否添加过路由,然后再添加路由

import store from '@/store'
//这里我用vuex的一个变量 asyncRoutestMark 来标识是否拼接过路由
router.beforeEach((to, from, next) => {
    if (!store.state.asyncRoutestMark) {
        // navigationList 是上面模拟接口返回的数据
        // 这里将新的路由都作为 home 的子路由(实际开发根据情况)
        // meta 是存储一些信息,可以用于权限校验或其他
        navigationList.forEach( navigation => {
          router.addRoute('home', {
            path: navigation.url,
            meta: { name: navigation.name, isAsync: true, icon: navigation.icon },
            name: menu.url,
            component: () => import(`../views/${menu.url}`)
          })
        })
        console.log(router.getRoutes(), '查看现有路由')
        store.commit('setAsyncRoutestMark', true) // 添加路由后更改标识为true
        next({ ...to, replace: true })     //路由进行重定向放行
    } else {
      next()
    }
})
navigationList : [
     {
        id: 1,
        icon: 'icon-jurassic_user',
        name: '用户管理',
        url: '/user'
    },
     {
        id: 2,
        icon: 'icon-jurassic_user',
        name: '角色管理',
        url: '/role'
     },
     {
        id: 3,
        icon: 'icon-shebei',
        name: '设备管理',
        url: '/device'
      }
] 

v2侧边栏动态渲染

添加链接描述

//父组件
<template>
  <div class="bg">
    <div class="main">
      <el-menu
        background-color="#304156"
        @open="handleOpen"
        @close="handleClose"
        class="el-menu-vertical-demo"
        text-color="#BFCBD9"
        router
      >
        <SideBarItem :routes="routes"></SideBarItem>
      </el-menu>
    </div>
  </div>
</template>

<script>
import { routes } from "@/router/index";
import SideBarItem from "./SideBarItem.vue";
export default {
  name: "SideBar",
  data() {
    return {
      routes,
    };
  },
  methods: {
    clickMenu() {},
    handleOpen(key, keyPath) {
      console.log(key, keyPath);
    },
    handleClose(key, keyPath) {
      console.log(key, keyPath);
    },
  },
  components: {
    SideBarItem,
  },
};
</script>

<style lang="scss" scoped>
.el-menu-vertical-demo:not(.el-menu--collapse) {
  width: 200px;
  height: 100%;
  position: fixed;
  top: 50px;
  left: 0;
  bottom: 0;
}
</style>
//TreeItem.vue
//递归的子组件
<template>
  <div class="main">
    <template v-for="(item, index) in routes">
      <!-- 首页 -->
      <div :key="index" v-if="item.name === '1'">
        <el-menu-item :index="item.path">
          <i class="el-icon-menu"></i>
          <span slot="title">{{ item.meta.title }}</span>
        </el-menu-item>
      </div>
      <!-- 没有子路由 -->
      <div :key="index" v-if="!item.children">
        <el-menu-item :index="item.path">
          <i class="el-icon-menu"></i>
          <span slot="title">{{ item.meta.title }}</span>
        </el-menu-item>
      </div>
      <!-- 有子路由把home排除的 -->
      <div :key="index" v-if="item.children && item.name !== '1'">
        <el-submenu :index="item.path">
          <template slot="title">
            <i class="el-icon-menu"></i>
            <span>{{ item.meta.title }}</span>
          </template>
          <!-- 二级子路由遍历 -->
          <template v-for="child in item.children">
            <!-- 无子路由 -->
            <div :key="child.meta.title" v-if="!child.children">
              <el-menu-item :index="item.path + '/' + child.path">{{
                child.meta.title
              }}</el-menu-item>
            </div>
          </template>
        </el-submenu>
      </div>
    </template>
  </div>
</template>

<script>
export default {
  name: "SideBarItem",
  props: {
    routes: {
      type: Array,
      default: () => {},
    },
  },
  data() {
    return {};
  },
};
</script>

vue中使用element-resize-detector

init 初始化setOption()挂载

添加链接描述

这是一个用于监听DOM元素尺寸变化的插件。我们已经对窗口缩放做了监听,但是有时候其父级容器的大小也会动态改变的。
我们对父级容器的宽度进行监听,当父级容器的尺寸发生变化时,echart能调用自身的resize方法,保持视图正常。
当然,这个不适用于tab选项卡的情况,在tab选项卡中,父级容器从display:none到有实际的clientWidth,可能会比注册一个resizeDetector先完成,所以等开始监听父级容器resize的时候,可能为时已晚。
解决这个问题,最有效的方法还是在切换选项卡时手动去通过ref获取echart实例,并手动调用resize方法,这是最安全的,也是最有效的。

解决没有触发window.onresize的问题
在echart 页面大小发生改变,设置了resize方法,但是图表没有改变的时候使用
在这里插入图片描述

1.下载

npm install element-resize-detector --save

2.引入

import resizeDetector from 'element-resize-detector'

3.使用

 import resizeDetector from 'element-resize-detector'

export default {
  mounted() {
    this.chartResize()
  },
  methods: {
    chartResize() {
      let erd = resizeDetector()
      erd.listenTo(this.$el, () => {
        this.chart.resize()
        console.log('chart resize')
      })
    }
  }
}

    //要移除
    beforeDestroy () {
    window.removeEventListener('resize', this.chartResize)
  },

【vue】+【Echarts】+【element-resize-detector】通过自定义指令实现图表自适应

适配大屏幕的EChart

解决 他图表变大,但是字体太小的问题

1.使用移动端的方法求出字体大小(1080/12=>2160/24)

2.使用element-resize-detector对父级容器的宽度进行监听,当父级容器的尺寸发生变化时,echart能调用自身的resize方法,保持视图正常。

普通响应式echart
在有些场景下,我们希望当容器大小改变时,图表的大小也相应地改变。

比如,图表容器是一个高度为 400px、宽度为页面 100% 的节点,你希望在浏览器宽度改变的时候,始终保持图表宽度是页面的 100%。

这种情况下,可以监听页面的 window.onresize 事件获取浏览器大小改变的事件,然后调用 echartsInstance.resize 改变图表的大小。
<style>
  #main,
  html,
  body {
    width: 100%;
  }
  #main {
    height: 400px;
  }
</style>
<div id="main"></div>
<script type="text/javascript">
  var myChart = echarts.init(document.getElementById('main'));
  window.onresize = function() {
    myChart.resize();
  };
</script>

vue2封装三级下拉框联动

在这里插入图片描述

<template>
  <div>
    <el-form :inline="true" class="demo-form-inline" :model="cForm">
      <el-form-item label="一级分类">
        <el-select
          placeholder="请选择"
          v-model="cForm.coategory1Id"
          @change="handler1"
          :disable="show"
        >
          <el-option
            :label="c1.name"
            :value="c1.id"
            v-for="c1 in list1"
            :key="c1.id"
          ></el-option>
        </el-select>
      </el-form-item>
      <el-form-item label="二级分类">
        <el-select
          placeholder="请选择"
          :disable="show"
          v-model="cForm.coategory2Id"
          @change="handler2"
        >
          <el-option
            :label="c2.name"
            :value="c2.id"
            v-for="c2 in list2"
            :key="c2.id"
          ></el-option>
        </el-select>
      </el-form-item>
      <el-form-item label="三级分类">
        <el-select
          placeholder="请选择"
          :disable="show"
          v-model="cForm.coategory3Id"
          @change="handler3"
        >
          <el-option
            :label="c3.name"
            :value="c3.id"
            v-for="c3 in list3"
            :key="c3.id"
          ></el-option>
        </el-select>
      </el-form-item>
    </el-form>
  </div>
</template>

<script>
export default {
  name: "categorySelect",
  data() {
    return {
      //一级分类
      list1: [],
      list2: [],
      list3: [],
      cForm: {
        coategory1Id: "",
        coategory2Id: "",
        coategory3Id: "",
      },
    };
  },
  props: {
    show: {
      type: Boolean,
    },
  },
  mounted() {
    //获取一级分类数据的方法
    this.getCategory1List();
  },
  methods: {
    async getCategory1List() {
      let result = await this.$API.attr.reqCategory1List();
      if (result.code == 200) {
        this.list1 = result.data;
      }
    },
    async handler1() {
      //一级分类事件回调
      this.list2 = [];
      this.list3 = [];
      this.cForm.coategory2Id = "";
      this.cForm.coategory3Id = "";
      const { coategory1Id } = this.cForm;
      this.$emit("getCategoryId", { categoryId: coategory1Id, level: 1 });
      let result = await this.$API.attr.reqCategory2List(coategory1Id);
      if (result.code == 200) {
        this.list2 = result.data;
      }
    },
    async handler2() {
      //2级分类事件回调
      this.list3 = [];
      this.cForm.coategory3Id = "";
      const { coategory2Id } = this.cForm;
      this.$emit("getCategoryId", { categoryId: coategory2Id, level: 2 });
      let result = await this.$API.attr.reqCategory3List(coategory2Id);
      if (result.code == 200) {
        this.list3 = result.data;
      }
    },
    handler3() {
      const { coategory3Id } = this.cForm;
      this.$emit("getCategoryId", { categoryId: coategory3Id, level: 3 });
    },
  },
};
</script>
使用
      <category-select
        @getCategoryId="getCategoryId"
        :show="!isShowTable"
      ></category-select>

getCategoryId({ categoryId, level }){//id,第几级
}

输入input span切换,可以给obj的每一项添加一个属性

在这里插入图片描述
在这里插入图片描述

根据添加的flag属性判断是显示还是隐藏,后面提交数据的时候delete删除这个属性,

vue2中清空data对象,变为初始化

在这里插入图片描述

//表单提交完成后,清空数据
  Object.assign(this._data, this.$options.data());
//  this.$options.data()执行完成后,返回初始化data数据,赋值给data

Vue打开新页面

//方法一
handleRouterBlank(val) {
  let {href} = this.$router.resolve({
    name: val
  });
  window.open(href, '_blank');
},
//router-link添加 tag="a"
<router-link tag="a" target="_blank" :to="{name:'detail',query:{goodsId:'1111'}}">热门好货</router-link>
//给a标签动态设置href路径,然后主动触发click事件来跳转。

就是在页面添加一个a标签,款高设为零或者隐藏,只要看不到就行,设置好target属性为_blank,href设为空

<a class="target" href="" target="_blank"></a>
给目标元素绑定@click=“test”事件,当点击目标元素的时候触发:

test() {
        let target = this.$refs.target
        target.setAttribute('href', window.location.origin + '/home/integral-record')
        target.click()
      } 

vue3 deep 样式穿透 element-plugs

:deep(.el-form-item__content) {
  flex-wrap: nowrap;
}

vue3使用md5

npm install --save js-md5
npm i --save-dev @types/js-md5
//需要这么引入
const md5 = require("js-md5");
md5('hello')

分页删除/修改一条数据,留在当前页

在这里插入图片描述

<el-pagination
      :current-page="page"
      @current-change="getSkuList"
    >
    </el-pagination>
 data() {
    return {
      page: 1,
    };
  },
async getSkuList(pages = 1) {
      this.page = pages;
      //获取spu列表
      const { page, limit, category3Id } = this;
      let result = await this.$API.spu.reqSpuList(page, limit, category3Id);
    },
//修改数据
getSpuList()//如果不传值就用默认值1 ,请求第一页
getSpuList(2//传值就用
//如果是删除就需要判断,最后一页还有东西么,要是都没了,就请求上一页
 getSkuList(this.records.length>1?this.page:this.page-1);

vue 动态设置主题颜色,切换主题

element-ui更改主题

效果
<div data-theme="dark"></div>

在该节点的父节点切换data-theme属性,即可实现主题切换。

//新建 theme.scss 文件。
//主题参数
$themes: (
  default: (
    color: #000000,
    bg_color: #FFFFFF,
    border_color: #000000
  ),
  colorful: (
    color: #EE6666,
    bg_color: #9DD3E8,
    border_color: #879BD7
  )
);

//生成对应元素的主题样式代码
@mixin theme {
  @each $themes-key, $themes-map in $themes {
    $themes-map: $themes-map !global;
    [data-theme=#{$themes-key}] & {
      @content;
    }
  }
}

//获取对应的主题数据
@function t($key){
  @return map-get($themes-map, $key);
};

<template>
  <div class="container" :data-theme="theme">
    <p class="texture">
      <span @click="theme = theme == 'default'?'colorful':'default'">Hello World!</span>
    </p>
  </div>
</template>

<script>
export default {
  data() {
    return {
      theme: 'default'
    };
  }
}
</script>

<style lang="scss" scoped>
@import '@/assets/styles/theme.scss';
.texture{
  padding: 10rem 0 0;
  text-align: center;

  span{
    display: inline-block;
    padding: 10px;
    font-size: 40px;
    border-radius: 10px;
    cursor: pointer;
    transition: .5s all;

    @include theme{
      color: t('color');
      background-color: t('bg_color');
      border: 1px solid t('border_color');
    }
  }
}
</style>

element-ui tree组件没办法获取到父节点的id

需要修改源码

修改源码
不修改源码

tree 只获取到子节点的id

在这里插入图片描述

在这里插入图片描述

我只想获取9,10
this.$refs.tree.getCheckedKeys(true)//true只返回子节点的id

vue tree组件把有子属性所有的展开

在这里插入图片描述

//默认只展示2级,这样设置完全部都展示
 add(item) {
      for (let i = 0; i < item.length; i++) {
        if (item[i].children !== undefined) {
          this.node.push(item[i].id);
          this.add(item[i].children);
        }
      }
    },

vue tree 只能单选

<el-tree
      :data="data"
      show-checkbox
      node-key="id"
      ref="tree"
      :props="defaultProps"
      @check-change="handleCheckChange"
    >
    </el-tree>
  listQuery: {
        menuId: 0,
      },
 handleCheckChange(data, checked) {
      if (checked) {
        this.listQuery.menuId = data.id;
        this.$refs.tree.setCheckedKeys([data.id], true);
        console.log(this.listQuery.menuId);
      } else {
        this.listQuery.menuId = null;
      }
    },
  },

vue 刷新页面,实现滚动条还停留在上次访问的位置

vue用户在列表页进入详情之后,回来还在之前的浏览位置

使用keep-alive
方法1
方法2
使用scrollBehavior :区别主要是针对#app元素的,其他元素滚动就用自定义的吧

 {
          path: '/',
          name: 'home',
          component: Home,
          meta: {
            keepAlive: true // 需要缓存
          }
        }
<keep-alive>
    <router-view v-if="$route.meta.keepAlive"></router-view>
</keep-alive>
<router-view v-if="!$route.meta.keepAlive"></router-view>
//页面使用
data: {
    box: '',
    scrollY: ''
}

mounted: {
  // 监听scroll变化
   this.$nextTick(()=>{
        this.box = document.querySelector('.recordContent')
        this.box.addEventListener('scroll', function(){
          this.scrollY = document.querySelector('.recordContent').scrollTop
        }, false)
      })   
},
beforeRouteEnter (to, from, next) {
      next(vm => {
        //因为当钩子执行前,组件实例还没被创建
        // vm 就是当前组件的实例相当于上面的 this,所以在 next 方法里你就可以把 vm 当 this 来用了。
        console.log(vm);//当前组件的实例
        // 为div元素重新设置保存的scrollTop值
        document.querySelector('.recordContent').scrollTop = vm.scrollY 
      });
    },
    //记录离开时的位置
    beforeRouteLeave (to, from, next) { 
        //保存滚动条元素div的scrollTop值
        this.scrollY = document.querySelector('.recordContent').scrollTop 
      next()
    },

瀑布流的封装V3+ts

原文链接

富文本tinymce

原文

富文本simplemde-markdown-editor

github链接

富文本vue-quill-editor

添加链接描述

使用 el-upload 作为上传组件
默认情况下,此组件隐藏
点击 vue-quill-editor 中的图片按钮时,触发 el-upload 组件的单击事件,打开文件选择框
上传成功后,获取图片地址,插入到光标处

富文本wangeditor vue

Vue3使用
添加链接描述

//wangeditor自定义的图片上传设置
[添加链接描述](https://www.wangeditor.com/v5/getting-started.html)
  instance.config.uploadFileName = "file";//修改图片上传的属性名
   instance.config.uploadImgHooks = {//返回的参数数据自定义
        customInsert: function (insertImgFn: any, result: any) {
          // result 即服务端返回的接口
          console.log("customInsert", result);
          // insertImgFn 可把图片插入到编辑器,传入图片 src ,执行函数即可
          insertImgFn(result.data);
        },
      };

vue更改document.title 浏览器的 title 跟随路由的名称变化

添加链接描述

1.先设置一个公共文件setting.js
2.路由引入setting设置路由拦截beforEach,document.title=“xxx”
3.在 vue.config.js 中设置 name属性和 public/index.html中的title

vue2 使用国际化i18n

vue2使用i18n
elementui+i18n

下载@8的版本 “vue-i18n”: “^8.26.7”,
先创建一个一个文件夹存放语言的配置文件(inex.js,zh.js,en.js)
index.js中引入elementui的语言文件,和zh/en.js的文件,合并所有语言文件。设置默认语言(从本地存储中获取)
main.js中修改成Vue.use(Element,{i18n:})
页面中展示{{$ t(‘message’)}},切换语言环境this.$i18n.locale=‘zh’/‘en’
navigator.language;可以获取浏览器使用的语言环境
如果数据是动态获取的就把用到的全写上,用属性的方式 $ t(info.name)查找渲染

剪贴板功能vue2

npm install --save vue-clipboard2

在 main.js 中引入
import VueClipboard from 'vue-clipboard2'
Vue.use(VueClipboard)
<span>{{msg}}</span>
 
<img src="../../static/img/d1.png"
@click="handleFun"
v-clipboard:copy="msg" 
v-clipboard:success="copy" 
v-clipboard:error="onError">
data() {
    return {
        msg: ''
    }
},
methods: {
    copy(e) {
        console.log(e.text);
    },
    onError(e) {
        console.log(e);
    },
    handleFun () {
        this.msg = '8Z0W'
    }
}

下载base64的图片保存到本地vue2

借助canvas

//或者
<a download="bbbbb.jpg"  href="data:image/jpg;base64,iVBORw..." >下载</a>

vue 大文件上传,如何监听文件上传进度——切片上传(1)

如何监听上传进度,使用progress
1.先用input file进行上传,@change监听
2.e.target.files[0]拿到上传文件的name,size,type
3.对文件进行一个切割按照1兆,2M兆为一个基数,file.slice(start,start+基数)
4.循环上传每一片,使用创建new FromDate对象上传,fromDate.append(new File)传递每一片加索引值参数。更新propgress的百分比
如果是控制并发利用(async-pool分为es6/7版本),传递三个参数(并发数,任务数组)Promise.all 和 Promise.race 函数特点,再结合 ES7 中提供的 async await 特性,

请添加图片描述
请添加图片描述
请添加图片描述

文件上传(2)通过Blob实现大文件切片上传,通过async-pool控制并发限制

async-pool

vue2文件下载使用blob

下载有两种形式,一种是后台接口直接返回下载的路径,根据路径下载,另一种是后台接口返回流文件(一堆乱码,如下图所示)
在这里插入图片描述

 downloadFile(id) {
      this.downloading = true
      downloadVideo(id).then((res) => {
        // type是文件类,详情可以参阅blob文件类型
        let blob = new Blob([res], { type: 'video/mp4' })
        let objectUrl = URL.createObjectURL(blob)// 生成下载链接
        let a = document.createElement('a')// 创建a标签用于文件下载
        a.href = objectUrl// 赋值下载路径
        a.download = Math.random().toString(36).slice(-6) + '.mp4'// 下载的文件名称(非必填) 
        document.body.appendChild(a)// 插入DOM树
        a.click()// 点击a标签触发
        document.body.removeChild(a)// 删除a标签
        this.downloading = false
      })
    },
blobValue
blob表示二进制大对象,专门存放二进制数据 var blob = new Blob([“Hello World!”],{type:“text/plain”});
FormDataFormData我们可以异步上传一个二进制文件,而这个二进制文件,就是我们上面讲的Blob对象。

评论

websocket 发送和接收消息

重连和心跳

添加链接描述

vue-cli中使用腾讯TcPlayer播放器

vue 三级联动

页面初始请求第一个搜索框,监听chang事件获取下一个。
把三级联动封装成全局组件,

页面使用 <category-select  @getCategoryId="getCategoryId" ></category-select>
 getCategoryId({ categoryId, level }) {}//传回来一个对象categoryId是id,level 是第几层
//category-select  组件内部
  this.$emit("getCategoryId", { categoryId: coategory2Id, level: 2 });

echarts 数据堆叠

 {
            name: '视频广告',
            type: 'bar',
            stack: 'overlap1',//堆叠效果(字符需要统一)
            data: [150, 232, 201, 154, 190, 330, 410]
        },
        {
            name: '百度',
            type: 'bar',
            stack: 'overlap1',//堆叠效果(字符需要统一)
            data: [620, 732, 701, 734, 1090, 1130, 1120]
        }, 

在这里插入图片描述
在这里插入图片描述

由折线图可以看到,华夏的实际值为932,但在图表中的点值在1837左右,说明数据堆叠
去掉series中stack属性,或者将stack设置为不同的值

生成word文档

添加链接描述
请添加图片描述
请添加图片描述

element -UI el-dropdown 单独绑定@click无效

@click.native=“seeTable” 绑定

<el-dropdown>
          <span class="el-dropdown-link" ref="echarType">
           柱状图<i class="el-icon-arrow-down el-icon--right"></i>
          </span>
          <el-dropdown-menu slot="dropdown" >
            <el-dropdown-item>柱状图</el-dropdown-item>
            <el-dropdown-item @click.native="seeTable">表格</el-dropdown-item>
          </el-dropdown-menu>
        </el-dropdown>

element-ui v2密码输入框小眼睛

主要实现根据一个变量控制图标的不同和type=password和text的效果

添加链接描述

百度地图画出区域并获取区域坐标范围

在这里插入图片描述

添加链接描述

vue 复制功能,剪贴板

 copyCoordinate() {
      //点击经纬坐标复制
      navigator.clipboard
        .writeText(document.getElementById("coordinate").value)
        .then(() => {
          this.$message.success("复制成功");
        });
    },

百度地图问题BMap is not defined

在mounted初始化地图的时候,因为异步问题会导致BMap is not
defined,也就是百度的api还没完全引入或者加载完成,就已经进行地图初始化了

添加链接描述

图片保存在阿里ali-oss

创建一个js文件
在这里插入图片描述
使用的时候引入

import { client, getFileNameUUID } from "../../../../static/js/ali-oss.js"; //前面的ali-js文件内的两个封装函数

echarts 数值接近怎么让他更明显

在这里插入图片描述

 yAxis: {
      type: 'value',
      scale: true//这个控制
  },

小程序引入vr链接

使用 webview

swiper 下面的图片存在加载慢的问题

如果swiper 小程序图片过于多,有可能存在底下图片加载缓慢

导出按钮导出文件

 exportBtn() {
      this.axios
        .post(
          this.websiteUrl2 +
            "/pcbackend/web/api/SpecialInspectionExport/exportSpecialInspectionList",
          this.taizhang_canshu
        )
        .then(res => {
          if (res.data.code == 200) {
            this.exportHref = res.data.data;
            this.downloadImg();
          }
        })
        .catch(function(error) {
          console.log(error, "直接走error,");
        });
    },
    //导出下载地址
    downloadImg() {
      let alink = document.createElement("a");
      alink.href = this.exportHref;
      alink.download = "teamplte"; //文件名
      alink.click();
    },

echart 节点点击事件

mychartsale.on('click', function (param) {
            console.log(param);//这里根据param填写你的跳转逻辑
            }

背景图片居中

 background: #000 url('../images/banner-1.jpg') no-repeat;
    width: 100%;
    height: 600px;
    background-position: center 0;
    display: block;
    position: relative;
    z-index: 10;
    top: -47px;
    overflow: hidden;

element -plugs checkbox 点击2次问题

  if (e.target.tagName === "INPUT") return;

element -plugs message被el-dialog遮住

给el-dialog设置属性,官网有,给他设置一个class类名然后全局设置类名的z-index
或者不能有scope
Logo

前往低代码交流专区

更多推荐