<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>清哥的小屋</title><description>一个菜鸟的成长之路</description><link>https://qingge-cabana.vercel.app/</link><language>zh_CN</language><item><title>前端工程师养成计划：从入门到面试，一站式学习资源全导航</title><link>https://qingge-cabana.vercel.app/posts/%E5%89%8D%E7%AB%AF%E5%B7%A5%E7%A8%8B%E5%B8%88%E5%85%BB%E6%88%90%E8%AE%A1%E5%88%92%E4%BB%8E%E5%85%A5%E9%97%A8%E5%88%B0%E9%9D%A2%E8%AF%95%E4%B8%80%E7%AB%99%E5%BC%8F%E5%AD%A6%E4%B9%A0%E8%B5%84%E6%BA%90%E5%85%A8%E5%AF%BC%E8%88%AA-151833021/</link><guid isPermaLink="true">https://qingge-cabana.vercel.app/posts/%E5%89%8D%E7%AB%AF%E5%B7%A5%E7%A8%8B%E5%B8%88%E5%85%BB%E6%88%90%E8%AE%A1%E5%88%92%E4%BB%8E%E5%85%A5%E9%97%A8%E5%88%B0%E9%9D%A2%E8%AF%95%E4%B8%80%E7%AB%99%E5%BC%8F%E5%AD%A6%E4%B9%A0%E8%B5%84%E6%BA%90%E5%85%A8%E5%AF%BC%E8%88%AA-151833021/</guid><description>前端工程师养成计划：从环境搭建、三件套基础到框架、工具、AI 提效与面试，一站式学习资源导航。</description><pubDate>Thu, 18 Sep 2025 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;本文同步自 &lt;a href=&quot;https://blog.csdn.net/weixin_44980732/article/details/151833021&quot;&gt;CSDN · 清阿哥&lt;/a&gt;，原文发布于 2025-09-18。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;前端工程师养成计划：从入门到面试，一站式学习资源全导航&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;写在前面&lt;/strong&gt;：作为一名前端开发者，我深知基础知识（HTML、CSS、JavaScript）的重要性。框架可以速成，但底层基础决定了你的天花板。这份手册是我结合多年经验整理的学习路径和资源库，希望能为你的前端之旅保驾护航。星光不问赶路人，时光不负有心人，加油！&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr /&gt;
&lt;h3&gt;一、 学前准备：环境搭建&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Node.js 与版本管理 (nvm/n)&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://blog.csdn.net/weixin_45811256/article/details/130860444&quot;&gt;Windows 系统 nvm &amp;amp; nrm 安装配置详解&lt;/a&gt; - 详解如何在 Windows 上管理多个 Node.js 版本。&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://juejin.cn/post/7065534944101007391&quot;&gt;Mac 系统 Node.js 管理工具 &lt;code&gt;n&lt;/code&gt; 使用指南&lt;/a&gt; - Mac 用户优雅管理 Node.js 版本的工具。&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://npmmirror.com/mirrors/node/&quot;&gt;Node.js 安装包镜像地址&lt;/a&gt; - 从国内镜像站快速下载 Node.js。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;二、 基础入门：核心三件套&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;综合学习平台&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.w3school.com.cn/index.html&quot;&gt;W3School&lt;/a&gt; - 经典的入门教程，适合快速查阅和练习。&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.mozilla.org/zh-CN/docs/Web&quot;&gt;MDN Web Docs&lt;/a&gt; - 最权威的 Web 技术文档，前端开发者的圣经。&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://alidocs.dingtalk.com/i/nodes/vy20BglGWOekXn2YU16AQRz0JA7depqY?utm_scene=team_space&quot;&gt;《前端开发规范》&lt;/a&gt; - 了解良好的代码规范和团队协作习惯。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;HTML&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.ruanyifeng.com/blog/2009/05/guide_to_semantic_html_elements.html&quot;&gt;HTML 元素语义化指南（阮一峰）&lt;/a&gt; - 写出更专业、更利于SEO的HTML。&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.ruanyifeng.com/blog/2009/05/html_table_mastering.html&quot;&gt;精通 HTML 表格（阮一峰）&lt;/a&gt; - 深度讲解表格的各种用法。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;CSS&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;布局&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.ruanyifeng.com/blog/2015/07/flex-grammar.html&quot;&gt;Flex 布局教程：语法篇（阮一峰）&lt;/a&gt; - 理解 Flex 布局的核心概念。&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.ruanyifeng.com/blog/2015/07/flex-examples.html&quot;&gt;Flex 布局教程：实例篇（阮一峰）&lt;/a&gt; - 通过经典案例掌握 Flex 布局。&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.ruanyifeng.com/blog/2020/08/five-css-layouts-in-one-line.html&quot;&gt;一行代码实现五种经典 CSS 布局&lt;/a&gt; - 体验现代 CSS 的强大。&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://juejin.cn/post/7208484366955085883&quot;&gt;Grid 布局教程&lt;/a&gt; - 迈向二维布局自由。&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://juejin.cn/post/6844903634849759239&quot;&gt;全面 CSS 布局学习指南&lt;/a&gt; - 汇总所有布局技术。&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://alidocs.dingtalk.com/i/nodes/KGZLxjv9VG34Qbqvt5D5dE60V6EDybno?utm_scene=person_space&quot;&gt;常用布局实战练习&lt;/a&gt; - 通过模仿巩固布局技能。
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://alidocs.dingtalk.com/api/doc/transit?dentryUuid=MNDoBb60VLrMknQefY69GdDR8lemrZQ3&quot;&gt;《登录页》实战&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://alidocs.dingtalk.com/api/doc/transit?dentryUuid=kDnRL6jAJM3jPrGetygjQ9PQWyMoPYe1&quot;&gt;《首页-小米商城》实战&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;特性与技巧&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.runoob.com/w3cnote/css-style-priority.html&quot;&gt;CSS 选择器及优先级详解&lt;/a&gt; - 必须掌握的底层规则。&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://mp.weixin.qq.com/s?__biz=MzI0MzIyMDM5Ng==&amp;amp;mid=2649827921&amp;amp;idx=1&amp;amp;sn=70e5aa140be1cff1e3f0561df0a041eb&quot;&gt;不常见但实用的 CSS 属性&lt;/a&gt; - 挖掘 CSS 的潜力。&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://juejin.cn/post/6844904176141467655&quot;&gt;你不知道的 CSS 技巧&lt;/a&gt; - 提升开发效率的奇技淫巧。&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://csscoco.com/inspiration/#/./init&quot;&gt;CSS Inspiration&lt;/a&gt; - 在这里找到写 CSS 的灵感！&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.zhangxinxu.com/wordpress/&quot;&gt;张鑫旭的个人博客&lt;/a&gt; - 国内 CSS 大佬，有很多深度文章。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;动画与效果库&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://animate.style/&quot;&gt;Animate.css&lt;/a&gt; - 强大的即用型 CSS 动画库。&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://ianlunn.github.io/Hover/&quot;&gt;Hover.css&lt;/a&gt; - 专为悬停效果设计的动画库。&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://animista.net/play&quot;&gt;Animista&lt;/a&gt; - 在线预览、定制并生成 CSS 动画代码。&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://animatedbackgrounds.me/&quot;&gt;Animated Backgrounds&lt;/a&gt; - 炫酷的 CSS 背景动画集合。&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://tobiasahlin.com/spinkit/&quot;&gt;SpinKit&lt;/a&gt; - 轻量级 CSS 加载动画（Loading Spinners）。&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://css-loaders.com/dots/&quot;&gt;CSS Loaders&lt;/a&gt; - 又一个可爱的加载动画库。&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://guowc.github.io/animastore/&quot;&gt;guowc-基础动画库&lt;/a&gt; - 个人开发的动画集合。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;JavaScript&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://javascript.ruanyifeng.com/&quot;&gt;JavaScript 标准参考教程（阮一峰）&lt;/a&gt; - 经典的 JS 教程。&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://zh.javascript.info/&quot;&gt;现代 JavaScript 教程&lt;/a&gt; - 非常系统且现代的教程，强烈推荐。&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://es6.ruanyifeng.com/&quot;&gt;ES6 入门教程（阮一峰）&lt;/a&gt; - &lt;strong&gt;重点学习&lt;/strong&gt;，现代 JS 开发的基石。&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://juejin.cn/post/6844904166192578567&quot;&gt;JS 底层原理：76张脑图&lt;/a&gt; - 可视化地理解 JS 核心知识。&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://javascript.ruanyifeng.com/dom/event-type.html#toc0&quot;&gt;HTML DOM 事件&lt;/a&gt; - 深入理解事件机制。&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.runoob.com/jsref/dom-obj-event.html&quot;&gt;DOM 事件大全&lt;/a&gt; - 便捷的查询手册。&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.liaoxuefeng.com/wiki/1022910821149312&quot;&gt;廖雪峰的 JS 教程&lt;/a&gt; - 另一份优秀的中文教程。&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://juejin.cn/post/7371312967781777418&quot;&gt;N个实用的 JS 代码片段&lt;/a&gt; - 收集来用的“工具包”。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;正则表达式&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://any86.github.io/any-rule/&quot;&gt;Any-rule&lt;/a&gt; - 最常用的正则表达式大全，支持可视化生成。&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.cnblogs.com/zxin/archive/2013/01/26/2877765.html&quot;&gt;常用正则大全&lt;/a&gt; - 博客园的一份详细整理。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;TypeScript&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://ts.xcatliu.com/&quot;&gt;TypeScript 入门教程&lt;/a&gt; - 从 JavaScript 程序员的角度理解 TS。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;三、 框架与工程化&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Vue.js&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;官方文档&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://v2.cn.vuejs.org/v2/guide/&quot;&gt;Vue 2&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://cn.vuejs.org/guide/introduction.html&quot;&gt;Vue 3&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;源码学习&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://ustbhuangyi.github.io/vue-analysis/&quot;&gt;Vue.js 技术揭秘&lt;/a&gt; - 非常好的 Vue 2/3 源码解析。&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://vue-js.com/learn-vue/&quot;&gt;Vue.js 源码解析&lt;/a&gt; - 逐行分析 Vue 2 源码。&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/answershuto/learnVue&quot;&gt;learnVue (GitHub)&lt;/a&gt; - 源码分析文章集合。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;生态与资源&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://vue3js.cn/&quot;&gt;Vue3 One Piece&lt;/a&gt; - Vue 3 生态资源集合站。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;优秀开源项目（学习与参考）&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/PanJiaChen/vue-element-admin&quot;&gt;vue-element-admin&lt;/a&gt; - 基于 Vue 2 + Element UI 的后台管理系统典范。&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://doc.ruoyi.vip/&quot;&gt;RuoYi-Vue&lt;/a&gt; - 基于 Vue 2/3 的权限管理系统，Java后端。&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/macrozheng/mall&quot;&gt;mall&lt;/a&gt; - 全套电商系统，学习项目架构。&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://doc.vvbin.cn/&quot;&gt;Vben Admin&lt;/a&gt; - 现代 Vue 3 + Vite + Ant-Design-Vue 技术栈。&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/pure-admin/vue-pure-admin&quot;&gt;vue-pure-admin&lt;/a&gt; - 简洁高效的 Vue 3 + Vite + Element-Plus 解决方案。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Git 版本控制&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://alidocs.dingtalk.com/i/nodes/14lgGw3P8vvezdRZiA6rBP6d85daZ90D?utm_scene=team_space&quot;&gt;《前端 Git Commit 规范》&lt;/a&gt; - 养成提交好习惯。&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://alidocs.dingtalk.com/api/doc/transit?dentryUuid=Z0LYK27vwxp80j1rbOn0Vo5Olb4md9eP&quot;&gt;《Git 分支管理规范》&lt;/a&gt; - 了解团队协作流程。&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://note.youdao.com/s/7QhegMht&quot;&gt;Git 使用总结&lt;/a&gt; - 个人整理的常用命令。&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://learngitbranching.js.org/?locale=zh_CN&quot;&gt;Learn Git Branching&lt;/a&gt; - &lt;strong&gt;强烈推荐&lt;/strong&gt;，图形化交互式学习 Git，趣味十足！&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://blog.csdn.net/sunyctf/article/details/130587970&quot;&gt;Git Flow 工作流详解&lt;/a&gt; - 一种经典的分支管理模型。&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.git-tower.com/learn/git/ebook/cn/command-line/advanced-topics/git-flow&quot;&gt;Git Flow 工作流学习&lt;/a&gt; - 另一份不错的教程。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;四、 学习平台与社区&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;日常学习与交流&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://juejin.im/timeline&quot;&gt;掘金&lt;/a&gt; - 国内高质量技术社区，干货多。&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://segmentfault.com/questions&quot;&gt;SegmentFault 思否&lt;/a&gt; - 技术问答社区。&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.csdn.net/&quot;&gt;CSDN&lt;/a&gt; - 老牌IT社区，文章量巨大。&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.jianshu.com/techareas/frontend&quot;&gt;简书&lt;/a&gt; - 轻度阅读。&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.v2ex.com/&quot;&gt;V2EX&lt;/a&gt; - 创意工作者社区，有大量程序员话题。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;算法刷题&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://leetcode.cn/&quot;&gt;力扣 LeetCode&lt;/a&gt; - 准备面试，提升编程思维必刷。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;资源导航&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.codefather.cn/&quot;&gt;编程导航&lt;/a&gt; - 帮你发现优质编程资源。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;五、 综合资源与进阶学习&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;资源聚合网站&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://webjike.com/web.html#row-15&quot;&gt;前端导航&lt;/a&gt; - 一站式前端资源导航站。&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://awesomeopensource.com/categories/programming-languages&quot;&gt;Awesome Open Source&lt;/a&gt; - 按语言分类的开源项目大全。&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://caibaojian.com/fedbook/&quot;&gt;前端开发者手册&lt;/a&gt; - 前端知识体系梳理。&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://blog.csdn.net/wzg0817/article/details/107444725&quot;&gt;博客站点集合&lt;/a&gt; - 关注一些优秀作者的博客。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;优秀 GitHub 仓库&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/KieSun/awesome-fe-study-source&quot;&gt;awesome-fe-study-source&lt;/a&gt; - 出色的前端学习资料集合。&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/mgbq/front-end-Doc&quot;&gt;front-end-Doc&lt;/a&gt; - 前端文档汇总。&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/GitHubDaily/GitHubDaily&quot;&gt;GitHubDaily&lt;/a&gt; - 分享 GitHub 上高质量项目。&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/xy-sea/blog&quot;&gt;xy-sea/blog&lt;/a&gt; - 前端知识体系博客。&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/mqyqingfeng/Blog&quot;&gt;mqyqingfeng/Blog&lt;/a&gt; - 深入系列、专题系列、ES6 系列文章。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;开发书籍&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://ebookfoundation.github.io/free-programming-books-search/&quot;&gt;免费编程书籍搜索&lt;/a&gt; - 寻找免费的编程中文书籍。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;进阶指南&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://juejin.cn/post/6844904103504527374&quot;&gt;初中级前端进阶指南&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://juejin.cn/post/7477025419418730532&quot;&gt;2025 前端+大前端+全栈 知识体系（上）&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://juejin.cn/post/7479331614620975115&quot;&gt;2025 前端+大前端+全栈 知识体系（下）&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://juejin.cn/post/7360528073631318027&quot;&gt;只写后台管理怎么提升自我&lt;/a&gt; - 解决很多人的职业困惑。&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://juejin.cn/post/7166031357418668040&quot;&gt;线上崩了？如何快速定位问题&lt;/a&gt; - 学习排查线上问题的方法。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;六、 开发工具&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;编辑器与IDE&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://code.visualstudio.com/&quot;&gt;Visual Studio Code&lt;/a&gt; - 主流前端编辑器。
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.cnblogs.com/Can-daydayup/p/17643761.html&quot;&gt;22款必备 VSCode 插件推荐&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://juejin.cn/post/7205753233599578170&quot;&gt;2023 最新最全 VSCode 插件推荐&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;文档编写&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://typora.io/#feature&quot;&gt;Typora&lt;/a&gt; - 极简优雅的 Markdown 编辑器。
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://zhuanlan.zhihu.com/p/293557841&quot;&gt;Typora 语法指南&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.cnblogs.com/hider/p/11614688.html&quot;&gt;Typora 入门教程&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;终端&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://cmder.app/&quot;&gt;Cmder (Windows)&lt;/a&gt; - Windows 下高颜值终端。&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://iterm2.com/&quot;&gt;iTerm2 (macOS)&lt;/a&gt; - macOS 终端神器。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;在线工具&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://caniuse.com/&quot;&gt;Can I use&lt;/a&gt; - 查询 CSS/JS 特性浏览器兼容性。&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://tinypng.com/&quot;&gt;TinyPNG&lt;/a&gt; - 智能图片压缩工具。&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.jyshare.com/&quot;&gt;菜鸟工具&lt;/a&gt; - 各种在线小工具集合。&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://web-abin.github.io/abinWeb/tools/&quot;&gt;前端助手&lt;/a&gt; - 时间戳、正则调试等工具。&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://unbug.github.io/codelf/&quot;&gt;CODELF&lt;/a&gt; - 变量命名神器，支持中文。&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.bitbug.net/&quot;&gt;Bitbug&lt;/a&gt; - 在线制作 favicon 图标。&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://scrollbar.app/&quot;&gt;Scrollbar&lt;/a&gt; - 自定义浏览器滚动条样式。&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://gtmetrix.com/&quot;&gt;GTmetrix&lt;/a&gt; - 网站性能分析工具。&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://tinygeeker.blog.csdn.net/article/details/117178547&quot;&gt;好用的开发工具集合&lt;/a&gt; - 更多工具推荐。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;七、 AI 提效&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;如何使用 AI&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://codemoss.flowus.cn/codemoss/share/cc713ac4-34c2-46a4-9524-e857f7bf920d&quot;&gt;OpenAI：GPT 最佳实践（解读版）&lt;/a&gt; - 学习如何向 AI 高效提问。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;AI 工具站&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://chat18.aichatos.xyz/&quot;&gt;AICHATOS&lt;/a&gt; - 免费 ChatGPT 镜像站。&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.yeschat.ai/zh-CN/yeschat&quot;&gt;YesChat&lt;/a&gt; - 每天限量使用 GPT-4、Claude 3。&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.phind.com/&quot;&gt;Phind&lt;/a&gt; - 面向开发者的智能搜索引擎。&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://ai-bot.cn/&quot;&gt;AI 工具集导航&lt;/a&gt; - 全网 AI 工具汇总。&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.aihub.cn/&quot;&gt;AI 汇总网站&lt;/a&gt; - 另一个 AI 导航站。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;VSCode AI 插件&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://marketplace.visualstudio.com/items?itemName=Alibaba-Cloud.tongyi-lingma&quot;&gt;通义灵码 (阿里)&lt;/a&gt; - 功能全面的国产插件。&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://marketplace.visualstudio.com/items?itemName=aminer.codegeex&quot;&gt;CodeGeeX&lt;/a&gt; - 清华团队的AI编程助手。&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://marketplace.visualstudio.com/items?itemName=Codeium.codeium&quot;&gt;Codeium&lt;/a&gt; - 免费且强大的替代品。&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://marketplace.visualstudio.com/items?itemName=TabNine.tabnine-vscode&quot;&gt;Tabnine&lt;/a&gt; - 老牌 AI 补全工具。&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://comate.baidu.com/zh&quot;&gt;Baidu Comate&lt;/a&gt; - 百度推出的编程助手。&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://marketplace.visualstudio.com/items?itemName=WhenSunset.chatgpt-china&quot;&gt;ChatGPT 中文版&lt;/a&gt; - VSCode 内直接调用 ChatGPT。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;AI 编辑器&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://cursor.com/&quot;&gt;Cursor&lt;/a&gt; - 基于 AI 的代码编辑器，革命性体验。&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://copilot.tencent.com/ide/&quot;&gt;CodeBuddy (腾讯)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://qoder.com/&quot;&gt;Qoder (阿里)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;AI 教程&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://mp.weixin.qq.com/s/Kz8JrFki6227tJoUfltxzA&quot;&gt;DeepSeek 保姆级教程&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://yunyinghui.feishu.cn/wiki/QuK2wYgOXiH3m6k1tADcxuhjnEg&quot;&gt;DeepSeek 使用手册&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;八、 面试准备&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://juejin.cn/post/7067707781716901901&quot;&gt;面试自我介绍 5 句话公式&lt;/a&gt; - 掌握开场技巧。&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://fe.ecool.fun/&quot;&gt;前端面试宝典&lt;/a&gt; - 按知识点分类的面试题。&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.mianshipai.com/&quot;&gt;前端面试派&lt;/a&gt; - 双越老师带队创作，系统全面。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;高频面试题汇总&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://juejin.cn/post/7343484473184698405&quot;&gt;2024 前端 Vue 高频面试题&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://juejin.cn/post/7330065707358208010&quot;&gt;2024 前端 JS 高频面试题&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://juejin.cn/post/6844904013620592654&quot;&gt;2023 前端 CSS 高频面试题&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://juejin.cn/post/7316349850855211046&quot;&gt;2024 前端 HTML 高频面试题&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://juejin.cn/post/6844903885488783374&quot;&gt;前端综合面试题&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h3&gt;学习路径建议&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;对于基础较弱的同学&lt;/strong&gt;：&lt;br /&gt;
&lt;code&gt;HTML/CSS&lt;/code&gt; -&amp;gt; &lt;code&gt;仿写静态页面&lt;/code&gt; -&amp;gt; &lt;code&gt;JavaScript&lt;/code&gt; -&amp;gt; &lt;code&gt;实现页面交互&lt;/code&gt; -&amp;gt; &lt;code&gt;Vue2&lt;/code&gt; -&amp;gt; &lt;code&gt;Vue2 + Element-UI + Axios 实战&lt;/code&gt; -&amp;gt; &lt;code&gt;独立开发一个功能全面的后台管理页面&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;对于基础不错的同学&lt;/strong&gt;：&lt;br /&gt;
直接从 &lt;code&gt;仿写页面&lt;/code&gt; 开始，遇到问题再针对性补足知识点。&lt;strong&gt;JavaScript 是重中之重&lt;/strong&gt;，需要持续深入学习。框架是延伸，底层是根本。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;通用建议&lt;/strong&gt;：
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;面向搜索引擎和 AI 编程&lt;/strong&gt;：90% 的问题都能找到答案。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;虚心请教&lt;/strong&gt;：工作中遇到难题，及时与同事讨论。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;总结记录&lt;/strong&gt;：将解决的问题和理解的知识记录下来，内化成自己的东西。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;拓展全栈技能&lt;/strong&gt;：学一门后端语言（如 Node.js/Java/Go）是巨大的加分项。&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>Vue 开发中常用的工具函数 强烈建议收藏！</title><link>https://qingge-cabana.vercel.app/posts/vue-%E5%BC%80%E5%8F%91%E4%B8%AD%E5%B8%B8%E7%94%A8%E7%9A%84%E5%B7%A5%E5%85%B7%E5%87%BD%E6%95%B0-%E5%BC%BA%E7%83%88%E5%BB%BA%E8%AE%AE%E6%94%B6%E8%97%8F-146388587/</link><guid isPermaLink="true">https://qingge-cabana.vercel.app/posts/vue-%E5%BC%80%E5%8F%91%E4%B8%AD%E5%B8%B8%E7%94%A8%E7%9A%84%E5%B7%A5%E5%85%B7%E5%87%BD%E6%95%B0-%E5%BC%BA%E7%83%88%E5%BB%BA%E8%AE%AE%E6%94%B6%E8%97%8F-146388587/</guid><description>整理 Vue 开发中高频工具函数，涵盖常用数据处理与业务辅助方法，便于项目里直接复用。</description><pubDate>Thu, 20 Mar 2025 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;本文同步自 &lt;a href=&quot;https://blog.csdn.net/weixin_44980732/article/details/146388587&quot;&gt;CSDN · 清阿哥&lt;/a&gt;，原文发布于 2025-03-20。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;所有的工具函数使用背景都是 vue 框架（vue2 vue3）＋ ts 项目中使用部分需要自行调整&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;1. 防抖函数&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;使用场景：按钮点击（请求接口） 输入框输入（模糊搜索）计算鼠标的位置&lt;br /&gt;
之前有写过一篇关于 &lt;a href=&quot;https://blog.csdn.net/weixin_44980732/article/details/116020069?fromshare=blogdetail&amp;amp;sharetype=blogdetail&amp;amp;sharerId=116020069&amp;amp;sharerefer=PC&amp;amp;sharesource=weixin_44980732&amp;amp;sharefrom=from_link&quot;&gt;vue 中使用节流防抖&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code&gt;/**
 * 防抖函数
 * @param fn 回调函数
 * @param timeout 延迟时间
 * @param immediate 是否立即执行
 */
export const debounce = &amp;lt;T extends FunctionArgs&amp;gt;(
  fn: T,
  timeout: MaybeRef&amp;lt;number&amp;gt; = 200,
  immediate = false
) =&amp;gt; {
  let timmer: TimeoutHandle;
  const wait = unref(timeout);
  return () =&amp;gt; {
    timmer &amp;amp;&amp;amp; clearTimeout(timmer);
    if (immediate) {
      if (!timmer) {
        fn();
      }
      timmer = setTimeout(() =&amp;gt; (timmer = null), wait);
    } else {
      timmer = setTimeout(fn, wait);
    }
  };
};
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2. 节流函数&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;/**
 *
 * @param fn 回调函数
 * @param timeout 延迟时间
 */
export const throttle = (fn, timeout = 200) =&amp;gt; {
  let lastExecutedTime = 0;
  return function (...args) {
    const currentTime = Date.now();
    if (currentTime - lastExecutedTime &amp;gt; timeout) {
      fn.apply(this, args);
      lastExecutedTime = currentTime;
    }
  };
};
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;3. 检查当前设备类型&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;/**
 * 检测当前设备是否为移动设备
 * 
 * 通过检查用户代理(User Agent)字符串判断当前设备类型
 * 
 * @returns {boolean} 如果是移动设备返回true，桌面设备返回false
 * 
 * @example
 * if (deviceDetection()) {
 *   // 移动设备逻辑
 * } else {
 *   // 桌面设备逻辑
 * }
 */
export const deviceDetection = (): boolean =&amp;gt; {
  const userAgent: string = navigator.userAgent.toLowerCase();

  // 定义移动设备的特征标识
  const mobileIdentifiers = [
    /iphone os/i,     // iOS设备
    /midp/i,          // 早期Java手机平台
    /rv:1.2.3.4/i,    // 特定UC浏览器版本
    /ucweb/i,         // UC浏览器
    /android/i,       // 安卓设备
    /windows ce/i,    // Windows CE设备
    /windows mobile/i // Windows Mobile设备
  ];

  // 检查是否匹配任一移动设备标识
  return mobileIdentifiers.some(pattern =&amp;gt; pattern.test(userAgent));
};

