做网站准备什么问题,做网站怎么引流,江苏电信网站备案,确保网站地址没有做301跳转前言用 vue 全家桶开发一年多了#xff0c;踩过不少坑#xff0c;也解决了很多的问题#xff0c;把其中的一些点记录下来#xff0c;希望能帮到大家。以下内容基于最新版的 vue vuex vue-router axios less elementUI#xff0c;vue 脚手架是 vue-cli3。css 的 scope… 前言用 vue 全家桶开发一年多了踩过不少坑也解决了很多的问题把其中的一些点记录下来希望能帮到大家。以下内容基于最新版的 vue vuex vue-router axios less elementUIvue 脚手架是 vue-cli3。css 的 scoped 属性vue 为了防止 css 污染当组件的 标签有 scoped 属性时它的 css 只作用于当前组件中的元素。实现原理很简单给当前组件中的每个标签都加上唯一的自定义属性data-v-唯一的属性然后 css 选择器都加上属性选择器.article-title[data-v-唯一的属性]这样这个 css 只会匹配到当前页面的这个元素。注意每个组件的最外层的标签会带上父组件的data-v-属性也就是这个标签会被父组件的样式匹配到所以父组件尽量不要使用标签选择器这个标签不要使用父组件中的 id 或者 class。在父组件想修改子组件的 css(修改 elementUI 组件的样式)我们可以借助深度作用选择器 div .el-input{ width: 100px;}/* sass/less的话可能无法识别这时候需要使用 /deep/ 选择器。*/div /deep/ .el-input{ width: 100px;}深度作用选择器会去掉后面元素的属性选择器[data-v-]即上面代码会编译成div[data-v-12345667] .el-input{}。就可以匹配到子组件的元素从而覆盖样式。父子组件的生命周期钩子函数执行先后顺序组件的生命周期钩子函数是到了某个生命周期点就会触发而不是在这个钩子函数中进行生命周期比如说 DOM 加载好了就会触发mounted 钩子函数所以在created 里面写一个延迟定时器mounted 钩子不会等定时器执行。各个周期钩子函数触发的时间点参考(图来源于网络)关于父子组件的生命周期不同的钩子函数有不同的表现。父组件的虚拟 DOM 先初始化好了(beforeMount)才会去初始化子组件的虚拟 DOM (beforeMount)而 mounted 事件等价于 window.onload子组件 DOM 没加载好父组件 DOM 永远不可能加载好。所以基本生命周期钩子函数执行顺序是父beforeCreate - 父created - 父beforeMount - 子beforeCreate - 子created - 子beforeMount - 子mounted - 父mounted父子组件的 update 和 beforeUpdate 执行先后顺序数据修改虚拟 DOM 准备好会触发 beforeUpdate换句话说 beforeUpdate 等价于 beforeMount而 update 等价于 mounted。所以先后顺序是父 beforeUpdate - 子 beforeUpdate - 子 update - 父 update。同理beforeDestory和destoryed的先后顺序是父 beforeDestory - 子 beforeDestory - 子 destoryed - 父 destoryed。生命周期钩子函数其实也可以写成数组的形式mounted: [mounted1, mounted2],同一个生命周期可以触发多个函数这也是mixin(混入)的原理mixin里面也可以写生命周期钩子最终会和组件里面的生命周期钩子函数一起变成数组形式mixin里面的钩子函数会先执行。异步请求数据在哪个钩子函数中执行比较好生命周期钩子函数的中异步会放入事件队列而不会在这个钩子函数中执行。也就是说你在 created 和 mounted 中请求数据是一样的都不会立即更新数据所以不会导致虚拟 DOM 重新加载也不影响页面中静态的部分加载。生命周期钩子函数中的异步赋值vue 会在一遍流程走完之后执行update。另外给数据赋值然后更新 DOM 也是异步的侦听到数据变化Vue 将开启一个队列并缓冲在同一事件循环中发生的所有数据变更去掉重复赋值然后更新。生命周期钩子函数中的异步行为测试export default { data(){ return { list:[], } }, methods:{ getData(){ //生成指定范围的随机整数 const randomNum (min, max) Math.floor(Math.random() * (max - min 1)) min; //生成固定长度的非空数组 const randomArr length Array.from({ length }, (item, index) index * 2); const time randomNum(100,3000);//模拟请求时间 console.log(getData start); return new Promise(resolve { setTimeout(() { const arr randomArr(10); resolve(arr); },time) }) } }, async created(){ console.log(created); this.list await this.getData(); console.log(getData end); }, beforeMount() { console.log(beforeMount); }, mounted(){ console.log(mounted); }, updated(){ console.log(updated); }}结果如下图所以在 created 中和 mounted 中请求数据数据的更新时间是一样的在 created 中发起请求可以更早的请求到数据。并且使用服务端渲染 SSR 的时候 mounted 钩子不会加载。父组件监听子组件的生命周期可以写自定义事件然后在子组件的生命周期函数中触发这个自定义事件但是不优雅我们可以使用 hook复制代码从 A 页面切换到 B 页面A 页面中有一个定时器到了 B 页面用不上需要在离开 A 页面的时候清除掉办法很简单在 A 页面的生命周期钩子函数beforeDestory或者路由钩子函数beforeRouteLeave里面清除掉就行但是问题来了怎么拿到定时器呢把定时器写到 data 里面可行但是不优雅我们有如下写法//在初始化定时器之后this.$once(hook:beforeDestory,(){ clearInterval(timer);})is 属性的妙用由于 HTML 标签的限制tr 标签里面只能有 th, td 标签而写自定义标签则会被解析到 tr 标签外层所以这时候我们可以用 is 属性复制代码最近有个页面有大量的 SVG 图标我将每一个 SVG 都写成了一个组件。由于 SVG 组件名称又各不相同所以需要动态标签来表示arr: [ { id: 1, name: first }, { id: 2, name: second }, { id: 3, name: third }, ]item.nameitem.name给事件传额外参数原生 DOM 事件绑定的函数的第一个参数都会是事件对象event但是有时候我们想给这个函数传其他的参数直接传会覆盖掉event我们可以这么写变量$event就代表事件对象。如果要传的变量不是事件对象呢在使用 elementUI 的时候碰到这么一个情况在表格中使用了下拉菜单组件代码如下 下拉菜单黄金糕狮子头复制代码下拉菜单事件 command 函数自带一个参数为下拉选中的值这个时候我们想把表格数据传过去如果commandhandleCommand(row)这样写就会覆盖掉自带的参数该怎么办呢这时候我们可以借助箭头函数commandcommand handleCommand(row,command)完美解决传参问题。顺便说一下elementUI 的表格可以用变量$index代表当前的列数和$event一样的使用编辑复制代码经掘友指点默认参数有多个的时候可以这样写current-change(...defaultArgs) treeclick(ortherArgs, ...defaultArgs)v-slot 语法v-slot 的用法(slot 语法已经废弃)相当于在组件中留一个空位使用该组件的时候可以传一些标签过去插入到对应的空位。可以有多个空位取不同的名字即可默认是 default。同时还可以将一些数据传过去简写是#。Here might be a page titleA paragraph for the main content.And another one.Heres some contact info总结其他名称的 slot(非 default)仅能用于 template 标签。插槽里面的标签拿不到传给子标签的数据(插槽相当于孙子组件)在这里访问不到data数据插槽可以使用解构语法v-slot{ user }。子组件修改父组件传过来的值v-model在使用的时候很像双向绑定的但是 Vue 是单项数据流v-model 只是语法糖而已父组件用v-bind将值传给子组件子组件通过 change/input 事件触发修改父组件的值。复制代码v-model 不仅仅能在 input 上用在组件上也能使用。vue 组件间传递数据是单向的即数据总是由父组件传递到子组件子组件在其内部可以有自己维护的数据但它无权修改父组件传递给它的数据我们也可以参照v-model语法糖进行修改父组件的值但是每次都这样写太麻烦了vue 提供了一个修饰符.sync,用法如下父组件通过 ref 访问到子组件虽然 vue 提供 $parent 和 $children来访问父/子组件但是组件的父组件/子组件存在很多不确定性例如组件被复用他的父组件有多种情况。我们可以通过 ref 访问到子组件的数据和方法。复制代码注意ref 必须等 DOM 加载好了才可以访问虽然 mounted 生命周期 DOM 已经加载好了但是为了以防万一我们可以使用 $nextTick 函数背景图、css 的 import 使用路径别名在用 Webpack 处理打包时可将某一目录配置一个别名代码中就能使用与别名的相对路径引用资源import tool from /utils/test; // Webpack 能正确识别并打包。但是在 css 文件如 less, sass, stylus 中使用 import /style/theme 的语法引用相对 的目录确会报错。解决办法是是在引用路径的字符串最前面添加上 ~ 符号。css module 中import ~/style/theme.lesscss 属性中background: url(~/assets/xxx.jpg)html 标签中vue-router 的 hash 模式和 history 模式我们先来看一个完整的 URLhttps://www.baidu.com/blog/guide/vuePlugin.html#vue-router。其中 https://www.baidu.com 是网站根目录/blog/guide/是子目录vuePlugin.html是子目录下的文件(如果只有目录没有指定文件会默认请求index.html文件)而#vue-router就是哈希值。vue 是单页应用打包之后只有一个 index.html将他部署到服务器上之后访问对应文件的目录就是访问这个文件。hash 模式网址后面跟着 hash 值hash 值对应每一个 router 的名称hash 值改变意味着router 改变监听 onhashchange 事件来替换页面内容。history 模式网址后面跟着‘假的目录名’其值就是 router 的名称而浏览器会去请求这个目录的文件(并不存在会 404)所以 history 模式需要服务器配合配置 404 页面重定向到到我们的 index.html然后 vue-router 会根据目录的名称来替换页面内容。优缺点hash 模式的 # 号很丑使用的是 onhashchange 事件切换路由兼容性会好一点不需要服务器配合history 模式好看点但是本地开发、网站上线都需要服务器额外配置并且还需要自己写 404 页面使用的是 HTML5 的 history API兼容性差一点。两者的配置区别在于const router new VueRouter({ mode: history, //hash模式是默认的无需配置 base: /,//默认配置 routes: [...]})vue-cli3 的 vue.config.js 配置module.exports { publicPath: ./, // hash模式打包用 // publicPath: /, // history模式打包用 devServer: { open: true, port: 88, // historyApiFallback: true, //history模式本地开发用 }}如果是网站部署在根目录router 的 base 就不用填。如果整个单页应用服务在 /app/ 下然后 base 就应该设为 /app/同时打包配置(vue.config.js)的 publicPath 也应该设置成/app/。vue-cli3生成新项目的时候会有选择路由的模式选择history模式就会帮你都配置好。vue-router的钩子函数钩子函数分三种组件内钩子全局钩子路由独享钩子。APP.vue没有组件内钩子函数因为APP.vue是页面的入口这个组件是必定会加载的而使用组件内钩子函数可以阻止组件加载。全局钩子主要用于路由鉴权但是消耗很大。组件内的钩子beforeRouteLeave主要用于用户离开前的提示(比如说有未保存的文章)这个钩子有一些坑hash模式下浏览器的后退按钮无法触发这个钩子函数。同时我们还可以监听用户的关闭当前窗口/浏览器事件window.onbeforeunload e 确定离开当前页面你的修改将不会被保存!;为了防止恶意网站用户关闭窗口/浏览器事件是不可阻止的只能提示而且不同的浏览器兼容性也不同。Vuex 持久化存储Vuex 中的数据刷新页面之后就会丢失。要实现持久化存储需要借助本地存储(cookie 和 storage 等)一般是登录之后返回的数据(角色权限token 等)需要存储到 Vuex所以我们可以在登录页将数据存储到本地而在主页面(除了登录页其他所有页面的入口)进入之前(beforeCreate 或者路由钩子 beforeRouteEnter)读取出来并提交到 Vuex 就好了。这样即使刷新也会触发主页面的进入钩子函数会被提交到 Vuex。beforeRouteEnter (to, from, next) { const token localStorage.getItem(token); let right localStorage.getItem(right); try{ right JSON.parse(right); }catch{ next(vm { //弹窗采用elementUI vm.$alert(获取权限失败).finally(() { vm.$router.repalce({name:login}) }) }) } if(!right || !token){ next({name:login,replace:true}) }else{ next(vm { //这里面的事件会在mounted之后触发 vm.$store.commit(setToken,token); vm.$store.commit(setRight,right); }) }}beforeRouteEnter的回调会在mounted钩子之后触发这就比较蛋疼了。而主页面的mounted会在所有子组件的mounted之后触发所以我们可以这样写。import store from ^/store;//将实例化的store引入进来beforeRouteEnter (to, from, next) { const token localStorage.getItem(token); if(!token){ next({name:login,replace:true}) }else{ store.commit(setToken,token); next(); }}要想实现数据修改之后仍能持久化存储我们可以先把数据存到localstorage然后监听window.onstorage事件数据有修改提交到Vuex。mutations 里面触发 actionmutations 是同步修改 state 的值假如另一个值是异步获取(action)的依赖于这个同步的值的修改需要在 mutations 里面赋值之前触发 action 里面的事件我们可以给实例化的 Vuex 命名在 mutations 里面拿到 store 对象。const store new Vuex.Store({ state: { age: 18, name: zhangsan, }, mutations: { setAge(state, val) { // 假如age变化了之后name也要跟着变化 // 需要在每次给age赋值的时候同步触发action里面的getName state.age val; store.dispatch(getName); }, setName(state, val) { state.name val; }, }, actions: { getName({ commit }) { const name fetch(name); //从接口异步获取 commit(setName, name); }, },});Vue.observable进行组件通信如果项目很小不需要用到 vuex 可以用Vue.observable来模拟一个//store.jsimport Vue from vue;const store Vue.observable({ name: 张三, age: 20 });const mutations { setAge(age) { store.age age; }, setName(name) { store.name name; },};export { store, mutations };axios 的 qs 插件get 请求的数据放在 url 里面类似于http://www.baidu.com?a1b2其中a1b2就是 get 的参数而对于 post 请求参数放到 body 里面常用的数据格式有表单数据和 json 数据两者的差异就是数据格式不同表单数据编码格式和 get 一样只不过是放在 body 里面而 json 数据则是 json 字符串qs 基本使用:import qs from qs; //qs是axios里面自带的所以直接引入就可以了const data qs.stringify({ username: this.formData.username, oldPassword: this.formData.oldPassword, newPassword: this.formData.newPassword1,});this.$http.post(/changePassword.php, data);qs.parse()是将 URL 解析成对象的形式qs.stringify()是将对象 序列化成 URL 的形式以进行拼接。而对于不同的数据格式axios 会自动设置对应的content-type不需要手动设置。表单数据(不带文件)的 content-type 是application/x-www-form-urlencoded表单数据(带文件)的 content-type 是multipart/form-datajson 数据的 content-type 是application/json碰到过一次接口需要我用表单传一个数组。假设数据是arr [1,2,3]如果直接使用 qs.stringify()则数据会变成arr[]1arr[]2arr[]3很容易看出来多了一个[]让接口把参数名改成arr[]就能用但是这样不好。不过可以发现表单传数组的本质就是同名参数传多次这时候我们也可以这样const data new FormData();arr.forEach(item { data.append(arr, item);});测试一下完美解决但是事情到这里还没完翻一下qs 官方文档qs 转换支持第二个参数完美解决我们的问题。const data qs.stringify(arr, { arrayFormat: repeat }); // arr1arr2arr3elementUI 的一些总结表单验证同步写法避免多层嵌套函数const valid await new Promise(resolve this.$refs.form.validate(resolve));if (!valid) return按需引入之后级联菜单高度撑满屏幕。解决办法加一句全局样式.el-cascader-menu .el-scrollbar__wrap{ height: 250px;}级联菜单的数据是按需获取的没法回显。解决办法根据已有的路径数据去请求树数据然后给级联菜单加一个v-if等数据都请求好了再显示出来。比如说省市县三级联动数据已知用户选择的是广东省-深圳市-南山区那么分别去请求所有省、广东省、深圳市的数据然后将数据拼成一个 tree 绑定到级联菜单然后设置v-iftrue。表格高度自适应可以给表格外层加一个 div 然后给这个 div 计算高度(或者弹性盒子自适应高度)表格属性 /* less写法 */.table-wrap{ height: calc(~100vh - 200px); /* 部分版本这样写会失效需要加上下面一句 */ /deep/ .el-table{ height: 100% !important; }}使用多个 upload 组件需要将这些文件一起上传到服务器。可以通过this.$refs.poster.uploadFiles拿到文件对象。然后自己手动组装成表单数据。更新文件上传文件tips: 建议上传尺寸250*140methods:{ //选择图片之后替换旧图片和显示略缩图 changePhoto(file, fileList) { //创建的Blob URL可直接预览图片 this.previewUrl window.URL.createObjectURL(file.raw); if (fileList.length 1) { fileList.shift(); } }, revokeUrl(e) { //图片加载完成之后销毁Blob URL if (e.target.src.startsWith(blob:)) window.URL.revokeObjectURL(e.target.src); }, //提交表单数据 async submitData() { const template this.$refs.template.uploadFiles[0], //模板文件 poster this.$refs.poster.uploadFiles[0], //海报文件 formData new FormData(); if (!template) return this.$message.warning(必须选择模板文件); if (!poster) return this.$message.warning(必须选择海报文件); formData.append(zip, template.raw); formData.append(poster, poster.raw); const res await this.$http.post(url, formData); },}使用VueI18n国际化需要将elementUI的语言包和项目中的语言包合并成一个。import VueI18n from vue-i18n;import zhLocale from ./locales/zh.js;/* 引入本地简体中文语言包 */import zhTWLocale from ./locales/zh-TW.js;/* 引入本地繁体中文语言包 */import enLocale from ./locales/en.js;/* 引入本地英语语言包 */import zhElemment from element-ui/lib/locale/lang/zh-CN//引入elementUI简体中文语言包import zhTWElemment from element-ui/lib/locale/lang/zh-TW//引入elementUI繁体中文语言包import enElemment from element-ui/lib/locale/lang/en//引入elementUI英语语言包Vue.use(VueI18n);const messages {//语言包 zh: Object.assign(zhLocale, zhElemment),//本地语言包加入elementUI的语言包 zh-TW: Object.assign(zhTWLocale, zhTWElemment),//本地语言包加入elementUI的语言包 en: Object.assign(enLocale, enElemment)//本地语言包加入elementUI的语言包};const i18n new VueI18n({ locale: zh, //zh默认是简体中文 messages});Vue.use(ElementUI, { i18n: (key, value) i18n.t(key, value)})最后有写错的或者有什么问题欢迎大家评论作者沉末_ 链接https://juejin.im/post/5d8c6a97e51d45782c23fa69推荐阅读1、GitHub 上能挖矿的神仙技巧 - 如何发现优秀开源项目2. 9 种你或许不知道的 Vue 好用小技巧3. Vue TypeScript Element 项目实战及踩坑记4. 重磅硬核前端面试开源项目汇总(进大厂必备)