vue每次发布线上传部分资源访问404! 问题分析经过每次 run build 构建 -> 发布 -> 到线上环境阶段, 若未通知测试人员, 已发布最新内容, 或者 测试人员 未及时刷新浏览器 获取最新已构建的新静态资源时, 这个时候在去访问 已更新的模块内容,便会引起 静态资源 无法 加载,404 问题!
原因分析该问题出现的原因,是因为操作着在发布新的内容前,已经处于系统界面状态,这时候的资源,已经被浏览器所缓存,每次访问加载资源时,便会通过缓存将页面呈现! 但是,当开发人员 每次 构建 到 发布新的内容时,浏览器不刷新,则认为,还是拿旧的资源去服务器里找,但这时,服务器里的资源已被更新成新的资源,导致无法加载所引起的问题!
效果问题处理注意: 这里暂时针对前端模块进行问题处理,目前环境采用的 `vue2.x` `vue-cli3`
目前根据上述问题得知,只有在浏览器刷新后,才能重新获取新的静态资源! 所以,我们需要在每次打包发布后,通过在本地新建version.json来指定每次打包所更新的唯一版本! 最后通过 路由跳转 或者 定时器 的方式, 来获取version.json中 版本内容, 因此把 版本信息 保留在 localStorage 中 便 每次用来判断 是否有新的内容更新,若有,便通过提示信息告知用户有新的内容更新,随后通过location.reload() 重新加载页面获取新的浏览资源!
新建version.json public/version.json
1 2 3 4 5 { "name" : "project-name" , "versionNo" : "1.0.0" , "updateTime" : "date-time" }
新建updateVersion.js 此脚本作用,用来每次打包 将新的 版本 写入 public/version.json 文件当中!
src/script/version.json
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 const fs = require ('fs' );const path = require ('path' );const filePath = path.join ( __dirname, '../../public/version.json' );async function upgradedVersion ( ) { const d = new Date () let result = await fs.readFileSync (filePath,{ encoding :"utf-8" }) if ( !result ) return let version = JSON .parse (result) version.updateTime = d.toLocaleString () version.version = ['getFullYear' , 'getMonth' , 'getDate' , 'getHours' , 'getTime' ].map (x => { if (d[x]) return x === 'getMonth' ? d[x]() +1 : d[x]() return '' }).join ('.' ) version.buildCount ++ console .log ('version' , version) fs.writeFile (filePath, JSON .stringify (version,null , 2 ), () => { console .log ('新版本号生成成功' ); }); } upgradedVersion ()
修改package.json run build 选项 1 2 3 4 5 6 7 "scripts" : { "serve" : "vue-cli-service serve" , "dev" : "vue-cli-service serve --mode development" , "test" : "vue-cli-service serve --mode test" , "prod" : "vue-cli-service serve --mode prod" , "build" : "node ./src/script/updateVersion.js && vue-cli-service build --mode prod" , } ,
新建 utils/routerUtils.js 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 import defaultSettings from '@/settings' import request from '@/utils/request' import { MessageBox } from 'element-ui' ;const url = `${location.origin} /version.json` function messageAlert ( ){ MessageBox .confirm ('新版本已更新,请刷新页面后在进行相关业务操作,给您带来不便,尽情谅解!' , '提示' , { confirmButtonText : '刷新' , showClose : false , showCancelButton : false , closeOnClickModal : false , closeOnPressEscape : false , closeOnHashChange : false , type : 'warning' }).then (() => { location.reload () }) } export async function getNewVersion ( ) { if ( process.env .NODE_ENV === 'production' ) { let localStorage = window .localStorage await request.get (url).then ((response ) => { console .log (response) let localVersion = localStorage .getItem ('version' ) let lastVersion = response let { version : newVersion, buildCount } = lastVersion if ( !localVersion ) { localStorage .setItem ('version' , JSON .stringify (lastVersion)) if ( buildCount === 1 ) { messageAlert () } } else { let { version : oldVersion } = JSON .parse (localVersion) console .log ('version' , localVersion, oldVersion, newVersion) if ( oldVersion !== newVersion ) { localStorage .removeItem ("version" ) messageAlert () } } }) } }
注意:
const url = ${location.origin}/version.json 这里的 requset 请求的url 存在问题!
最终 发布到线上的时候,通过 以上 路径 访问,会出现本地磁盘缓存问题,这样就会导致,每次请求都是旧的数据,无法拿到新的版本,进而就无法更新版本对比了!
磁盘缓存问题处理:
这里我们给url加上时间戳,保持每次请求都是新的,以便浏览器不在缓存此资源!
1 2 3 4 5 request.get (`${location.origin} /version.json?timestamp=${Date .now()} ` )
在 permission.js 路由拦截中添加, 或者通过定时器加载!
1 2 3 4 let timer = null ;timer = setInterval (getNewVersion (), 2000 );
注意: 使用定时器,一定要注意在合理条件内 清楚,避免内存浪费! 比如浏览器关闭的时候....
监听浏览器右上角关闭 需求 需要 关闭浏览器之前 做一次 版本清除 和 用户注销操作!
App.vue1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 export default { data ( ){ return { beforeunloadTime : 0 , unloadTime : 0 } }, methods : { beforeunloadHandler (e ) { let userAgent = navigator.userAgent ; let is_fireFox = userAgent.indexOf ("Firefox" ) > -1 ; this .beforeunloadTime = new Date ().getTime () if ( is_fireFox && e.srcDocument ) { let { token, loginId } = this .userInfo let API_URI = `${process.env.VUE_APP_BASE_API} /sso/redirect?type=logout&token=${token} &loginId=${loginId || null } ` navigator.sendBeacon (API_URI ) clearVersion () console .log ('unload close window!' ) } }, unload ( ) { let userAgent = navigator.userAgent ; let is_fireFox = userAgent.indexOf ("Firefox" ) > -1 ; this .unloadTime = new Date ().getTime () let _gap_time = this .unloadTime - this .beforeunloadTime if ( _gap_time < 5 && !is_fireFox) { let { token, loginId } = this .userInfo let API_URI = `${process.env.VUE_APP_BASE_API} /sso/redirect?type=logout&token=${token} &loginId=${loginId || null } ` navigator.sendBeacon (API_URI ) clearVersion () console .log ('unload close window!' ) } } }, mounted ( ) { window .addEventListener ('beforeunload' , e => this .beforeunloadHandler (e)); window .addEventListener ('unload' , e => this .unload (e)); }, beforeDestroy ( ) { window .removeEventListener ('beforeunload' , e => this .beforeunloadHandler (e)); window .removeEventListener ('unload' , e => this .unload (e)); }, }
注意 火狐浏览器,不大友好!: 火狐浏览器 刷新 和 关闭 会执行 beforeunload 火狐浏览器 刷新 会执行 unload 因此 暂且无法判断到底是 刷新 还是 关闭操作 还有一点 关闭之前打断点时,浏览器会顿以下,然后就会关掉,这个很头疼! 谷歌浏览器 会走 debugger 其次 手段关闭 debugger 模式后 才会关闭窗口!
注意 谷歌: 谷歌浏览器 刷新 和 关闭 会执行 beforeunload 谷歌浏览器 关闭 会执行 unload 因此-> 在beforeunload 内 初始化事件戳,待执行完后, 在unload内部 在创建一个新的时间戳,进行对比,来判断是否关闭或刷新!
JavaScript相关问题 JSON字符串对长整型字段精度丢失的问题 处理方案 替换JSON串解析后 长整型 精度丢失
1 2 3 4 5 6 7 8 export const replaceJsonLongTranStr = ( jsonStr = '' ) => { return jsonStr.replace (/"\w+":\s*\d{16,}/g , function (longVal ){ let split = longVal.split (":" ); return split[0 ] + ':' + '"' + split[1 ].trim () + '"' ; }) }
使用 1 2 3 4 5 6 7 8 9 10 11 12 let jsonStr = '[ { id: 9218932178937819273, name: ' wdhuais' }, { id: 9218932178937812343, name: ' jwiqjid' } ]' ;let newJsonStr = replaceJsonLongTranStr (jsonStr);let parseJson = JSON .parse (newJsonStr)
ElementUi相关问题 el-row 和 el-col 导致布局错乱问题 1 2 3 4 .el-row { display : flex; flex-wrap : wrap; // 自动换行 }
el-table toggleRowSelection无效 需求描述 带有复选框并可支持多选的表格,关键:在选择表格中的数据并保存至后端时,下回看详情或者修改的时候要把上次保存的数据默认勾选至表格! 因此便考虑使用官方API toggleRowSelection来处理此需求!
有关toggleRowSelection此方法描述 用于多选表格,切换某一行的选中状态,如果使用了第二个参数,则是设置这一行选中与否(selected 为 true 则选中)
参数描述 row: 表格中选中的那一行数据 Object selected: 是否被勾选 Boolean
实现思路 将保存至后端的数据通过详情接口反查得到对应数据后,对原表格列表进行唯一字段过滤[filter]得到对应的 row 当作参数传递至该toggleRowSelection方法中!
发现问题! 按照上述思路处理,并未达到预期勾选效果,经排查,原因时,最终过滤出来的row必须是 原 table 中 所绑定的 list 列表中的数据!
1 2 3 <el-table :data ="sourceTableData" > <el-column > </el-column > </el-table >
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 let saved = [ {id : '734234323' , name : '测试2' }, {id : '431231231' , name : '测试4' } ] let sourceTableData = [ {id : '512421123' , name : '测试1' }, {id : '734234323' , name : '测试2' }, {id : '763421312' , name : '测试3' }, {id : '431231231' , name : '测试4' } ] saved.map (item => { let row = sourceTableData.find (i => i.id === item.id ) this .$refs ['table' ].toggleRowSelection (row, true ) })
this.$msgBox内部使用自定义h函数无法双向绑定问题 问题描述 使用 elementui 中 this.$msgBox() 提示框函数,内部自定义 render h 函数,无法双向绑定问题!
错误案例 以下代码 无法实现 双向绑定,输入的内容 通过 input 事件 依然不能 将 新 的内容 绑定至 testMsg属性上!
1 <el-button @click ="testMsg" > testMsg</el-button >
1 2 3 4 5 6 7 data ( ){ return { formData : { testMsg : '' }, } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 testMsg (event ){ const h = this .$createElement ; const tag = h ('el-input' , { props : { type : 'textarea' , placeholder : "请输入内容" , value : this .formData .testMsg }, on : { 'input' : (value ) => { this .formData .testMsg = value } } }, []) this .$msgbox({ title : 'testMsg' , message : tag, showCancelButton : true , confirmButtonText : '确认' , cancelButtonText : '取消' , beforeClose : ( action, instance, done ) => { console .log ('action instance done' , action, instance, done) done (); } }) },
处理方案 直接修改原组件所绑定的value值 会导致 vue 发出错误提示, 通过 该配置 Vue.config.silent 可以取消所有的日志与警告!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 testMsg (event ){ const h = this .$createElement ; const tag = h ('el-input' , { props : { type : 'textarea' , placeholder : "请输入内容" , value : this .formData .testMsg }, on : { 'input' : (value ) => { const bakSilent = Vue .config .silent = true tag.componentInstance .value = value this .formData .testMsg = value Vue .config .silent = bakSilent } } }, []) console .log ('event' , event) this .$msgbox({ title : 'testMsg' , message : tag, showCancelButton : true , confirmButtonText : '确认' , cancelButtonText : '取消' , beforeClose : ( action, instance, done ) => { console .log ('action instance done' , action, instance, done) done (); } }) },
el-checkbox-group 下使用文本时 不显示问题 如图所示,在使用ElementUi el-checkbox-group 多选组 组件时 所遇到的问题,内部使用文本即不显示的情况!
原因,是因为 el-checkbox-group组件 内置样式 设置了 front-size: 0,解决方法,将文本元素重新设置下大小即可!
ElementUI表格中图片懒加载无法显示的问题 ElementUI表格el-table-column中el-image图片懒加载无法显示的问题
是父元素同时设置了overflow: auto; 导致Element不能找到正确的container了
添加 表格的滚动scroll-container=".el-table__body-wrapper" 成功解决
注意: 注意不要在`el-image`中开`preview-src-list` 否则懒加载会失效(实际图片已全部请求)如要开启放大预览要用`el-image-viewer`组件!
项目优化记录 el-select 加载万条数据引起界面渲染卡顿接口返回上万条数据,引起el-select渲染Dom条数导致页面加载缓慢卡顿情况!
处理方法
Object.freeze冻结对象 在vue中, data对象里的属性都是响应式(getter, setter)的,vue会循环遍历data中的属性并通过defineProperty来为每个属性增加getter、setter! 所以,当数量庞大的数组或者对象,我们可以对他们的属性进行冻结操作(Object.freeze),vue中会通过Object.isFrozen(obj);来判断此对象是否被冻结,被冻结的数据是不会被处理的!对数据进行分片处理 数据量庞大,对频繁大量的创建Dom操作会对页面渲染造成界面渲染卡顿,因此,我们,需要将渲染的数量进行分割处理,保证每次渲染的数量不会影响到界面加载速度!
虚拟滚动虚拟滚动意思就是在固定可视范围内通过position定位形式来下拉展示不同
插件使用 查看vue-virtual-scroll-list教程
1 npm install vue-virtual-scroll-list --save
引用站外地址
vue-virtual-scroll-list npm地址
https://www.npmjs.com/package/vue-virtual-scroll-list
分页加载 考虑后端返回的数据没有带分页情况: 分页需要前端进行处理, 通过slice 并监听scroll事件,当scroll到底部的时候,给个截取范围`beginIndex endIndex返回截取后的数据并push到一个新的数组里 options给el-select去加载!代码片段 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 methods : { loadMore ( ){ let selectOptions = this .selectOptions .options if ( selectOptions.length > 150 ) { if ( !firstTag ) { this .queryParams .page ++ } const begin = this .queryParams .limit * (this .queryParams .page - 1 ) const end = (this .queryParams .limit * (this .queryParams .page - 1 )) + this .queryParams .limit selectOptions.slice (begin, end).map (item => this .options .push (item)) this .options = unique (this .options , this .selectOptions .value ) return } } }
待优化项
图片附件预览download问题 平时回显预览图片所做的 平时在开发过程中在预览图片时,会先通过后端返回的fileId去下载相应的照片附件(ArrayBuffer),然后将ArrayBuffer类型的图片附件在转换为base64形式当作url供el-image or el-image-preview or el-upload 各个组件回显图片附件便可做预览展示!
然而附件如果是图片时,且无需download后在转换base64供页面回显! 完全可以通过地址加http://download/file?id=fileId参数供 url给 el-image or el-image-preview or el-upload 各个组件去直接展示! 这对于一个页面需要展示很多图片附件时很有效果,且完全没有必要download by fileId 后在转换base64形式去回显图片!
说下为什么这么做download 图片的时侯需要耗费资源,如果一个页面有7、8张图片附件甚至更多的时候,我们需要去循环遍历拿到 fileId 并 download获取,难免我们需要 将异步请求,改为同步请求,带数据正常返回后,在进行数据回显,如果图片附件资源较大 ,这意味着页面,将会消耗更多时间来获取对应资源,并阻塞页面渲染!
当然,附件资源比较大的情况下,对本地内存也有一定的影响,避免引起内存消耗,无法进行回收问题! 这是多张
这是一张大图
代码参考 template
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 <template > <div class ="lazy-image" > <collapse headerTitle ="elUpload图片预览懒加载" style ="height: 400px; overflow-y: scroll" > <div style ="height: 850px; border: 1px solid #b3d8ff" > </div > <el-upload class ="upload-demo" ref ="upload" list-type ="picture-card" action ="https://jsonplaceholder.typicode.com/posts/" v-lazy-image ="fileList" :auto-upload ="true" > </el-upload > </collapse > <collapse headerTitle ="elTable图片懒加载" > <pagination-table ref ="paginationTableRef" :columns ="columns" :data ="tableData" height ="280" > </pagination-table > </collapse > </div > </template >
script1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 data ( ){ return { fileList : [{name : 'food.jpg' , url : 'http://ip:port/oss/download?id=10db8d58-7c3e-4794-bfe4-5d95b79e920d' }], columns : [ { image : true , fileSrcField : 'imgSrc' } ], tableData : [ { imgSrc : 'http://ip:port/oss/download?id=10db8d58-7c3e-4794-bfe4-5d95b79e920d' }, { imgSrc : 'http://ip:port/oss/download?id=7dfb2d39-7546-484d-999d-1f6fd9126f84' }, { imgSrc : 'http://ip:port/oss/download?id=bb4b11ea-4c35-48b9-96d9-494e5a0ddcc4' }, { imgSrc : 'http://ip:port/oss/download?id=6ae9aa77-87eb-4f55-b355-7d2b836f00f2' }, { imgSrc : 'http://ip:port/oss/download?id=50b5a9c6-1954-41b2-9177-6f80cb1e2aff' }, ] } }
处理后
结果看起来没什么不一样
处理前: 图片附件是在渲染整体页面前加载 1 2 3 4 5 6 7 8 9 10 11 getImg (fileId ){ return new Promise (async (resolve, reject) => { let data = await this .$axios .commonExportFile (fileId) if (data.data .byteLength > 0 ){ let img = `data:image/jpeg;base64,${arrayBufferToBase64(data.data)} ` resolve (img) }else { resolve ('' ) } }); },
处理后: 放在运行时处理的, 也就是说,这种情况不会影响到整体页面渲染加载,只会涉及到图片本身加载! 图片在第一次请求加载完后,就不会在请求第二遍了!
1 <el-image src ="http://ip:port/your/download?id=fb78fb33-969d-4310-954c-8d2fa882b4d1" > </el-image >
当然,后面会结合 图片懒加载 及各种场景下进行下一步优化! 组件描述FormPaginationTable 这个组件时平时开发中经常用到的组件之一,该组件可以帮我们完成一个二次封装后的可编辑表格式组件,支持下拉、输入、日期、tree常用编辑属性配置!
为何优化FormPaginationTable 组件,平时使用的时候,可能多列情况下,支持可编辑字段,多个列可支持下拉 or 输入框 or 日期等编辑组件,因此,当我们,一旦给这些字段配置可编辑后,el-table组件中,就会持久渲染更多可编辑表单组件!其次,就是多列中可编辑下拉的组件,该组件,不仅在平时新增操作,或者修改以及详情下,都会请求大量的字典数据,去支持下拉,以及回显所保存过的字段值,过滤等,会比较消耗资源和内存空间! 优化方案这里优化需要结合数据字典项优化来进行处理,避免 多个下拉组件 在获取数据字典项时,根据`code`获取对应字典,引起资源耗费!
在需要编辑的时候,才需要显示编辑组件,编辑完后,在把需要可编辑的组件替换成文本组件! el-select、tree-select特殊组件,需要单独处理,因为我们需要做,数据字典 以及 组织机构等可能需要异步处理来获取的数据,这里才是比较复杂的一块儿! 看下效果
EditTable 可以按需使用,当需要选择编辑的时候,才会显示编辑组件,否则只会显示文本!el-select 下拉组件,支持异步加载,和 自定义下拉内容 options,当我点击的时候才会去加载options内容,不需要每次新建 修改 详情的时候做一次性的初始化操作!el-tab 组件按需加载注意: 该方案只适用于: tab 栏各个组件以及父组件之间没有可交互依赖关系的! 例如: 信息修改: 父组件有个save按钮,该save按钮,保存前或获取子组件中的param,以及会校验子组件中的表单项,这种情况,不适用! 在了解优化方案前的先了解下这个属性:is属性,该属性值需要传入组件的name属性,可以通过name属性来帮我们动态渲染组件!
引用站外地址
el-tabs 配合 component is 属性动态切换组件
https://blog.csdn.net/weixin_52691965/article/details/128535653
图片懒加载 v-lazy-image bq-image index.vue v-lazy-image 指令
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 const lazyImage = { intersectionObserver : null , bind ( el, binding, vNode ){ }, inserted (el, binding, vNode ) { lazyImage.intersectionObserver = new IntersectionObserver ((entries, observer ) => { let isInWindow = entries[0 ].intersectionRatio === 1 if ( isInWindow ) { let { componentInstance } = vNode let fileList = binding.value if ( componentInstance?.uploadFiles && !componentInstance?.uploadFiles .length ) { componentInstance.uploadFiles = fileList.map (item => { return { ...item, uid : Date .now () } }) return } if ( componentInstance && !componentInstance.src ){ componentInstance.src = fileList[0 ] } } }, { threshold : 1.0 }); lazyImage.intersectionObserver .observe (el) }, unbind (el ){ lazyImage.intersectionObserver .unobserve (el) lazyImage.intersectionObserver .disconnect () } } export default lazyImage
bq-image component
el-image 中的 src prop属性是不可以直接被修改的,为了避免此问题, 单独封装了el-image组件!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 <template > <el-image v-bind ="$attrs" v-on ="$listeners" :src ="src" > </el-image > </template > <script > export default { name : "BqImage" , data ( ) { return { src : '' } }, } </script > <style scoped > </style >
vue中使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 <template > <div class ="lazy-image" > <collapse headerTitle ="elUpload图片预览懒加载" style ="height: 400px; overflow-y: scroll" > <div style ="height: 850px; border: 1px solid #b3d8ff" > </div > <el-upload class ="upload-demo" ref ="upload" list-type ="picture-card" action ="https://jsonplaceholder.typicode.com/posts/" v-lazy-image ="fileList" :auto-upload ="true" > </el-upload > </collapse > <collapse headerTitle ="elTable图片懒加载" > <pagination-table ref ="paginationTableRef" :columns ="columns" :data ="tableData" height ="280" > </pagination-table > </collapse > </div > </template > <script > export default { name : "index" , data ( ){ return { fileList : [{name : 'food.jpg' , url : 'http://ip:port/oss/download?id=a46173b4-18a2-416c-b4c8-86552f57019b' }], columns : [ { image : true , fileSrcField : 'imgSrc' } ], tableData : [ { imgSrc : 'https://img2.baidu.com/it/u=3618236253,1028428296&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=500' }, { imgSrc : 'https://img2.baidu.com/it/u=2421090168,324781765&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=500' }, { imgSrc : 'https://img0.baidu.com/it/u=4060770951,4069855872&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=500' }, { imgSrc : 'https://img2.baidu.com/it/u=1579991524,66947472&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=500' }, { imgSrc : 'https://img1.baidu.com/it/u=2136061792,3055265331&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=500' }, { imgSrc : 'https://img0.baidu.com/it/u=178849715,820264547&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=500' }, { imgSrc : 'https://img0.baidu.com/it/u=2571430731,2285101823&fm=253&fmt=auto&app=138&f=JPEG?w=504&h=500' }, { imgSrc : 'https://img1.baidu.com/it/u=22553051,3834724655&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=500' }, { imgSrc : 'https://img1.baidu.com/it/u=1856720522,683752130&fm=253&fmt=auto&app=138&f=JPEG?w=529&h=500' }, { imgSrc : 'https://img0.baidu.com/it/u=1577303935,3801284994&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=500' }, ] } } } </script > <style scoped > </style >
数据字典项优化 数据字典这个问题比较头疼,这个问题从开始,一直延申至今,只要有el-select的地方,就难免逃不掉字典的魔爪!
历史接口只有通过字典 code 来获取对应字典数据,所谓的就是一个 select 对应一个 字典 code,则多个 select 就需要多个 code!这避免不了一个页面会出现七八个 select,一打开一个菜单界面,就会init下七八个字典数据,甚至更多!来瞅瞅这个可怕阵容做点人事儿吧后端来了一位救世主,给前端提供了一个查全量字典的接口,数据给我们了,剩下的难道不就想怎么玩就怎么玩了不是吗! 全量字典是通用的,那我们就需要把字典放到全局作用域下, ...enmmm vuex,需要的时候在拿来玩吧!这时候filter出场了,我们把需要的code摘出来,通过filter来过滤,获取对应的options即刻!这样,一次收获,便可到处使用了,不用在每次初始化,页面来请求很多接口获取对应字典了!
注意: 可以完美结合 bq-form edit-table 两个组件,来快速配置字典数据,可生成表单 以及 可编辑表格!
bq-form: 内置,会根据JSON formConfigItems 配置项 列入字典 code,组件内部在渲染表单的时候,可同时根据配置项 code 从全局字典中获取对应options 一并设置到 select!bq-table: 内置,会根据JSON columns 配置项列入字典 code 同上,一样会处理拿到对应options并设置给select组件!
前端获取ip最近iot项目中有需求,需要通过在前端获取到用户的ip地址, 然后登录调用接口的时候顺便把ip地址信息传入后端! 当然,确实没做过这方面的需求,也没啥思路和头绪,只能投靠百度来寻找答案emm…. 因为我们期望获取到的ip 是用户的内网ip,所以因此百度给的答案大部分都是一致的!
通过三方 api获取ip http://api.ipify.org?format=json
使用搜狐apihttp://pv.sohu.com/cityjson?ie=utf-8
在本地调用接口返回结果你会发现var returnCitySN = {"cip": "127.0.0.1", "cid": "00", "cname": "未知"};
最后通过三方来获取api显示是不满足需求的!
使用js内置rtc方法获取ip 引用站外地址
webRtc相关介绍
https://blog.csdn.net/qq_37247349/article/details/134522987
使用此方法需要做兼容处理,这个方法使用的时候还需要通过浏览器内置配置rtc后,便可获取内网ip!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 function getIPAddress ( ) { if (window .RTCPeerConnection || window .mozRTCPeerConnection || window .webkitRTCPeerConnection ) { let pc = new RTCPeerConnection ({ iceServers : [] }); pc.createDataChannel ('' ); pc.createOffer (function (offerDesc ) { pc.setLocalDescription (offerDesc); }, function (e ) { }); pc.onicecandidate = function (evt ) { console .log ("evt" , evt) if (evt.candidate ) ip = evt.candidate .address ; else ip = "no IP found" ; console .log (ip); pc.close (); }; } else { ip = "WebRTC is not supported by your browser!" ; console .log (ip); } } getIPAddress ();
在浏览器环境下运行,你会发现
注意: 以下结果 在浏览器控制台输入返回的结果!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 function getIPAddress ( ) { if (window .RTCPeerConnection || window .mozRTCPeerConnection || window .webkitRTCPeerConnection ) { let pc = new RTCPeerConnection ({ iceServers : [] }); pc.createDataChannel ('' ); pc.createOffer (function (offerDesc ) { pc.setLocalDescription (offerDesc); }, function (e ) { }); pc.onicecandidate = function (evt ) { console .log ("evt" , evt) if (evt.candidate ) ip = evt.candidate .address ; else ip = "no IP found" ; console .log (ip); pc.close (); }; } else { ip = "WebRTC is not supported by your browser!" ; console .log (ip); } } getIPAddress ()undefined VM561 :12 evt RTCPeerConnectionIceEvent {isTrusted : true , candidate : RTCIceCandidate , type : 'icecandidate' , target : RTCPeerConnection , currentTarget : RTCPeerConnection , …}VM561 :17 e0b7289e-9525 -405f-8c06-9cc6523f870d.local
以上内容结果返回undefined,当然为什么会返回undefined呢,因为在浏览器中以被限制~ ·某些网络和浏览器可能会出于隐私和安全原因限制WebRTC功能。
通过以下方法来配置`浏览器`的限制 !!
在 chrome 浏览器地址栏中输入 chrome://flags/ 在 edge 浏览器地址栏中输入edge://flags/ 搜索 #enable-webrtc-hide-local-ips-with-mdns 该配置 并将属性改为 disabled 然后点击Return重新加载浏览器就可以了!
此方法虽然可以获取到ip,出于浏览器的兼容性,以及浏览器隐私的考虑,我们不建议使用!
使用nodejs获取ip 最后尝试通过nodejs服务获取ip!
nodejs 环境下获取ip地址,需要安装npm包 express or koa!
初始化node项目 安装express依赖 创建server/index.js1 2 3 4 5 6 7 8 9 10 11 const express = require ('express' );const app = express ();const port = 3000 ;app.get ('/' , (req, res ) => { res.json ({ ip : req.ip }) }); app.listen (port, () => { console .log (`Example app listening at http://localhost:${port} ` ) }
启动服务 访问服务放在 浏览器url直接访问即可!
注意: 启动服务后,若获取用户ip需要直接访问,不可以使用代理或者其他方式转发此来访问该服务,否则获取到的ip这是代理转发的ip地址,而并不是用户的ip!
1 2 3 4 5 6 { "ip" : "192.168.31.111" }
注意: 为了演示,此ip为虚假ip!
注意: 在测试访问服务时不要使用 http://localhost:3000 去访问,否则会返回以下结果!最后在服务器上安装node环境并将node服务部署到服务器上即可!
html导出word问题在使用第三方库html-docx-js将html里的报表导出到word文档中时发现问题,就是导出结果里的表格,出现了两次展示的问题,如下图所示:
本地环境vue2 + elementui,导出界面为el-table相关报表!
问题处理 后来排查问题发现el-table中 el-table-column有一个fiexd属性,该属性是用来固定列的,因此查看f12元素后,发现会下方多一个.el-table_fiexd如下图所示:
1 <el-table-column prop ="date" label ="日期" width ="180" fixed > </el-table-column >
.el-table__fiexd 元素下面 会有一个原.el-table的复制版! 因此我们通过第三方插件进行导出时,出现重复的问题! 这里只需要把fiexd属性去掉后,就可以解决问题了!
具体代码 安装插件
1 npm install html-docx-js file-saver -S
in-table组件
table 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 <template > <div class ="in-table" > <el-table :data ="data" :height ="height" border > <el-table-column v-if ="showIndex" label ="序号" width ="70" align ="center" > <template slot-scope ="scope" > {{ scope.$index + 1 }} </template > </el-table-column > <template > <el-table-column v-for ="(column, index) in columns" :key ="`${column.prop}-${index}`" :prop ="column.prop || ''" :label ="column.label" :align ="column.align || 'center'" :width ="column.width || 'auto'" :min-width ="column.minWidth || 150" header-align ="center" :show-overflow-tooltip ="!column.editable" > <template slot-scope ="scope" > <template v-if ="column.cellStyle" > <span :style ="column.cellStyle" > {{ scope.row[column.prop] }} </span > </template > <template v-else > {{ scope.row[column.prop] }}</template > </template > </el-table-column > </template > </el-table > </div > </template > <script > export default { name : "InTable" , props : { showIndex : Boolean , data : { type : Array , default : () => [], }, height : { type : [String , Number ], default : 600 , }, isMultiHeader : { type : Boolean , default : false , }, columns : { type : Array , default : () => [], }, }, data ( ) { return {}; }, }; </script > <style lang ="scss" scoped > </style >
导入插件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 import { saveAs } from "file-saver" ;import htmlDocx from "html-docx-js/dist/html-docx" ;export default { name : "index" , methods : { exportWord ( ) { let contentHTML = document .querySelector ("#report-table" ).innerHTML ; let htmlString = `<html lang="en"> <head> <meta charset="UTF-8"/> <title>Document</title> <style> body { font-family: "微软雅黑"; font-size: 12px; padding: 25px; } table{ border-radius: 5px; } table, table th, table tr, table td { border: 1px solid #aecce8; } </style> </head> <body> ${contentHTML} </body> </html>` ; const converted = htmlDocx.asBlob (htmlString); saveAs (converted, `销售报表.docx` ); } } }