## 3. 获取当前浏览器信息
```js

/**
 * 获取当前浏览器信息，包括浏览器类型和版本号
 * 
 * @returns {BrowserInter} 包含浏览器类型和版本的对象
 * @property {string} browser - 浏览器类型 (msie|firefox|chrome|opera|safari)
 * @property {string} version - 浏览器版本号
 * 
 * @example
 * const { browser, version } = getBrowserInfo();
 * console.log(`当前浏览器: ${browser} ${version}`);
 */
export const getBrowserInfo = (): BrowserInter =&amp;gt; {
  const userAgent = navigator.userAgent.toLowerCase();
  const regExp = /(msie|firefox|chrome|opera|version).*?([\d.]+)/;
  const match = userAgent.match(regExp);

  if (!match || match.length &amp;lt; 3) {
    return {
      browser: &apos;unknown&apos;,
      version: &apos;0.0&apos;
    };
  }

  // 特殊处理Safari浏览器（Safari使用&apos;version&apos;而不是&apos;safari&apos;标识）
  const browser = match[1] === &apos;version&apos; ? &apos;safari&apos; : match[1];

  return {
    browser,
    version: match[2]
  };
};
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;4. js 操作 Class 类名相关&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;/**
 * 检查DOM元素是否包含指定的类名
 * 
 * @param {RefType&amp;lt;any&amp;gt;} ele - 要检查的DOM元素
 * @param {string} cls - 要检查的类名
 * @returns {boolean} 如果元素包含指定类名则返回true，否则返回false
 * 
 * @example
 * if (hasClass(element, &apos;active&apos;)) {
 *   // 元素包含&apos;active&apos;类的处理逻辑
 * }
 */
export const hasClass = (ele: RefType&amp;lt;any&amp;gt;, cls: string): boolean =&amp;gt; {
  if (!ele || !ele.className || typeof ele.className !== &apos;string&apos;) return false;
  return new RegExp(&quot;(\\s|^)&quot; + cls + &quot;(\\s|$)&quot;).test(ele.className);
};

/**
 * 为DOM元素添加一个或两个类名
 * 
 * @param {RefType&amp;lt;any&amp;gt;} ele - 要添加类名的DOM元素
 * @param {string} cls - 要添加的主要类名
 * @param {string} [extracls] - 可选的额外类名
 * @returns {void}
 * 
 * @example
 * addClass(element, &apos;active&apos;);
 * addClass(element, &apos;primary&apos;, &apos;large&apos;);
 */
export const addClass = (
  ele: RefType&amp;lt;any&amp;gt;,
  cls: string,
  extracls?: string
): void =&amp;gt; {
  if (!ele || !cls) return;

  if (!hasClass(ele, cls)) {
    ele.className = ele.className ? `${ele.className} ${cls}` : cls;
  }

  if (extracls &amp;amp;&amp;amp; !hasClass(ele, extracls)) {
    ele.className = `${ele.className} ${extracls}`;
  }
};

/**
 * 从DOM元素移除一个或两个类名
 * 
 * @param {RefType&amp;lt;any&amp;gt;} ele - 要移除类名的DOM元素
 * @param {string} cls - 要移除的主要类名
 * @param {string} [extracls] - 可选的额外要移除的类名
 * @returns {void}
 * 
 * @example
 * removeClass(element, &apos;active&apos;);
 * removeClass(element, &apos;primary&apos;, &apos;large&apos;);
 */
export const removeClass = (
  ele: RefType&amp;lt;any&amp;gt;,
  cls: string,
  extracls?: string
): void =&amp;gt; {
  if (!ele || !ele.className || !cls) return;

  if (hasClass(ele, cls)) {
    const reg = new RegExp(&quot;(\\s|^)&quot; + cls + &quot;(\\s|$)&quot;);
    ele.className = ele.className.replace(reg, &quot; &quot;).trim();
  }

  if (extracls &amp;amp;&amp;amp; hasClass(ele, extracls)) {
    const regs = new RegExp(&quot;(\\s|^)&quot; + extracls + &quot;(\\s|$)&quot;);
    ele.className = ele.className.replace(regs, &quot; &quot;).trim();
  }
};

/**
 * 根据条件切换DOM元素的类名
 * 
 * @param {boolean} flag - 决定是否添加类名的标志，true添加，false移除
 * @param {string} clsName - 要切换的类名
 * @param {RefType&amp;lt;any&amp;gt;} [target] - 目标DOM元素，默认为document.body
 * @returns {void}
 * 
 * @example
 * // 根据darkMode变量切换body上的dark-theme类
 * toggleClass(darkMode, &apos;dark-theme&apos;);
 * // 切换特定元素的visible类
 * toggleClass(isVisible, &apos;visible&apos;, menuElement);
 */
export const toggleClass = (
  flag: boolean,
  clsName: string,
  target?: RefType&amp;lt;any&amp;gt;
): void =&amp;gt; {
  if (!clsName) return;

  const targetEl = target || document.body;
  if (!targetEl) return;

  // 首先移除类名
  let { className } = targetEl;
  const reg = new RegExp(&quot;(\\s|^)&quot; + clsName + &quot;(\\s|$)&quot;);
  className = className.replace(reg, &quot; &quot;).trim();

  // 根据flag决定是否重新添加
  targetEl.className = flag ? `${className} ${clsName}` : className;
};
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;5. requestAnimationFrame 实现的节流函数&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;/**
 * 创建一个使用requestAnimationFrame实现的节流函数
 * 
 * 该函数确保回调在每个动画帧内最多执行一次，适用于滚动、调整大小等高频事件处理
 * 
 * @template T - 任意函数类型
 * @param {T} fn - 需要节流的原始函数
 * @returns {T} 节流处理后的函数
 * 
 * @example
 * // 创建一个节流的滚动处理函数
 * const throttledScroll = useRafThrottle(() =&amp;gt; {
 *   console.log(window.scrollY);
 * });
 * 
 * // 添加滚动事件监听
 * window.addEventListener(&apos;scroll&apos;, throttledScroll);
 */
import type { FunctionArgs } from &quot;@vueuse/core&quot;;

export function useRafThrottle&amp;lt;T extends FunctionArgs&amp;gt;(fn: T): T {
  let locked = false;

  // @ts-ignore
  return function (...args) {
    if (locked) return;

    locked = true;
    window.requestAnimationFrame(() =&amp;gt; {
      fn.apply(this, args);
      locked = false;
    });
  };
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;6. 监听元素变化（大小）&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;/**
 * ResizeObserver的回调处理函数
 * 处理元素大小变化事件，并触发所有注册的监听函数
 * 
 * @param {ResizeObserverEntry[]} entries - ResizeObserver提供的条目数组
 * @private
 */
const resizeHandler = function (entries: ResizeObserverEntry[]): void {
  for (const entry of entries) {
    const listeners = entry.target.__resizeListeners__ || [];
    if (listeners.length) {
      listeners.forEach(fn =&amp;gt; {
        fn();
      });
    }
  }
};
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;7. 为 DOM 元素添加/移除大小变化监听器&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;/**
 * 为DOM元素添加大小变化监听器
 * 
 * 使用ResizeObserver监听元素大小变化，当元素大小变化时触发回调函数
 * 
 * @param {HTMLElement} element - 要监听大小变化的DOM元素
 * @param {Function} fn - 元素大小变化时调用的回调函数
 * @returns {void}
 * 
 * @example
 * // 监听容器大小变化并更新图表
 * addResizeListener(chartContainer, () =&amp;gt; {
 *   chart.resize();
 * });
 */
export const addResizeListener = function (element: any, fn: () =&amp;gt; void): void {
  if (isServer || !element) return;

  // 初始化元素上的监听器数组
  if (!element.__resizeListeners__) {
    element.__resizeListeners__ = [];

    // 创建ResizeObserver实例并开始观察元素
    element.__ro__ = new ResizeObserver(resizeHandler);
    element.__ro__.observe(element);
  }

  // 避免重复添加相同的监听器
  if (element.__resizeListeners__.indexOf(fn) === -1) {
    element.__resizeListeners__.push(fn);
  }
};

/**
 * 移除DOM元素上的大小变化监听器
 * 
 * 如果移除后元素上没有其他监听器，则停止对该元素的观察
 * 
 * @param {HTMLElement} element - 要移除监听器的DOM元素
 * @param {Function} fn - 要移除的特定监听器函数
 * @returns {void}
 * 
 * @example
 * // 移除之前添加的监听器
 * removeResizeListener(chartContainer, updateChart);
 */
export const removeResizeListener = function (element: any, fn: () =&amp;gt; void): void {
  if (isServer || !element || !element.__resizeListeners__) return;

  // 查找并移除特定监听器
  const index = element.__resizeListeners__.indexOf(fn);
  if (index !== -1) {
    element.__resizeListeners__.splice(index, 1);
  }

  // 如果没有剩余监听器，停止观察并清理
  if (!element.__resizeListeners__.length &amp;amp;&amp;amp; element.__ro__) {
    element.__ro__.disconnect();
    element.__ro__ = null;
  }
};
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;8. 本地存储 sessionStorage localStorage 使用封装&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;/**
 * Web存储代理接口
 * 定义了存储操作的标准方法集
 */
interface ProxyStorage {
  /**
   * 获取存储项
   * @param {string} key - 存储项的键
   * @returns {any} 存储的值
   */
  getItem(key: string): any;

  /**
   * 设置存储项
   * @param {string} key - 存储项的键
   * @param {string} value - 要存储的值
   */
  setItem(key: string, value: string): void;

  /**
   * 移除存储项
   * @param {string} key - 要移除的存储项的键
   */
  removeItem(key: string): void;

  /**
   * 清除所有存储项
   */
  clear(): void;
}

/**
 * sessionStorage操作代理类
 * 提供对sessionStorage的操作封装，支持存取JSON对象
 */
class SessionStorageProxy implements ProxyStorage {
  /**
   * 被代理的存储对象
   * @protected
   */
  protected storage: ProxyStorage;

  /**
   * 构造函数
   * @param {ProxyStorage} storageModel - 被代理的存储对象
   */
  constructor(storageModel: ProxyStorage) {
    this.storage = storageModel;
  }

  /**
   * 存储数据，自动将对象转换为JSON字符串
   * @param {string} key - 存储项的键
   * @param {any} value - 要存储的值，可以是任意类型
   */
  public setItem(key: string, value: any): void {
    if (key === undefined || key === null || key === &apos;&apos;) {
      throw new Error(&apos;Storage key cannot be empty&apos;);
    }

    try {
      const valueStr = typeof value === &apos;object&apos;
        ? JSON.stringify(value)
        : String(value);
      this.storage.setItem(key, valueStr);
    } catch (error) {
      console.error(&apos;Failed to store item:&apos;, error);
    }
  }

  /**
   * 获取数据，自动将JSON字符串转换回对象
   * @param {string} key - 存储项的键
   * @returns {any} 解析后的存储值，如果项不存在或解析失败则返回null
   */
  public getItem(key: string): any {
    if (key === undefined || key === null || key === &apos;&apos;) {
      return null;
    }

    try {
      const value = this.storage.getItem(key);
      if (value === null || value === undefined) {
        return null;
      }
      return JSON.parse(value);
    } catch (error) {
      console.warn(`Error parsing stored item ${key}:`, error);
      return this.storage.getItem(key);
    }
  }

  /**
   * 移除指定的存储项
   * @param {string} key - 要移除的存储项的键
   */
  public removeItem(key: string): void {
    if (key) {
      this.storage.removeItem(key);
    }
  }

  /**
   * 清除所有存储项
   */
  public clear(): void {
    this.storage.clear();
  }
}

/**
 * localStorage操作代理类
 * 提供对localStorage的操作封装，继承自SessionStorageProxy
 */
class LocalStorageProxy extends SessionStorageProxy implements ProxyStorage {
  /**
   * 构造函数
   * @param {ProxyStorage} localStorage - 被代理的localStorage对象
   */
  constructor(localStorage: ProxyStorage) {
    super(localStorage);
  }
}

/**
 * sessionStorage实例
 * 用于操作浏览器的会话存储
 * 页面会话结束时数据会被清除
 * 
 * @example
 * // 存储数据
 * storageSession.setItem(&apos;user&apos;, { name: &apos;John&apos;, age: 30 });
 * 
 * // 获取数据
 * const user = storageSession.getItem(&apos;user&apos;);
 * 
 * // 删除数据
 * storageSession.removeItem(&apos;user&apos;);
 */
export const storageSession = new SessionStorageProxy(sessionStorage);

/**
 * localStorage实例
 * 用于操作浏览器的本地存储
 * 数据永久保存，除非被显式清除
 * 
 * @example
 * // 存储数据
 * storageLocal.setItem(&apos;theme&apos;, &apos;dark&apos;);
 * 
 * // 获取数据
 * const theme = storageLocal.getItem(&apos;theme&apos;);
 * 
 * // 删除数据
 * storageLocal.removeItem(&apos;theme&apos;);
 */
export const storageLocal = new LocalStorageProxy(localStorage);
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;9. js-cookie 使用封装&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;import Cookies from &quot;js-cookie&quot;;

import useUserStoreHook from &quot;/@/store/modules/user&quot;;

/**
 * 认证令牌的Cookie键名
 * 用于存储和获取认证令牌
 */
const TokenKey = &quot;Admin-Token&quot;;

/**
 * 获取当前用户的认证令牌
 * 
 * 从Cookie中读取存储的令牌
 * 
 * @returns {string | undefined} 用户认证令牌，如果未登录则返回undefined
 * 
 * @example
 * const token = getToken();
 * if (token) {
 *   // 用户已登录
 * } else {
 *   // 用户未登录，重定向到登录页
 * }
 */
export function getToken(): string | undefined {
  return Cookies.get(TokenKey);
}

/**
 * 设置用户认证令牌
 * 
 * 将认证令牌存储到Cookie中，同时更新Vuex存储的状态
 * 
 * @param {object} data - 包含令牌信息的对象
 * @param {string} data.token - 用户认证令牌
 * @returns {void}
 * 
 * @example
 * // 登录成功后设置令牌
 * setToken({ token: &apos;eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...&apos; });
 */
export function setToken(data: { token: string }): void {
  if (!data || typeof data !== &apos;object&apos;) {
    console.error(&apos;Invalid token data provided&apos;);
    return;
  }

  const { token: accessToken } = data;

  if (!accessToken) {
    console.error(&apos;No access token provided&apos;);
    return;
  }

  // store 里面保存 token 的方法 更新Vuex存储的令牌状态
  useUserStoreHook().SET_TOKEN(accessToken);

  // 存储令牌到Cookie
  // 注意：这里没有设置过期时间，使用默认的会话Cookie
  Cookies.set(TokenKey, accessToken);
}

/**
 * 移除用户认证令牌
 * 
 * 从Cookie中删除存储的令牌，通常在用户登出时调用
 * 
 * @returns {void}
 * 
 * @example
 * // 用户登出
 * removeToken();
 * router.push(&apos;/login&apos;);
 */
export function removeToken(): void {
  Cookies.remove(TokenKey);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;10. 获取当前环境变量&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;/**
 * 环境变量工具模块
 * 用于访问和加载Vite应用的环境变量
 */

/**
 * 加载应用的环境变量
 * 
 * 从Vite的import.meta.env中获取当前应用的所有环境变量。
 * 环境变量通常在.env文件中定义，并在构建时由Vite处理。
 * 
 * @returns {ViteEnv} 包含所有环境变量的对象
 * 
 * @example
 * // 获取所有环境变量
 * const env = loadEnv();
 * console.log(&apos;当前模式:&apos;, env.MODE);
 * console.log(&apos;API基础URL:&apos;, env.VITE_API_BASE_URL);
 * 
 * // 也可以直接解构需要的环境变量
 * const { VITE_APP_TITLE, VITE_API_BASE_URL } = loadEnv();
 */
export const loadEnv = (): ViteEnv =&amp;gt; {
  try {
    // 确保我们在浏览器环境中
    if (typeof import.meta.env === &apos;undefined&apos;) {
      console.warn(&apos;环境变量不可用，可能不在Vite环境中运行&apos;);
      return {} as ViteEnv;
    }

    return import.meta.env;
  } catch (error) {
    console.error(&apos;加载环境变量时发生错误:&apos;, error);
    return {} as ViteEnv;
  }
};
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;第一篇先更新 10 个 后续继续更新第二篇 第三篇…敬请期待~ 如果感觉有所帮助 欢迎点赞收藏推荐 如有不恰当的地方或者更好的建议 也欢迎留言交流~~ 感谢阅读 再见~&lt;/p&gt;
&lt;/blockquote&gt;
</content:encoded></item><item><title>【前端面试】面试官：纯前端如何实现录屏并保存视频到本地？</title><link>https://qingge-cabana.vercel.app/posts/%E5%89%8D%E7%AB%AF%E9%9D%A2%E8%AF%95%E9%9D%A2%E8%AF%95%E5%AE%98%E7%BA%AF%E5%89%8D%E7%AB%AF%E5%A6%82%E4%BD%95%E5%AE%9E%E7%8E%B0%E5%BD%95%E5%B1%8F%E5%B9%B6%E4%BF%9D%E5%AD%98%E8%A7%86%E9%A2%91%E5%88%B0%E6%9C%AC%E5%9C%B0-134284892/</link><guid isPermaLink="true">https://qingge-cabana.vercel.app/posts/%E5%89%8D%E7%AB%AF%E9%9D%A2%E8%AF%95%E9%9D%A2%E8%AF%95%E5%AE%98%E7%BA%AF%E5%89%8D%E7%AB%AF%E5%A6%82%E4%BD%95%E5%AE%9E%E7%8E%B0%E5%BD%95%E5%B1%8F%E5%B9%B6%E4%BF%9D%E5%AD%98%E8%A7%86%E9%A2%91%E5%88%B0%E6%9C%AC%E5%9C%B0-134284892/</guid><description>前端面试题精讲：纯前端如何实现录屏并把视频保存到本地，梳理 API 思路与实现要点。</description><pubDate>Wed, 08 Nov 2023 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;本文同步自 &lt;a href=&quot;https://blog.csdn.net/weixin_44980732/article/details/134284892&quot;&gt;CSDN · 清阿哥&lt;/a&gt;，原文发布于 2023-11-08。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;作为一个资深的切图仔，我们难免会需要用到把自己写的页面的一些功能通过视频的方式分享给别人。还有一个场景，就是当我们面试的时候，我们需要把我们的屏幕分享给面试官看，那么这些都是怎么实 现的呢？&lt;br /&gt;
那么接下来我们就通过这篇文章来学习一下如何实现一个浏览器录屏的功能。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;navigator.mediaDevices&lt;/h3&gt;
&lt;p&gt;mediaDevices 是 Navigator 只读属性，返回一个 MediaDevices 对象，该对象可提供对相机和麦克风等媒体输入设备的连接访问，也包括屏幕共享。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;const media = navigator.mediaDevices;&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;通过原型链可以查找到一些方法，如下图所示：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://i-blog.csdnimg.cn/blog_migrate/e595cfe7aa14128203c835ff107da9c6.png&quot; alt=&quot;20230919062009&quot; /&gt;&lt;/p&gt;
&lt;p&gt;其中 getDisplayMedia 方法来获取用户的屏幕分享或屏幕捕获流，通常用于制作屏幕录像或视频会议等应用。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;navigator.mediaDevices.getDisplayMedia({ video: true, });&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;在这段代码中，请求获取包含视频流的媒体许可，并将其存储在名为 stream 的变量中，以便后续使用。这个流可以用于在网页上显示屏幕共享或进行其他媒体处理操作。&lt;/p&gt;
&lt;p&gt;又因为该方法是异步的，我们要编写如下代码：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;  &amp;lt;body&amp;gt;
    &amp;lt;button&amp;gt;共享屏幕&amp;lt;/button&amp;gt;
    &amp;lt;script&amp;gt;
      const button = document.querySelector(&apos;button&apos;)
      button.addEventListener(&apos;click&apos;, async () =&amp;gt; {
        const stream = await navigator.mediaDevices.getDisplayMedia({
          video: true
        })
      })
    &amp;lt;/script&amp;gt;
  &amp;lt;/body&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;通过点击 button 按钮后会如下图所示效果：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://i-blog.csdnimg.cn/blog_migrate/41867cd93f4e0f4a99623f6dd57575b1.png&quot; alt=&quot;20230919064010&quot; /&gt;&lt;/p&gt;
&lt;p&gt;这个时候 stream 流我们是拿到了。&lt;/p&gt;
&lt;h3&gt;MediaRecorder&lt;/h3&gt;
&lt;p&gt;MediaRecorder 是一个 Web API，用于在浏览器中进行媒体录制，主要用于录制音频和视频。它允许你从不同的来源，例如麦克风、摄像头、屏幕共享或其他媒体元素，捕获音频和视频数据，并将其保存为文件或进行实时流媒体传输。&lt;/p&gt;
&lt;p&gt;通过调用 MediaRecorder.isTypeSupported() 方法会判断其 MIME 格式能否被客户端录制。它支持的类型主要有以下几种方式：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const types = [
  &quot;video/webm&quot;,
  &quot;audio/webm&quot;,
  &quot;video/webm;codecs=vp8&quot;,
  &quot;video/webm;codecs=daala&quot;,
  &quot;video/webm;codecs=h264&quot;,
  &quot;audio/webm;codecs=opus&quot;,
  &quot;video/mpeg&quot;
];
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;通过这种方式来查看当前浏览器是否支持该类型,如下代码所示:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const mime = MediaRecorder.isTypeSupported(&quot;video/webm;codecs=h264&quot;)
  ? &quot;video/webm;codecs=h264&quot;
  : &quot;video/webm&quot;;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;结果如下图所示:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://i-blog.csdnimg.cn/blog_migrate/cfa44d70270961f8dda18b549e93f29a.png&quot; alt=&quot;20230919070220&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;const mediaRecorder = new MediaRecorder(stream, { mimeType: mime });&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;在这段代码里面,执行 new 关键字实例化 MediaRecorder 对象。该对象的结构如下图所示：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://i-blog.csdnimg.cn/blog_migrate/f948e68c1cd65b2e51f3e1688b7918bf.png&quot; alt=&quot;20230919070608&quot; /&gt;&lt;/p&gt;
&lt;p&gt;我们可以通过监听它的事件来进行不同的操作,如下代码所示：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const chunks = [];
 mediaRecorder.addEventListener(&quot;dataavailable&quot;, function (e) {   chunks.push(e.data); });
  mediaRecorder.addEventListener(&quot;stop&quot;, () =&amp;gt; {   const blob = new Blob(chunks, { type: chunks[0].type });   
 const url = URL.createObjectURL(blob);   
 const a = document.createElement(&quot;a&quot;);   
 a.href = url;   
 a.download = &quot;video.webm&quot;;   
 a.click(); });
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;接下来我们来看看段代码的详细步骤:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;dataavailable 事件的监听器。当 MediaRecorder 有可用的音频/视频数据块时，它会触发此事件。在这里，每当数据可用时，将数据块（e.data）添加到 chunks 数组中。这是为了在录制完成后将这些数据块合并成一个完整的媒体文件。&lt;/li&gt;
&lt;li&gt;stop 事件的监听器。当录制停止时，MediaRecorder 会触发此事件。&lt;/li&gt;
&lt;li&gt;const blob = new Blob(chunks, { type: chunks[0].type });：使用 Blob 构造函数将 chunks 数组中的数据块合并成一个二进制对象（Blob 对象）。chunks[0].type 用于指定 Blob 的媒体类型，通常是 WebM。&lt;/li&gt;
&lt;li&gt;const url = URL.createObjectURL(blob);：使用 URL.createObjectURL 方法将 Blob 对象转换为一个临时 URL。这个 URL 可以用于创建下载链接。通过这个 url 传递给新创建的 a 元素用于下载功能。&lt;/li&gt;
&lt;li&gt;a.click() 通过模拟点击超链接来触发下载操作。用户将看到一个下载对话框，允许他们保存录制的媒体文件&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;这样就实现了一个屏幕录制功能了，并且通过该方法实现了一个音视频下载的功能。&lt;/p&gt;
&lt;h3&gt;完整代码&lt;/h3&gt;
&lt;p&gt;该功能的完整代码如下所示：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html lang=&quot;en&quot;&amp;gt;
  &amp;lt;head&amp;gt;
    &amp;lt;meta charset=&quot;UTF-8&quot; /&amp;gt;
    &amp;lt;meta name=&quot;viewport&quot; content=&quot;width=device-width, initial-scale=1.0&quot; /&amp;gt;
    &amp;lt;title&amp;gt;Document&amp;lt;/title&amp;gt;
  &amp;lt;/head&amp;gt;
  &amp;lt;body&amp;gt;
    &amp;lt;button&amp;gt;共享屏幕&amp;lt;/button&amp;gt;
    &amp;lt;script&amp;gt;
      const button = document.querySelector(&apos;button&apos;);
      button.addEventListener(&apos;click&apos;, async () =&amp;gt; {
        const stream = await navigator.mediaDevices.getDisplayMedia({
          video: true,
        });
        const mime = MediaRecorder.isTypeSupported(&apos;video/webm;codecs=h264&apos;)
          ? &apos;video/webm;codecs=h264&apos;
          : &apos;video/webm&apos;;
        const mediaRecorder = new MediaRecorder(stream, { mimeType: mime });
        const chunks = [];
        mediaRecorder.addEventListener(&apos;dataavailable&apos;, function (e) {
          chunks.push(e.data);
        });
        mediaRecorder.addEventListener(&apos;stop&apos;, () =&amp;gt; {
          const blob = new Blob(chunks, { type: chunks[0].type });
          const url = URL.createObjectURL(blob);
          const a = document.createElement(&apos;a&apos;);
          a.href = url;
          a.download = &apos;video.webm&apos;;
          a.click();
        });
        mediaRecorder.start();
      });
    &amp;lt;/script&amp;gt;
  &amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;完整效果如下图所示:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://i-blog.csdnimg.cn/blog_migrate/9df9ceb3cce3120cfe9fe9663b18d887.png&quot; alt=&quot;请添加图片描述&quot; /&gt;&lt;/p&gt;
&lt;p&gt;你也可以直接在其他任何一个页面，将 js 代码修改一下,然后直接复制粘贴到控制台上，并点击页面，如下所示：&lt;/p&gt;
&lt;h4&gt;diff&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;-const button = document.querySelector(&quot;button&quot;);
 -button.addEventListener(&quot;click&quot;, async () =&amp;gt; {
  +const body = document.body; 
  +body.addEventListener(&quot;click&quot;, async () =&amp;gt; {
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;紧接着将代码复制粘贴到浏览器控制台上面，并点击页面的空白处：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://i-blog.csdnimg.cn/blog_migrate/96ccf343da38e31ffe95839e28c66b70.png&quot; alt=&quot;20230919073632&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://i-blog.csdnimg.cn/blog_migrate/32f80b29c3d5a9f631431034bc62896f.png&quot; alt=&quot;20230919073648&quot; /&gt;&lt;/p&gt;
&lt;p&gt;通过这种方式就实现了一种屏幕录制或者屏幕分享的功能了。&lt;/p&gt;
&lt;p&gt;本文转自 &lt;a href=&quot;https://juejin.cn/post/7280057907055869992&quot;&gt;https://juejin.cn/post/7280057907055869992&lt;/a&gt;&lt;/p&gt;
</content:encoded></item><item><title>vue2之 vue-i18n 插件报错-Uncaught TypeError: Cannot read properties of undefined (reading ‘0‘)</title><link>https://qingge-cabana.vercel.app/posts/vue2%E4%B9%8B-vue-i18n-%E6%8F%92%E4%BB%B6%E6%8A%A5%E9%94%99-uncaught-typeerror-cannot-read-propertie-132968891/</link><guid isPermaLink="true">https://qingge-cabana.vercel.app/posts/vue2%E4%B9%8B-vue-i18n-%E6%8F%92%E4%BB%B6%E6%8A%A5%E9%94%99-uncaught-typeerror-cannot-read-propertie-132968891/</guid><description>排查 Vue2 使用 vue-i18n 时 Cannot read properties of undefined 报错，给出版本与初始化相关修复。</description><pubDate>Mon, 18 Sep 2023 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;本文同步自 &lt;a href=&quot;https://blog.csdn.net/weixin_44980732/article/details/132968891&quot;&gt;CSDN · 清阿哥&lt;/a&gt;，原文发布于 2023-09-18。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;1. 前言&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;在对 showdoc 开源项目进行二次开发的时候遇到了这个报错，在本地没有问题，发布到开发环境之后就报这个错误，一开始一头雾水无从下手，经过一番筛查（百度）之后发现是和项目里面的插件 vue-i8n 有关系，然后又经过很长时间的排查才发现是 vue-i18n 的版本问题… 排查的过程中简直太艰难了…&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;2. 问题&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://i-blog.csdnimg.cn/blog_migrate/f0a7e3187bc5ad6f54f41c21a57249ff.jpeg#pic_center&quot; alt=&quot;报错&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;3. 解决过程&lt;/h3&gt;
&lt;p&gt;在报错指向的第一个报错文件中找到了 &lt;code&gt;_watchers[0].constructor&lt;/code&gt; undefined 造成的问题 说明这个插件在初始化的时候没有&lt;code&gt;_watcher&lt;/code&gt; 这个方法，导致报错之后导致页面阻塞加载白屏的状态。&lt;br /&gt;
无意中看到了一篇文章产生了启发，感觉会不会是版本的问题&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;升级插件版本（原来是 5.0.3 node v14.19.0）
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;npm i vue-i18n@8.28.2 -S&lt;/code&gt; 也可以试试其他高一点的版本&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;4. 代码&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;// lang.js
import Vue from &apos;vue&apos; // 引入Vue
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>google 浏览器出现 ERR_PROXY_CONNECTION_FAILED 无法访问网络</title><link>https://qingge-cabana.vercel.app/posts/google-%E6%B5%8F%E8%A7%88%E5%99%A8%E5%87%BA%E7%8E%B0-err_proxy_connection_failed-%E6%97%A0%E6%B3%95%E8%AE%BF%E9%97%AE%E7%BD%91%E7%BB%9C-128440106/</link><guid isPermaLink="true">https://qingge-cabana.vercel.app/posts/google-%E6%B5%8F%E8%A7%88%E5%99%A8%E5%87%BA%E7%8E%B0-err_proxy_connection_failed-%E6%97%A0%E6%B3%95%E8%AE%BF%E9%97%AE%E7%BD%91%E7%BB%9C-128440106/</guid><description>记录 Chrome 出现 ERR_PROXY_CONNECTION_FAILED 无法上网的排查过程，给出代理相关的解决办法。</description><pubDate>Mon, 26 Dec 2022 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;本文同步自 &lt;a href=&quot;https://blog.csdn.net/weixin_44980732/article/details/128440106&quot;&gt;CSDN · 清阿哥&lt;/a&gt;，原文发布于 2022-12-26。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;1. 问题&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;早上来公司突然发现谷歌浏览器访问所有的东西都出现 ERR_PROXY_CONNECTION_FAILED 网络不可用的提示 这一串的单词的意思是 代理连接失败 真的是一脸懵逼 经过一番百度后发现是网络代理作的妖…&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;img src=&quot;https://i-blog.csdnimg.cn/blog_migrate/52da0cdd7837664d007886ebae41f390.jpeg&quot; alt=&quot;请添加图片描述&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;2. 解决方法&lt;/h2&gt;
&lt;p&gt;其实很简单将 wifi 和网络里面所有的代理全部取消即可&lt;br /&gt;
&lt;img src=&quot;https://i-blog.csdnimg.cn/blog_migrate/4008c359331d41508a7244abaa168aa6.jpeg&quot; alt=&quot;请添加图片描述&quot; /&gt;&lt;br /&gt;
全部取消勾选 还不明白为什么代理被自动勾选了… 有明白的大佬 可以解释下 （wifi 和网络设置同理）&lt;br /&gt;
&lt;img src=&quot;https://i-blog.csdnimg.cn/blog_migrate/38cb4a579b9e261c4fef02d76c6c2344.jpeg&quot; alt=&quot;请添加图片描述&quot; /&gt;&lt;br /&gt;
&lt;strong&gt;可以正常访问了&lt;/strong&gt;&lt;br /&gt;
&lt;img src=&quot;https://i-blog.csdnimg.cn/blog_migrate/f70a540a122b3deb6d439ddb95962a49.jpeg&quot; alt=&quot;请添加图片描述&quot; /&gt;&lt;br /&gt;
如有问题请留言交流，有更好的方法希望不吝赐教，如有错误还请指出多多包涵，如果对你有所帮助希望可以点个赞 Thanks♪(･ω･)ﾉ&lt;/p&gt;
</content:encoded></item><item><title>获取当天，本周，本月，本季度，本半年，本年时间</title><link>https://qingge-cabana.vercel.app/posts/%E8%8E%B7%E5%8F%96%E5%BD%93%E5%A4%A9%E6%9C%AC%E5%91%A8%E6%9C%AC%E6%9C%88%E6%9C%AC%E5%AD%A3%E5%BA%A6%E6%9C%AC%E5%8D%8A%E5%B9%B4%E6%9C%AC%E5%B9%B4%E6%97%B6%E9%97%B4-127886569/</link><guid isPermaLink="true">https://qingge-cabana.vercel.app/posts/%E8%8E%B7%E5%8F%96%E5%BD%93%E5%A4%A9%E6%9C%AC%E5%91%A8%E6%9C%AC%E6%9C%88%E6%9C%AC%E5%AD%A3%E5%BA%A6%E6%9C%AC%E5%8D%8A%E5%B9%B4%E6%9C%AC%E5%B9%B4%E6%97%B6%E9%97%B4-127886569/</guid><description>封装获取当天、本周、本月、本季度、本半年、本年起止时间的工具方法，方便报表与筛选场景。</description><pubDate>Wed, 16 Nov 2022 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;本文同步自 &lt;a href=&quot;https://blog.csdn.net/weixin_44980732/article/details/127886569&quot;&gt;CSDN · 清阿哥&lt;/a&gt;，原文发布于 2022-11-16。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;1. 使用场景&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;elemet-plus 组件库，日期选择器快捷使用选择本月，本季度时间…&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;2. 方法&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;const useDate = () =&amp;gt; {
  const now = new Date() // 当前日期
  const nowYear = now.getFullYear() //当前年
  const nowMonth = now.getMonth() //当前月
  const nowDay = now.getDate() // 当前日
  const nowDayOfWeek = now.getDay() // 周几
  const jd = Math.ceil((nowMonth + 1) / 3) // 当前季度

  // 当天
  function getCurDay() {
    return [now, now]
  }

  // 本周
  function getCurWeek() {
    const startDate = new Date(nowYear, nowMonth, nowDay - nowDayOfWeek)
    const endDate = new Date(nowYear, nowMonth, nowDay + (6 - nowDayOfWeek))
    return [startDate, endDate]
  }
  // 本月
  function getCurMonth() {
    const startDate = new Date(nowYear, nowMonth, 1)
    const endDate = new Date(nowYear, nowMonth + 1, 0)
    return [startDate, endDate]
  }
  
  // 本季度
  function getCurQuarter() {
    const startDate = new Date(nowYear, (jd - 1) * 3, 1)
    const endDate = new Date(nowYear, jd * 3, 0)
    return [startDate, endDate]
  }
  
  // 本半年
  function getCurHalfYear() {
    const startD = new Date(nowYear, nowMonth - 4, 1)
    const startDate = new Date(nowYear, 0, 1)
    const endD = new Date(nowYear, nowMonth + 1, 0)
    const year = startD.getFullYear()
    if (year === nowYear) {
      return [startD, endD]
    } else {
      return [startDate, endD]
    }
  }
  
  // 本年
  function getCurYear() {
    const startDate = new Date(nowYear, 0, 1)
    const endDate = new Date(nowYear, 11, 0)
    return [startDate, endDate]
  }

  return {
    getCurDay,
    getCurWeek,
    getCurMonth,
    getCurQuarter,
    getCurHalfYear,
    getCurYear,
  }
}

const {
  getCurDay,
  getCurWeek,
  getCurMonth,
  getCurQuarter,
  getCurHalfYear,
  getCurYear,
} = useDate()

export const shortcuts = [
  {
    text: &apos;当天&apos;,
    value: getCurDay(),
  },
  {
    text: &apos;本周&apos;,
    value: getCurWeek(),
  },
  {
    text: &apos;本月&apos;,
    value: getCurMonth(),
  },
  {
    text: &apos;本季度&apos;,
    value: getCurQuarter(),
  },
  {
    text: &apos;半年&apos;,
    value: getCurHalfYear(),
  },
  {
    text: &apos;当年&apos;,
    value: getCurYear(),
  },
]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如有疑问欢迎留言交流，有更好的方法希望不吝赐教~&lt;/p&gt;
</content:encoded></item><item><title>el-form之表单校验自动定位到报错位置</title><link>https://qingge-cabana.vercel.app/posts/el-form%E4%B9%8B%E8%A1%A8%E5%8D%95%E6%A0%A1%E9%AA%8C%E8%87%AA%E5%8A%A8%E5%AE%9A%E4%BD%8D%E5%88%B0%E6%8A%A5%E9%94%99%E4%BD%8D%E7%BD%AE-125689424/</link><guid isPermaLink="true">https://qingge-cabana.vercel.app/posts/el-form%E4%B9%8B%E8%A1%A8%E5%8D%95%E6%A0%A1%E9%AA%8C%E8%87%AA%E5%8A%A8%E5%AE%9A%E4%BD%8D%E5%88%B0%E6%8A%A5%E9%94%99%E4%BD%8D%E7%BD%AE-125689424/</guid><description>实现 ElementUI el-form 校验失败后自动滚动定位到报错表单项，提升长表单的填写体验。</description><pubDate>Sat, 09 Jul 2022 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;本文同步自 &lt;a href=&quot;https://blog.csdn.net/weixin_44980732/article/details/125689424&quot;&gt;CSDN · 清阿哥&lt;/a&gt;，原文发布于 2022-07-09。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;1. 背景&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;表单校验大多数的表单都会用到，一般情况下只是提示当前哪些项校验不通过，但是如果表单比较需要用户自己去找是哪项校验不通过，这样的用户体验不太好，如果能自动定位到当前校验不通过的表单项体验会更好一些（这里是以elementui 的 el-from 组件为例子）&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;2. 实现思路&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;目前有两个实现方法：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;方法一：
&lt;ul&gt;
&lt;li&gt;表单校验不通过的时候，会在未校验成功的表单 el-form-item 标签上加一个类名 is-error，可以从这个类名下手，获取当前校验不通过的所有的类名，然后通过 scrollIntoView 方法进行自动滑动到当前报错的表单项位置&lt;br /&gt;
&lt;img src=&quot;https://i-blog.csdnimg.cn/blog_migrate/92b9615ac543f32e5511a36ea071a30f.png&quot; alt=&quot;在这里插入图片描述&quot; /&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;ul&gt;
&lt;li&gt;方法二：
&lt;ul&gt;
&lt;li&gt;利用el-form提供的 validate 方法的第二个参数，获取当前校验不通过的rule 对象，获取到报错的 prop 后，在每个需要加表单校验的项上加上 ref，ref 的绑定值和 prop 保持一致这样方便直接定位到哪个prop，然后和第一个方法一样利用 scrollIntoView 方法结合$ref 获取到 $el 实现自动定位到校验不通过的表单项&lt;br /&gt;
&lt;img src=&quot;https://i-blog.csdnimg.cn/blog_migrate/7ab8f185530e94b661cbf4873d51d005.png&quot; alt=&quot;在这里插入图片描述&quot; /&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;3. 实现代码&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;方法一实现代码：&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt; 	// 表单校验
    verifyForm() {
      this.$refs.basicInfoRule.validate(valid =&amp;gt; {
        if (valid) {
          // 校验通过执行的逻辑
        } else {
          // 校验不通过自动定位到不通过的表单项
          this.moveToErr()
        }
      })
    },

    // 自动定位到表单报错项
    moveToErr() {
      this.$nextTick(() =&amp;gt; {
        let isError = document.getElementsByClassName(&apos;is-error&apos;)
        if (isError.length) {
          isError[0].scrollIntoView({
            block: &apos;center&apos;,
            behavior: &apos;smooth&apos;
          })
	// 这个当滑动到报错项之后自动获取输入框的焦点，方便用户直接进行输入，延迟 800ms 是因为需要都能到定位成功后在进行获取焦点体验更好一些
          setTimeout(() =&amp;gt; {
            if (isError[0].previousElementSibling.querySelector(&apos;input&apos;)) {
              isError[0].previousElementSibling.querySelector(&apos;input&apos;).focus()
            }
          }, 800)
        }
      })
    }
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;strong&gt;方法二实现代码：&lt;/strong&gt;&lt;br /&gt;
模板示例代码&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;   &amp;lt;el-form-item label=&quot;姓名：&quot; prop=&quot;name&quot; ref=&quot;name&quot;&amp;gt;
        &amp;lt;el-input
          v-model.number=&quot;form.&quot;
          placeholder=&quot;请输入姓名&quot;
          clearable
        &amp;gt;&amp;lt;/el-input&amp;gt;
      &amp;lt;/el-form-item&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt; verifyForm() {
      this.$refs.ruleForm.validate((valid, object) =&amp;gt; {
        if (valid) {
          //校验通过执行逻辑
        } else {
          let errorArr = [];
          for (let key in object) {
            object[key].forEach((item) =&amp;gt; {
              errorArr.push(item.message);
            });
            let dom = this.$refs[Object.keys(object)[0]];
            if (Object.prototype.toString.call(dom) !== &apos;[object Object]&apos;) {
              dom = dom[0];
              break; //结束语句并跳出语句，进行下个语句执行
            }
            // 定位到报错项
            dom.$el.scrollIntoView({
              block: &apos;center&apos;,
              behavior: &apos;smooth&apos;,
            });
          }
          // 页面提示未通过校验字段项，
          this.$message.error(errorArr[0]);
        }
      });
    },
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;5. 拓展（踩坑…）&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;由于页面比较复杂分了好几个子组件，而且子组件中都有需要校验的表单项，在父组件保存表单前，需要调用各个子组件的表单校验方法，发现el-form 的 validate 方法不能返会校验失败还是校验成功&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;错误代码示范:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// 子组件代码
   verifyForm() {
      this.$refs.basicInfoRule.validate(valid =&amp;gt; {
        if (valid) {
          // 校验通过
          return true
        } else {
          // 校验不通过
          return false
        }
      })
    },
    
    // 父组件代码
    // 校验子组件表单（通过子组件绑定的ref来调用子组件的校验方法）
    async handleVerify() {
      if (
        !(await this.$refs.basicInfo?.verifyForm()) ||
        !(await this.$refs.priceSetting?.verifyForm())
      ) {
        this.$message.warning(&apos;请完善表单必填项&apos;)
        return false
      }
      return true
    },
    // 保存
      async saveMainDetail() {
      try {
        if (!(await this.handleVerify())) return
        } catch (e) {}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;上述代码在父组件里面不能获取到组件的校验状态，子组件换一个写法就行了&lt;/strong&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;strong&gt;正确代码示范：&lt;/strong&gt;&lt;br /&gt;
使用 Promise 来返回表单的是否校验成功状态能在父组件成功拿到了&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// 子组件代码
 verifyForm() {
      return new Promise((resolve, reject) =&amp;gt; {
        this.$refs.basicInfoRule.validate(valid =&amp;gt; {
          if (valid) {
            console.log(valid, &apos;校验通过&apos;)
            resolve(true)
          } else {
            console.log(valid, &apos;校验不通过&apos;)
            reject(new Error())
          }
        })
      })
    }
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;6. 结语&lt;/h3&gt;
&lt;p&gt;实现功能的思路有很多，这里我列举出来的两种，看实际的功能和需求找到适合自己的方法，有错误的地方还望指出，有疑问欢迎留言交流，如果你有更好的方法也希望不吝赐教，如果能帮到你我很荣幸~&lt;/p&gt;
</content:encoded></item><item><title>js 合并两个二维数组成为数组对象</title><link>https://qingge-cabana.vercel.app/posts/js-%E5%90%88%E5%B9%B6%E4%B8%A4%E4%B8%AA%E4%BA%8C%E7%BB%B4%E6%95%B0%E7%BB%84%E6%88%90%E4%B8%BA%E6%95%B0%E7%BB%84%E5%AF%B9%E8%B1%A1-122655104/</link><guid isPermaLink="true">https://qingge-cabana.vercel.app/posts/js-%E5%90%88%E5%B9%B6%E4%B8%A4%E4%B8%AA%E4%BA%8C%E7%BB%B4%E6%95%B0%E7%BB%84%E6%88%90%E4%B8%BA%E6%95%B0%E7%BB%84%E5%AF%B9%E8%B1%A1-122655104/</guid><description>演示如何用 JavaScript 将两个二维数组合并为对象数组，适合列表映射与数据重组场景。</description><pubDate>Sun, 23 Jan 2022 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;本文同步自 &lt;a href=&quot;https://blog.csdn.net/weixin_44980732/article/details/122655104&quot;&gt;CSDN · 清阿哥&lt;/a&gt;，原文发布于 2022-01-23。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h5&gt;使用场景&lt;/h5&gt;
&lt;blockquote&gt;
&lt;p&gt;在使用element-ui的级联选择器展示省市区（多选），后端需要传递数组对象格式里面包含每个选中的省市区的name和code数据.但是只能直接取到选中的code，可以使用ref来拿到级联选择器的getCheckedNodes方法来获取当前选中的省市区name；&lt;br /&gt;
最终将两个二维数组合成一个数组对象的格式传给后端，数据结构以及代码如下：&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h5&gt;模板代码&lt;/h5&gt;
&lt;pre&gt;&lt;code&gt; &amp;lt;el-cascader
   ref=&quot;serveArea&quot;
    v-model=&quot;form.installServiceArea&quot;
    :options=&quot;regionData&quot;
    :props=&quot;areaProps&quot;
    clearable
    placeholder=&quot;请选择服务区域&quot;
  &amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h5&gt;js 代码&lt;/h5&gt;
&lt;pre&gt;&lt;code&gt;handleParams() {
	...
	const { installServiceArea } = this.mainDetail;
	 // 服务区域
      if (installServiceArea.length) {
      // 服务区域的 name
      	let serveAreaName = this.$refs.baseInfo.$refs.serveArea.getCheckedNodes();
        console.log(serveAreaName);
        // 将服务区域处理成数组对象的数据格式
        let serveAreaName = serveAreaName.map((i) =&amp;gt; {
          return {
            provinceName: i.pathLabels[0],
            cityName: i.pathLabels[1],
            regionName: i.pathLabels[2],
          };
        });

        console.log(serveAreaName, &apos;serveAreaName&apos;);
		// 将服务区域的name和code合并成一个数组对象
        let serveAreaParams = serveAreaName.map((i, ind) =&amp;gt; {
          return {
            ...i,
            provinceCode: installServiceArea[ind][0],
            cityCode: installServiceArea[ind][1],
            regionCode: installServiceArea[ind][2],
          };
        });

        console.log(serveAreaParams, &apos;serveAreaParams&apos;);
      }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;打印结果如下图：&lt;/strong&gt;&lt;br /&gt;
&lt;img src=&quot;https://i-blog.csdnimg.cn/blog_migrate/a63da5adf929a273e216e942850a8c3b.png&quot; alt=&quot;原始数据是这样的&quot; /&gt;&lt;br /&gt;
&lt;img src=&quot;https://i-blog.csdnimg.cn/blog_migrate/ae8100696cde2db5cc7aa0b1284fb025.png&quot; alt=&quot;在这里插入图片描述&quot; /&gt;&lt;br /&gt;
&lt;img src=&quot;https://i-blog.csdnimg.cn/blog_migrate/ae0b13c479b409f86d2a4cd5972d2eee.png&quot; alt=&quot;在这里插入图片描述&quot; /&gt;&lt;/p&gt;
&lt;p&gt;不积跬步，无以至千里;不积小流，无以成江海。&lt;br /&gt;
有问题可以留言一起交流讨论~&lt;/p&gt;
</content:encoded></item><item><title>css 完美解决图片变形问题</title><link>https://qingge-cabana.vercel.app/posts/css-%E5%AE%8C%E7%BE%8E%E8%A7%A3%E5%86%B3%E5%9B%BE%E7%89%87%E5%8F%98%E5%BD%A2%E9%97%AE%E9%A2%98-116006057/</link><guid isPermaLink="true">https://qingge-cabana.vercel.app/posts/css-%E5%AE%8C%E7%BE%8E%E8%A7%A3%E5%86%B3%E5%9B%BE%E7%89%87%E5%8F%98%E5%BD%A2%E9%97%AE%E9%A2%98-116006057/</guid><description>讲解用 CSS 解决图片变形问题的实用写法，适配 H5 多尺寸图片展示，避免拉伸与裁切异常。</description><pubDate>Thu, 22 Apr 2021 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;本文同步自 &lt;a href=&quot;https://blog.csdn.net/weixin_44980732/article/details/116006057&quot;&gt;CSDN · 清阿哥&lt;/a&gt;，原文发布于 2021-04-22。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;开发 h5 的时候测试给提了一个 bug 图片变形，开发的时候还真没注意，确实变形了开起来怪怪的，然后就顺利找到了解决办法，废话不多说先呈上解决方法：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;div class=&quot;imgWrap&quot;&amp;gt;
    &amp;lt;img src=&quot;@/assets/images/copter.jpg&quot; alt=&quot;&quot;&amp;gt;
&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt; .imgWrap {
    width: 300px;
    height: 300px;
    border: 2px solid skyblue;
  }

  img {
    widows: 100%;
    height: 100%;
    object-fit: cover;
  }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;搞定就这么简单！&lt;/p&gt;
&lt;p&gt;效果图：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://i-blog.csdnimg.cn/blog_migrate/be37db5308eba13da814e2b3af9ba483.png&quot; alt=&quot;在这里插入图片描述&quot; /&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;注意一点： 图片一定要设置宽高，否则设置object-fit无效！&lt;br /&gt;
解释：object-position相当于background-position，它的默认值是50% 50%，也就是居中，所以一般不写，加了object-fit，默认就居中了。而object-fit，相当于background-size，即图片填充方式（这里不是图片大小）&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;想了解 &lt;code&gt;object-fit&lt;/code&gt; 自行去菜鸟上看吧 &lt;a href=&quot;https://www.runoob.com/cssref/pr-object-fit.html&quot;&gt;object-fit&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;有问题可以留言交流，希望能够帮到你！&lt;/p&gt;
</content:encoded></item><item><title>vue 中使用节流和防抖</title><link>https://qingge-cabana.vercel.app/posts/vue-%E4%B8%AD%E4%BD%BF%E7%94%A8%E8%8A%82%E6%B5%81%E5%92%8C%E9%98%B2%E6%8A%96-116020069/</link><guid isPermaLink="true">https://qingge-cabana.vercel.app/posts/vue-%E4%B8%AD%E4%BD%BF%E7%94%A8%E8%8A%82%E6%B5%81%E5%92%8C%E9%98%B2%E6%8A%96-116020069/</guid><description>在 Vue 中实现节流与防抖，对比两种策略的差异，并给出按钮点击、搜索输入等常见使用场景。</description><pubDate>Thu, 22 Apr 2021 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;本文同步自 &lt;a href=&quot;https://blog.csdn.net/weixin_44980732/article/details/116020069&quot;&gt;CSDN · 清阿哥&lt;/a&gt;，原文发布于 2021-04-22。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;今天事情比较少，就特意研究了下在vue中如何使用节流和防抖，还有节流防抖我真的是傻傻的分不清楚，不知道什么时候使用节流，什么时候使用防抖…&lt;/p&gt;
&lt;h4&gt;1. 节流-方法1&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;在 utils.js 文件里面加入下面的方法&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code&gt;// 节流
export function _throttle(fn, wait = 200) {
  let last, timer,now;
  return function() {
    now = Date.now();
    if (last &amp;amp;&amp;amp; now - last &amp;lt; wait) {
      clearTimeout(timer);
      timer = setTimeout(function() {
        last = now;
        fn.call(this, ...arguments);
      }, wait);
    } else {
      last = now;
      fn.call(this, ...arguments);
    }
  };
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;2. 防抖&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;// 防抖
export function _debounce(fn, wait = 200) {
  let timer;
    return function () {
      let context = this;
      let args = arguments;
      if (timer) clearTimeout(timer);
      timer = setTimeout(() =&amp;gt; {
        fn.apply(context, args);
      }, wait)
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;3. 组件中使用&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;import { _debounce, _throttle } from &apos;@/assets/scripts/utils&apos;;

initQuery: _throttle(function() {
     // 要做的事情
}, 2000)

initQuery: _debounce(function() {
     // 要做的事情
}, 2000)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;防抖：触发高频事件后n秒内函数只会执行一次，如果n秒内高频事件再次被触发，则重新计算时间。&lt;br /&gt;
应用：因为防抖的特性，一直执行最后一次的触发，所以可以用于鼠标移动确定最后一次移动的时候的坐标位置。&lt;br /&gt;
输入框的模糊搜索&lt;br /&gt;
节流：高频事件触发，但在n秒内只会执行一次，所以节流会稀释函数的执行频率&lt;br /&gt;
应用：因为节流是监听到第一次触发事件后就执行，所以可以用来防止按钮多次点击执行多次，且按照第一次点击的事件为准&lt;/p&gt;
</content:encoded></item><item><title>elementui el-pagination 分页组件封装</title><link>https://qingge-cabana.vercel.app/posts/elementui-el-pagination-%E5%88%86%E9%A1%B5%E7%BB%84%E4%BB%B6%E5%B0%81%E8%A3%85-115900338/</link><guid isPermaLink="true">https://qingge-cabana.vercel.app/posts/elementui-el-pagination-%E5%88%86%E9%A1%B5%E7%BB%84%E4%BB%B6%E5%B0%81%E8%A3%85-115900338/</guid><description>封装 ElementUI el-pagination 分页组件，统一表格场景的前后端分页用法，减少重复配置。</description><pubDate>Tue, 20 Apr 2021 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;本文同步自 &lt;a href=&quot;https://blog.csdn.net/weixin_44980732/article/details/115900338&quot;&gt;CSDN · 清阿哥&lt;/a&gt;，原文发布于 2021-04-20。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;分页组件是比较常用的，有表格的地方基本上都存在分页，使用 elementui 里面的分页进行封装方便使用。&lt;/p&gt;
&lt;h3&gt;一. 后端分页&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;template&amp;gt;
  &amp;lt;div class=&quot;pagination&quot;&amp;gt;
    &amp;lt;el-pagination @size-change=&quot;sizeChange&quot;
      @current-change=&quot;currentChange&quot;
      :current-page=&quot;currentPage&quot;
      :page-sizes=&quot;propPageSizes&quot;
      :page-size=&quot;pageSize&quot;
      background
      layout=&quot;total, sizes, prev, pager, next, jumper&quot;
      :total=&quot;total&quot;
      v-if=&quot;total &amp;gt; 0&quot;&amp;gt;
    &amp;lt;/el-pagination&amp;gt;
  &amp;lt;/div&amp;gt;
&amp;lt;/template&amp;gt;

&amp;lt;script&amp;gt;
export default {
  props: {
    callback: { // 获取列表数据方法
      type: Function,
      default() {
        return function() {};
      }
    },
    propCurrentPage: { // 当前页
      type: [Number],
      default: 1
    },
    propPageSizes: { // 配置每页显示多少条
      type: Array,
      default: function() {
        return [10, 20, 30];
      }
    },
    propPageSize: { // 默认每页显示条数
      type: [Number],
      default: 10
    },
    total: { // 数据总条数
      type: [Number],
      default: 0
    }
  },
  data() {
    return {
      currentPage: this.propCurrentPage, // 页面的当前页
      pageSize: this.propPageSize // 页面分页器大小
    };
  },
  methods: {
    // 改变当前页
    currentChange(val) {
      this.currentPage = val;
      this[&apos;callback&apos;]();
    },
    // 改变每页显示多少条
    sizeChange(val) {
      this.pageSize = val;
      this.currentPage = 1;
      this[&apos;callback&apos;]();
    }
  }
};
&amp;lt;/script&amp;gt;

&amp;lt;style lang=&quot;less&quot; scoped&amp;gt;
// 样式根据自己的实际情况修改，也可以用下边的
.pagination {
  margin-top: 20px;
  text-align: right;

  /deep/ .el-pagination.is-background .el-pager li,
  /deep/ .el-pagination.is-background .btn-prev,
  /deep/ .el-pagination.is-background .btn-next {
    color: #666;
    background-color: #fff;
    border: 1px solid #e4e9ec;
    border-radius: 4px;
  }

  /deep/ .el-pagination.is-background .el-pager li:not(.disabled).active {
    color: #11889c;
    background-color: #fff;
    border: 1px solid #11889c;
  }

  /deep/ .el-pagination.is-background .btn-prev:disabled,
  /deep/ .el-pagination.is-background .btn-next:disabled {
    color: #c0c4cc;
  }
}
&amp;lt;/style&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;Pagination 
  ref=&quot;pagination&quot;
  :callback=&quot;getMsgList&quot;
  :total=&quot;total&quot;&amp;gt;
&amp;lt;/Pagination&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;效果图&lt;/strong&gt;&lt;br /&gt;
&lt;img src=&quot;https://i-blog.csdnimg.cn/blog_migrate/7b4276093194f42a89cc918bc08f44d3.png&quot; alt=&quot;在这里插入图片描述&quot; /&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h4&gt;二. 前端实现分页&lt;/h4&gt;
&lt;p&gt;待更新。。。&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;这个分页其实也没什么难度，是后端实现的分页，后面再总结下前端实现分页的方法。有问题，欢迎大家随时留言交流！&lt;/p&gt;
</content:encoded></item><item><title>ElementUI upload 组件封装</title><link>https://qingge-cabana.vercel.app/posts/elementui-upload-%E7%BB%84%E4%BB%B6%E5%B0%81%E8%A3%85-115733537/</link><guid isPermaLink="true">https://qingge-cabana.vercel.app/posts/elementui-upload-%E7%BB%84%E4%BB%B6%E5%B0%81%E8%A3%85-115733537/</guid><description>基于 ElementUI 封装可复用的 upload 上传组件，覆盖合同上传等业务场景的常用配置与二次封装思路。</description><pubDate>Thu, 15 Apr 2021 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;本文同步自 &lt;a href=&quot;https://blog.csdn.net/weixin_44980732/article/details/115733537&quot;&gt;CSDN · 清阿哥&lt;/a&gt;，原文发布于 2021-04-15。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;开发中遇到了个上传合同的需求，使用的是 elementui 的上传组件，项目中使用比较多，进行了封装，开发完成后总结一下，供大家和自己使用时查看。&lt;br /&gt;
&lt;strong&gt;上代码！&lt;/strong&gt;&lt;/p&gt;
&lt;h4&gt;1. 模板代码&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;template&amp;gt;
    &amp;lt;div class=&quot;uploadFile&quot; style=&quot;margin-top:15px;&quot;&amp;gt;
      &amp;lt;!-- 上传组件 --&amp;gt;
        &amp;lt;el-upload
          v-loading=&quot;loading&quot;
          multiple
          ref=&quot;upload&quot;
          :action=&quot;actionUrl&quot;
          :limit=&quot;limit&quot;
          :accept=&quot;accepted&quot;
          :auto-upload=&quot;true&quot;
          :file-list=&quot;fileListData&quot;
          :list-type=&quot;listType&quot;
          :before-upload=&quot;handleBeforeUpload&quot;
          :on-preview=&quot;handlePictureCardPreview&quot;
          :on-exceed=&quot;handleExceed&quot;
          :on-progress=&quot;handleUploadProgress&quot;
          :on-success=&quot;handleSuccess&quot;
          :on-error=&quot;handleError&quot;
          :http-request=&quot;handleRequest&quot;
          :element-loading-text=&quot;loadingText&quot;
          :class=&quot;isPreview?&apos;hide_box&apos;:&apos;&apos;&quot;
        &amp;gt;
            &amp;lt;i class=&quot;el-icon-plus&quot; slot=&quot;default&quot;&amp;gt;&amp;lt;/i&amp;gt;
            &amp;lt;div slot=&quot;file&quot; slot-scope=&quot;{file}&quot;&amp;gt;
              &amp;lt;!-- 图片显示正常的缩略图 --&amp;gt;
              &amp;lt;img
                v-if=&quot;allPicType.includes(file.name.split(&apos;.&apos;)[1])&amp;amp;&amp;amp;imgConfigEmpty&quot;
                class=&quot;el-upload-list__item-thumbnail&quot;
                :src=&quot;file.url&quot; alt=&quot;图片预览失败&quot;
              &amp;gt;
              &amp;lt;!-- 非图片显示默认缩略图 --&amp;gt;
              &amp;lt;img
                v-if=&quot;!allPicType.includes(file.name.split(&apos;.&apos;)[1])&amp;amp;&amp;amp;imgConfigEmpty&quot;
                class=&quot;el-upload-list__item-thumbnail&quot;
                src=&quot;../../assets/images/common/defaultFilePic.png&quot; alt=&quot;图片预览失败&quot;
              &amp;gt;
              &amp;lt;!-- 预览，上传，删除文件 --&amp;gt;
              &amp;lt;span class=&quot;el-upload-list__item-actions&quot;&amp;gt;
                &amp;lt;span
                  class=&quot;el-upload-list__item-preview&quot;
                  @click=&quot;handlePictureCardPreview(file)&quot;
                &amp;gt;
                  &amp;lt;i class=&quot;el-icon-zoom-in&quot;&amp;gt;&amp;lt;/i&amp;gt;
                &amp;lt;/span&amp;gt;
                &amp;lt;span class=&quot;el-upload-list__item-delete&quot;&amp;gt;
                  &amp;lt;a :href=&quot;file.url&quot; download style=&quot;color: #fff;&quot;&amp;gt;
                    &amp;lt;i class=&quot;el-icon-download&quot;&amp;gt;&amp;lt;/i&amp;gt;
                  &amp;lt;/a&amp;gt;
                &amp;lt;/span&amp;gt;
                &amp;lt;span
                  v-if=&quot;isDisabledDel&quot;
                  class=&quot;el-upload-list__item-delete&quot;
                  @click=&quot;removeFile(file)&quot;
                &amp;gt;
                  &amp;lt;i class=&quot;el-icon-delete&quot;&amp;gt;&amp;lt;/i&amp;gt;
                &amp;lt;/span&amp;gt;
              &amp;lt;/span&amp;gt;
            &amp;lt;/div&amp;gt;
            &amp;lt;!-- 上传提示语 --&amp;gt;
            &amp;lt;div slot=&quot;tip&quot; class=&quot;el-upload__tip&quot; v-if=&quot;!fileListData.length&quot;&amp;gt;
                {{ uploadTips }}
            &amp;lt;/div&amp;gt;
        &amp;lt;/el-upload&amp;gt;
        &amp;lt;!-- 预览时的文件名称样式 --&amp;gt;
        &amp;lt;template v-if=&quot;fileListData.length&quot;&amp;gt;
          &amp;lt;span v-for=&quot;(name,ind) in fileListData&quot; :key=&quot;name + ind&quot; class=&quot;fileName&quot; :style=&quot;{left: `${ind * 155 + &apos;px&apos; }`}&quot;&amp;gt;{{ name.name }}&amp;lt;/span&amp;gt;
        &amp;lt;/template&amp;gt;
        &amp;lt;!-- 上传是的文件名称样式 --&amp;gt;
        &amp;lt;template v-else&amp;gt;
          &amp;lt;span v-for=&quot;(name,ind) in fileNames&quot; :key=&quot;name + ind&quot; class=&quot;fileName&quot; :style=&quot;{left: `${ind * 155 + &apos;px&apos; }`}&quot;&amp;gt;{{ name }}&amp;lt;/span&amp;gt;
        &amp;lt;/template&amp;gt;
        &amp;lt;!-- 预览图片 --&amp;gt;
        &amp;lt;el-dialog :visible.sync=&quot;dialogVisible&quot; title=&quot;查看&quot; append-to-body &amp;gt;
            &amp;lt;img width=&quot;100%&quot; :src=&quot;dialogImageUrl&quot; alt=&quot;&quot; /&amp;gt;
        &amp;lt;/el-dialog&amp;gt;
    &amp;lt;/div&amp;gt;
&amp;lt;/template&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;参数解释：
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;multiple&lt;/code&gt;：是否支持多选文件&lt;/li&gt;
&lt;li&gt;&lt;code&gt;actionUrl&lt;/code&gt;：上传服务url&lt;/li&gt;
&lt;li&gt;&lt;code&gt;http-request&lt;/code&gt;： 自定义上传（我这里是上传到金山云）&lt;/li&gt;
&lt;li&gt;&lt;code&gt;:class=&quot;isPreview?&apos;hide_box&apos;:&apos;&apos;&quot;&lt;/code&gt; 预览隐藏上传操作&lt;/li&gt;
&lt;li&gt;&lt;code&gt;element-loading-text&lt;/code&gt; 上传加载提示语&lt;/li&gt;
&lt;li&gt;&lt;code&gt;auto-upload&lt;/code&gt; 是否在选取文件后立即进行上传&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h4&gt;2. js代码&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;import Vue from &apos;vue&apos;;
// import UUID from &apos;uuid&apos;; 上传是所需要的一个随机生成的id

export default {
  props: {
    fileListData: { // 合同文件列表
      type: Array,
      default: () =&amp;gt; {
        return [];
      }
    },
    isPreview: { // 是否需要隐藏上传操作
      type: Boolean,
      default: false
    },
    limit: { // 限制文件上传个数
      type: Number,
      default: 1
    },
    noLimitSize: { // 是否限制文件上传大小
      type: Boolean,
      default: true
    },
    fileType: { // 文件类型
      type: Array,
      default: function() {
        return [&apos;png&apos;, &apos;jpg&apos;, &apos;jpeg&apos;];
      }
    },
    url: { // 文件上传的地址
      type: String,
      default: &apos;&apos;
    },
    loadingText: { // 加载文案
      type: String,
      default: &apos;加载中...&apos;
    },
    // * 文件的大小个单位   [3,&apos;kb&apos;]或[3.&apos;m&apos;]
    fileSize: {
      type: Array,
      default: () =&amp;gt; {
        return [];
      }
    },
    listType: { // 文件列表的类型 (&apos;text&apos; &apos;picture&apos; &apos;picture-car&apos;)
      type: String,
      default: () =&amp;gt; {
        return &apos;picture-car&apos;;
      }
    },
    headers: { // 设置上传的请求头部
      type: Object,
      default: () =&amp;gt; {
        return {};
      }
    },
    isDisabledDel: { // 是否展示删除功能
      type: Boolean,
      default: false
    },
    isShowFileName: { // 是否展示文件名称
      type: Boolean,
      default: true
    }
  },
  data() {
    return {
      dialogVisible: false, // 图片预览框
      dialogImageUrl: &apos;&apos;, // 图片预览路径
      uploadTips: &apos;&apos;, // 提示文案
      accepted: &apos;&apos;, // 支持上传的文件类型
      allPicType: [&apos;jpg&apos;, &apos;png&apos;, &apos;jpeg&apos;], // * 图片使用弹窗预览
      loading: false, // 加载
      form: {}, // 上传金山云参数
      imgConfig: {}, // 上传金山云参数
      actionUrl: &apos;&apos;, // 上传服务路径
      fileLists: [], // 当前上传的文件
      fileNames: [] // 当前上传的文件名称
    };
  },
  computed: {
    imgConfigEmpty() {
      return JSON.stringify(this.imgConfig) !== &apos;{}&apos;;
    }
  },
  mounted() {
    this.getImgConfig();
    this.init();
  },
  methods: {
    init() {
      // 提示内容
      if (!this.fileType.length) {
        if (!this.fileSize.length) {
          this.uploadTips = `最多支持上传${this.limit}个` + &apos;可以上传jpg/png/jpeg/doc/docx/pdf文件&apos;;
        } else {
          this.uploadTips = `最多支持上传${this.limit}个` + &apos;可以上传jpg/png/jpeg/doc/docx/pdf文件,且文件不能大于&apos; + this.fileSize[0] + this.fileSize[1];
        }
      } else {
        this.uploadTips = `最多支持上传${this.limit}个` + &apos;可以上传&apos;;
        if (this.fileSize.length === 0) {
          for (let i = 0; i &amp;lt; this.fileType.length; i++) {
            if (i === this.fileType.length - 1) {
              this.uploadTips += this.fileType[i] + &apos;文件&apos;;
            } else {
              this.uploadTips += this.fileType[i] + &apos;/&apos;;
            }
          }
        } else {
          for (let i = 0; i &amp;lt; this.fileType.length; i++) {
            if (i === this.fileType.length - 1) {
              this.uploadTips += this.fileType[i] + &apos;文件,且文件不能大于&apos; + this.fileSize[0] + this.fileSize[1];
            } else {
              this.uploadTips += this.fileType[i] + &apos;/&apos;;
            }
          }
        }
      }
      // 允许上传的文件类型
      if (this.fileType.length) this.accepted = this.fileType.join(&apos;,&apos;);
    },
    handlePictureCardPreview(file) { // 预览文件
      if (!file.url) {
        this.$message.error(&apos;下载失败&apos;);
        return;
      }
      const type = file.name.split(&apos;.&apos;)[1];
      if ([&apos;jpg&apos;, &apos;png&apos;, &apos;jpeg&apos;].includes(type)) {
        this.dialogImageUrl = file.url;
        this.dialogVisible = true;
        return;
      }
      // 判断文件类型
      if (type === &apos;doc&apos; || type === &apos;docx&apos;) {
        // 在当前浏览器直接下载
        document.location.href = file.url;
      } else {
        // 图片在浏览器打开 新的页面展示
        window.open(file.url, &apos;预览&apos;);
      }
    },
    handlerUploadErr() { // 上传失败提示
      if (!((Object.keys(this.imgConfig)).length)) {
        this.$message.error(&apos;上传文件失败，请稍后再试&apos;);
        return false;
      }
    },
    getImgConfig() { //* 获取金山云权限
      Vue.http
        .get(&apos;url&apos;)
        .then(config =&amp;gt; {
          if (config.code === 0) {
            this.imgConfig = config.data;
            this.actionUrl = `https://${config.data.host}`;
          } else {
            this.$message.error(&apos;网络错误，文件将无法上传&apos;);
            this.loading = false;
            return false;
          }
        }).catch(err =&amp;gt; {
          this.$handleError(err, &apos;网络错误，文件将无法上传&apos;);
          this.loading = false;
        });
    },
    handleRequest(fileObj) { // * 将文件上传至金山云
      this.getImgConfig(); // ! 防止金山云凭证过期
      this.handlerUploadErr(); // 获取金山云权限失败禁止继续上传
      let imgConfig = this.imgConfig;
      let form = this.form;
      let formData = new FormData();
      // 处理参数
      formData.append(&apos;key&apos;, form.keyPrefix + &apos;/&apos; + form.key);
      formData.append(&apos;KSSAccessKeyId&apos;, form.KSSAccessKeyId);
      formData.append(&apos;policy&apos;, form.policy);
      formData.append(&apos;signature&apos;, form.signature);
      formData.append(&apos;file&apos;, form.file);
      this.loading = true;
      let hostAddress = imgConfig.host.indexOf(&apos;http&apos;) &amp;gt; -1 ? imgConfig.host : `https://${imgConfig.host}`;
      return Vue.axios({
        timeout: 0,
        headers: {
          &apos;Content-Type&apos;: &apos;multipart/form-data;&apos;
        },
        onUploadProgress: progressEvent =&amp;gt; {
          const complete = (progressEvent.loaded / progressEvent.total * 100 | 0);
          fileObj.onProgress({ percent: complete });
        }
      })
        .post(hostAddress, formData)
        .then(() =&amp;gt; {
          let url = &apos;/&apos; + imgConfig.bucket + &apos;/&apos; + form.keyPrefix + &apos;/&apos; + form.key;
          return this.getImgUrl(url).then(result =&amp;gt; {
            return {
              uid: form.file.uid,
              name: form.name,
              size: form.file.size,
              url: result.urls[0] || &apos;&apos;
            };
          });
        }).catch(err =&amp;gt; {
          this.$handleError(err, &apos;上传文件失败&apos;);
          this.loading = false;
        });
    },
    getImgUrl(url) { // * 获取文件金山云路径
      return Vue.http
        .post(&apos;url&apos;, {
          keys: [url]
        })
        .then(resp =&amp;gt; Vue.checkResp(resp))
        .catch(err =&amp;gt; {
          this.$handleError(err, &apos;上传文件失败&apos;);
          this.loading = false;
        });
    },
    handleBeforeUpload(file) { // 上传之前校验
      let imgConfig = this.imgConfig;
      let lastIdx = file.name.lastIndexOf(&apos;.&apos;);
      let keyName = &apos;&apos;;
      if (lastIdx &amp;gt; 0) {
        let fileSuffix = file.name.substring(lastIdx);
        let reg = /[\u4e00-\u9fa5]/g;
        if (!reg.test(fileSuffix)) {
          keyName = fileSuffix;
        }
      }
      this.form = {
        name: file.name,
        key: feConfig.IMG_SERVICE + UUID.v4() + keyName,
        KSSAccessKeyId: imgConfig.ak,
        policy: imgConfig.policy,
        signature: imgConfig.signature,
        keyPrefix: imgConfig.keyPrefix,
        file
      };
      if (!this.noLimitSize) {
        this.$message.warning(&apos;上传文件大小不能超过 &apos; + this.fileSize[0] + this.fileSize[1]);
        return false;
      }
      let splitName = file.name.split(&apos;.&apos;);
      let nameLength = splitName.length;
      if (this.fileType.indexOf(splitName[nameLength - 1]) &amp;lt; 0) {
        this.$message.error(&apos;上传文件格式错误!&apos;);
        return false;
      }
    },
    removeFile(file) { // 处理移出文件
      this.fileLists.splice(this.fileLists.findIndex(i =&amp;gt; i.uid === file.uid), 1);
      this.fileNames.splice(this.fileNames.findIndex(i =&amp;gt; i === file.name), 1);
      this.$emit(&apos;removeFileName&apos;, file);
    },

    handleExceed(files, fileList) { // 判断文件上传数量
      this.$message.warning(
        `当前限制最多上传 ${this.limit} 个文件，
        本次选择了 ${files.length} 个文件，
        共选择了 ${files.length + fileList.length} 个文件`
      );
    },
    handleSuccess(response, file, fileList) { // 上传文件成功之后的回调
      this.fileNames = [];
      this.$nextTick(() =&amp;gt; {
        fileList.forEach(i =&amp;gt; this.fileNames.push(i.name));
      });
      clearInterval(this.interval);
      this.fileLists = fileList;
      this.$emit(&apos;uploadSuccess&apos;, response, file, fileList);
      this.loading = false;
      this.uploadPercent = file.percentage;
      const timeOut = setTimeout(() =&amp;gt; {
        this.flag = false;
        this.uploadPercent = 0;
        clearTimeout(timeOut);
      }, 1000);
    },
    handleError() { // 处理上传失败的回调
      this.loading = false;
      this.flag = false;
      this.uploadPercent = 0;
      this.$message.error(&apos;文件&apos; + this.form.name + &apos;上传失败&apos;);
    },
    handleUploadProgress(event, file, fileList) { // 上传进度
    }
  }
};
&amp;lt;/script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;js 代码代码中的上传金山云如果用不到可以去掉这三个函数 &lt;code&gt;getImgConfig(), handleRequest(), getImgUrl()&lt;/code&gt; 直接把你的后端的 url 加到 &lt;code&gt;actionUrl&lt;/code&gt; 中即可，下载使用的浏览器自带的功能。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4&gt;3. css 代码&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;style scoped&amp;gt;
.uploadFile {
  position: relative;
}

