vue

  • vue 首页加载空白页
  • 使用vue骨架屏
    • 使用骨架屏插件
  • vue 大数据卡顿
  • vue 样式初始化normalize.css
  • token过期,无感知刷新
  • token过期后,Token 刷新并发处理解决方案
  • 登录鉴权
  • 扫码支付
  • 手机号短信验证码登录
  • 扫码登录
  • 微信授权登录(pc)
  • 微信授权登录(H5)
  • 手机号登录
  • 生成二维码 qrcode插件
  • vue 样式格式化
  • 两个日期相差多少天
  • v-if和else button一直高亮问题
  • post传值问题
  • 关闭语法检查 lintOnSave:false,
  • vue-router路由页面不刷新的解决办法
  • fullPath获取完整路由,重新渲染
  • 组件刷新
    • 使用v-if【一般的方式】
    • forceUpdate【强制刷新】
    • :key=“”【key属性】
  • 页面刷新
  • vue3 页面组件刷新使用if+provide+inject
  • V2 vant 全局使用Toast
  • V3 vant 全局使用Toast
  • 静态资源路径问题
  • img动态路径
  • 普通js文件引入vue实例,调用vue上的$message等方法
  • 简单上下按钮分页效果
  • vue更新数据却不渲染页面解决方案
    • 1.使用set设置数组
    • 2. this.$forceUpdate()
    • 3.路由参数变化时,页面不更新,本质上就是数据没有更新
    • 4.在异步更新执行之前操作DOM数据不会变化
    • 5.获取后台返回的数组进行排序处理了,页面内容却不排序
    • 6.vue修改数据后dom的更新异步的,解决setTimeout
  • v3 reactive 请求回来的数据进行赋值
  • vue同时给一个元素绑定单机和双击事件
  • input默认获取焦点 二次点击获取不到焦点
    • 只用获取一次焦点使用autofocus
  • 拖拽元素换位置tabel不用插件
  • vue使用vue.draggable
  • vscode vue2的一些扩展
  • vscode vue3的扩展
    • vue报错:“TypeScript intellisense is disabled on template.” 解决
  • vue里面使用swiper
    • 1.下载
    • 2.引入
    • 另一种引入
    • 2.1下载
    • 2.2 注册引入
    • 2.3 页面结构
    • swiper刷新后不起效果
    • 异步加载数据,存在延时的问题,使用watch来监听,并且this.$nextTick()
  • vue使用element-ui
    • 使用message
    • 使用loading
    • element 表单校验
    • input 表单验证联动效果
  • vue 日历插件
  • vue 购物车逻辑
  • vue 使用节流防抖
    • utils下放文件
    • 页面使用
  • input输入提示词防抖
  • vue 配置移动端
  • vue 使用better-scroll
    • 1.下载
    • 2.使用
    • ajax 动态获取数据
    • 左右联动
  • 起一个node服务器
  • proxy 跨域
  • axios的封装1
    • 1.创建文件夹request
    • api.js
    • http.js
    • 页面使用
  • axios的封装2
  • axios 连续多次请求同一接口,取消上一次请求
  • v3 封装axios
    • axios 中的一个属性‘cancelToken’
  • 路由/->/home默认打开首页
  • 路由嵌套一级路由高亮显示错误
  • v2路由页面左边出来平移跳转
  • v3路由页面左边出来平移跳转
  • svg 圆形进度条
  • cookie登录
  • npm run serve之后自动打开
  • 关闭eslint校验
  • 根据路由是否呈现footer
  • 编程式导航路由参数不变,多次点击会报错
  • 当前页面跳转,去掉query/pramas参数
  • 一分钟倒计时验证码
  • (v2)增删改查 列表使用input修改
  • (v2)使用v-model循环增删改查
  • v2 table span和input输入切换
  • (v3)增删改查 input+span
  • vue使用nprogress进度条
  • vue使用mockjs拦截请求
    • mock用到的图片需要放到poblic文件夹下
  • vue使用json-server
    • 起服务
  • vuex刷新数据会初始化
    • 1.保存在本地存储
    • 2.从新请求数据
  • vue ssr服务器端渲染
    • 实现
  • 打包空白页问题
  • 三级路由跳转优化
  • 切换路由列表重复加载问题
  • 合并路由跳转的params,query参数
  • 路由登录跳转之前点击的页面
  • 判断是否是从指定页面过来,否则停留在当前页面
  • vue使用懒加载lazyload
  • 数字增加效果组件requestAnimationFrame
  • 使用vue-animate-number实现数据动态改变
  • 父组件异步数据通过props方式传递给子组件,子组件接收不到的问题
    • 1. 使用v-if控制子组件渲染的时机
    • 2.子组件使用watch监听父组件传递过来的数据
  • 兄弟之间组件传递对象数据,深拷贝浅拷贝问题
  • vuex 修改对象数据,深浅拷贝监听问题
  • keep-alive路由切换,点击过的数据就不从新加载,每点击过再从新获取
  • 当前页面下,监听路由的变化,再次发送请求
  • 项目登录流程
    • 路由前置守卫
  • 退出登录流程
  • 登录token 刷新问题
  • 路由设置是否登录,登录就跳转,没有就跳转登录,登录鉴权
  • 后台路由鉴权,权限
  • 路由点击页面权限校验
  • 按钮鉴权自定义指令
  • 在子组件标签上添加click事件不生效.native
  • v2封装分页器
  • 路由拆分到一个单独js文件
  • 无法找到模块“...”的声明文件。“..”隐式拥有 "any" 类型。
  • 要关闭any类型的警告
  • vue使用Day.js库
  • vue ajax 组件,Vue.js中ajax请求代码应该写在组件的methods中还是vuex的actions中?
  • 页面try,catch接收 vuex 返回的请求状态
  • vue3中reactive定义的引用类型直接赋值导致数据失去响应式
  • 如何修改props传进来的值
    • 方法一:v-model的另一种写法
    • 方法二:使用官方使用的计算属性
    • 方法三 使用emit
    • 方法四 v-model
  • 单例 生成全局唯一uuid,存到store里面
  • v3使用@路径符(不全)
  • vue3 封装axios ts+api.js+vite
  • 配置跨域vite+ts+v3
  • 过渡效果列表增加删除换位置,有过渡效果
  • v3 element plus 使用messagebox
  • v3 element plus 引入icon vite
  • promise.all 循环请求多个次
  • 后台管理系统路由面包屑+tags删除添加
  • vue3文件上传下载
  • 使用vee-validate vue表单验证
    • 1.创建文件
    • main.js中引入
    • 使用页面
  • 后台管理系统模板github
  • v3+ts+scss 修改主题颜色
  • 重置路由方法
  • addRoute 动态添加路由
  • v2侧边栏动态渲染
  • vue中使用element-resize-detector
    • 1.下载
    • 2.引入
    • 3.使用
  • 适配大屏幕的EChart
  • vue2封装三级下拉框联动
  • 输入input span切换,可以给obj的每一项添加一个属性
  • vue2中清空data对象,变为初始化
  • Vue打开新页面
  • vue3 deep 样式穿透 element-plugs
  • vue3使用md5
  • 分页删除/修改一条数据,留在当前页
  • vue 动态设置主题颜色,切换主题
  • element-ui tree组件没办法获取到父节点的id
    • tree 只获取到子节点的id
    • vue tree组件把有子属性所有的展开
    • vue tree 只能单选
  • vue 刷新页面,实现滚动条还停留在上次访问的位置
  • vue用户在列表页进入详情之后,回来还在之前的浏览位置
  • 瀑布流的封装V3+ts
  • 富文本tinymce
  • 富文本simplemde-markdown-editor
  • 富文本vue-quill-editor
  • 富文本wangeditor vue
  • vue更改document.title 浏览器的 title 跟随路由的名称变化
  • vue2 使用国际化i18n
  • 剪贴板功能vue2
  • 下载base64的图片保存到本地vue2
  • vue 大文件上传,如何监听文件上传进度——切片上传(1)
  • 文件上传(2)通过Blob实现大文件切片上传,通过async-pool控制并发限制
  • vue2文件下载使用blob
  • 评论
  • websocket 发送和接收消息
  • vue-cli中使用腾讯TcPlayer播放器
  • vue 三级联动
  • echarts 数据堆叠
  • 生成word文档
  • element -UI el-dropdown 单独绑定@click无效
  • element-ui v2密码输入框小眼睛
  • 百度地图画出区域并获取区域坐标范围
  • vue 复制功能,剪贴板
  • 百度地图问题BMap is not defined
  • 图片保存在阿里ali-oss
  • echarts 数值接近怎么让他更明显
  • 小程序引入vr链接
  • swiper 下面的图片存在加载慢的问题
  • 导出按钮导出文件
  • echart 节点点击事件
  • 背景图片居中
  • element -plugs checkbox 点击2次问题
  • element -plugs message被el-dialog遮住

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的请求接口的方法 这个是自定义的 看你们自己的项目来定,要用原生的axiosreFreshToken().then(result => {isFreshToken = true // 方法重新打开let token = result.token; // 拿到新的token// 重新赋值tokenlocalStorage.setItem('token', token);// 重新执行 之前缓存的方法数组 使用最新的tokencasheRequests.forEach(cb => cb(token))// 重置为空 降缓存的请求方法casheRequests = []// 然后重新执行本次的方法config.headers['token']= token // 使用最新的tokenreturn request(config); })//} else {// token 正在刷新中 其他的请求先放在队列中return new Promise(resolve => {// 将resolve放进队列,用一个函数形式来保存,等token刷新后直接执行casheRequests.push((token) => {config.headers['token']= tokenresolve(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,geturl:'/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 DOMthis.renderComponent = false;this.$nextTick(() => {// Add the component back inthis.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-menuv-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<imgstyle="width:100%;height:100%;":src="require('../'+item.imgSrc)"/>图片存放在assets/banner/banner.jpgimgs: [{id: '01',imgSrc: 'assets/banner/banner1.jpg',}, {id: '02',imgSrc: 'assets/banner/banner2.png'}, {id: '03',imgSrc: 'assets/banner/banner3.jpg'}]

方法2

<imgstyle="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 = truesetTimeout(() => {if (this.flag) {console.log(1);}}, 300);},b() {this.flag = falseconsole.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-formv-loading="loading":model="formData"//:rules="rules"//:ref="ruleForm"//><el-form-itemlabel="设备名称"prop="name"//><el-input v-model="formData.name"></el-input></el-form-item><el-form-itemlabel="设备分类"prop="category"//><el-select v-model="formData.category"><el-optionv-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><liv-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-周6year年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.isAllCheckedthis.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.numprice+=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){//节流throttlelet canRun = true; // 通过闭包保存一个标记return function () {if (!canRun) return;//在delay时间内,直接返回,不执行fncanRun = false;setTimeout(() => {fn.apply(this, arguments);canRun = true;//直到执行完fn,也就是delay时间后,打开开关,可以执行下一个fn}, delay );};
}
function debounce(func, delay = 300, immediate = false) {//防抖debouncelet timer = nullreturn 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.lengththis.$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){//获取htmlvar docEl=doc.documentElement,//用于获取事件//in window 用于查询window的一些事件和方法是否存在orientationchange屏幕翻转resizeEvt="orientationchange" in window?"orientationchange":"resize";function fun(){var w=docEl.clientWidth;if(!w) returnif(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.jsonautoprefixer: {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(() => {//设置scrollthis.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=indexthis.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                    });                    // 清除tokenlocalStorage.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,有就每次请求携带tokenconfig.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) {//设置CancelTokencancel = 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('/').lengthconst fromPath=from.path.split('/').lengththis.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
//关闭eslintlintOnSave:false

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

# 忽略目录
build/
tests/
node_modules/
src/micpo# 忽略文件
**/*-min.js
**/*.min.jsiconfont.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-inputplaceholder="请输入内容"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><inputtype="text"v-model="inp"><button @click="add">添加</button><ul><list-itemv-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><inputv-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-gitv-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><HelloWorldv-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和123if (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()  用于将对象转为 JSONJSON.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.根标签<divclass="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,携带pramaslet 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.pathif (toPath.indexOf('/trade') == -1 || toPath.indexOf('/pay') != -1 || toPath.indexOf('/center') !== -1) {//都没登录进入loginnext('/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.idthis.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.idthis.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//tokenlet 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已经失效 就登出 并且回到loginstore.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()}
})
//登录.vuemethods: {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><buttonv-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) // 20214000000date1.diff(date2, 'month') // 7date1.diff(date2, 'month', true) // 7.645161290322581date1.diff(date2, 'day') // 233//3.一个时间在另一个时间之前dayjs().isBefore(Dayjs, unit? : String);dayjs().isBefore(dayjs()); // falsedayjs().isBefore(dayjs(), 'year'); // false//4、一个时间是否等于另一个时间dayjs().isSame(Dayjs, unit? : String);dayjs().isSame(dayjs()); // truedayjs().isSame(dayjs(), 'year'); // true//5、一个时间在另一个时间之后。dayjs().isAfter(Dayjs, unit? : String);dayjs().isAfter(dayjs()); // falsedayjs().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返回一个promisetry {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.tsimport 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.statusconsole.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 + 1function 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 循环请求多个次

//storedeleteCartListBySkuId(){}//删除单个//实现删除多个,需要多次调用删除单个的函数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代码.

//storerouterArr: ["/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><inputref="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’; //表单验证

使用页面

//输入框<inputtype="text"placeholder="请输入你的手机号"v-model="phone"name="phone"v-validate="{required:true,regex:/^1\d{10}$/}"><span class="error-msg">{{errors.first('phone')}}</span>
//密码框<inputtype="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>
//密码再次确认<inputtype="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>
//确认勾选框(需要自定义校验)<inputtype="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-color2background-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.jsconst 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) // 添加路由后更改标识为truenext({ ...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-menubackground-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-selectplaceholder="请选择"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-selectplaceholder="请选择":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-selectplaceholder="请选择":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.targettarget.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-checkboxnode-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 = truedownloadVideo(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})},
blob Value
blob 表示二进制大对象,专门存放二进制数据 var blob = new Blob([“Hello World!”],{type:“text/plain”});
FormData FormData我们可以异步上传一个二进制文件,而这个二进制文件,就是我们上面讲的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

vue遇到的小问题,封装功能相关推荐

  1. vue 开发微信小程序定位功能

    边做边总结系列: 小程序里获取个人定位,如果没有在app.json文件里配置小程序接口权限:permission,会提示↓ 配置信息图下: 贴代码: "permission": { ...

  2. vue、uniapp 小程序点击事件禁止多次点击方法封装

    vue.uniapp 小程序点击事件禁止多次点击方法封装 在写uniapp 小程序时发现个bug,疯狂点击按钮第一点击事件还没执行完就疯狂多次调用该事件,很是影响用户体验以及消耗性能,所以这里我封装个 ...

  3. 前端知识总结汇总!(HTML、CSS、JS、jQuery、vue、微信小程序)

    前端知识总结汇总!(HTML.CSS.JS.jQuery.vue.微信小程序) 前端理论考核题 1 / HTML 1.DOCTYPE 的作用是什么?标准模式与兼容模式各有什么区别? !DOCTYPE是 ...

  4. vue uniapp 微信小程序 搜索下拉框 模糊搜索

    vue uniapp 微信小程序 搜索下拉框 模糊搜索 话不多说 直接贴代码 template <template><view class="index"> ...

  5. uniapp 小程序封装左滑效果组件

    uniapp 小程序封装左滑效果组件 引言 封装组件 页面使用 注意事项 引言 小程序电商项目购物车,往往都会有左滑删除功能,在不想使用插件的前提下,就需要自己编写,因此我个人写了一个左滑效果组件 封 ...

  6. vue中手写一个放大镜功能

    vue中手写一个放大镜功能 有的时候需要对图片进行放大,类似于电商的商品放大功能,于是在这个想法上写了一个放大镜的功能,并且在放大镜的基础上新添加了一些小功能,下面开始吧! 放大镜是封装的组件的形式, ...

  7. springboot+vue练手小项目[前台搭建+后台编写](非常详细)

    [ springboot+vue练手小项目 ] 技术栈: springboot+vue3+element-plus +Mybaties-plus+hutool +mysql8 项目介绍 :最近刚学了s ...

  8. 用php做一个简单的汇率,vue实现简单实时汇率计算功能

    最近在自己摸索vue的使用,因为相对于只是去看教程和实例,感觉不如自己动手写一个demo入门来的快.刚好看到小程序中有一个简单但是很精致的应用极简汇率,而且它的表现形式和vue的表现形式很像,于是想着 ...

  9. [vue] vue和微信小程序写法上有什么区别?

    [vue] vue和微信小程序写法上有什么区别?写了vue项目和小程序,发现二者有许多相同之处,在此想总结一下二者的共同点和区别. 一.生命周期 先贴两张图: vue生命周期 小程序生命周期 相比之下 ...

  10. 微信小程序和vue双向绑定哪里不一样_浅析Vue 和微信小程序的区别、比较

    写了vue项目和小程序,发现二者有许多相同之处,在此想总结一下二者的共同点和区别. 一.生命周期 先贴两张图: vue生命周期 小程序生命周期 相比之下,小程序的钩子函数要简单得多. vue的钩子函数 ...

最新文章

  1. 在OpenCV中使用单应性进行姿势估计
  2. python爬虫程序说明_Python即时网络爬虫:API说明
  3. Android系统编译so库提示error undefined reference to '__android_log_print问题的解决
  4. 基于WINCE6.0+S3C6410通过USB下载stepldr
  5. 电脑wifi不见了_大家好,我是来给你家 WiFi 提速的
  6. SAP Fiori里Contact Support的按钮渲染逻辑
  7. 转: ADO Connection Strings
  8. 关于CMS垃圾回收器的几个问题
  9. 帕特·基辛格被任命为英特尔CEO已有一年
  10. 主内存和工作内存交互
  11. 输入广义表建立子女兄弟链表示的树
  12. formidable模块的使用
  13. vi 打开文件,行末尾有^M
  14. windows自带录屏_电脑版免费的录屏软件有哪些?
  15. 阿里云域名怎么注册和使用(新手教程)
  16. 【计量经济学】工具变量估计与两阶段最小二乘法
  17. 优动漫PAINT提高创作效率的小技巧——中间色与近似色
  18. Python使用for实现无限循环的多种方法
  19. elementUI的表格标题换行
  20. 前端----let关键字、const关键字

热门文章

  1. 家用洗地扫地机一体机哪家好、家用小型洗地机推荐
  2. LeGO-LOAM资料整理
  3. linux r的数据是存在,R语言通过loess去除某个变量对数据的影响
  4. 【经验分享】BMPR文件及其打开软件Balsamiq Wireframes的下载和安装
  5. 苹果M1 Mac 如何卸载 iPhone 和 iPad 应用程序?
  6. SpaceDesk PC版+安卓版(安装包下载)
  7. 22. 地下城与勇士
  8. win7系统屏幕不休眠,怎么设置
  9. 8.17 一个博客demo
  10. 下载喜马拉雅FM的音频