/* vue深度选择器 &amp;lt;&amp;lt;&amp;lt; 等同于 /deep/ */
.hide_box &amp;gt;&amp;gt;&amp;gt; .el-upload--picture-card {
  display: none;
}

.fileName {
  position: absolute;
  top: 150px;
  left: 0;
  width: 150px;
  overflow: hidden;
  color: #12a1b7;
  text-align: center;
  text-overflow: ellipsis;
  white-space: nowrap;
}
&amp;lt;/style&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;4. 组件中使用&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;// 根据自己的实际开发情况添加参数；
&amp;lt;fileUpload
          :is-preview=&quot;isPreview&quot;
          :file-list-data=&quot;item.contract||[]&quot;
          :is-disabled-del=&quot;false&quot;
          :list-type=&quot;listType&quot;
          :loading-text=&quot;loadingText&quot;
          :no-limit-size=&quot;true&quot;
          @removeFileName=&quot;removeFileName&quot;
          @uploadSuccess=&quot;uploadSuccess&quot;
        /&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;5. 效果图&lt;/h4&gt;
&lt;p&gt;&lt;img src=&quot;https://i-blog.csdnimg.cn/blog_migrate/01bbffc647d6dd333e0a45e44b5b0ff3.png&quot; alt=&quot;在这里插入图片描述&quot; /&gt;&lt;br /&gt;
PS：开发中还是遇到挺多问题的，文件名称的显示写的不太好 使用&lt;code&gt;calc&lt;/code&gt; 动态调节的样式这个后面可以优化，在网上查找都是重写上传组件，时间紧迫就没有搞了，上传组件的上传进度之前使用的 &lt;code&gt;el-progress&lt;/code&gt; 但是有问题，后续再研究添加上上传进度显示，有些过的可以留言交流下。&lt;/p&gt;
&lt;p&gt;希望能够帮助到你，有问题可以随时留言交流！&lt;/p&gt;
</content:encoded></item><item><title>vue pdf 预览下载功能 vue-pdf</title><link>https://qingge-cabana.vercel.app/posts/vue-pdf-%E9%A2%84%E8%A7%88%E4%B8%8B%E8%BD%BD%E5%8A%9F%E8%83%BD-vue-pdf-115443136/</link><guid isPermaLink="true">https://qingge-cabana.vercel.app/posts/vue-pdf-%E9%A2%84%E8%A7%88%E4%B8%8B%E8%BD%BD%E5%8A%9F%E8%83%BD-vue-pdf-115443136/</guid><description>基于 vue-pdf 实现 PDF 预览与下载，记录合同类文件在线查看的接入步骤与注意点。</description><pubDate>Mon, 05 Apr 2021 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;本文同步自 &lt;a href=&quot;https://blog.csdn.net/weixin_44980732/article/details/115443136&quot;&gt;CSDN · 清阿哥&lt;/a&gt;，原文发布于 2021-04-05。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;开发新项目的时候遇到了合同的预览下载功能，之前没有做过，实现之后记录一下，方便以后查阅。&lt;/p&gt;
&lt;h4&gt;1. 下载 vue-pdf 插件&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;npm install --save vue-pdf
或者
cnpm install --save vue-pdf
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;2. 所需页面引入 vue-pdf&lt;/h4&gt;
&lt;p&gt;我的 pdf 预览是在弹窗中展示， 根据自己的项目实际情况来展示&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;!-- 模板代码 --！&amp;gt;
&amp;lt;div class=&quot;pdfPreview&quot;&amp;gt;
    &amp;lt;el-dialog
      :close-on-click-modal=&quot;false&quot;
      :visible.sync=&quot;dialogVisible&quot;
      :fullscreen=&quot;true&quot;
      :before-close=&quot;handleClose&quot;
      title=&quot;合同预览&quot;&amp;gt;
      &amp;lt;div class=&quot;agreement_picture&quot;&amp;gt;
        &amp;lt;div class=&quot;tools&quot;&amp;gt;
          &amp;lt;el-button @click=&quot;prePage&quot; class=&quot;mr10&quot;&amp;gt; 上一页&amp;lt;/el-button&amp;gt;
          &amp;lt;el-button @click=&quot;nextPage&quot; class=&quot;mr10&quot;&amp;gt; 下一页&amp;lt;/el-button&amp;gt;
          &amp;lt;span class=&quot;page&quot;&amp;gt;{{pageNum}}/{{pageTotalNum}} &amp;lt;/span&amp;gt;
          &amp;lt;el-button @click=&quot;handleClose&quot; class=&quot;mr10 fl-r btn-cancel&quot;&amp;gt; 取消&amp;lt;/el-button&amp;gt;
          &amp;lt;el-button @click=&quot;downPDF&quot; class=&quot;mr10 dowmBtn&quot;&amp;gt; 下载&amp;lt;/el-button&amp;gt;
        &amp;lt;/div&amp;gt;
        &amp;lt;pdf ref=&quot;pdf&quot; 
          :src=&quot;src&quot; 
          :page=&quot;pageNum&quot;
          @page-loaded=&quot;pageLoaded($event)&quot; 
          @num-pages=&quot;pageTotalNum=$event&quot; 
          @error=&quot;pdfError($event)&quot; 
          @link-clicked=&quot;page = $event&quot;&amp;gt;
        &amp;lt;/pdf&amp;gt;
      &amp;lt;/div&amp;gt;
    &amp;lt;/el-dialog&amp;gt;
&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;3. js 代码&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;import pdf from &apos;vue-pdf&apos;;

export default {
  name: &apos;PdfPreview&apos;,
  components: {
    pdf
  },

  props: {
    dialogVisible: {
      type: Boolean,
      default: false
    }
  },

  data() {
    return {
      url: &quot;http://storage.xuetangx.com/public_assets/xuetangx/PDF/PlayerAPI_v1.0.6.pdf&quot;, // pdf 地址（网上的pdf例子地址，此地址就是后端返回的真正需要预览的pdf地址）
      src: &apos;&apos;, // 预览地址
      pageNum: 1, // 当前页码
      pageTotalNum: 1, // 总页数
    };
  },

  mounted () {
    // 有时PDF文件地址会出现跨域的情况,这里最好处理一下
    var url = this.url
    this.src = pdf.createLoadingTask(url);
  },

  methods: {
    // 上一页函数，
    prePage() {
      var page = this.pageNum
      page = page &amp;gt; 1 ? page - 1 : this.pageTotalNum
      this.pageNum = page
    },
    // 下一页函数
    nextPage() {
      var page = this.pageNum
      page = page &amp;lt; this.pageTotalNum ? page + 1 : 1
      this.pageNum = page
    },
    // 页面加载回调函数，其中e为当前页数
    pageLoaded(e) {
      this.curPageNum = e
    },
    // 抛出错误的回调函数。
    pdfError(error) {
      console.error(error)
    },
    downPDF() { // 下载 pdf
				var url = this.url
				var tempLink = document.createElement(&quot;a&quot;);
				tempLink.style.display = &quot;none&quot;;
				tempLink.href = url;
				tempLink.setAttribute(&quot;download&quot;, &apos;XXX.pdf&apos;);
				if (typeof tempLink.download === &quot;undefined&quot;) {
					tempLink.setAttribute(&quot;target&quot;, &quot;_blank&quot;);
				}
				document.body.appendChild(tempLink);
				tempLink.click();
				document.body.removeChild(tempLink);
        this.handleClose();
			},
    handleClose() {
      this.$emit(&apos;update:dialogVisible&apos;, false);
    }
  }
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;效果预览&lt;br /&gt;
&lt;img src=&quot;https://i-blog.csdnimg.cn/blog_migrate/8fcbf77dd95a9e41f84f2cc452caba14.png&quot; alt=&quot;在这里插入图片描述&quot; /&gt;&lt;br /&gt;
如果对你有所帮助，欢迎大家留言交流！&lt;/p&gt;
</content:encoded></item><item><title>Elementui el-input 输入框校验以及表单校验</title><link>https://qingge-cabana.vercel.app/posts/elementui-el-input-%E8%BE%93%E5%85%A5%E6%A1%86%E6%A0%A1%E9%AA%8C%E4%BB%A5%E5%8F%8A%E8%A1%A8%E5%8D%95%E6%A0%A1%E9%AA%8C-115219944/</link><guid isPermaLink="true">https://qingge-cabana.vercel.app/posts/elementui-el-input-%E8%BE%93%E5%85%A5%E6%A1%86%E6%A0%A1%E9%AA%8C%E4%BB%A5%E5%8F%8A%E8%A1%A8%E5%8D%95%E6%A0%A1%E9%AA%8C-115219944/</guid><description>汇总 ElementUI el-input 与表单校验的常用正则与实践，覆盖数字、整数、手机号等日常输入限制。</description><pubDate>Thu, 25 Mar 2021 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;本文同步自 &lt;a href=&quot;https://blog.csdn.net/weixin_44980732/article/details/115219944&quot;&gt;CSDN · 清阿哥&lt;/a&gt;，原文发布于 2021-03-25。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4&gt;一. 常用的 element-ui el-input 输入框&lt;/h4&gt;
&lt;h5&gt;1. 过滤字母e，在js中属于数字，但是正则匹配 \d 是拦不住字母e 的&lt;/h5&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;el-input type=&quot;number&quot; placeholder=&quot;请输入&quot; min=&quot;1&quot; onKeypress=&quot;return (/[\d]/.test(String.fromCharCode(event.keyCode || event.which))) || event.which === 8&quot; v-model.number=&quot;count&quot;&amp;gt;&amp;lt;/el-input&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h5&gt;2. 只能输入正整数&lt;/h5&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;el-input type=&quot;number&quot; placeholder=&quot;请输入&quot; min=&quot;1&quot; oninput =&quot;value=value.replace(/[^\d]/g,&apos;&apos;)&quot;  v-model.number=&quot;count&quot;&amp;gt;&amp;lt;/el-input&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h5&gt;3. 只允许输入数字和小数 / 数字和空格&lt;/h5&gt;
&lt;pre&gt;&lt;code&gt;oninput =&quot;value=value.replace(/[^0-9.]/g,&apos;&apos;)&quot;
oninput =&quot;value=value.replace(/^[\d\s]+$/g,&apos;&apos;)&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;4. 只允许输入正整数且不能以0开头&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;方法-：
&amp;lt;el-input
  v-model=&quot;scope.row.weight&quot;
  oninput=&quot;value = Number(value) || 0&quot;
&amp;gt;
方法二：
&amp;lt;el-input
  v-model=&quot;scope.row.weight&quot;
  oninput=&quot;value=value.replace(/\D|^0/g, &apos;&apos;)&quot;
&amp;gt;
方法三：
&amp;lt;el-input
  v-model=&quot;scope.row.weight&quot;
  oninput=&quot;value=value.replace(/[^\d]|^[0]/g, &apos;&apos;)&quot;
&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h5&gt;4. 允许输入小数点后几位&lt;/h5&gt;
&lt;pre&gt;&lt;code&gt;// 保留一位小数
oninput=&quot;if(isNaN(value)) { value = parseFloat(value) } if(value.indexOf(&apos;.&apos;)&amp;gt;0){value=value.slice(0,value.indexOf(&apos;.&apos;)+2)}&quot; 
 
// 若需要保留N位小数，则把 2 改为 1 + n即可
&lt;/code&gt;&lt;/pre&gt;
&lt;h5&gt;5. 设置范围，最大值，最小值&lt;/h5&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;el-input min=&quot;0&quot; max=&quot;100&quot; type=&quot;number&quot; @input=&quot;numberChange($event, 100)&quot; @change=&quot;numberChange($event, 100)&quot; onKeypress=&quot;return (/[\d]/.test(String.fromCharCode(event.keyCode || event.which))) || event.which === 8&quot; v-model.number=&quot;count&quot;&amp;gt;&amp;lt;/el-input&amp;gt; 

numberChange (val, max) {
   this.$nextTick(() =&amp;gt; {
      this.count = Math.min(parseInt(val), max)
    })
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h5&gt;6. form 表单中校验输入框只能输入数字和两位小数&lt;/h5&gt;
&lt;pre&gt;&lt;code&gt;rules: {
        giveHour: [
          { required: true, message: &apos;请输入客户退费金额&apos;, trigger: &apos;blur&apos; },
          { pattern: /(^[1-9]([0-9]+)?(\.[0-9]{1,2})?$)|(^(0){1}$)|(^[0-9]\.[0-9]([0-9])?$)/, message: &apos;请输入正确额格式,可保留两位小数&apos; }
        ]
      }
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;7. 禁止 / 只允许 输入中文&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;onkeyup=&quot;this.value=this.value.replace(/[\u4E00-\u9FA5]/g,&apos;&apos;)&quot;
onkeyup=&quot;value=value.replace(/[^\u4E00-\u9FA5]/g,&apos;&apos;)&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;8. 只允许输入数字和英文&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;el-input
	 v-model.trim=&quot;form.userNumber&quot;
	 placeholder=&quot;请输入用户编号&quot;
	 clearable
	 onkeyup=&quot;this.value=this.value.replace(/[^\w]/g,&apos;&apos;)&quot;
&amp;gt;&amp;lt;/el-input&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;9. 禁止输入特殊字符&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;el-input oninput=&quot;this.value=this.value.replace(/[^u4e00-u9fa5w]/g,&apos;&apos;)&quot;&amp;gt;&amp;lt;/el-input&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;10. 只允许输入英文&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;el-input oninput = &quot;value=value.replace(/[^\a-\z\A-\Z]/g,&apos;&apos;)&quot;&amp;gt;&amp;lt;/el-input&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;11. 常见表单校验&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;校验方法可以封装到 util.js 里面&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code&gt;// utils.js

// 全局函数
export function validateMobile(str) {
  // 检查手机号码格式
  return /^((13[0-9])|(14[5-9])|(15([0-3]|[5-9]))|(16[6-7])|(17[1-8])|(18[0-9])|(19[1|3])|(19[5|6])|(19[8|9]))\d{8}$/.test(
    str,
  );
}

export function validateEmail(str) {
  // 检查邮箱格式
  return /^([A-Za-z0-9_\-.])+@([A-Za-z0-9_\-.])+\.([A-Za-z]{2,4})$/.test(str);
}

export function validateBankCard(str) {
  // 检查银行卡
  return /^[1-9]\d{9,29}$/.test(str);
}

export function validatePhone(str) {
  // 检查电话格式
  return /^(0\d{2,4}-)?\d{8}$/.test(str);
}

export function validateQQ(str) {
  // 检查QQ格式
  return /^[1-9][0-9]{4,}$/.test(str);
}

// 检查验证码格式
export function validateSmsCode(str) {
  return /^\d4$/.test(str);
}
// 校验 URL
export function validURL(url) {
  const reg =
    /^(https?|ftp):\/\/([a-zA-Z0-9.-]+(:[a-zA-Z0-9.&amp;amp;%$-]+)*@)*((25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9][0-9]?)(\.(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])){3}|([a-zA-Z0-9-]+\.)*[a-zA-Z0-9-]+\.(com|edu|gov|int|mil|net|org|biz|arpa|info|name|pro|aero|coop|museum|[a-zA-Z]{2}))(:[0-9]+)*(\/($|[a-zA-Z0-9.,?&apos;\\+&amp;amp;%$#=~_-]+))*$/
  return reg.test(url)
}

// 校验特殊字符
export function specialCharacter(str) {
  const reg = new RegExp(
    // eslint-disable-next-line quotes
    &quot;[`~!@#$^&amp;amp;*()=|{}&apos;:;&apos;,\\[\\]&amp;lt;&amp;gt;《》/?~！@#￥……&amp;amp;*（）——|{}【】‘；：”“&apos;。，、？ ]&quot;
  )
  return reg.test(str)
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;12. 输入非数字组合(登录账号6-16位)&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;// 非纯数字非纯字母 /^(?![0-9]+$)[0-9A-Za-z|a-zA-Z]{6,16}$/
    let userNameReg = /^(?![0-9]+$)[0-9A-Za-z|a-zA-Z]{6,16}$/;
      if (!userNameReg.test(form.userName)) {
        this.$message.warning(&apos;请输入6-16位的非纯数字登录账号~&apos;);
        return false;
      }
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;13. 设置密码校验(限制输入 8-20位 数字 字母 特殊符号任意两种类型组合)&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;  let passwordReg = /^(?![0-9]+$)(?![a-z]+$)(?![A-Z]+$)(?!([^(0-9a-zA-Z)])+$).{8,20}$/;
  if (!passwordReg.test(form.password)) {
      this.$message.warning(&apos;请输入正确的密码格式~&apos;);
      return false;
    }
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;13. 只允许输入中文，英文，数字 / 空格 / 小数点&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;el-input oninput = &quot;value=value.replace(/[^\a-\z\A-\Z0-9\u4E00-\u9FA5]/g,&apos;&apos;)&quot;&amp;gt;&amp;lt;/el-input&amp;gt;
&amp;lt;el-input oninput = &quot;value=value.replace(/[^\a-\z\A-\Z0-9\u4E00-\u9FA5\ ]/g,&apos;&apos;)&quot;&amp;gt;&amp;lt;/el-input&amp;gt;
&amp;lt;el-input oninput = &quot;value=value.replace(/[^\a-\z\A-\Z0-9\u4E00-\u9FA5\.]/g,&apos;&apos;)&quot;&amp;gt;&amp;lt;/el-input&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;14. 空格校验&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;// 去除所有的空格：
&amp;lt;el-input oninput = &quot;value=value.replace(/\s*/g,&quot;&quot;)&quot;&amp;gt;&amp;lt;/el-input&amp;gt;

// 去除两头的空格：
方法一：&amp;lt;el-input oninput = &quot;value=value.replace(/^\s*|\s*$/g,&quot;&quot;)&quot;&amp;gt;&amp;lt;/el-input&amp;gt;
方法二：&amp;lt;el-input v-model.trim=&quot;&quot;&amp;gt;&amp;lt;/el-input&amp;gt;

// 去除左侧的空格：
&amp;lt;el-input oninput = &quot;value=value.replace(/^\s*/g,&quot;&quot;)&quot;&amp;gt;&amp;lt;/el-input&amp;gt;
　　
// 去除右侧的空格：
&amp;lt;el-input oninput = &quot;value=value.replace(/(\s*$)/g,&quot;&quot;)&quot;&amp;gt;&amp;lt;/el-input&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;Tips: 最近测试给提了个bug，window 系统电脑，使用 oninput 方法进行输入框正则校验，如果疯狂进行输入的话会出现当前输入框的双向绑定失效，导致输入框无法正常输入值。这时候推荐使用 @input 方法可以避免这个方法，还有一个办法是失焦的时候重新进行一次赋值操作也可以解决。mac系统没有出现这个问题…&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;持续更新中，欢迎大家留言交流！&lt;/strong&gt;&lt;/p&gt;
</content:encoded></item><item><title>elementui el-dialog 作为子组件使用关闭时报错</title><link>https://qingge-cabana.vercel.app/posts/elementui-el-dialog-%E4%BD%9C%E4%B8%BA%E5%AD%90%E7%BB%84%E4%BB%B6%E4%BD%BF%E7%94%A8%E5%85%B3%E9%97%AD%E6%97%B6%E6%8A%A5%E9%94%99-115122783/</link><guid isPermaLink="true">https://qingge-cabana.vercel.app/posts/elementui-el-dialog-%E4%BD%9C%E4%B8%BA%E5%AD%90%E7%BB%84%E4%BB%B6%E4%BD%BF%E7%94%A8%E5%85%B3%E9%97%AD%E6%97%B6%E6%8A%A5%E9%94%99-115122783/</guid><description>排查 el-dialog 作为子组件关闭时报错的原因，给出父子通信与销毁时机相关的修复写法。</description><pubDate>Tue, 23 Mar 2021 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;本文同步自 &lt;a href=&quot;https://blog.csdn.net/weixin_44980732/article/details/115122783&quot;&gt;CSDN · 清阿哥&lt;/a&gt;，原文发布于 2021-03-23。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4&gt;1. 弹窗作为子组件&lt;/h4&gt;
&lt;p&gt;父组件通过点击事件来打开弹窗，然而，在弹窗中点击窗外之外的空白区域或者右上角叉时候一直报错:&lt;br /&gt;
&lt;img src=&quot;https://i-blog.csdnimg.cn/blog_migrate/c01771cffeae695fcf352fefd68085a5.png&quot; alt=&quot;![在这里插入图片描述](https://img-blog.csdnimg.cn/2021032314353958.p&quot; /&gt;&lt;br /&gt;
翻译：&lt;strong&gt;避免直接改变一个道具，因为当父组件重新渲染时，这个值会被覆盖。相反，使用基于道具值的数据或计算属性。正在发生突变的道具:“editVisible”&lt;/strong&gt;&lt;/p&gt;
&lt;h4&gt;2. 原因分析&lt;/h4&gt;
&lt;p&gt;从翻译来看，子组件中不能直接更改父组件传递过来的数据，然后就按照提示将传递过来的数据在 data 和 computed 都重新复制一遍，仍然报同样的错。&lt;/p&gt;
&lt;h4&gt;3. 解决方法&lt;/h4&gt;
&lt;p&gt;最后通过一行代码顺利解决，在弹窗上加上 &lt;strong&gt;:before-close&lt;/strong&gt; 方法顺利解决！&lt;br /&gt;
&lt;strong&gt;再次分析原因：&lt;/strong&gt;&lt;br /&gt;
通过该方法关闭弹窗的时候，直接通过父组件来改变关闭，该方法是在关闭弹窗前的回调函数，不在子组件改变弹窗的状态就不会报错了！&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// 父组件
&amp;lt;editDialog :edit-visible.sync=&quot;editVisible&quot;&amp;gt;
&amp;lt;/editDialog&amp;gt;

// 子组件
&amp;lt;el-dialog
      title=&quot;我的线索&quot;
      :visible.sync=&quot;editVisible&quot;
      :before-close=&quot;handleClose&quot;
      top=&quot;5vh&quot;
      width=&quot;80%&quot;&amp;gt;
&amp;lt;/el-dialog&amp;gt;

props: {
    editVisible: {
      type: Boolean,
      default: false
    },
};

 handleClose() {
    this.$emit(&quot;update:editVisible&quot;,!this.editVisible);
 },
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这么一个简单的问题，解决之路曲曲折折，希望对你有所帮助，有问题可以留言交流！&lt;/p&gt;
</content:encoded></item><item><title>elementui dialog组件固定高度</title><link>https://qingge-cabana.vercel.app/posts/elementui-dialog%E7%BB%84%E4%BB%B6%E5%9B%BA%E5%AE%9A%E9%AB%98%E5%BA%A6-114889334/</link><guid isPermaLink="true">https://qingge-cabana.vercel.app/posts/elementui-dialog%E7%BB%84%E4%BB%B6%E5%9B%BA%E5%AE%9A%E9%AB%98%E5%BA%A6-114889334/</guid><description>解决 ElementUI Dialog 高度不固定的问题，通过样式约束实现内容区可滚动、整体高度稳定的弹窗。</description><pubDate>Tue, 16 Mar 2021 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;本文同步自 &lt;a href=&quot;https://blog.csdn.net/weixin_44980732/article/details/114889334&quot;&gt;CSDN · 清阿哥&lt;/a&gt;，原文发布于 2021-03-16。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;弹窗高度过大，想设置个自适应的高度，固定头尾&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/deep/.el-dialog {
  margin: 5vh auto !important;
}

/deep/ .el-dialog__body {
  height: 70vh;
  overflow: auto;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;margin，height 可根据实际情况设置大小。&lt;br /&gt;
效果图：&lt;br /&gt;
&lt;img src=&quot;https://i-blog.csdnimg.cn/blog_migrate/a0f5f3e21ee69b1741e8fbcef99fd5b8.png&quot; alt=&quot;在这里插入图片描述&quot; /&gt;&lt;br /&gt;
欢迎大家留言交流！&lt;/p&gt;
</content:encoded></item><item><title>最全数组方法（ES5,ES6）总结</title><link>https://qingge-cabana.vercel.app/posts/%E6%9C%80%E5%85%A8%E6%95%B0%E7%BB%84%E6%96%B9%E6%B3%95es5es6%E6%80%BB%E7%BB%93-113520143/</link><guid isPermaLink="true">https://qingge-cabana.vercel.app/posts/%E6%9C%80%E5%85%A8%E6%95%B0%E7%BB%84%E6%96%B9%E6%B3%95es5es6%E6%80%BB%E7%BB%93-113520143/</guid><description>系统总结 ES5 / ES6 数组方法，覆盖遍历、转换、查找、聚合等高频 API 与使用示例。</description><pubDate>Mon, 01 Feb 2021 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;本文同步自 &lt;a href=&quot;https://blog.csdn.net/weixin_44980732/article/details/113520143&quot;&gt;CSDN · 清阿哥&lt;/a&gt;，原文发布于 2021-02-01。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;总结一些经常用到的数组的方法，便于自己和大家开发使用中的查阅。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;arr.push()&lt;/strong&gt; 从后面添加元素，返回值为添加完后的数组的长度&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;let arr = [1,2,3,4,5]
console.log(arr.push(5))   // 6
console.log(arr) // [1,2,3,4,5,5]
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;arr.pop()&lt;/strong&gt; 从后面删除元素，只能是一个，返回值是删除的元素&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;let arr = [1,2,3,4,5]
console.log(arr.pop())     // 5
console.log(arr)  //[1,2,3,4]
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;arr.shift()&lt;/strong&gt; 从前面删除元素，只能删除一个 返回值是删除的元素&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;let arr = [1,2,3,4,5]
console.log(arr.unshift(2))    // 6
console.log(arr)  //[2,1,2,3,4,5]
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;arr.splice(i,n)&lt;/strong&gt; 删除从i(索引值)开始之后的那个元素。返回值是删除的元素 (参数： i 索引值 n 个数)&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;let arr = [1,2,3,4,5]
console.log(arr.splice(2,2))     //[3,4]
console.log(arr)    // [1,2,5]
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;arr.concat()&lt;/strong&gt; 连接两个数组 返回值为连接后的新数组&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;let arr = [1,2,3,4,5]
console.log(arr.concat([1,2]))  // [1,2,3,4,5,1,2]
console.log(arr)   // [1,2,3,4,5]
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;str.split(sep,many)&lt;/strong&gt; 将字符串转化为数组、截取字符串(sep:必需。字符串或正则表达式 many: 可选,该参数可指定返回的数组的最大长度)&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;let str = &apos;123456&apos;
console.log(str.split(&apos;&apos;)) // [&quot;1&quot;, &quot;2&quot;, &quot;3&quot;, &quot;4&quot;, &quot;5&quot;, &quot;6&quot;]

&quot;2:3:4:5&quot;.split(&quot;:&quot;)	//将返回[&quot;2&quot;, &quot;3&quot;, &quot;4&quot;, &quot;5&quot;]
&quot;|a|b|c&quot;.split(&quot;|&quot;)	//将返回[&quot;&quot;, &quot;a&quot;, &quot;b&quot;, &quot;c&quot;]

&quot;hello&quot;.split(&quot;&quot;, 3)	//可返回 [&quot;h&quot;, &quot;e&quot;, &quot;l&quot;]
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;arr.sort()&lt;/strong&gt; 将数组进行排序,返回值是排好的数组，默认是按照最左边的数字进行排序，不是按照数字大小排序的，见例子。&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;// 1.数字数组
let arr = [2,10,6,1,4,22,3]
console.log(arr.sort())   // [1, 10, 2, 22, 3, 4, 6]
let arr1 = arr.sort((a, b) =&amp;gt; a - b)  
console.log(arr1)   // [1, 2, 3, 4, 6, 10, 22]
let arr2 = arr.sort((a, b) =&amp;gt; b-a)  
console.log(arr2)  // [22, 10, 6, 4, 3, 2, 1]

// 2. 字符串数组
var fruits = [&quot;Banana&quot;, &quot;Orange&quot;, &quot;Apple&quot;, &quot;Mango&quot;];
fruits.sort();//默认升序　　Apple,Banana,Mango,Orange
fruits.sort().reverse();//降序　　Orange,Mango,Banana,Apple

//3. 数组对象排序 根据 distance 字段进行排序
var shoplist = [{
  &quot;name&quot;: &quot;上海&quot;,
  &quot;distance&quot;: 14307
}, {
  &quot;name&quot;: &quot;深圳&quot;,
  &quot;distance&quot;: 16606
}, {
  &quot;name&quot;: &quot;广州&quot;,
  &quot;distance&quot;: 18062
}, {
  &quot;name&quot;: &quot;郑州&quot;,
  &quot;distance&quot;: 18468
}]
// 实现 从近到远
shoplist.sort((a,b)=&amp;gt;{return a.distance-b.distance})
// 从远到近
shoplist.sort((a,b)=&amp;gt;{return a.distance-b.distance})
shoplist.reverse()
// 结果如下图
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://i-blog.csdnimg.cn/blog_migrate/8f9dab418654c58df3a7a9705411a08b.png&quot; alt=&quot;在这里插入图片描述&quot; /&gt;&lt;br /&gt;
&lt;img src=&quot;https://i-blog.csdnimg.cn/blog_migrate/2d338ec7f08ef1b011e1dc07774e4ae7.png&quot; alt=&quot;在这里插入图片描述&quot; /&gt;&lt;br /&gt;
8. &lt;strong&gt;arr.reverse()&lt;/strong&gt; 将数组反转,返回值是反转后的数组&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;let arr = [1,2,3,4,5]
console.log(arr.reverse())    // [5,4,3,2,1]
console.log(arr)    // [5,4,3,2,1]
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;arr.slice(start,end)&lt;/strong&gt; 切去索引值start到索引值end的数组，不包含end索引的值，返回值是切出来的数组&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;let arr = [1,2,3,4,5]
console.log(arr.slice(1,3))   // [2,3]
console.log(arr)    //  [1,2,3,4,5]
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;arr.forEach(callback) 遍历数组,无return 即使有return，也不会返回任何值，并且会影响原来的数组&lt;br /&gt;
callback的参数： value --当前索引的值 index --索引 array --原数组&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;let arr = [1,2,3,4,5]
arr.forEach( (value,index,array)=&amp;gt;{
        value = value * 2
        console.log(`value:${value}    index:${index}     array:${array}`)
    })   
console.log(arr)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;打印结果如下图：&lt;br /&gt;
&lt;img src=&quot;https://i-blog.csdnimg.cn/blog_migrate/7aae57c89a5b621a588f553694b1dcfc.png&quot; alt=&quot;在这里插入图片描述&quot; /&gt;&lt;br /&gt;
11. &lt;strong&gt;arr.map(callback)&lt;/strong&gt; 映射数组(遍历数组),有return 返回一个新数组 。&lt;br /&gt;
callback的参数： value --当前索引的值 index --索引 array --原数组&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;let arr = [1,2,3,4,5]
arr.map((value,index,array) =&amp;gt; {
        value = value * 2
        console.log(`value:${value}    index:${index}     array:${array}`)
})   
console.log(arr)

var arr1 = [1,2,3,4]; 
var res1 = arr1.map((item,index,arr)=&amp;gt;{ 
 item = item * 3; 
 return item; 
})
console.log(arr1); // [1,2,3,4]
console.log(res1); // [3,6,9,12]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;ps: arr.forEach()和arr.map()的区别&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;arr.forEach()是和for循环一样，arr.map()是修改数组中的项的数据，并返回新的数据。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;arr.forEach() 没有return arr.map() 有return。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;arr.filter(callback)&lt;/strong&gt; 过滤数组，返回一个满足要求的数组&lt;br /&gt;
callback的参数： value --当前索引的值 index --索引&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;let arr = [1,2,3,4,5]
let arr1 = arr.filter((value, index) =&amp;gt; value&amp;lt;3)
console.log(arr1)    // [1, 2]
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;arr.every(callback)&lt;/strong&gt; 依据判断条件，数组的元素是否全满足，若满足则返回ture&lt;br /&gt;
callback的参数： value --当前索引的值 index --索引&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;let arr = [1,2,3,4,5]
let arr1 = arr.every( (value, index) =&amp;gt;value&amp;lt;3)
console.log(arr1) // false
let arr2 = arr.every( (value, index) =&amp;gt;value&amp;lt;6)
console.log(arr2)  // true
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;arr.every(callback)&lt;/strong&gt; 依据判断条件，数组的元素是否全满足，若满足则返回ture&lt;br /&gt;
callback的参数： value --当前索引的值 index --索引&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;let arr = [1,2,3,4,5]
let arr1 = arr.every( (value, index) =&amp;gt;value&amp;lt;3)
console.log(arr1) // false
let arr2 = arr.every( (value, index) =&amp;gt;value&amp;lt;6)
console.log(arr2)  // true
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;arr.some()&lt;/strong&gt; 依据判断条件，数组的元素是否有一个满足，若有一个满足则返回ture&lt;br /&gt;
callback的参数： value --当前索引的值 index --索引&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;let arr = [1,2,3,4,5]
let arr1 = arr.some( (value, index) =&amp;gt;value&amp;lt;3)
console.log(arr1) // true
let arr2 = arr.some( (value, index) =&amp;gt;value&amp;gt;6)
console.log(arr2) // false
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;arr.reduce(callback, initialValue)&lt;/strong&gt; 迭代数组的所有项，累加器，数组中的每个值（从左到右）合并，最终计算为一个值&lt;br /&gt;
参数： callback:&lt;br /&gt;
　　 pre 必选 --上一次调用回调返回的值，或者是提供的初始值（initialValue）&lt;br /&gt;
　　　 cur 必选 --数组中当前被处理的数组项&lt;br /&gt;
　　　 index 可选 --当前数组项在数组中的索引值&lt;br /&gt;
　　　 array 可选 --原数组&lt;br /&gt;
initialValue: 可选 --初始值&lt;br /&gt;
实行方法：回调函数第一次执行时，preValue 和 curValue 可以是一个值，如果 initialValue 在调用 reduce() 时被提供，那么第一个 preValue 等于 initialValue ，并且curValue 等于数组中的第一个值；如果initialValue 未被提供，那么preValue 等于数组中的第一个值.&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;// 求数组之和
let arr = [0,1,2,3,4]
let arr1 = arr.reduce((preValue, curValue) =&amp;gt; 
   preValue + curValue
)
console.log(arr1)    // 10

// 求数组项最大值
var max = arr.reduce(function (prev, cur) {
   return Math.max(prev,cur);
});

// 数组去重
var newArr = arr.reduce(function (prev, cur) {
   prev.indexOf(cur) === -1 &amp;amp;&amp;amp; prev.push(cur);
   return prev;
},[]);

// 求字符串出现的最多的次数
const str = &apos;sfhjasfjgfasjuwqrqadqeiqsajsdaiwqdaklldflas-cmxzmnha&apos;;
const res = str.split(&apos;&apos;).reduce((accumulator, cur) =&amp;gt; {accumulator[cur] ? accumulator[cur]++ : accumulator[cur] = 1; return accumulator;}, {})

// 将多维数组转化为一维
let arr = [[0, 1], [2, 3], [4,[5,6,7]]]
const newArr = function(arr){
  return arr.reduce((pre,cur)=&amp;gt;pre.concat(Array.isArray(cur)?newArr(cur):cur),[])
}
console.log(newArr(arr)); //[0, 1, 2, 3, 4, 5, 6, 7]
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;arr.reduceRight(callback, initialValue)&lt;/strong&gt; 与arr.reduce()功能一样，不同的是，reduceRight()从数组的末尾向前将数组中的数组项做累加。&lt;br /&gt;
实行方法：reduceRight()首次调用回调函数callbackfn时，prevValue 和 curValue 可以是两个值之一。如果调用 reduceRight() 时提供了 initialValue 参数，则 prevValue 等于 initialValue，curValue 等于数组中的最后一个值。如果没有提供 initialValue 参数，则 prevValue 等于数组最后一个值， curValue 等于数组中倒数第二个值。&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;let arr = [0,1,2,3,4]
let arr1 = arr.reduceRight((preValue, curValue) =&amp;gt; 
    preValue + curValue
)
console.log(arr1)    // 10
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;推荐链接：&lt;a href=&quot;https://www.w3cplus.com/javascript/array-part-8.html&quot;&gt;reduce和reduceRight讲解&lt;/a&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;arr.indexOf()&lt;/strong&gt; 查找某个元素的索引值，若有重复的，则返回第一个查到的索引值若不存在，则返回 -1&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;let arr = [1,2,3,4,5,2]
let arr1 = arr.indexOf(2)
console.log(arr1)  // 1
let arr2 = arr.indexOf(9)
console.log(arr2)  // -1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;ES6方法&lt;/strong&gt;&lt;br /&gt;
19. &lt;strong&gt;arr.lastIndexOf()&lt;/strong&gt; 和arr.indexOf()的功能一样，不同的是从后往前查找&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;let arr = [1,2,3,4,5,2]
let arr1 = arr.lastIndexOf(2)
console.log(arr1)  // 5
let arr2 = arr.lastIndexOf(9)
console.log(arr2)  // -1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;20 &lt;strong&gt;Array.from()&lt;/strong&gt; 将伪数组变成数组，就是只要有length的就可以转成数组.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;let str = &apos;12345&apos;
console.log(Array.from(str))    // [&quot;1&quot;, &quot;2&quot;, &quot;3&quot;, &quot;4&quot;, &quot;5&quot;]
let obj = {0:&apos;a&apos;,1:&apos;b&apos;,length:2}
console.log(Array.from(obj))   // [&quot;a&quot;, &quot;b&quot;]
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Array.of()&lt;/strong&gt; 将一组值转换成数组，类似于声明数组&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;let str = &apos;11&apos;
console.log(Array.of(str))   // [&apos;11&apos;]
// 类似于
console.log(new Array(&apos;11&apos;))  // [&apos;11]

// 区别
console.log(new Array(2))   //[empty × 2]  是个空数组
console.log(Array.of(2))    // [2]
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;arr.find(callback)&lt;/strong&gt; 找到第一个符合条件的数组成员&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;let arr = [1,2,3,4,5,2,4]
let arr1 = arr.find((value, index, array) =&amp;gt;value &amp;gt; 2)
console.log(arr1)   // 3
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;arr.findIndex(callback)&lt;/strong&gt; 找到第一个符合条件的数组成员的索引值&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;let arr = [1,2,3,4,5]
let arr1 = arr.findIndex((value, index, array) =&amp;gt; value &amp;gt; 3)
console.log(arr1)  // 3
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;arr.fill(target, start, end)&lt;/strong&gt; 使用给定的值，填充一个数组,ps:填充完后会改变原数组&lt;br /&gt;
　　参数： target – 待填充的元素&lt;br /&gt;
　　　　　 start – 开始填充的位置-索引&lt;br /&gt;
　　　　 end – 终止填充的位置-索引（不包括该位置)&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;let arr = [1,2,3,4,5]
let arr1 = arr.fill(5)
console.log(arr1)  // [5, 5, 5, 5, 5]
console.log(arr)   // [5, 5, 5, 5, 5]
let arr2 = arr.fill(5,2)
console.log(arr2) // [1,2,5,5,5]
let arr3 = arr.fill(5,1,3)
console.log(arr3) // [1,5,5,5,5]
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;arr.includes()&lt;/strong&gt; 判断数中是否包含给定的值&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;let arr = [1,2,3,4,5]
let arr1 = arr.includes(2)  
console.log(arr1)   // ture
let arr2 = arr.includes(9) 
console.log(arr2)    // false
let arr3 = [1,2,3,NaN].includes(NaN)
console.log(arr3)  // true

ps:与indexOf()的区别：
1 indexOf()返回的是数值，而includes()返回的是布尔值
2 indexOf() 不能判断NaN,null,undefined，返回为-1 ，includes()则可以判断.
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;arr.keys()&lt;/strong&gt; 遍历数组的键名&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;let arr = [1,2,3,4]
let arr2 = arr.keys()
for (let key of arr2) {
    console.log(key);   // 0,1,2,3
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;arr.values()&lt;/strong&gt; 遍历数组键值&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;let arr = [1,2,3,4]
let arr1 = arr.values()
for (let val of arr1) {
     console.log(val);   // 1,2,3,4
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;arr.entries()&lt;/strong&gt; 遍历数组的键名和键值&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;let arr = [1,2,3,4]
let arr1 = arr.entries()
for (let e of arr1) {
    console.log(e);   // [0,1] [1,2] [2,3] [3,4]
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;数组去重&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;var arr = [1,1,2,3,4,5,6,6]
ES6
方法一：
var b = Array.from(new Set(arr));
console.log(b) // [1,2,3,4,5,6]
方法二：
var c = [...new Set(arr)];
console.log(c) // [1,2,3,4,5,6]
ES5：
方法三：
function newArr(arr){
    for(var i=0;i&amp;lt;arr.length;i++){
        for(var j=i+1;j&amp;lt;arr.length;j++){
            if(arr[i]==arr[j]){ 
            //如果第一个等于第二个，splice方法删除第二个
            arr.splice(j,1);
            j--;
            }
        }
    }
    return arr;
}
var arr = [1,1,2,5,6,3,5,5,6,8,9,8];
console.log(newArr(arr)) // [1,2,5,6,3,8,9]
方法四：
function newArr(array){ 
    //一个新的数组 
    var arrs = []; 
    //遍历当前数组 
    for(var i = 0; i &amp;lt; array.length; i++){ 
        //如果临时数组里没有当前数组的当前值，则把当前值push到新数组里面 
        if (arrs.indexOf(array[i]) == -1){ 
            arrs.push(array[i])
        }; 
    } 
    return arrs; 
}
var arr = [1,1,2,5,5,6,8,9,8];
console.log(newArr(arr)) // [1,2,5,6,8,9]
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;排序&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;方法一：冒泡排序
var arr = [1, 9, 4, 50, 49, 6, 3, 2];
function test(){
  for (var i = 0; i &amp;lt; arr.length - 1; i++){
    for (var j = i + 1; j &amp;lt; arr.length; j++){
      var tempi = arr[i]; //获取第一个值，并与后一个值比较
      var tempj = arr[j];
      if (tempi &amp;gt; tempj){
        arr[i] = tempj;
        arr[j] = tempi;//如果前一个值比后一个值大，那么相互交换
      }
    }
  } 
  console.log(arr); //return arr；
}
test(arr);  //[1, 2, 3, 4, 6, 9, 49, 50]

方法二：sort()
var arr = [1, 9, 4, 50, 49, 6, 3, 2];
function test(arr) {
  if( arr.length &amp;lt; 1){
    return      //如果数组长度小于1，直接返回，没有比较的必要
  }
 return  arr.sort(sortNum)
};
function sortNum(a,b) {
    return a - b;   //倒序就是b-a
}
test(arr)  //[1, 2, 3, 4, 6, 9, 49, 50]

// 数组的随机排序
var arr = [1, 9, 4, 50, 49, 6, 3, 2];
function test(arr) {
    if(arr.length &amp;lt; 1) {
        return 
    }
    return arr.sort(randomSrot)
}
function randomSrot(a,b) {
    return Math.random() &amp;gt; 0.5 ? -1 : 1;
}
test(arr);  
//利用Math.random()方法可以返回0~1之间的随机数，然后和0.5比较，来决定返回值是1还是-1
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;获取数组汇总最大值和最小值&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;let arr = [1, 2, 5, 8, 10, 100, -1]
方法一：
Math.max(...arr) 
方法二：
Math.max.apply(null, arr) 
方法三：
Math.max.call(null, ...arr) 
方法四：
function getMax(prev, next) {
    return Math.max(prev, next)
}
arr.reduce(getMax)
// 求最小值将max换成min
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;欢迎留言交流，持续更新中…&lt;/p&gt;
</content:encoded></item><item><title>vue使用 vue-qr 和 html2canvas 链接生成普通二维码和海报二维码</title><link>https://qingge-cabana.vercel.app/posts/vue%E4%BD%BF%E7%94%A8-vue-qr-%E5%92%8C-html2canvas-%E9%93%BE%E6%8E%A5%E7%94%9F%E6%88%90%E6%99%AE%E9%80%9A%E4%BA%8C%E7%BB%B4%E7%A0%81%E5%92%8C%E6%B5%B7%E6%8A%A5%E4%BA%8C%E7%BB%B4%E7%A0%81-109849492/</link><guid isPermaLink="true">https://qingge-cabana.vercel.app/posts/vue%E4%BD%BF%E7%94%A8-vue-qr-%E5%92%8C-html2canvas-%E9%93%BE%E6%8E%A5%E7%94%9F%E6%88%90%E6%99%AE%E9%80%9A%E4%BA%8C%E7%BB%B4%E7%A0%81%E5%92%8C%E6%B5%B7%E6%8A%A5%E4%BA%8C%E7%BB%B4%E7%A0%81-109849492/</guid><description>使用 vue-qr 与 html2canvas，把链接生成普通二维码与可下载的海报二维码。</description><pubDate>Fri, 20 Nov 2020 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;本文同步自 &lt;a href=&quot;https://blog.csdn.net/weixin_44980732/article/details/109849492&quot;&gt;CSDN · 清阿哥&lt;/a&gt;，原文发布于 2020-11-20。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;首先下载插件&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;npm i vue-qr --save
npm i html2canvas --save
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;链接生成二维码并下载到本地&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// 模板中的代码
      &amp;lt;el-dialog :visible.sync=&quot;showDownloadDialog&quot;
                 title=&quot;测评下载&quot;
                 width=&quot;35%&quot;&amp;gt;
        &amp;lt;div class=&quot;dialog-content&quot;&amp;gt;
          &amp;lt;el-radio-group v-model=&quot;radio&quot;&amp;gt;
            &amp;lt;el-radio :label=&quot;1&quot;&amp;gt;海报二维码&amp;lt;/el-radio&amp;gt;
            &amp;lt;el-radio :label=&quot;2&quot;&amp;gt;高清二维码&amp;lt;/el-radio&amp;gt;
          &amp;lt;/el-radio-group&amp;gt;
          &amp;lt;div class=&quot;evaluationUrl&quot;&amp;gt;
            测评链接：
            &amp;lt;div class=&quot;copyUrl&quot;&amp;gt;
              &amp;lt;span class=&quot;oneLine&quot;&amp;gt;{{ QRUrl }}&amp;lt;/span&amp;gt;
              &amp;lt;span class=&quot;copy&quot;
                    :data-clipboard-text=&quot;QRUrl&quot;
                    @click=&quot;copy&quot;&amp;gt;复制链接&amp;lt;/span&amp;gt;
            &amp;lt;/div&amp;gt;
            &amp;lt;div id=&quot;qrcode&quot;&amp;gt;
              &amp;lt;vue-qr :text=&quot;QRUrl&quot; // 链接
                      :size=&quot;175&quot; // 二维码的宽高
                      :margin=&quot;1&quot;&amp;gt;&amp;lt;/vue-qr&amp;gt;
            &amp;lt;/div&amp;gt;
          &amp;lt;/div&amp;gt;
        &amp;lt;/div&amp;gt;
        &amp;lt;div slot=&quot;footer&quot;
             class=&quot;dialog-footer&quot;&amp;gt;
          &amp;lt;el-button @click=&quot;showDownloadDialog = false&quot;
                     class=&quot;btn-cancel&quot;&amp;gt;取消&amp;lt;/el-button&amp;gt;
          &amp;lt;el-button @click=&quot;confirmDownload&quot;
                     type=&quot;primary&quot;&amp;gt;下载&amp;lt;/el-button&amp;gt;
        &amp;lt;/div&amp;gt;
      &amp;lt;/el-dialog&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;在使用的组件中引入 注册在组件中&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import VueQr from &apos;vue-qr&apos;;
import html2canvas from &apos;html2canvas&apos;;
export default {
  name: &apos;EvaluationList&apos;,
  components: {
    VueQr
  },
 }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;下载二维码到本地&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;confirmDownload() {
      // 确认下载二维码或者海报二维码
      if (this.radio === 2) {
        // 下载高清二维码
        var oQrcode = document.querySelector(&apos;#qrcode img&apos;);
        var url = oQrcode.src;
        var a = document.createElement(&apos;a&apos;);
        var event = new MouseEvent(&apos;click&apos;);
        // 下载图名字
        a.download = &apos;高清二维码&apos;;
        // url
        a.href = url;
        // 合成函数，执行下载
        a.dispatchEvent(event);
      } else {
        // 下载海报二维码
        this.saveImg();
      }
    },
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;由于需求是不需要预览 直接下载 所以将二维码进行了隐藏&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#qrcode {
  display: none;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;效果图如下&lt;/strong&gt;&lt;br /&gt;
下载弹窗&lt;br /&gt;
&lt;img src=&quot;https://i-blog.csdnimg.cn/blog_migrate/881350c3e926490b4a55acdbb6216709.png#pic_center&quot; alt=&quot;![在这里插入图片描述](https://img-blog.csdnimg.cn/20201120150513273.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80NDk4MDczMg==,size_16,color_FFFFFF,t_70#pic_center&quot; /&gt;&lt;br /&gt;
&lt;img src=&quot;https://i-blog.csdnimg.cn/blog_migrate/80e8115e1d32799e6635c984f93e3d5d.png#pic_center&quot; alt=&quot;在这里插入图片描述&quot; /&gt;&lt;br /&gt;
&lt;strong&gt;二维码和背景图片合成海报二维码下载到本地&lt;/strong&gt;&lt;br /&gt;
模板中的代码&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;div class=&quot;bgCode&quot;
           id=&quot;mycanvas&quot;&amp;gt;
        &amp;lt;vue-qr class=&quot;QrcodeBg&quot;
                :text=&quot;QRUrl&quot; // 链接
                :margin=&quot;1&quot;
                :size=&quot;175&quot;&amp;gt;
        &amp;lt;/vue-qr&amp;gt;
      &amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;下载海报到本地&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;saveImg() {
      // 先获取你要转换为img的dom节点
      var node = document.getElementById(&apos;mycanvas&apos;); // 传入的id名称
      var width = node.offsetWidth; // dom宽
      var height = node.offsetHeight; // dom高
      var scale = 2; // 放大倍数 这个相当于清晰度 调大一点更清晰一点
      html2canvas(node, {
        width: width,
        heigth: height,
        backgroundColor: &apos;#ffffff&apos;, // 背景颜色 为null是透明
        dpi: window.devicePixelRatio * 2, // 按屏幕像素比增加像素
        scale: scale,
        X: 0,
        Y: 0,
        scrollX: -3, // 如果左边多个白边 设置该偏移-3 或者更多
        scrollY: 0,
        useCORS: true, // 是否使用CORS从服务器加载图像 !!!
        allowTaint: true // 是否允许跨域图像污染画布  !!!
      }).then((canvas) =&amp;gt; {
        var url = canvas.toDataURL(); // 这里上面不设值cors会报错
        var a = document.createElement(&apos;a&apos;); // 创建一个a标签 用来下载
        a.download = &apos;海报二维码&apos;; // 设置下载的图片名称
        var event = new MouseEvent(&apos;click&apos;); // 增加一个点击事件
        a.href = url; // 此处的url为base64格式的图片资源
        a.dispatchEvent(event); // 触发a的单击事件 即可完成下载
      });
    },
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;css&lt;br /&gt;
需求不需要展示海报二维码 所以使用z-index 降低层级 不展示在页面上 将链接转成二维码后定位到背景图片放置二维码的位置&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;.bgCode {
  position: fixed;
  top: 10px;
  left: 10px;
  z-index: -10;
  width: 375px;
  height: 667px;
  background: url(&apos;~@/assets/images/AIEvaluation/bg.png&apos;);
  background-size: contain;

  .QrcodeBg {
    position: absolute;
    top: 348px;
    left: 100px;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;效果图如下&lt;br /&gt;
&lt;img src=&quot;https://i-blog.csdnimg.cn/blog_migrate/33e17f75bd226edd57605e8c4ce8120d.png#pic_center&quot; alt=&quot;在这里插入图片描述&quot; /&gt;&lt;br /&gt;
&lt;img src=&quot;https://i-blog.csdnimg.cn/blog_migrate/c0f47cfdf81017ea68bcc2bec36534d3.png#pic_center&quot; alt=&quot;在这里插入图片描述&quot; /&gt;&lt;br /&gt;
&lt;strong&gt;vue-qr 常用属性介绍&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;text 二维码内容&lt;br /&gt;
size 二维码宽高大小，因为是正方形，所以设一个参数即可&lt;br /&gt;
margin 默认边距20px，不喜欢的话自己设为0&lt;br /&gt;
colorDark 实点的颜色，注意要和colorLight一起设置才有效&lt;br /&gt;
colorLight 空白的颜色，注意要和colorDark一起设置才有效&lt;br /&gt;
bgSrc 嵌入背景图地址，没什么卵用，不建议设置&lt;br /&gt;
logoSrc 二维码中间的图，这个是好东西，设置一下显得专业点&lt;br /&gt;
logoScale 中间图的尺寸，不要设太大，太大会导致扫码失败的&lt;br /&gt;
dotScale 那些小点点的大小，这个也没什么好纠结的，不建议设置了&lt;/p&gt;
&lt;p&gt;官方vue-qr：&lt;a href=&quot;https://www.npmjs.com/package/vue-qr&quot;&gt;官方文档&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;html2canvas 配置项介绍 &lt;a href=&quot;https://allenchinese.github.io/html2canvas-docs-zh-cn/docs/html2canvas-configuration.html&quot;&gt;html2canvas 中文文档&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;有什么问题留言一起交流下吧。&lt;/p&gt;
</content:encoded></item><item><title>element tooltip组件在table组件中的使用</title><link>https://qingge-cabana.vercel.app/posts/element-tooltip%E7%BB%84%E4%BB%B6%E5%9C%A8table%E7%BB%84%E4%BB%B6%E4%B8%AD%E7%9A%84%E4%BD%BF%E7%94%A8-109820134/</link><guid isPermaLink="true">https://qingge-cabana.vercel.app/posts/element-tooltip%E7%BB%84%E4%BB%B6%E5%9C%A8table%E7%BB%84%E4%BB%B6%E4%B8%AD%E7%9A%84%E4%BD%BF%E7%94%A8-109820134/</guid><description>总结 Element Tooltip 在 Table 中的用法，处理溢出文本提示与单元格内悬浮交互的常见问题。</description><pubDate>Thu, 19 Nov 2020 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;本文同步自 &lt;a href=&quot;https://blog.csdn.net/weixin_44980732/article/details/109820134&quot;&gt;CSDN · 清阿哥&lt;/a&gt;，原文发布于 2020-11-19。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;需求：在表格中超出两行后显示省略号，鼠标进入在tooltip框中显示文本信息，随后将tooltip封装成立组件使用。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;OverFlowText.vue

&amp;lt;!-- 组件功能：文本溢出时，显示tooltip --&amp;gt;
&amp;lt;template&amp;gt;
  &amp;lt;el-tooltip manual
              v-model=&quot;isShow&quot;
              :disabled=&quot;!isShow&quot;
              :width=&quot;tableWidth&quot;
              placement=&quot;top&quot;&amp;gt;
    &amp;lt;div class=&quot;tooltip-content&quot;
         slot=&quot;content&quot;&amp;gt;{{ content }}&amp;lt;/div&amp;gt;
    &amp;lt;div :class=&quot;`text-wrap ${rows&amp;gt;1?&apos;text-wrap-column&apos;:&apos;text-wrap-row&apos;}`&quot;
         @mouseenter=&quot;handleMouseenter($event,)&quot;
         @mouseleave=&quot;handleMouseleave&quot;
         :style=&quot;rows&amp;gt;1?`-webkit-line-clamp:${rows}`:&apos;&apos;&quot;&amp;gt;
      &amp;lt;slot /&amp;gt;
    &amp;lt;/div&amp;gt;
  &amp;lt;/el-tooltip&amp;gt;
&amp;lt;/template&amp;gt;

&amp;lt;script&amp;gt;
export default {
  props: {
    content: {
      // tooltip展示内容
      type: String
    },
    rows: {
      // 超过n行省略
      type: Number,
      default: 1
    },
    tableWidth: {
      // 需要展示tooltip框元素的宽度
      type: Number
    }
  },
  data() {
    return {
      isShow: false
    };
  },
  methods: {
    handleMouseenter({ target: { offsetWidth } }) {
      // 获取当前文本宽度
      let TemporaryTag = document.createElement(&apos;span&apos;);
      TemporaryTag.innerText = this.content;
      TemporaryTag.className = &apos;getTextWidth&apos;;
      document.querySelector(&apos;body&apos;).appendChild(TemporaryTag);
      let currentWidth = document.querySelector(&apos;.getTextWidth&apos;).offsetWidth;
      document.querySelector(&apos;.getTextWidth&apos;).remove();
      /* cellWidth为表格容器的宽度 */
      const cellWidth = offsetWidth;
      /* 当文本宽度小于||等于容器宽度两倍时，代表文本显示未超过两行 */
      currentWidth &amp;lt;= (2 * cellWidth) ? this.isShow = false : this.isShow = true;
    },
    handleMouseleave() {
      this.isShow = false;
    }
  }
};
&amp;lt;/script&amp;gt;

&amp;lt;style scoped&amp;gt;
.text-wrap {
  width: 100%;
  overflow: hidden;
  word-break: break-word;
  box-sizing: border-box;
}

.text-wrap-row {
  text-overflow: ellipsis;
  white-space: nowrap;
}

.text-wrap-column {
  display: -webkit-box;
  -webkit-box-orient: vertical;
}

.tooltip-content {
  max-width: 500px;
  word-break: break-word;
  white-space: pre-wrap;
}
&amp;lt;/style&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;页面中的使用&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/**
* width 表格宽度 
* content 需要显示在tooltip 框中的内容
* row 需要几行显示省略号 隐藏文本内容
**/
&amp;lt;el-table-column prop=&quot;subject&quot;
                           label=&quot;学科&quot;
                           align=&quot;center&quot;
                           width=&quot;180&quot;&amp;gt;
            &amp;lt;template slot-scope=&quot;scoped&quot;&amp;gt;
              &amp;lt;over-flow-text :rows=&quot;2&quot;
                            :width=&quot;180&quot;
                           	:content=&quot;Object.keys(scoped.row.subjects).join(&apos;，&apos;)&quot;&amp;gt; 
                &amp;lt;div&amp;gt;
                  {{ Object.keys(scoped.row.subjects).join(&apos;，&apos;) }}
                &amp;lt;/div&amp;gt;
              &amp;lt;/over-flow-text&amp;gt;
            &amp;lt;/template&amp;gt;
          &amp;lt;/el-table-column&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;效果图如下：&lt;br /&gt;
&lt;img src=&quot;https://i-blog.csdnimg.cn/blog_migrate/cbd231cc4407f42094a2e6387cda8fac.png#pic_center&quot; alt=&quot;在这里插入图片描述&quot; /&gt;&lt;br /&gt;
展示需要展示 tooltip 的需求都可以使用，上面的组件。&lt;br /&gt;
如果有什么问题，可以留言一起交流。&lt;/p&gt;
</content:encoded></item><item><title>element table组件用css实现鼠标进入表格当前行显示删除图片</title><link>https://qingge-cabana.vercel.app/posts/element-table%E7%BB%84%E4%BB%B6%E7%94%A8css%E5%AE%9E%E7%8E%B0%E9%BC%A0%E6%A0%87%E8%BF%9B%E5%85%A5%E8%A1%A8%E6%A0%BC%E5%BD%93%E5%89%8D%E8%A1%8C%E6%98%BE%E7%A4%BA%E5%88%A0%E9%99%A4%E5%9B%BE%E7%89%87-109714684/</link><guid isPermaLink="true">https://qingge-cabana.vercel.app/posts/element-table%E7%BB%84%E4%BB%B6%E7%94%A8css%E5%AE%9E%E7%8E%B0%E9%BC%A0%E6%A0%87%E8%BF%9B%E5%85%A5%E8%A1%A8%E6%A0%BC%E5%BD%93%E5%89%8D%E8%A1%8C%E6%98%BE%E7%A4%BA%E5%88%A0%E9%99%A4%E5%9B%BE%E7%89%87-109714684/</guid><description>用 CSS 实现 Element Table 鼠标悬停当前行显示删除图标，兼顾交互反馈与布局不跳动。</description><pubDate>Mon, 16 Nov 2020 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;本文同步自 &lt;a href=&quot;https://blog.csdn.net/weixin_44980732/article/details/109714684&quot;&gt;CSDN · 清阿哥&lt;/a&gt;，原文发布于 2020-11-16。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;现在有一个需求鼠标进入表格的当前行，显示操作列的删除图标，鼠标移出隐藏，发现一种超级简单的实现方法，只用ss就能实现。&lt;br /&gt;
直接上代码&lt;br /&gt;
在需要隐藏显示的元素上加上 delImg 就可以了 就这么简单粗暴&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;.delImg {
  cursor: pointer;
  opacity: 0;
}

.el-table__body tr:hover {
  .delImg {
    opacity: 1;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;ps：说下为什么要用 opacity 属性进行显示隐藏，如果用display 属性进行操作的话，因为它不占据原来的位置，显示隐藏的操作列的宽度会变，体验不好，所以就用了透明属性元素占据原来的位置。&lt;/p&gt;
</content:encoded></item><item><title>使用css实现滚动到页面对应的位置</title><link>https://qingge-cabana.vercel.app/posts/%E4%BD%BF%E7%94%A8css%E5%AE%9E%E7%8E%B0%E6%BB%9A%E5%8A%A8%E5%88%B0%E9%A1%B5%E9%9D%A2%E5%AF%B9%E5%BA%94%E7%9A%84%E4%BD%8D%E7%BD%AE-109351935/</link><guid isPermaLink="true">https://qingge-cabana.vercel.app/posts/%E4%BD%BF%E7%94%A8css%E5%AE%9E%E7%8E%B0%E6%BB%9A%E5%8A%A8%E5%88%B0%E9%A1%B5%E9%9D%A2%E5%AF%B9%E5%BA%94%E7%9A%84%E4%BD%8D%E7%BD%AE-109351935/</guid><description>使用 CSS / 锚点思路实现点击滚动到页面指定位置，适合目录导航与返回顶部类交互。</description><pubDate>Thu, 29 Oct 2020 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;本文同步自 &lt;a href=&quot;https://blog.csdn.net/weixin_44980732/article/details/109351935&quot;&gt;CSDN · 清阿哥&lt;/a&gt;，原文发布于 2020-10-29。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;废话不多说先上代码&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;div class=&quot;back-top-item&quot; @click=&quot;turnFeeConsultation&quot;&amp;gt;
      &amp;lt;div class=&quot;feeConsultation&quot;&amp;gt;&amp;lt;/div&amp;gt;
     &amp;lt;p class=&quot;text&quot;&amp;gt;收费咨询&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;turnFeeConsultation() {
      this.$nextTick(() =&amp;gt; 
        document.getElementsByClassName(&apos;paidConsultation&apos;)[0].scrollIntoView({ behavior: &apos;smooth&apos;, block: &apos;start&apos;, inline: &apos;nearest&apos; });
      });
    }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;scrolIintoView 布尔值参数&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;scrollintoView(true) 或者scrollIntoView()相当于scrollIntoViewOptions: {block: “start”, inline: “nearest”}&lt;/li&gt;
&lt;li&gt;scrollintoView(false) 相当于scrollIntoViewOptions: {block: “end”, inline: “nearest”}&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;scrolIintoView 布对象参数&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;behavior（可选）定义动画过渡效果， “auto” 瞬间跳转（没有动画效果）或 “smooth”（由动画效果平滑的到指定位置） 之一。默认为 “auto”&lt;/li&gt;
&lt;li&gt;block（可选）定义垂直方向的对齐， “start”, “center”, “end”, 或 &quot;nearest&quot;之一。默认为 “start”。&lt;/li&gt;
&lt;li&gt;inline（可选）定义水平方向的对齐， “start”, “center”, “end”, 或 &quot;nearest&quot;之一。默认为 “nearest”&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>vue.js学习总结（持续更新）</title><link>https://qingge-cabana.vercel.app/posts/vuejs%E5%AD%A6%E4%B9%A0%E6%80%BB%E7%BB%93%E6%8C%81%E7%BB%AD%E6%9B%B4%E6%96%B0-106114357/</link><guid isPermaLink="true">https://qingge-cabana.vercel.app/posts/vuejs%E5%AD%A6%E4%B9%A0%E6%80%BB%E7%BB%93%E6%8C%81%E7%BB%AD%E6%9B%B4%E6%96%B0-106114357/</guid><description>持续更新的 Vue.js 学习笔记，沉淀组件、指令、生命周期等基础与实践要点。</description><pubDate>Fri, 04 Sep 2020 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;本文同步自 &lt;a href=&quot;https://blog.csdn.net/weixin_44980732/article/details/106114357&quot;&gt;CSDN · 清阿哥&lt;/a&gt;，原文发布于 2020-09-04。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;一. vue.js教程总结&lt;/h3&gt;
&lt;h4&gt;1. Object.freeze()&lt;/h4&gt;
&lt;p&gt;阻止 数据改变时，视图会进行重渲染的现象。&lt;/p&gt;
&lt;h4&gt;2. vue暴露了一些有用的实例属性与方法，加上前缀 $&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;vm.$data === data // =&amp;gt; true
vm.$el === document.getElementById(&apos;example&apos;) // =&amp;gt; true
// $watch 是一个实例方法
vm.$watch(&apos;a&apos;, function (newValue, oldValue) {
  // 这个回调将在 `vm.a` 改变后调用
})
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;注意&lt;/strong&gt; ：&lt;br /&gt;
&lt;strong&gt;不要在选项属性或回调上使用 箭头函数&lt;br /&gt;
比如 created: () =&amp;gt; console.log(this.a) 或 vm.$watch(‘a’, newValue =&amp;gt; this.myMethod())。&lt;br /&gt;
因为箭头函数并没有 this，this 会作为变量一直向上级词法作用域查找，直至找到为止，&lt;br /&gt;
经常导致 Uncaught TypeError: Cannot read property of undefined 或 Uncaught TypeError: this.myMethod is not a function 之类的错误。&lt;/strong&gt;&lt;/p&gt;
&lt;h4&gt;4. v-once&lt;/h4&gt;
&lt;p&gt;当数据改变时，插值处的内容不会更新&lt;/p&gt;
&lt;h4&gt;5. v-html&lt;/h4&gt;
&lt;p&gt;输出真正HTML&lt;br /&gt;
&lt;strong&gt;注意：&lt;/strong&gt;&lt;br /&gt;
&lt;em&gt;你的站点上动态渲染的任意 HTML 可能会非常危险，因为它很容易导致 XSS 攻击。请只对可信内容使用 HTML 插值，绝不要对用户提供的内容使用插值&lt;/em&gt;。&lt;/p&gt;
&lt;h4&gt;6. 动态参数 (2.6.0 新增)&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;a v-bind:[attributeName]=&quot;url&quot;&amp;gt; ... &amp;lt;/a&amp;gt;
// Vue 实例有一个 data 属性 attributeName，其值为 &quot;href&quot; 等价于  v-bind:href
// 不能使用 空格或者引号的表达式
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;7. v-bind 缩写&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;!-- 完整语法 --&amp;gt;
&amp;lt;a v-bind:href=&quot;url&quot;&amp;gt;...&amp;lt;/a&amp;gt;

&amp;lt;!-- 缩写 --&amp;gt;
&amp;lt;a :href=&quot;url&quot;&amp;gt;...&amp;lt;/a&amp;gt;

&amp;lt;!-- 动态参数的缩写 (2.6.0+) --&amp;gt;
&amp;lt;a :[key]=&quot;url&quot;&amp;gt; ... &amp;lt;/a&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;8. v-on 缩写&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;!-- 完整语法 --&amp;gt;
&amp;lt;a v-on:click=&quot;doSomething&quot;&amp;gt;...&amp;lt;/a&amp;gt;

&amp;lt;!-- 缩写 --&amp;gt;
&amp;lt;a @click=&quot;doSomething&quot;&amp;gt;...&amp;lt;/a&amp;gt;

&amp;lt;!-- 动态参数的缩写 (2.6.0+) --&amp;gt;
&amp;lt;a @[event]=&quot;doSomething&quot;&amp;gt; ... &amp;lt;/a&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;9. 计算属性&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;div id=&quot;example&quot;&amp;gt;
  &amp;lt;p&amp;gt;Original message: &quot;{{ message }}&quot;&amp;lt;/p&amp;gt;
  &amp;lt;p&amp;gt;Computed reversed message: &quot;{{ reversedMessage }}&quot;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;var vm = new Vue({
  el: &apos;#example&apos;,
  data: {
    message: &apos;Hello&apos;
  },
  computed: {
    // 计算属性的 getter
    reversedMessage: function () {
      // `this` 指向 vm 实例
      return this.message.split(&apos;&apos;).reverse().join(&apos;&apos;)
    }
  }
})
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;10. 计算属性和方法的区别&lt;/h4&gt;
&lt;p&gt;计算属性是基于它们的响应式依赖进行缓存的。只在相关响应式依赖发生改变时它们才会重新求值。&lt;br /&gt;
相比之下，每当触发重新渲染时，调用方法将总会再次执行函数。&lt;/p&gt;
&lt;h4&gt;11. 计算属性的getter 和 setter&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;computed: {
  fullName: {
    // getter
    get: function () {
      return this.firstName + &apos; &apos; + this.lastName
    },
    // setter
    set: function (newValue) {
      var names = newValue.split(&apos; &apos;)
      this.firstName = names[0]
      this.lastName = names[names.length - 1]
    }
  }
}
 // 现在再运行 vm.fullName = &apos;John Doe&apos; 时，setter 会被调用，vm.firstName 和 vm.lastName 也会相应地被更新。
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;12. watch 侦听器&lt;/h4&gt;
&lt;p&gt;可以监视 data 中指定数据的变化，然后触发这个 watch 中对应的 function 处理函数&lt;br /&gt;
该方法可以不用绑定事件&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;body&amp;gt;
  &amp;lt;div id=&quot;app&quot;&amp;gt;
      &amp;lt;input type=&quot;text&quot; v-model=&quot;firstname&quot;&amp;gt;+
      &amp;lt;input type=&quot;text&quot; v-model=&quot;lastname&quot;&amp;gt;=
      &amp;lt;input type=&quot;text&quot; v-model=&quot;fullname&quot;&amp;gt;
  &amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;  &amp;lt;script&amp;gt;
    // 创建 Vue 实例，得到 ViewModel
    var vm = new Vue({
      el: &apos;#app&apos;,
      data: {
          firstname: &apos;&apos;,
          lastname: &apos;&apos;,
          fullname: &apos;&apos;
      },
      methods: {},
      watch: {
          &apos;firstname&apos;: function(oldVal,newVal){
              this.fullname = newVal + &apos;-&apos; + this.lastname
          },
          &apos;lastname&apos;: function(oldVal,newVal){
              this.fullname = this.firstname + &apos;-&apos; + newVal
          }
          // 监听的是对象或者数组需要加上 deep: true;
      }
    });
  &amp;lt;/script&amp;gt;
&amp;lt;/body&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;13. watch、computed和methods之间的对比&lt;/h4&gt;
&lt;p&gt;computed 属性的结果会被缓存，除非依赖的响应式属性变化才会重新计算。主要当作属性来使用；&lt;br /&gt;
methods 方法表示一个具体的操作，主要书写业务逻辑；&lt;br /&gt;
watch 一个对象，键是需要观察的表达式，值是对应回调函数。主要用来监听某些特定数据的变化，从而进行某些具体的业务逻辑操作；可以看作是computed和methods的结合体；&lt;/p&gt;
&lt;h4&gt;14. Class 与 Style 绑定&lt;/h4&gt;
&lt;h5&gt;14.1 绑定 HTML Class&lt;/h5&gt;
&lt;h6&gt;14.1.1 对象语法&lt;/h6&gt;
&lt;pre&gt;&lt;code&gt;data: {
  isActive: true,
  hasError: false
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;div v-bind:class=&quot;{ active: isActive }&quot;&amp;gt;&amp;lt;/div&amp;gt;
&amp;lt;div class=&quot;active&quot;&amp;gt;&amp;lt;/div&amp;gt;
// active 这个 class 存在与否将取决于数据属性 isActive 是否为true

// 可以在对象中传入更多属性来动态切换多个 class
// 与普通的 class 属性共存
&amp;lt;div
  class=&quot;static&quot;
  v-bind:class=&quot;{ active: isActive, &apos;text-danger&apos;: hasError }&quot;
&amp;gt;&amp;lt;/div&amp;gt;
// 渲染结果
&amp;lt;div class=&quot;static active&quot;&amp;gt;&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;// 绑定的数据对象不必内联定义在模板里
&amp;lt;div v-bind:class=&quot;classObject&quot;&amp;gt;&amp;lt;/div&amp;gt;
// 也可以绑定一个返回对象的计算属性
&amp;lt;div v-bind:class=&quot;classObject&quot;&amp;gt;&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;data: {
  classObject: {
    active: true,
    &apos;text-danger&apos;: false
  }
}
// 计算属性
data: {
  isActive: true,
  error: null
},
computed: {
  classObject: function () {
    return {
      active: this.isActive &amp;amp;&amp;amp; !this.error,
      &apos;text-danger&apos;: this.error &amp;amp;&amp;amp; this.error.type === &apos;fatal&apos;
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h6&gt;14.1.2 数组语法&lt;/h6&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;div v-bind:class=&quot;[activeClass, errorClass]&quot;&amp;gt;&amp;lt;/div&amp;gt;
// 渲染结果
&amp;lt;div class=&quot;active text-danger&quot;&amp;gt;&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;data: {
  activeClass: &apos;active&apos;,
  errorClass: &apos;text-danger&apos;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;根据条件切换可以选择三元表达式&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;div v-bind:class=&quot;[isActive ? activeClass : &apos;&apos;, errorClass]&quot;&amp;gt;&amp;lt;/div&amp;gt;
// 这样写始终添加 errorClass，但是只有在 isActive 是 truthy[1] 时才添activeClass
// 多个条件是还可以这样写
&amp;lt;div v-bind:class=&quot;[{ active: isActive }, errorClass]&quot;&amp;gt;&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h6&gt;14.1.3 用在组件上&lt;/h6&gt;
&lt;p&gt;当在一个自定义组件上使用 class 属性时，这些 class 将被添加到该组件的根元素上面。这个元素上已经存在的 class 不会被覆盖。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// 声明一个组件之后
Vue.component(&apos;my-component&apos;, {
  template: &apos;&amp;lt;p class=&quot;foo bar&quot;&amp;gt;Hi&amp;lt;/p&amp;gt;&apos;
})
// 在使用该组件的时候再加一些class
&amp;lt;my-component class=&quot;baz boo&quot;&amp;gt;&amp;lt;/my-component&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;// 将会渲染成这样
&amp;lt;p class=&quot;foo bar baz boo&quot;&amp;gt;Hi&amp;lt;/p&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h5&gt;14.2 绑定内联样式&lt;/h5&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;div v-bind:style=&quot;{ color: activeColor, fontSize: fontSize + &apos;px&apos; }&quot;&amp;gt;&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;data: {
  activeColor: &apos;red&apos;,
  fontSize: 30
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;如果样式多可以绑定在一个对象上 而且看起来模板更加清晰&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;div v-bind:style=&quot;styleObject&quot;&amp;gt;&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;data: {
  styleObject: {
    color: &apos;red&apos;,
    fontSize: &apos;13px&apos;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;数组语法可以将多个对象应用到同一个元素上&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;div v-bind:style=&quot;[baseStyles, overridingStyles]&quot;&amp;gt;&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h6&gt;14.2.1 多重值&lt;/h6&gt;
&lt;p&gt;可以用于提供多个前缀的值&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// 用于适配
&amp;lt;div :style=&quot;{ display: [&apos;-webkit-box&apos;, &apos;-ms-flexbox&apos;, &apos;flex&apos;] }&quot;&amp;gt;&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;15. 条件渲染 v-if， v-else， v-else-if&lt;/h4&gt;
&lt;h5&gt;15.1 在 元素上使用 v-if 条件渲染分组&lt;/h5&gt;
&lt;p&gt;如果想切换多个元素的话，可以将v-if绑定在一个 元素当做不可见的包裹元素，并使用v-if&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;template v-if=&quot;ok&quot;&amp;gt;
  &amp;lt;h1&amp;gt;Title&amp;lt;/h1&amp;gt;
  &amp;lt;p&amp;gt;Paragraph 1&amp;lt;/p&amp;gt;
  &amp;lt;p&amp;gt;Paragraph 2&amp;lt;/p&amp;gt;
&amp;lt;/template&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;v-else 元素必须紧跟在带 v-if 或者 v-else-if 的元素的后面，否则它将不会被识别。&lt;/p&gt;
&lt;h5&gt;15.2 用 key 管理可复用的元素&lt;/h5&gt;
&lt;p&gt;&lt;strong&gt;Vue 会尽可能高效地渲染元素，通常会复用已有元素而不是从头开始渲染&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// 当这两个元素是完全独立的，不要复用它们时 只需添加一个具有唯一值的 key 属性即可
&amp;lt;template v-if=&quot;loginType === &apos;username&apos;&quot;&amp;gt;
  &amp;lt;label&amp;gt;Username&amp;lt;/label&amp;gt;
  &amp;lt;input placeholder=&quot;Enter your username&quot; key=&quot;username-input&quot;&amp;gt;
&amp;lt;/template&amp;gt;
&amp;lt;template v-else&amp;gt;
  &amp;lt;label&amp;gt;Email&amp;lt;/label&amp;gt;
  &amp;lt;input placeholder=&quot;Enter your email address&quot; key=&quot;email-input&quot;&amp;gt;
&amp;lt;/template&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h5&gt;15.3 v-show&lt;/h5&gt;
&lt;p&gt;&lt;strong&gt;不同的是带有 v-show 的元素始终会被渲染并保留在 DOM 中。v-show 只是简单地切换元素的 CSS 属性 display。&lt;/strong&gt;&lt;/p&gt;
&lt;h5&gt;15.4 v-show 和 v-if 区别&lt;/h5&gt;
&lt;p&gt;v-if 是“真正”的条件渲染，因为它会确保在切换过程中条件块内的事件监听器和子组件适当地被销毁和重建。&lt;br /&gt;
v-show 就是简单的css样式的切换&lt;br /&gt;
需要非常频繁地切换，则使用 v-show 较好；&lt;br /&gt;
如果在运行时条件很少改变，则使用 ==v-if ==较好。&lt;/p&gt;
&lt;h5&gt;15.5 v-if 和 v-for 一起使用&lt;/h5&gt;
&lt;p&gt;&lt;strong&gt;当 v-if 与 v-for 一起使用时，v-for 具有比 v-if 更高的优先级&lt;/strong&gt;&lt;/p&gt;
&lt;h4&gt;16. 列表渲染&lt;/h4&gt;
&lt;h5&gt;16.1 在v-for使用对象&lt;/h5&gt;
&lt;p&gt;&lt;strong&gt;可以提供第二个的参数为 property 名称 (也就是键名)：&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;new Vue({
  el: &apos;#v-for-object&apos;,
  data: {
    object: {
      title: &apos;How to do lists in Vue&apos;,
      author: &apos;Jane Doe&apos;,
      publishedAt: &apos;2016-04-10&apos;
    }
  }
})
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;div v-for=&quot;(value, name) in object&quot;&amp;gt;
  {{ name }}: {{ value }}
&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;显示为：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt; title: How to do lists in Vue
 author: Jane Doe
 publishedAt: 2016-04-10
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;还可以用第三个参数作为索引：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;div v-for=&quot;(value, name, index) in object&quot;&amp;gt;
  {{ index }}. {{ name }}: {{ value }}
&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h5&gt;16.2 维护状态（绑定 :key）&lt;/h5&gt;
&lt;p&gt;&lt;strong&gt;为什么添加key属性？&lt;/strong&gt;&lt;br /&gt;
当 Vue 正在更新使用 v-for 渲染的元素列表时，它默认使用“就地更新”的策略。如果数据项的顺序被改变，Vue 将不会移动 DOM 元素来匹配数据项的顺序，而是就地更新每个元素，并且确保它们在每个索引位置正确渲染。&lt;/p&gt;
&lt;h5&gt;16.3 数组的更新检测&lt;/h5&gt;
&lt;p&gt;&lt;strong&gt;变异方法&lt;/strong&gt;&lt;br /&gt;
会改变原始数组&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;push()
pop()
shift()
unshift()
splice()
sort()
reverse()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;非变异方法&lt;/strong&gt;&lt;br /&gt;
不会改变原始数组，而总是返回一个新数组&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;filter()、concat() 和 slice()
&lt;/code&gt;&lt;/pre&gt;
&lt;h5&gt;16.4 显示过滤/排序后的结果&lt;/h5&gt;
&lt;p&gt;可以创建一个计算属性，来返回过滤或排序后的数组。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;li v-for=&quot;n in evenNumbers&quot;&amp;gt;{{ n }}&amp;lt;/li&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;data: {
  numbers: [ 1, 2, 3, 4, 5 ]
},
computed: {
  evenNumbers: function () {
    return this.numbers.filter(function (number) {
      return number % 2 === 0
    })
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;计算属性不适用的时候 例如 嵌套v-for循环中&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;ul v-for=&quot;set in sets&quot;&amp;gt;
  &amp;lt;li v-for=&quot;n in even(set)&quot;&amp;gt;{{ n }}&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;data: {
  sets: [[ 1, 2, 3, 4, 5 ], [6, 7, 8, 9, 10]]
},
methods: {
  even: function (numbers) {
    return numbers.filter(function (number) {
      return number % 2 === 0
    })
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;v-for 与 v-if 一同使用&lt;/strong&gt;&lt;br /&gt;
v-for 的优先级比 v-if 更高，这意味着 v-if 将分别重复运行于每个 v-for 循环中。当你只想为部分项渲染节点时，这种优先级的机制会十分有用&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;li v-for=&quot;todo in todos&quot; v-if=&quot;!todo.isComplete&quot;&amp;gt;
  {{ todo }}
&amp;lt;/li&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;如果是想有条件的跳过循环，可以将v-if至于外层&lt;/strong&gt;&lt;/p&gt;
&lt;h5&gt;16.5 在组件上使用 v-for&lt;/h5&gt;
&lt;p&gt;&lt;strong&gt;2.2.0+ 的版本里，当在组件上使用 v-for 时，key 现在是必须的。&lt;/strong&gt;&lt;br /&gt;
任何数据都不会被自动传递到组件里，因为组件有自己独立的作用域。为了把迭代数&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;my-component v-for=&quot;item in items&quot; :key=&quot;item.id&quot;&amp;gt;&amp;lt;/my-component&amp;gt;
// 据传递到组件里，我们要使用prop
&amp;lt;my-component
  v-for=&quot;(item, index) in items&quot;
  v-bind:item=&quot;item&quot;
  v-bind:index=&quot;index&quot;
  v-bind:key=&quot;item.id&quot;
&amp;gt;&amp;lt;/my-component&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;注意&lt;/strong&gt;&lt;br /&gt;
is=“todo-item” 这种做法在使用 DOM 模板时是十分必要的，因为在&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;元素内只有&lt;/li&gt;
&lt;li&gt;元素会被看作有效内容。这样做实现的效果与 相同，但是可以避开一些潜在的浏览器解析错误&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;17. 事件处理 v-on&lt;/h4&gt;
&lt;h5&gt;17.1 内联处理器的方法&lt;/h5&gt;
&lt;p&gt;有时也需要在内联语句处理器中访问原始的 DOM 事件。可以用特殊变量 $event 把它传入方法：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;button v-on:click=&quot;warn(&apos;Form cannot be submitted yet.&apos;, $event)&quot;&amp;gt;
  Submit
&amp;lt;/button&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;methods: {
  warn: function (message, event) {
    // 现在我们可以访问原生事件对象
    if (event) {
      event.preventDefault()
    }
    alert(message)
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h5&gt;17.2 修饰符&lt;/h5&gt;
&lt;h6&gt;17.2.1 事件修饰符&lt;/h6&gt;
&lt;pre&gt;&lt;code&gt;.stop
.prevent
.capture
.self
.once
.passive

&amp;lt;!-- 阻止单击事件继续传播 --&amp;gt;
&amp;lt;a v-on:click.stop=&quot;doThis&quot;&amp;gt;&amp;lt;/a&amp;gt;

&amp;lt;!-- 提交事件不再重载页面 --&amp;gt;
&amp;lt;form v-on:submit.prevent=&quot;onSubmit&quot;&amp;gt;&amp;lt;/form&amp;gt;

&amp;lt;!-- 修饰符可以串联 --&amp;gt;
&amp;lt;a v-on:click.stop.prevent=&quot;doThat&quot;&amp;gt;&amp;lt;/a&amp;gt;

&amp;lt;!-- 只有修饰符 --&amp;gt;
&amp;lt;form v-on:submit.prevent&amp;gt;&amp;lt;/form&amp;gt;

&amp;lt;!-- 添加事件监听器时使用事件捕获模式 --&amp;gt;
&amp;lt;!-- 即内部元素触发的事件先在此处理，然后才交由内部元素进行处理 --&amp;gt;
&amp;lt;div v-on:click.capture=&quot;doThis&quot;&amp;gt;...&amp;lt;/div&amp;gt;

&amp;lt;!-- 只当在 event.target 是当前元素自身时触发处理函数 --&amp;gt;
&amp;lt;!-- 即事件不是从内部元素触发的 --&amp;gt;
&amp;lt;div v-on:click.self=&quot;doThat&quot;&amp;gt;...&amp;lt;/div&amp;gt;

&amp;lt;!-- 点击事件将只会触发一次 --&amp;gt;
&amp;lt;a v-on:click.once=&quot;doThis&quot;&amp;gt;&amp;lt;/a&amp;gt;

&amp;lt;!-- 滚动事件的默认行为 (即滚动行为) 将会立即触发 --&amp;gt;
&amp;lt;!-- 而不会等待 `onScroll` 完成  --&amp;gt;
&amp;lt;!-- 这其中包含 `event.preventDefault()` 的情况 --&amp;gt;
// 这个 .passive 修饰符尤其能够提升移动端的性能
&amp;lt;div v-on:scroll.passive=&quot;onScroll&quot;&amp;gt;...&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;多个修饰符的顺序是自前往后&lt;/strong&gt;&lt;/p&gt;
&lt;h6&gt;17.2.2 按键修饰符&lt;/h6&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;!-- 只有在 `key` 是 `Enter` 时调用 `vm.submit()` --&amp;gt;
&amp;lt;input v-on:keyup.enter=&quot;submit&quot;&amp;gt;

&amp;lt;input v-on:keyup.page-down=&quot;onPageDown&quot;&amp;gt;
&amp;lt;!--处理函数只会在 $event.key 等于 PageDown 时被调用。--&amp;gt;

&amp;lt;!--使用 keyCode attribute 也是允许的：--&amp;gt;
&amp;lt;input v-on:keyup.13=&quot;submit&quot;&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;为了在必要的情况下支持旧浏览器，Vue 提供了绝大多数常用的按键码的别名&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;.enter
.tab
.delete (捕获“删除”和“退格”键)
.esc
.space
.up
.down
.left
.right
&lt;/code&gt;&lt;/pre&gt;
&lt;h6&gt;17.2.3 系统修饰符&lt;/h6&gt;
&lt;pre&gt;&lt;code&gt;.ctrl
.alt
.shift
.meta
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;.ctrl
.alt
.shift
.meta
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;.exact 修饰符允许你控制由精确的系统修饰符组合触发的事件。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;!-- 即使 Alt 或 Shift 被一同按下时也会触发 --&amp;gt;
&amp;lt;button v-on:click.ctrl=&quot;onClick&quot;&amp;gt;A&amp;lt;/button&amp;gt;

&amp;lt;!-- 有且只有 Ctrl 被按下的时候才触发 --&amp;gt;
&amp;lt;button v-on:click.ctrl.exact=&quot;onCtrlClick&quot;&amp;gt;A&amp;lt;/button&amp;gt;

&amp;lt;!-- 没有任何系统修饰符被按下的时候才触发 --&amp;gt;
&amp;lt;button v-on:click.exact=&quot;onClick&quot;&amp;gt;A&amp;lt;/button&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h6&gt;17.2.4 鼠标修饰符&lt;/h6&gt;
&lt;pre&gt;&lt;code&gt;.left
.right
.middle
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;使用v-on的好处&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;扫一眼 HTML 模板便能轻松定位在 JavaScript 代码里对应的方法。&lt;/li&gt;
&lt;li&gt;因为你无须在 JavaScript 里手动绑定事件，你的 ViewModel 代码可以是非常纯粹的逻辑，和 DOM 完全解耦，更易于测试&lt;/li&gt;
&lt;li&gt;当一个 ViewModel 被销毁时，所有的事件处理器都会自动被删除。你无须担心如何清理它们。&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;18. 表单输入&lt;/h4&gt;
&lt;h5&gt;18.1 修饰符&lt;/h5&gt;
&lt;p&gt;.lazy&lt;br /&gt;
在默认情况下，v-model 在每次 &lt;strong&gt;input 事件触发后&lt;/strong&gt;将输入框的值与数据进行同步 (除了上述输入法组合文字时)。你可以添加 lazy 修饰符，从而转为在 &lt;strong&gt;change 事件之后&lt;/strong&gt;进行同步&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;!-- 在“change”时而非“input”时更新 --&amp;gt;
&amp;lt;input v-model.lazy=&quot;msg&quot;&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;.number&lt;br /&gt;
转为数字类型&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;input v-model.number=&quot;age&quot; type=&quot;number&quot;&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;19. 组件基础&lt;/h4&gt;
&lt;h5&gt;19.1 data必须是一个函数&lt;/h5&gt;
&lt;p&gt;一个组件的 data 选项必须是一个函数，因此每个实例可以维护一份被返回对象的独立的拷贝&lt;/p&gt;
&lt;h4&gt;20. 插槽&lt;/h4&gt;
&lt;h5&gt;20.1 具名插槽（新语法）&lt;/h5&gt;
&lt;p&gt;一个带有如下模板的 子组件&lt;br /&gt;
对于这样的情况， 元素有一个特殊的 attribute：name。这个 attribute 可以用来定义额外的插槽&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;div class=&quot;container&quot;&amp;gt;
  &amp;lt;header&amp;gt;
    &amp;lt;slot name=&quot;header&quot;&amp;gt;&amp;lt;/slot&amp;gt;
  &amp;lt;/header&amp;gt;
  &amp;lt;main&amp;gt;
    &amp;lt;slot&amp;gt;&amp;lt;/slot&amp;gt;
  &amp;lt;/main&amp;gt;
  &amp;lt;footer&amp;gt;
    &amp;lt;slot name=&quot;footer&quot;&amp;gt;&amp;lt;/slot&amp;gt;
  &amp;lt;/footer&amp;gt;
&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;一个不带 name 的 出口会带有隐含的名字“default”。&lt;/strong&gt;&lt;br /&gt;
在向具名插槽提供内容的时候，我们可以在一个 元素上使用 v-slot 指令，并以 v-slot 的参数的形式提供其名称：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;父子件中
&amp;lt;base-layout&amp;gt;
  &amp;lt;template v-slot:header&amp;gt;
    &amp;lt;h1&amp;gt;Here might be a page title&amp;lt;/h1&amp;gt;
  &amp;lt;/template&amp;gt;

  &amp;lt;!--任何没有被包裹在带有 v-slot 的 &amp;lt;template&amp;gt; 中的内容都会被视为默认插槽的内容。--&amp;gt;
  &amp;lt;p&amp;gt;A paragraph for the main content.&amp;lt;/p&amp;gt;
  &amp;lt;p&amp;gt;And another one.&amp;lt;/p&amp;gt;
  &amp;lt;!--希望更明确点仍然可以在一个 &amp;lt;template&amp;gt; 中包裹默认插槽的内容--&amp;gt;
   &amp;lt;template v-slot:default&amp;gt;
    &amp;lt;p&amp;gt;A paragraph for the main content.&amp;lt;/p&amp;gt;
    &amp;lt;p&amp;gt;And another one.&amp;lt;/p&amp;gt;
  &amp;lt;/template&amp;gt;

  &amp;lt;template v-slot:footer&amp;gt;
    &amp;lt;p&amp;gt;Here&apos;s some contact info&amp;lt;/p&amp;gt;
  &amp;lt;/template&amp;gt;
&amp;lt;/base-layout&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;任何一种写法子组件中都会渲染出：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;div class=&quot;container&quot;&amp;gt;
  &amp;lt;header&amp;gt;
    &amp;lt;h1&amp;gt;Here might be a page title&amp;lt;/h1&amp;gt;
  &amp;lt;/header&amp;gt;
  &amp;lt;main&amp;gt;
    &amp;lt;p&amp;gt;A paragraph for the main content.&amp;lt;/p&amp;gt;
    &amp;lt;p&amp;gt;And another one.&amp;lt;/p&amp;gt;
  &amp;lt;/main&amp;gt;
  &amp;lt;footer&amp;gt;
    &amp;lt;p&amp;gt;Here&apos;s some contact info&amp;lt;/p&amp;gt;
  &amp;lt;/footer&amp;gt;
&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h5&gt;20.2 作用域插槽（新语法）&lt;/h5&gt;
&lt;p&gt;子组件模板如下&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;span&amp;gt;
  &amp;lt;slot v-bind:user=&quot;user&quot;&amp;gt;
    {{ user.lastName }}
  &amp;lt;/slot&amp;gt;
&amp;lt;/span&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在父组件中：&lt;br /&gt;
绑定在 元素上的 attribute 被称为插槽 prop&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;!-- slotProps 可以使任意的名字 --&amp;gt;
&amp;lt;current-user&amp;gt;
  &amp;lt;template v-slot:default=&quot;slotProps&quot;&amp;gt;
    {{ slotProps.user.firstName }}
  &amp;lt;/template&amp;gt;
&amp;lt;/current-user&amp;gt;
简化版
&amp;lt;!--当被提供的内容只有默认插槽时，组件的标签才可以被当作插槽的模板来使用--&amp;gt;
&amp;lt;current-user v-slot=&quot;slotProps&quot;&amp;gt;
  {{ slotProps.user.firstName }}
&amp;lt;/current-user&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;出现多个插槽，请始终为所有的插槽使用完整的基于 的语法：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;current-user&amp;gt;
  &amp;lt;template v-slot:default=&quot;slotProps&quot;&amp;gt;
    {{ slotProps.user.firstName }}
  &amp;lt;/template&amp;gt;

  &amp;lt;template v-slot:other=&quot;otherSlotProps&quot;&amp;gt;
    ...
  &amp;lt;/template&amp;gt;
&amp;lt;/current-user&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;解构插槽 Prop&lt;/strong&gt;&lt;br /&gt;
v-slot 的值实际上可以是任何能够作为函数定义中的参数的 JavaScript 表达式，可以使用 ES2015 解构来传入具体的插槽 prop&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;current-user v-slot=&quot;{ user }&quot;&amp;gt;
  {{ user.firstName }}
&amp;lt;/current-user&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;将 user 重命名为 person&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;current-user v-slot=&quot;{ user: person }&quot;&amp;gt;
  {{ person.firstName }}
&amp;lt;/current-user&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;你甚至可以定义后备内容，用于插槽 prop 是 undefined 的情形&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;current-user v-slot=&quot;{ user = { firstName: &apos;Guest&apos; } }&quot;&amp;gt;
  {{ user.firstName }}
&amp;lt;/current-user&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h5&gt;20.3 具名插槽的缩写&lt;/h5&gt;
&lt;p&gt;v-slot: 替换为字符== #== 例如 v-slot:header 可以被重写为 #header&lt;/p&gt;
&lt;p&gt;然而，和其它指令一样，该缩写只在其有参数的时候才可用。这意味着以下语法是无效的&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;!-- 这样会触发一个警告 --&amp;gt;
&amp;lt;current-user #=&quot;{ user }&quot;&amp;gt;
  {{ user.firstName }}
&amp;lt;/current-user&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;必须始终以明确插槽名&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;current-user #default=&quot;{ user }&quot;&amp;gt;
  {{ user.firstName }}
&amp;lt;/current-user&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;21. 动态组件和异步组件&lt;/h4&gt;
</content:encoded></item><item><title>element 上传组件使用</title><link>https://qingge-cabana.vercel.app/posts/element-%E4%B8%8A%E4%BC%A0%E7%BB%84%E4%BB%B6%E4%BD%BF%E7%94%A8-108147499/</link><guid isPermaLink="true">https://qingge-cabana.vercel.app/posts/element-%E4%B8%8A%E4%BC%A0%E7%BB%84%E4%BB%B6%E4%BD%BF%E7%94%A8-108147499/</guid><description>从零上手 Element 上传组件，覆盖单图上传配置、属性绑定与实际项目中的调整要点。</description><pubDate>Fri, 21 Aug 2020 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;本文同步自 &lt;a href=&quot;https://blog.csdn.net/weixin_44980732/article/details/108147499&quot;&gt;CSDN · 清阿哥&lt;/a&gt;，原文发布于 2020-08-21。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;1. 单张图片上传&lt;/h3&gt;
&lt;p&gt;第一次使用element的上传组件，总结一下希望能够帮助到大家，也便于自己以后使用。（大家可以根据自己所需来添加更改绑定的属性。）&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;el-upload
  class=&quot;avatar-uploader&quot;
  accept=&quot;image/jpeg&quot;
  :show-file-list=&quot;false&quot;
  :headers=&quot;{ token: token}&quot;
  :action=&quot;actionUrl&quot;
  :on-success=&quot;handleAvatarSuccess&quot;
  :before-upload=&quot;beforeAvatarUpload&quot;
&amp;gt;
  &amp;lt;img
    v-if=&quot;ruleForm.dialogImageUrl&quot;
    :src=&quot;ruleForm.dialogImageUrl&quot;
    class=&quot;avatar&quot;
  /&amp;gt;
  &amp;lt;i v-else class=&quot;el-icon-plus avatar-uploader-icon&quot;&amp;gt;&amp;lt;/i&amp;gt;
  &amp;lt;div slot=&quot;tip&quot; class=&quot;el-upload__tip&quot;&amp;gt;
    只能上传jpg/png文件，且不超过50kb
  &amp;lt;/div&amp;gt;
&amp;lt;/el-upload&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;// 这里上传的时候需要在请求头重添加 token 我的项目的 token 在登陆之后，把 token 保存在了本地存储中，大家根据自己项目的实际情况添加token,
  mounted () {
    this.token = sessionStorage.getItem(&quot;tk&quot;);
    this.actionUrl = this.common.data().shoppingMerchant + &quot;/imageUpload/brand&quot;; // 我的项目封装好的后台路径 根据自己项目的实际情况写
  },
  methods:{
	handleAvatarSuccess (res, file) { // 上传成功后展示图片
    this.ruleForm.dialogImageUrl = this.$baseImgUrl + res.data;
    this.$set(this.ruleForm, this.ruleForm.dialogImageUrl, res.data)
  },
  beforeAvatarUpload (file) { // 上传前校验图片（根据自己项目的实际需求添加）
    const imgType = file.type === &apos;image/jpeg&apos; || file.type === &apos;image/png&apos;;
    const isLt50k = file.size / 1024 / 1024 &amp;lt; 0.048;
    if (!imgType) {
      this.$message.error(&apos;上传头像图片只能是 JPG和png 格式!&apos;);
      return false
    }
    if (!isLt50k) {
      this.$message.error(&apos;上传头像图片大小不能超过 50k!&apos;);
      return false
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2. 上传多张图片&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;el-upload
  class=&quot;upload&quot;
  :headers=&quot;{ token: token}&quot;
  :action=&quot;actionUrl&quot;
  list-type=&quot;picture-card&quot;
  :on-remove=&quot;handleDetailRemove&quot;
  :before-upload=&quot;handleDetailBefore&quot;
  :on-success=&quot;handleDetailSuccess&quot;
  :limit=&quot;5&quot;
  :file-list=&quot;detailImgList&quot;
&amp;gt;
  &amp;lt;i class=&quot;el-icon-plus&quot;&amp;gt;&amp;lt;/i&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;file-list 属性是个关键他有自己的数据结构 这里需要多注意（上传成功后图片有三个属性url，uid，status）&lt;br /&gt;
&lt;img src=&quot;https://i-blog.csdnimg.cn/blog_migrate/834876ddf5e1b076202e5d1f302379e4.png#pic_center&quot; alt=&quot;在这里插入图片描述&quot; /&gt;&lt;br /&gt;
limit 限制上传几张图片&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;handleDetailRemove (file) { // 删除详情页图片
  var vm = this;
  var url = vm.common.data().shoppingMerchant + &quot;/file/delFile&quot;
  var data = {
    fileId: file.url
  }
  vm.common.shopGetData(vm, url, data, function (res) {
    vm.detailImgList.forEach((item, index) =&amp;gt; {
      if (item.uid == file.uid) {
        vm.detailImgList.splice(index, 1)
      }
    })
  });
  console.log(vm.detailImgList)
},
handleDetailBefore (file) { // 详情页图片上传大小类型限制
  const imgType = file.type === &apos;image/png&apos; || file.type == &apos;im
  const isLt300k = file.size / 1024 / 1024 &amp;lt; 0.29;
  if (!imgType) {
    this.$message.error(&apos;上传图片只支持 jpg,png和jpeg 格式图片!
    return false
  }
  if (!isLt300k) {
    this.$message.error(&apos;上传图片大小不能超过 300k!&apos;);
    return false
  }
},
handleDetailSuccess (res, file) { // 上传成功后显示图片
  if (res.returnCode == 10001) {
    this.ruleForm.detailImages.push(res.data)
    this.detailImgList.push({
      url: this.$baseImgUrl + res.data,
      uid: file.uid,
      status: file.status
    })
  } else {
    this.$message.error(&apos;详情页图片上传失败&apos;);
  }
},
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>element 开关组件显示开关文字总结</title><link>https://qingge-cabana.vercel.app/posts/element-%E5%BC%80%E5%85%B3%E7%BB%84%E4%BB%B6%E6%98%BE%E7%A4%BA%E5%BC%80%E5%85%B3%E6%96%87%E5%AD%97%E6%80%BB%E7%BB%93-108146575/</link><guid isPermaLink="true">https://qingge-cabana.vercel.app/posts/element-%E5%BC%80%E5%85%B3%E7%BB%84%E4%BB%B6%E6%98%BE%E7%A4%BA%E5%BC%80%E5%85%B3%E6%96%87%E5%AD%97%E6%80%BB%E7%BB%93-108146575/</guid><description>讲解 Element 开关组件如何在组件本体上显示开关文字，而不是仅放在旁侧的描述文案。</description><pubDate>Fri, 21 Aug 2020 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;本文同步自 &lt;a href=&quot;https://blog.csdn.net/weixin_44980732/article/details/108146575&quot;&gt;CSDN · 清阿哥&lt;/a&gt;，原文发布于 2020-08-21。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;1. 显示开关文字&lt;/h3&gt;
&lt;p&gt;表格中使用开关组件，官方例子的文字描述是显示在开关组件的后面，如何显示在组件上呢？废话不多说直接上教程。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;html
&amp;lt;el-table-column label=&quot;是否显示&quot;&amp;gt;
  &amp;lt;template v-slot=&quot;scope&quot;&amp;gt;
    &amp;lt;el-switch
      v-model=&quot;scope.row.show&quot;
      class=&quot;switchStyle&quot;
      :active-value=&quot;1&quot;  
      :inactive-value=&quot;0&quot;
      active-text=&quot;开&quot;
      inactive-text=&quot;关&quot;
      active-color=&quot;#13ce66&quot;
      inactive-color=&quot;#ff4949&quot;
      @change=&quot;getBrandList(scope.row.id)&quot;
    &amp;gt;
    &amp;lt;/el-switch&amp;gt;
  &amp;lt;/template&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;只有上面的html是不行的，需要调节下样式&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;style lang=&quot;less&quot;&amp;gt;
.switchStyle .el-switch__label {
  position: absolute;
  display: none;
  color: #fff;
}
.switchStyle .el-switch__label--left {
  z-index: 9;
  left: 6px;
}
.switchStyle .el-switch__label--right {
  z-index: 9;
  left: -14px;
}
.switchStyle .el-switch__label.is-active {
  display: block;
}
.switchStyle.el-switch .el-switch__core,
.el-switch .el-switch__label {
  width: 50px !important;
}
&amp;lt;/style&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;来看效果图&lt;br /&gt;
&lt;img src=&quot;https://i-blog.csdnimg.cn/blog_migrate/d3a2b0d4443ed11d657d69942a9dceed.png#pic_center&quot; alt=&quot;在这里插入图片描述&quot; /&gt;&lt;br /&gt;
上面的组件的属性如果有不懂的地方可以自行看下官方文档，有什么问题欢迎在下方留言交流。&lt;/p&gt;
</content:encoded></item><item><title>vue 路由拆分</title><link>https://qingge-cabana.vercel.app/posts/vue-%E8%B7%AF%E7%94%B1%E6%8B%86%E5%88%86-108103240/</link><guid isPermaLink="true">https://qingge-cabana.vercel.app/posts/vue-%E8%B7%AF%E7%94%B1%E6%8B%86%E5%88%86-108103240/</guid><description>介绍 Vue 路由拆分与模块化组织方式，让大型项目的路由配置更清晰、更易维护。</description><pubDate>Wed, 19 Aug 2020 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;本文同步自 &lt;a href=&quot;https://blog.csdn.net/weixin_44980732/article/details/108103240&quot;&gt;CSDN · 清阿哥&lt;/a&gt;，原文发布于 2020-08-19。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;1. 问题&lt;/h3&gt;
&lt;p&gt;随着项目越来越大，路由会越来越多，如果路由都写在同一个文件中会显得页面混乱，可读性很差，不易查找维护。&lt;br /&gt;
&lt;img src=&quot;https://i-blog.csdnimg.cn/blog_migrate/cf3e33da6fd4d32fe57ce9a3696e6a52.png#pic_center&quot; alt=&quot;在这里插入图片描述&quot; /&gt;&lt;br /&gt;
&lt;img src=&quot;https://i-blog.csdnimg.cn/blog_migrate/a6655fa9b5dd35016c306378f445b344.png#pic_center&quot; alt=&quot;在这里插入图片描述&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;2. 解决方法&lt;/h3&gt;
&lt;h4&gt;拆分路由：&lt;/h4&gt;
&lt;p&gt;在 router 文件夹中创建新的文件&lt;br /&gt;
&lt;img src=&quot;https://i-blog.csdnimg.cn/blog_migrate/e16576515a10b9ca3f48c675e2027c94.png#pic_center&quot; alt=&quot;在这里插入图片描述&quot; /&gt;&lt;/p&gt;
&lt;h4&gt;shoppingMallBusiness.js 文件&lt;/h4&gt;
&lt;p&gt;里面还按照正常路由写法&lt;br /&gt;
&lt;img src=&quot;https://i-blog.csdnimg.cn/blog_migrate/5a85ed92435f23e00a79eebdacbb3a86.png#pic_center&quot; alt=&quot;在这里插入图片描述&quot; /&gt;&lt;/p&gt;
&lt;h4&gt;index.js 文件&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;引入&lt;/strong&gt;&lt;br /&gt;
&lt;img src=&quot;https://i-blog.csdnimg.cn/blog_migrate/a4ef544f5f56257e23356d10cfd2ec4b.png#pic_center&quot; alt=&quot;在这里插入图片描述&quot; /&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const router =  new Router({
    routes: [
		...shoppingMallBusiness, // 电商商户端
	],
})
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>H5和Android ios交互</title><link>https://qingge-cabana.vercel.app/posts/h5%E5%92%8Candroid-ios%E4%BA%A4%E4%BA%92-107041442/</link><guid isPermaLink="true">https://qingge-cabana.vercel.app/posts/h5%E5%92%8Candroid-ios%E4%BA%A4%E4%BA%92-107041442/</guid><description>介绍 H5 与 Android / iOS 原生交互的判断与调用方式，适合 WebView 混合开发踩坑与联调。</description><pubDate>Tue, 30 Jun 2020 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;本文同步自 &lt;a href=&quot;https://blog.csdn.net/weixin_44980732/article/details/107041442&quot;&gt;CSDN · 清阿哥&lt;/a&gt;，原文发布于 2020-06-30。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;1. 判断手机是安卓或者ios方法&lt;/h3&gt;
&lt;h4&gt;1.1 添加判断机型方法（适用于Android和ios调用方法不一样）&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;// checkDeciveTyoe.js
function checkDevice() {
  // js判断是否是苹果设备
  function checkIsAppleDevice() {
      var u = navigator.userAgent,
          app = navigator.appVersion;
      var ios = !!u.match(/\(i[^;]+;( U;)? CPU.+Mac OS X/);
      var iPad = u.indexOf(&quot;iPad&quot;) &amp;gt; -1;
      var iPhone = u.indexOf(&quot;iPhone&quot;) &amp;gt; -1 || u.indexOf(&quot;Mac&quot;) &amp;gt; -1;
      if (ios || iPad || iPhone) {
          return true;
      } else {
          return false;
      }
  }
  //js判断是否为Android设备
  function checkIsAndroidDevice() {
      var u = navigator.userAgent;
      if (u.indexOf(&quot;Android&quot;) &amp;gt; -1 || u.indexOf(&quot;Adr&quot;) &amp;gt; -1) {
          return true;
      } else {
          return false;
      }
  }

  if (checkIsAppleDevice()) {
      return &quot;ios&quot;;
  } else {
      return &quot;Android&quot;;
  }
}
export default checkDevice;
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;1.2 页面中使用（vue）&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;// 引入检测机型方法
import checkDevice from &quot;@/utils/checkDeviceType&quot;;
export default{
	data: { 
		phone: &apos;&apos;,
		userInfo : {},
	};
	created(){
		this.phone = checkDevice();
		window.getAppUser = this.getAppUser // 接收ios返回的数据
	}，
	methods: {
	   handleQuit () {
  	   	 if (this.phone == &quot;Android&quot;) {
      		 // 调用安卓的方法
      		 window.android.goToAppHome();
     	 } else {
      		 // 调用ios方法
      	  window.webkit &amp;amp;&amp;amp;
          window.webkit.messageHandlers &amp;amp;&amp;amp;
          window.webkit.messageHandlers.closeWebView &amp;amp;&amp;amp;
          window.webkit.messageHandlers.closeWebView.postMessage &amp;amp;&amp;amp;
          window.webkit.messageHandlers.closeWebView.postMessage(null);
          }
      	},
      	getAppUser (str) { // ios 接受用户基本信息
      		this.userInfo = JSON.parse(str) // 原生返回的是JSON格式 需要转成对象
    	},
	}
}
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>CSS 水平居中，垂直居中，水平垂直居中总结</title><link>https://qingge-cabana.vercel.app/posts/css-%E6%B0%B4%E5%B9%B3%E5%B1%85%E4%B8%AD%E5%9E%82%E7%9B%B4%E5%B1%85%E4%B8%AD%E6%B0%B4%E5%B9%B3%E5%9E%82%E7%9B%B4%E5%B1%85%E4%B8%AD%E6%80%BB%E7%BB%93-107042664/</link><guid isPermaLink="true">https://qingge-cabana.vercel.app/posts/css-%E6%B0%B4%E5%B9%B3%E5%B1%85%E4%B8%AD%E5%9E%82%E7%9B%B4%E5%B1%85%E4%B8%AD%E6%B0%B4%E5%B9%B3%E5%9E%82%E7%9B%B4%E5%B1%85%E4%B8%AD%E6%80%BB%E7%BB%93-107042664/</guid><description>系统总结 CSS 水平居中、垂直居中与水平垂直居中的常见写法，覆盖行内元素、块级元素与定位方案。</description><pubDate>Tue, 30 Jun 2020 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;本文同步自 &lt;a href=&quot;https://blog.csdn.net/weixin_44980732/article/details/107042664&quot;&gt;CSDN · 清阿哥&lt;/a&gt;，原文发布于 2020-06-30。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;1. 水平居中&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;  &amp;lt;div class=&quot;father&quot;&amp;gt;
    &amp;lt;div class=&quot;son&quot;&amp;gt;水平居中&amp;lt;/div&amp;gt;
  &amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;1.  /* 行内元素 */
.father {
  text-align: center;
}
.son {
  background: red;
}

/* 块级元素 */
2. /* 已知宽度 */
.son {
  width: 200px;
  background: red;
  margin: 0 auto;
}
3. /* 未知宽度 */
.son {
  display: table;
  margin: 0 auto;
  background: red;
}

4. /* 转成行内元素 */
.father {
  text-align: center;
}
.son {
  display: inline-block;
}

5. /* flex布局 */
.father {
  display: flex;
  justify-content: center;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2. 垂直居中&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt; &amp;lt;div class=&quot;father&quot;&amp;gt;
    &amp;lt;div class=&quot;son&quot;&amp;gt;垂直居中&amp;lt;/div&amp;gt;
 &amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;1. /* 单行文本 */
.father {
  height: 100px;
  background: red;
}
.son {
  line-height: 100px;
}

2. /* 多行文本 */
.father {
  height: 100px;
  background: red;
  display: table;
}
.son {
  display: table-cell;
  vertical-align: middle;
}

3. /* 块级元素 垂直居中 */
.father {
  background: yellow;
  height: 200px;
  display: flex;
  align-items: center;
}

4. /* 子绝父相 + 子top + 负margin */
.father {
  position: relative;
  width: 200px;
  height: 200px;
  border: 1px solid #000;
}
.son {
  background: red;
  width: 100px;
  height: 100px;
  line-height: 100px;
  position: absolute;
  top: 50%;
  margin-top: -50px;
}

5. /* 子绝父相 子 margin: auto */
.father {
  width: 200px;
  height: 200px;
  position: relative;
  border: 1px solid #000;
}
.son {
  width: 100px;
  height: 100px;
  position: absolute;
  top: 0;
  bottom: 0;
  margin: auto;
  background: red;
}

6. /* 子绝父相 子transform */
.father {
  width: 200px;
  height: 200px;
  position: relative;
  border: 1px solid #000;
}
.son {
  width: 100px;
  height: 100px;
  position: absolute;
  top: 50%;
  transform: translate(0,-50%);
  background: red;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;3. 水平垂直居中&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;  &amp;lt;div class=&quot;father&quot;&amp;gt;
    &amp;lt;div class=&quot;son&quot;&amp;gt;水平垂直居中&amp;lt;/div&amp;gt;
  &amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;/* 父元素 flex布局 */
.father {
  display: flex;
  justify-content: center;
  align-items: center;
}

/* 子绝父相  子 margin: auto*/
.father {
  position: relative;
}
.son {
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
  margin: auto;
}

/* 子绝父相 子transform  未知宽度*/
.father {
  position: relative;
}
.son {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%,-50%);
}

/* 子绝父相 子margin 已知宽度*/
.father {
  width: 200px;
  height: 200px;
  position: relative;
}
.son {
  width: 10px;
  height: 100px;
  position: absolute;
  top: 50%;
  left: 50%;
  margin-top: -50px;;
  margin-left: -50px;
}

/* table-cell */
.father {
  width: 200px;
  height: 200px;
  display: table-cell;
  text-align: center;
  vertical-align: middle;
  border: 1px solid #000;
}

.son {
  background: green;
  display: inline-block;
}

/* display: flex-box 水平垂直终极方法 解决各种难题 */
.father {
  display: -webkit-flex;
  display: -moz-box;
  display: -ms-flexbox;
  display: -webkit-box;
  display: flex;
  -webkit-box-align: center;
  -moz-box-align: center;
  -ms-flex-align: center;
  -webkit-align-items: center;
  align-items: center;
  -webkit-box-pack: center;
  -moz-box-pack: center;
  -ms-flex-pack: center;
  -webkit-justify-content: center;
  justify-content: center;
  width: 200px;
  height: 200px;
  background-color: orange;
  text-align: center;
}
 
.son {
  width: 100px;
  height: 100px;
  line-height: 100px;
  background-color: red;
}
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>图片和span标签水平垂直对齐</title><link>https://qingge-cabana.vercel.app/posts/%E5%9B%BE%E7%89%87%E5%92%8Cspan%E6%A0%87%E7%AD%BE%E6%B0%B4%E5%B9%B3%E5%9E%82%E7%9B%B4%E5%AF%B9%E9%BD%90-106382621/</link><guid isPermaLink="true">https://qingge-cabana.vercel.app/posts/%E5%9B%BE%E7%89%87%E5%92%8Cspan%E6%A0%87%E7%AD%BE%E6%B0%B4%E5%B9%B3%E5%9E%82%E7%9B%B4%E5%AF%B9%E9%BD%90-106382621/</guid><description>解决图片与 span 水平垂直对齐的常见问题，给出 flex、line-height 等多种对齐写法。</description><pubDate>Wed, 27 May 2020 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;本文同步自 &lt;a href=&quot;https://blog.csdn.net/weixin_44980732/article/details/106382621&quot;&gt;CSDN · 清阿哥&lt;/a&gt;，原文发布于 2020-05-27。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h6&gt;HTML&lt;/h6&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;ul class=&quot;topUl&quot;&amp;gt;
  &amp;lt;li&amp;gt;
    &amp;lt;img src=&quot;@/assets/propertyPayCost.png&quot;&amp;gt;
    &amp;lt;span&amp;gt;物业缴费&amp;lt;/span&amp;gt;
  &amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h6&gt;CSS&lt;/h6&gt;
&lt;pre&gt;&lt;code&gt;.topUl li {
  height: 60px;
  line-height: 60px;
}
.topUl img {
  width: 24px;
  margin-right: 8px;
  vertical-align: middle;
}
.topUl span {
  vertical-align: middle;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h6&gt;效果图&lt;/h6&gt;
&lt;p&gt;&lt;img src=&quot;https://i-blog.csdnimg.cn/blog_migrate/482b73a382bdb18ea208edff65bedcbd.png&quot; alt=&quot;在这里插入图片描述&quot; /&gt;&lt;/p&gt;
</content:encoded></item><item><title>vue使用vant的list组件使用</title><link>https://qingge-cabana.vercel.app/posts/vue%E4%BD%BF%E7%94%A8vant%E7%9A%84list%E7%BB%84%E4%BB%B6%E4%BD%BF%E7%94%A8-106358727/</link><guid isPermaLink="true">https://qingge-cabana.vercel.app/posts/vue%E4%BD%BF%E7%94%A8vant%E7%9A%84list%E7%BB%84%E4%BB%B6%E4%BD%BF%E7%94%A8-106358727/</guid><description>讲解 Vant List 列表组件在 Vue 移动端的用法，覆盖上拉加载与分页数据对接。</description><pubDate>Tue, 26 May 2020 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;本文同步自 &lt;a href=&quot;https://blog.csdn.net/weixin_44980732/article/details/106358727&quot;&gt;CSDN · 清阿哥&lt;/a&gt;，原文发布于 2020-05-26。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h6&gt;模板代码&lt;/h6&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;van-pull-refresh
  v-model=&quot;refreshing&quot;
  @refresh=&quot;onRefresh&quot;
&amp;gt;
  &amp;lt;van-list
    v-model=&quot;loading&quot;
    :finished=&quot;finished&quot;
    :finished-text=&quot;finishedText&quot;
    @load=&quot;pullupLoadMore&quot;
    :immediate-check=&quot;false&quot;
    :error.sync=&quot;error&quot;
    error-text=&quot;请求失败，点击重新加载&quot;
  &amp;gt;
    &amp;lt;ul
      v-for=&quot;(item,id) in allHouseDetail&quot;
      :key=&quot;id&quot;
    &amp;gt;
      &amp;lt;li @click=&quot;chooseRoom(item.roomAddress,item)&quot;&amp;gt;{{ item.roomAddress }}&amp;lt;/li&amp;gt;
    &amp;lt;/ul&amp;gt;
  &amp;lt;/van-list&amp;gt;
&amp;lt;/van-pull-refresh&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h6&gt;js代码&lt;/h6&gt;
&lt;pre&gt;&lt;code&gt; data () {
    return {
      allHouseDetail: [], // 所有房屋数据
      loading: false, // 是否处在加载状态
      finished: false, // 是否完成加载
      finishedText: &apos;&apos;, // 加载完毕文本提示
      page: 1, // 当前页码
      page_size: 100, // 每页多少条
      total: 0, // 总条数
      error: false, // 错误提示
	  refreshing: false, // 下拉刷新
    };
  },
methods: {
  getHouseDetail () {
  this.communityId = JSON.parse(localStorage.getItem(&apos;areaDetail&apos;)).areaId
  this.areaName = JSON.parse(localStorage.getItem(&apos;areaDetail&apos;)).areaName
  this.$http({
    method: &apos;post&apos;,
    url: &apos;你的请求路径&apos;,
    data: {
      communityId: this.communityId,
      page: this.page,
      limit: this.page_size
    }
  }).then(res =&amp;gt; {
try {
  if (res.returnCode == 10001) {
    if (this.refreshing) { // 下拉刷新
      this.allHouseDetail = [];
      this.refreshing = false;
    }
    if (this.page == 1) {
      this.loading = false
      this.allHouseDetail = res.data
      this.total = res.total;
    } else {
      this.loading = false
      this.allHouseDetail = this.allHouseDetail.concat(res.data)
      this.total = res.total;
    }
    if (res.data.length == 0) {
      this.finished = true;
      this.finishedText = &apos;暂无数据&apos;;
    } else if (res.data.length &amp;lt; this.page_size) {
      this.finished = true;
      this.finishedText = &apos;没有更多啦&apos;;
    }
  } else {
    Toast.fail(res.returnMsg);
  }
} catch {
  this.error = true;
  }
  })
},
pullupLoadMore () { // 上拉加载更多
  this.page++; 
  this.getHouseDetail();
},
onRefresh () { // 下拉刷新
  this.finished = false;
  this.loading = true;
  this.refreshing = true
  this.page = 1
  this.getHouseDetail();
},
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h5&gt;遇到的问题&lt;/h5&gt;
&lt;p&gt;&lt;strong&gt;进入页面load事件就触发了&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;根据vant官方哪个提示，在模板中添加 :immediate-check=“false” 即可关闭初始化时改事件的触发&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;ps&lt;/strong&gt;&lt;br /&gt;
今天才发现vant 的list组件 对常见的使用问题下面有提示、&lt;br /&gt;
&lt;img src=&quot;https://i-blog.csdnimg.cn/blog_migrate/af7d98fbe6e5cb6d94d2e1d9fef87fa0.png&quot; alt=&quot;在这里插入图片描述&quot; /&gt;&lt;/p&gt;
&lt;p&gt;好了总结完毕！有什么问题可以留言一起交流下。&lt;/p&gt;
</content:encoded></item><item><title>vue移动端长按事件触发删除按钮</title><link>https://qingge-cabana.vercel.app/posts/vue%E7%A7%BB%E5%8A%A8%E7%AB%AF%E9%95%BF%E6%8C%89%E4%BA%8B%E4%BB%B6%E8%A7%A6%E5%8F%91%E5%88%A0%E9%99%A4%E6%8C%89%E9%92%AE-106334257/</link><guid isPermaLink="true">https://qingge-cabana.vercel.app/posts/vue%E7%A7%BB%E5%8A%A8%E7%AB%AF%E9%95%BF%E6%8C%89%E4%BA%8B%E4%BB%B6%E8%A7%A6%E5%8F%91%E5%88%A0%E9%99%A4%E6%8C%89%E9%92%AE-106334257/</guid><description>基于 touchstart / touchend / touchmove 实现移动端长按，用于触发删除按钮等交互。</description><pubDate>Mon, 25 May 2020 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;本文同步自 &lt;a href=&quot;https://blog.csdn.net/weixin_44980732/article/details/106334257&quot;&gt;CSDN · 清阿哥&lt;/a&gt;，原文发布于 2020-05-25。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;利用移动端的touchstart touchend touchmove 事件结合 实现长按事件的触发&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;div
  class=&quot;item&quot;
  @touchstart=&quot;gtouchstart(item, item.id, $event)&quot;
  @touchmove=&quot;gtouchmove($event, item, item.id)&quot;
  @touchend=&quot;gtouchend(item, item.id)&quot;
&amp;gt;
&amp;lt;p
  class=&quot;cancel&quot;
  v-show=&quot;item.show == &apos;true&apos; &amp;amp;&amp;amp; radio == item.id&quot;
  @click=&quot;deleteHouse(item.isDefault)&quot;
&amp;gt;删除&amp;lt;/p&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;gtouchstart (item, id, e) {
  this.phone = checkDevice();
  if (this.phone == &quot;Android&quot;) {
    // 长按3秒 开始触摸
    this.timeOutEvent = 0
    this.timeOutEvent = setTimeout(() =&amp;gt; {
      this.radio = id
      this.$set(item, &apos;show&apos;, &apos;true&apos;)
    }, 500)
  } else {
    this.moveDistance = 0 // 阻力滑动距离归0
    this.move = 0 // 移动距离归0
    this.startX = e.targetTouches[0].clientX // x轴归0
  }
},
gtouchmove (e, item, id) {
  this.phone = checkDevice();
  if (this.phone == &quot;Android&quot;) {
    clearTimeout(this.timeOutEvent)
    this.timeOutEvent = 0
  } else {
    // 获取左滑的距离
    this.move = this.startX - e.targetTouches[0].clientX
    console.log(this.move)
    // move大于0，说明手指移动了
    if (this.move &amp;gt; 0) {
      // 阻止默认事件，因为有些app会有默认事件
      e.preventDefault()
      // 增加滑动阻力，尤为重要，如果不要这一步，上下滑动就会不经意触发左右滑动事件
      this.moveDistance = Math.pow(this.move, 0.8)
      // 设置滑动的最大距离，如果阻力滑动的距离大于按钮的宽度，就赋值为按钮的宽度
      if (this.moveDistance &amp;gt; this.distance) {
        // 如果滑动距离大于15 出现隐藏按钮
        this.radio = id
        this.$set(item, &apos;show&apos;, &apos;true&apos;)
      } else {
        // 滑动距离并没有超过按钮的一半，给我归位
        this.radio = &apos;&apos;
        this.$set(item, &apos;show&apos;, &apos;false&apos;)
      }
    }
  }
},
gtouchend (item, id) {
  this.phone = checkDevice();
  if (this.phone == &quot;Android&quot;) {
    clearTimeout(this.timeOutEvent)
    this.timeOutEvent = 0
  } else {
    if (this.move == 0 &amp;amp;&amp;amp; item.show == &apos;true&apos;) {
      // 触发点击事件
      this.$emit(&apos;deleteHouse&apos;)
    } else if (this.move &amp;gt; 0 &amp;amp;&amp;amp; item.show == &apos;false&apos;) {
      // 如果滑动结束  滑动距离大于右侧按钮的一半  则出现按钮，否则隐藏按钮
      if (this.moveDistance &amp;gt; this.distance / 2) {
        this.radio = id
        this.$set(item, &apos;show&apos;, &apos;true&apos;)
      } else {
        // 滑动距离并没有超过按钮的一半，给我归位
        this.radio = &apos;&apos;
        this.$set(item, &apos;show&apos;, &apos;false&apos;)
      }
    } else {
      this.radio = &apos;&apos;
      this.$set(item, &apos;show&apos;, &apos;false&apos;)
    }
  }
},
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;实现的效果是 长按一秒显示删除按钮&lt;br /&gt;
&lt;img src=&quot;https://i-blog.csdnimg.cn/blog_migrate/30bc0da1bbecbe08b3724dcb05ae8c30.png&quot; alt=&quot;在这里插入图片描述&quot; /&gt;&lt;/p&gt;
</content:encoded></item><item><title>vue输入框使用模糊搜索</title><link>https://qingge-cabana.vercel.app/posts/vue%E8%BE%93%E5%85%A5%E6%A1%86%E4%BD%BF%E7%94%A8%E6%A8%A1%E7%B3%8A%E6%90%9C%E7%B4%A2-106326200/</link><guid isPermaLink="true">https://qingge-cabana.vercel.app/posts/vue%E8%BE%93%E5%85%A5%E6%A1%86%E4%BD%BF%E7%94%A8%E6%A8%A1%E7%B3%8A%E6%90%9C%E7%B4%A2-106326200/</guid><description>用 JS indexOf 实现输入框模糊搜索，给出模板结构与过滤逻辑的完整示例。</description><pubDate>Mon, 25 May 2020 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;本文同步自 &lt;a href=&quot;https://blog.csdn.net/weixin_44980732/article/details/106326200&quot;&gt;CSDN · 清阿哥&lt;/a&gt;，原文发布于 2020-05-25。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;实现原理&lt;/strong&gt;:&lt;br /&gt;
利用js的 indexOf 方法可返回某个指定的字符串值在字符串中首次出现的位置。&lt;/p&gt;
&lt;h6&gt;模板中的代码&lt;/h6&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;div class=&quot;search&quot;&amp;gt;
&amp;lt;!--输入框使用的是vant的输入框组件--&amp;gt;
	&amp;lt;van-search
       @input=&quot;autoSearch&quot;
       v-model=&quot;value&quot;
       placeholder=&quot;请输入搜索关键词&quot;
       style=&quot;width:90%; display:inline-block;&quot;
    /&amp;gt;
       &amp;lt;span&amp;gt;搜索&amp;lt;/span&amp;gt;
    &amp;lt;/div&amp;gt;
&amp;lt;!--展示数据--&amp;gt;
&amp;lt;ul
	v-for=&quot;(item) in allArea&quot;
    :key=&quot;item.communityId&quot;
 &amp;gt;
    &amp;lt;li v-if=&quot;allNewArea.length != 0&quot;&amp;gt;{{ item.communityName }}&amp;lt;/li&amp;gt;
     &amp;lt;li v-else&amp;gt;{{ item.communityName }}&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h6&gt;js代码&lt;/h6&gt;
&lt;pre&gt;&lt;code&gt;	getAreaDetail () { // 获取数据
      this.$http({
        method: &apos;post&apos;,
        url: &apos;/appProperty/getCommunity&apos;,
      }).then(res =&amp;gt; {
        this.allArea = res.data
        this.allNewArea = res.data
      })
    },
    autoSearch () { // 模糊搜索加节流（500ms触发一次）
      var allowPass = true
      if (!allowPass) {
        return
      }
      setTimeout(() =&amp;gt; {
        allowPass = false
        this.allArea = [];
        this.allNewArea.filter(item =&amp;gt; {
          if (item.communityName.indexOf(this.value) !== -1) { // 此处也可使用js的 search 方法实现indexOf 一样效果
            this.allArea.push(item);
          }
        })
      }, 500);
    },
&lt;/code&gt;&lt;/pre&gt;
&lt;h5&gt;拓展&lt;/h5&gt;
&lt;p&gt;&lt;strong&gt;节流函数&lt;/strong&gt; 节省流量节省内存性能的一种函数，可以理解为是一种性能优化方案&lt;/p&gt;
&lt;pre&gt;&lt;code&gt; //首先定义一个全局变量 当浏览器窗口大小发生变化也就是重新计算窗口大小的时候触发
    var canRun = true;
    window.onresize = function () {
      if (!canRun) {
        return
      }
      canRun = false//设置一个定时器进行轮询操作
      setTimeout(function () {//这是要做的事情
        console.log(&quot;函数节流&quot;)//最后记得重新赋值true继续让他取反
        canRun = true//每隔1000毫秒也就是1秒钟就执行一次
      }, 1000)
    }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;防抖函数&lt;/strong&gt; 当事件触发完成之后再延迟触发，并且只触发一次；如果在触发完成之前再次触发，则会再次刷新延迟&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;//定义方法即要做的事情
 function fun(){
   console.log(&apos;onresize&apos;)
 }
//定义事件触发要执行的方法，两个参数分别是传入的要做的事情和定时器的毫秒数
function debounce (fn, delay) {　　//定义一个变量作为等会清除对象
  var handle;　　//这里用到了闭包，一个函数里面return另一个函数，变量相互牵引导致垃圾回收机制不会销毁handle变量
  return function () {　　　　　　//在这里一定要清除前面的定时器，然后创建一个新的定时器
    clearTimeout(handle) 　　　　　　//最后这个定时器只会执行一次也就是事件触发完成之后延迟500毫秒再触发（这里的变量赋值是跟定时器建立连接，进行地址赋值，一定要重新赋值给handle  
    handle = setTimeout(function () {
      fn()
    }, delay)
  }
}
//给浏览器添加监听事件resize
window.addEventListener(&apos;resize&apos;, debounce(fun, 500));
&lt;/code&gt;&lt;/pre&gt;
&lt;h6&gt;两者区别&lt;/h6&gt;
&lt;ol&gt;
&lt;li&gt;防抖函数和节流函数都是用来提升性能优化及用户体验的一种方案；&lt;/li&gt;
&lt;li&gt;防抖函数在一个时间被触发多次，只有最后一次生效&lt;/li&gt;
&lt;li&gt;节流函数n秒后触发一次，n秒内触发后，定时器重新计时&lt;/li&gt;
&lt;/ol&gt;
</content:encoded></item><item><title>js数组求和方法</title><link>https://qingge-cabana.vercel.app/posts/js%E6%95%B0%E7%BB%84%E6%B1%82%E5%92%8C%E6%96%B9%E6%B3%95-106239501/</link><guid isPermaLink="true">https://qingge-cabana.vercel.app/posts/js%E6%95%B0%E7%BB%84%E6%B1%82%E5%92%8C%E6%96%B9%E6%B3%95-106239501/</guid><description>汇总 JS 数组求和的多种实现：for 循环、reduce 等，对比写法差异与适用场景。</description><pubDate>Wed, 20 May 2020 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;本文同步自 &lt;a href=&quot;https://blog.csdn.net/weixin_44980732/article/details/106239501&quot;&gt;CSDN · 清阿哥&lt;/a&gt;，原文发布于 2020-05-20。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;方法一：for循环遍历每一项相加&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt; function sumArr (arr) {
      var sum = 0;
      for(var i = 0;i&amp;lt;=arr.length;i++){
    	sum += arr[i];
    	// 若果不是数字类型  
    	// sum += +arr[i];
   	 }
  return sum;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;方法二：es6的forEach方法&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;function sumArr (arr) {
	var sum = 0;
    arr.forEach(function(val){
    	sum += val;
    })
    return sum;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;方法三：eval（）&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;//直接把他变成各个数的加法运算字符串
function sumArr(arr){
	return eval(arr.join(&quot;+&quot;)) 
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;方法四：函数式编程写法&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;array.reduce(function(prev, current, currentIndex, arr), initialValue)
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;prev：函数传进来的初始值或上一次回调的返回值&lt;/li&gt;
&lt;li&gt;current：数组中当前处理的元素值&lt;/li&gt;
&lt;li&gt;currentIndex：当前元素索引&lt;/li&gt;
&lt;li&gt;arr：当前元素所属的数组本身&lt;/li&gt;
&lt;li&gt;initialValue：传给函数的初始值&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;function sumArr(arr){
	return arr.reduce(function(prev,cur){
        return prev + cur;
    },0);
}
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>CSS文字自动换行</title><link>https://qingge-cabana.vercel.app/posts/css%E6%96%87%E5%AD%97%E8%87%AA%E5%8A%A8%E6%8D%A2%E8%A1%8C-106211274/</link><guid isPermaLink="true">https://qingge-cabana.vercel.app/posts/css%E6%96%87%E5%AD%97%E8%87%AA%E5%8A%A8%E6%8D%A2%E8%A1%8C-106211274/</guid><description>梳理 CSS 文字自动换行相关属性，说明 word-break、white-space 等在中英文混排场景下的用法与差异。</description><pubDate>Tue, 19 May 2020 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;本文同步自 &lt;a href=&quot;https://blog.csdn.net/weixin_44980732/article/details/106211274&quot;&gt;CSDN · 清阿哥&lt;/a&gt;，原文发布于 2020-05-19。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h6&gt;word-break属性:&lt;/h6&gt;
&lt;ul&gt;
&lt;li&gt;normal 只在允许的断字点换行（浏览器保持默认处理）。&lt;/li&gt;
&lt;li&gt;break-word 在长单词或 URL 地址内部进行换行。&lt;/li&gt;
&lt;/ul&gt;
&lt;h6&gt;white-space属性:&lt;/h6&gt;
&lt;ul&gt;
&lt;li&gt;normal 默认。空白会被浏览器忽略。&lt;/li&gt;
&lt;li&gt;pre 空白会被浏览器保留。其行为方式类似 HTML 中的 pre 标签。&lt;/li&gt;
&lt;li&gt;nowrap 文本不会换行，文本会在在同一行上继续，直到遇到&lt;br /&gt;
标签为止。&lt;/li&gt;
&lt;li&gt;pre-wrap 保留空白符序列，但是正常地进行换行。&lt;/li&gt;
&lt;li&gt;pre-line 合并空白符序列，但是保留换行符。&lt;/li&gt;
&lt;li&gt;inherit 规定应该从父元素继承 white-space 属性的值。&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;div&amp;gt;
   &amp;lt;span&amp;gt;户号&amp;lt;/span&amp;gt;
   &amp;lt;span class=&quot;tar&quot;&amp;gt;碧桂园龙城13号楼1单元201室扣篮大赛放假快乐大数据反馈了大数据发&amp;lt;/span&amp;gt;
&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;.tar {
  // 必须设置宽度
  width: 270px;
  word-break: break-word;
  white-space: pre-line;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;效果：&lt;br /&gt;
&lt;img src=&quot;https://i-blog.csdnimg.cn/blog_migrate/b45d8a4c6fdcdd9e623f3387c55a6ea7.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
</content:encoded></item><item><title>vue页面刷新文本框自动获取焦点</title><link>https://qingge-cabana.vercel.app/posts/vue%E9%A1%B5%E9%9D%A2%E5%88%B7%E6%96%B0%E6%96%87%E6%9C%AC%E6%A1%86%E8%87%AA%E5%8A%A8%E8%8E%B7%E5%8F%96%E7%84%A6%E7%82%B9-106146283/</link><guid isPermaLink="true">https://qingge-cabana.vercel.app/posts/vue%E9%A1%B5%E9%9D%A2%E5%88%B7%E6%96%B0%E6%96%87%E6%9C%AC%E6%A1%86%E8%87%AA%E5%8A%A8%E8%8E%B7%E5%8F%96%E7%84%A6%E7%82%B9-106146283/</guid><description>实现 Vue 页面刷新后文本框自动获取焦点，提升表单页的输入效率与体验。</description><pubDate>Fri, 15 May 2020 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;本文同步自 &lt;a href=&quot;https://blog.csdn.net/weixin_44980732/article/details/106146283&quot;&gt;CSDN · 清阿哥&lt;/a&gt;，原文发布于 2020-05-15。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;使用Vue.nextTick实现进入页面文本框自动获取焦点&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;场景:页面加载时需要让文本框获取焦点&lt;/li&gt;
&lt;li&gt;用法:在下次 DOM 更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法，获取更新后的 DOM&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;input type=&quot;text&quot; ref=&quot;inputs&quot;&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;mounted(){ //因为 mounted 阶段 dom 并未渲染完毕,所以需要$nextTick
  this.$nextTick(() =&amp;gt; {
    this.$refs.inputs.focus() //通过 $refs 获取dom 并绑定 focus 方法
  })
}
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>vue.config.js 常用配置总结</title><link>https://qingge-cabana.vercel.app/posts/vueconfigjs-%E5%B8%B8%E7%94%A8%E9%85%8D%E7%BD%AE%E6%80%BB%E7%BB%93-106097386/</link><guid isPermaLink="true">https://qingge-cabana.vercel.app/posts/vueconfigjs-%E5%B8%B8%E7%94%A8%E9%85%8D%E7%BD%AE%E6%80%BB%E7%BB%93-106097386/</guid><description>总结 vue.config.js 常用配置项，覆盖代理、打包、别名等日常开发高频设置。</description><pubDate>Wed, 13 May 2020 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;本文同步自 &lt;a href=&quot;https://blog.csdn.net/weixin_44980732/article/details/106097386&quot;&gt;CSDN · 清阿哥&lt;/a&gt;，原文发布于 2020-05-13。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;第一次写博客，哈哈哈。。。&lt;/strong&gt;&lt;/p&gt;
&lt;h4&gt;1. publicPath&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;部署应用包时的基本 URL&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;publicPath:&apos; &apos; 或者 publicPath:&apos;./&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;2. outputDir&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;打包生成文件出口&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;outputDir: &apos;dist&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;3. asstsDir&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;放置生成的静态资源 (js、css、img、fonts) 的 (相对于 outputDir 的) 目录。&lt;/strong&gt;&lt;/p&gt;
&lt;h4&gt;3. indexPath&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;指定输出路径 相当于outputDir，绝对路径&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;idnexPath: &apos;index.htnl&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;4. filenameHashing&lt;/h4&gt;
&lt;p&gt;&lt;em&gt;无法使用 Vue CLI 生成的 index HTML，你可以通过将这个选项设为 false 来关闭文件名哈希&lt;/em&gt;&lt;/p&gt;
&lt;h4&gt;5. pages&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;module.exports = {
  pages: {
    index: {
      // page 的入口
      entry: &apos;src/index/main.js&apos;,
      // 模板来源
      template: &apos;public/index.html&apos;,
      // 在 dist/index.html 的输出
      filename: &apos;index.html&apos;,
      // 当使用 title 选项时，
      // template 中的 title 标签需要是 &amp;lt;title&amp;gt;&amp;lt;%= htmlWebpackPlugin.options.title %&amp;gt;&amp;lt;/title&amp;gt;
      title: &apos;Index Page&apos;,
      // 在这个页面中包含的块，默认情况下会包含
      // 提取出来的通用 chunk 和 vendor chunk。
      chunks: [&apos;chunk-vendors&apos;, &apos;chunk-common&apos;, &apos;index&apos;]
    },
    // 当使用只有入口的字符串格式时，
    // 模板会被推导为 `public/subpage.html`
    // 并且如果找不到的话，就回退到 `public/index.html`。
    // 输出文件名会被推导为 `subpage.html`。
    subpage: &apos;src/subpage/main.js&apos;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;6. lintOnSave&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;开发环境下通过 eslint-loader 在每次保存时 lint 代码&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;lintOnSave: &apos;warning&apos; / &apos;true&apos; 
将 lint 错误输出为编译警告。默认情况下，警告仅仅会被输出到命令行，且不会使得编译失败
module.exports = {
  devServer: {
    overlay: { // 同时显示警告和错误
      warnings: true,
      errors: true
    }
    // 生产环境禁用
    module.exports = {
  lintOnSave: process.env.NODE_ENV !== &apos;production&apos;
	}
	// 或者
	lintOnSave: &apos;false&apos;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;7. productionSourceMap&lt;/h4&gt;
&lt;p&gt;&lt;em&gt;如果你不需要生产环境的 source map，可以将其设置为 false 以加速生产环境构建&lt;/em&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// 默认
productionSourceMap: true
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;8. css.extract&lt;/h4&gt;
&lt;p&gt;&lt;em&gt;Default: 生产环境下是 true，开发环境下是 false&lt;/em&gt;&lt;/p&gt;
&lt;h4&gt;9. devServer.proxy&lt;/h4&gt;
&lt;p&gt;&lt;em&gt;解决前端跨域&lt;/em&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// vue.config.js 文件
module.exports = {
  devServer: {
proxy: {
      &apos;/api&apos;: {
        target: &apos;http://192.168.30.31:30010&apos;, //接口请求的地址
        changeOrigin: true, // 表示是否跨域
        // ws: true,//websocket支持
        secure: false,
        pathRewrite: {
          &apos;^/api&apos;: &apos;&apos;, // 将&apos;/api&apos; 重写为&apos;&apos;,这里用&apos;/api&apos;代替target里面的地址,再接口其前面加上 /api
        }, // 例如 请求的路径是 &apos;http://xxxx:30010/chargingpile-app/charging_pile_user/login&apos; 组件中可以写成 &apos;/api/charging_pile_user/login&apos;
      		 },
    	},
	}
};
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;10. 出现的问题&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;// 如果加上这段代码启动项目后的Local和Network地址都会变成Local的地址，删除这行代码即可恢复正常
devServer: {
   host: &quot;localhost&quot;,
}
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item></channel></rss>