首页
留言
友链
关于
Search
1
思源笔记docker私有化部署及使用体验分享
3,028 阅读
2
windows11 远程提示:为安全考虑,已锁定该用户帐户,原因是登录尝试或密码更改尝试过多。
1,284 阅读
3
Pointer-Focus:一款功能强大的教学、录屏辅助软件
1,010 阅读
4
解决 nginxProxyManager 申请证书时的SSL失败问题
994 阅读
5
使用cspell对项目做拼写规范检查
824 阅读
Web前端
CSS
JavaScript
交互
Vue
小程序
后端
Java
Python
运维
项目
生活
其他
转载
软件
职场
登录
Search
标签搜索
docker
DevOps
magic-boot
Linux
酷壳
frp
RabbitMQ
gitlab
Node
git
工具
MybatisPlus
clickhouse
Syncthing
规范
前端
产品
nginx
markdown
axios
朱治龙
累计撰写
156
篇文章
累计收到
10
条评论
首页
栏目
Web前端
CSS
JavaScript
交互
Vue
小程序
后端
Java
Python
运维
项目
生活
其他
转载
软件
职场
页面
留言
友链
关于
搜索到
40
篇与
Web前端
的结果
2022-04-11
使用「Relation Graph」优化图表展现效果
背景说明近期检查前端工程的打包情况,发现@antv/g6占用了很大的空间:经过前端团队内部了解,这个主要是用于项目内超算中心的关系图展示,关系图这一块也随着业务的需求使用了不同的组件来实现:echarts > Mindelixir > @antv/G6实际使用@antv/G6实现完的效果,我的直观感觉也不是很理想,数据量大的情况下,显示太小,而且看了下相关的代码,感觉实现过程也不够优雅。所以秉承公司精益求精的企业文化,为了减少打包体积及优化使用体验等目的,准备使用「Relation Graph」对这一块进行大刀阔斧的改造。经过三四天的打磨,整体效果得到了运营同事的认可,先看看实现后的效果:本次调整后,打包体积上有较大的缩减:优化前:(原始大小:13.69M;压缩后:4.71M;开启gzip:1.42M)优化后:(原始大小:8.97M;压缩后:3.14M;开启gzip:957.42K)代码实现下面将项目中关系图相关的一些核心代码进行讲解视图代码<relation-graph ref="relationGraph" :options="graphOptions"> <div slot="node" slot-scope="{node}"> <el-popover v-if="node.data.status === 'user'" trigger="hover" placement="right" popper-class="account-graph-popver" > <div slot="reference" class="node-item flex align-center"> <div class="node-icon" :style="{color:node.data.iconColor}"> <em class="node-icon-inner" :class="node.data.nodeIconClassName" /> </div> <span class="node-name margin-left-sm" v-text="node.text" /> </div> <div class="popover-tips"> <p v-if="node.data.realname !== node.data.email && node.data.realname !== ''"> <span>{{ $t('userCenter.name') }}</span>: {{ node.data.realname }} </p> <p><span>{{ $t('common.email') }}</span>: {{ node.data.email }}</p> <p v-if="node.data.phone"> <span>{{ $t('common.mobile') }}</span>: {{ node.data.phone }} </p> <div v-if="belongingType==='use' && stopClick('accountMgmt')"> <el-divider /> <p class="text-center"> <el-button slot="reference" type="text" class="padding-lr-xs" @click="unBind(node)"> <para-lang code="selfService.group.accountUnbinding" /> </el-button> </p> </div> </div> </el-popover> <el-popover v-else-if="node.data.status !== 'accountMessage' && stopClick('accountMgmt')" trigger="hover" placement="right" popper-class="account-graph-popver" > <div slot="reference" class="node-item flex align-center"> <div class="node-icon" :style="{color:node.data.iconColor}"> <em class="node-icon-inner" :class="node.data.nodeIconClassName" /> </div> <span class="node-name margin-left-sm" v-text="node.text" /> </div> <div v-if="node.data.status === 'account'" class="popover-tips"> <p><span>{{ $t('common.SCAccount') }}</span>: {{ node.text }}</p> <div> <el-divider /> <p class="text-center"> <el-button v-if="belongingType==='belonging'" type="text" @click="modifyAccountRelation(node)"> 修改所属关系 </el-button> <el-button v-if="belongingType==='use'" type="text" @click="bindAccount(node)"> <para-lang code="selfService.group.accountBinding" /> </el-button> </p> </div> </div> <div v-if="node.data.status === 'ssc'" class="popover-tips"> <p><span>{{ $t('common.SCCode') }}</span>: {{ node.text }}</p> <el-divider /> <p class="text-center"> <el-button type="text" @click="showAccountApply(node.text)"> <para-lang code="selfService.group.applicationAccount" /> </el-button> </p> </div> </el-popover> <div v-else class="node-item flex align-center"> <div class="node-icon" :style="{color:node.data.iconColor}"> <em class="node-icon-inner" :class="node.data.nodeIconClassName" /> </div> <span class="node-name margin-left-sm" v-text="node.text" /> </div> </div> </relation-graph>图标配置项在Vue 组件的 data 里添加如下信息graphOptions: { 'debug': envName === 'development', // 仅开发或Istio环境下为调试模式 'allowShowMiniView': true, 'allowSwitchLineShape': true, 'defaultFocusRootNode': false, 'layouts': [ { 'label': '树状-横向', 'layoutName': 'tree', 'useLayoutStyleOptions': true, 'layoutClassName': 'member-layout-tree-v', 'defaultLineColor': '#DDDDDD', 'levelDistance': '300,300,300,500', 'min_per_width': 100, 'max_per_width': 300, 'min_per_height': 40, 'max_per_height': 100, 'defaultNodeShape': 1, 'defaultLineShape': 6, 'defaultJunctionPoint': 'lr', 'defaultExpandHolderPosition': 'right', 'from': 'left', 'defaultNodeHeight': '36', 'defaultNodeColor': '#C6E5FF', 'defaultNodeBorderWidth': 1, 'defaultNodeBorderColor': '#CED4D9', 'defaultNodeFontColor': '#666666' }, { 'label': '树状-纵向', 'layoutName': 'tree', 'useLayoutStyleOptions': true, // 'defaultFocusRootNode': false, 'layoutClassName': 'member-layout-tree-h', 'defaultLineColor': '#DDDDDD', // 'levelDistance': '120', 'min_per_width': 100, 'max_per_width': 400, 'min_per_height': 100, 'max_per_height': 500, 'defaultNodeShape': 1, 'defaultLineShape': 6, 'defaultJunctionPoint': 'tb', 'defaultExpandHolderPosition': 'bottom', 'from': 'top', 'defaultNodeHeight': '36', 'defaultNodeColor': '#C6E5FF', 'defaultNodeBorderWidth': 1, 'defaultNodeBorderColor': '#CED4D9', 'defaultNodeFontColor': '#666666' }, { 'label': '中心布局', 'layoutName': 'center', 'useLayoutStyleOptions': true, 'layoutClassName': 'member-layout-center', 'levelDistance': '80', 'defaultNodeShape': 1, 'defaultLineShape': 5, // 'defaultFocusRootNode': true, 'defaultLineColor': '#DDDDDD', 'defaultJunctionPoint': 'border', 'defaultNodeHeight': '36', 'defaultNodeColor': '#C6E5FF', 'defaultNodeBorderWidth': 1, 'defaultNodeBorderColor': '#CED4D9', 'defaultNodeFontColor': '#666666' } ] }将后端响应的数据组织成 Relation Graph 组件所需的数据在 methods 中添加如下方法 // 根据 RelationGraph 图表的数据组织形式,梳理内容 buildGraphData() { const _this = this const graphData = { rootId: '', nodes: [], links: [] } const nodes = graphData.nodes const links = graphData.links // 生成随机ID,针对数据无id属性的情况可生成一个随机ID const buildRandomId = function() { return 'mock-' + Date.now().toString(36) + '-' + Math.random().toString(36).substr(4) } var buildNodeData = function(node) { const nodeData = { 'id': node.id, 'name': node.name, 'data': { ...node } } const nodeClassMap = { 'ssc': { name: '超算中心', color: node.online === 1 ? '#9de173' : '#ccc', icon: 'console-svg svg-sc-center' }, 'user': { name: '用户', color: node.groupId === _this.user.userInfo.groupId ? '#C6E5FF' : '#8aabc1', iconColor: node.userId === _this.user.userInfo.group.payUserId ? '#f7c934' : '', icon: node.userId === _this.user.userInfo.group.payUserId ? 'console-svg svg-main-account' : 'console-svg svg-sub-account' }, 'account': { name: '超算账号', color: node.inSameGroup ? '#da97f9' : '#c7ceff', icon: 'console-svg svg-sc-account-2' }, 'accountMessage': { name: '账号信息', color: '#0f7dfa', icon: 'console-icon icon-sc-account' } } // nodeData.width = node.name.length * 12 nodeData.data.nodeIconClassName = nodeClassMap[node.status].icon nodeData.data.iconColor = nodeClassMap[node.status].iconColor || '#666' nodeData.color = nodeClassMap[node.status].color || '#C6E5FF' if (node.status === 'accountMessage') { nodeData.fontColor = '#fff' } return nodeData } const buildData = function(node) { let parentNodeId = node.id if (!parentNodeId) { node.id = parentNodeId = buildRandomId() } if (nodes.length === 0) { graphData.rootId = parentNodeId const nodeData = buildNodeData(node) nodes.push(nodeData) } if (node.children && node.children.length > 0) { for (const childNode of node.children) { // 若节点没有id,则给一个随机id值 if (!childNode.id) { childNode.id = buildRandomId() } const childNodeData = buildNodeData(childNode) const nodeLink = { from: parentNodeId, to: childNode.id, text: '' } nodes.push(childNodeData) links.push(nodeLink) // 递归添加子节点 if (childNode.children && childNode.children.length > 0) { buildData(childNode) } } } } if (this.drawData && this.drawData.length > 0) { buildData(this.drawData[0]) } return graphData }获取数据并初始化组件在 methods 中添加如下代码initGraph() { this.graphInited = false const graphData = this.buildGraphData() this.$nextTick(_ => { this.$refs.relationGraph.setJsonData(graphData, () => { this.graphInited = true }) }) }其他说明:layoutName 为 force 时存在一些问题,自动时会动态调整各节点的位置,导致一些交互受到影响,所以本工程没有启用自动布局使用纵向树状布局时节点较多的情况,后面的节点容易覆盖在上一个节点上,找了下github里的issue,说可以对各节点进行绝对布局实现,但是工作量会比较大,运营没在这个点上纠结,咱也就不考虑优化了。鼠标悬停在节点上时无法触发el-popover组件的悬停事件,经排查跟节点的 + - 折叠样式有关,该节点的宽度太宽,导致底部的节点内容无法响应鼠标事件,使用如下样式覆盖默认样式完美解决(以下样式代码包含图标组件其他元素的样式调整):.graph-container { ::v-deep .c-mini-graph { position: absolute; top:0; right:0; margin-top:0 } ::v-deep .c-mini-toolbar { margin-left:0; margin-top:0; right:0; bottom:10px; } /* 调整样式,避免出现账号悬停不显示用户信息的情况 */ ::v-deep .c-expand-positon-right{ width: auto; right:0; } ::v-deep .c-expand-positon-bottom { width: auto; left: 50%; margin-left: -10px; } ::v-deep .rel-node-shape-1 { overflow:hidden; padding:0; border-radius: 6px; } ::v-deep .node-item { padding: 0 10px 0 0; line-height:34px; .node-icon { background-color:#fff; width: 36px; text-align: center; line-height: 34px; vertical-align: middle; em { font-size:22px; } } .node-name { white-space:nowrap; display: inline-block; } } // 布局文字定位调整 ::v-deep .c-mb-child-panel .c-mb-button .c-mb-text{ padding-top:3px; display: block; width: 100%; position: relative; margin: 0; } }链接Relation Graph 官网Relation Graph 在Github的开源地址
2022年04月11日
524 阅读
0 评论
1 点赞
2022-04-01
Pointer-Focus:一款功能强大的教学、录屏辅助软件
现在这个时代电脑大家应该都有,你肯定有过给别人展示或者演示自己的操作,尤其是疫情期间会用得很多。例如上网课讲解题、腾讯会议开会、线上答辩什么的。为了表达更清晰,我们就很需要光标高亮、显示按键、聚光灯、画笔、放大镜等功能。Pointer-FocusPointerFocus 是一款非常出名的教学辅助工具,软件功能效果生动,助你讲解更加清晰,一套下来轻松不少。专为想要突出鼠标指针的演示者、培训师和教师而设计。该应用程序提供了一些易于使用的功能,例如高亮光标、鼠标聚光灯、击键可视化、放大镜和屏幕注释。在 PointerFocus 的帮助下,您可以让观众专注于感兴趣的领域,并使您的演示更易于理解。软件官网:https://www.pointerfocus.com/资金允许的情况推荐使用正版,正版官网加个为 $12.5谁需要 PointerFocus?老师演讲者演示制造商培训师PointerFocus 功能用彩色圆圈突出显示鼠标指针当您单击鼠标按钮时,环形动画可以向您的观众展示您的鼠标单击动作。可以高亮显示光标、显示圆圈、显示单击操作文本,还可以自定义大小和颜色。每次点击都会有特效,这样可以让人更加集中鼠标位置。启动热键是 F8,这个可以记一下。击键可视化可以向您的观众展示您刚刚按下了哪些快捷键。此功能可以帮助您使演示更易于理解。有时候你需要进行快捷键时,这个功能就非常恰到好处了!只要按下Ctrl、Shift、Alt、win、insert键开头,显示功能就会自动弹出来。启动热键是 F9,这个可以记一下。鼠标聚光灯当您选择此工具时,它将使屏幕变暗并在鼠标指针周围放置一个“聚光灯”。这可以将听众的注意力集中在感兴趣的领域。这可是个好东西,可以突出某些重要部分进行讲解,可以叫做“高光时刻”。图片启动热键是 F10,这个可以记一下。屏幕注释笔这个是必备的功能了,需要表示什么直接写,直接画。启动热键是F11,这个可以记一下。支持画笔、线条、箭头、矩形、椭圆,还可以更改画笔颜色和大小。屏幕放大镜当您选择此工具时,您可以在屏幕上显示任何部分的观众详细信息。这个和高光有一丁点像,但又不是同一个东西,可以在电脑局部放大。启动热键是F12,这个可以记一下。安卓远程控制您可以使用 Android App 来远程控制鼠标操作和 PointerFocus 功能。安卓客户端软件已打包到本文底部的下载压缩包了。必看安装教程下载地址:PointerFocus v2.4.zip软件安装完成后需要替换补丁和填写注册码直接复制补丁到安装目录下进行替换
2022年04月01日
1,010 阅读
0 评论
2 点赞
2022-02-04
《你不知道的JavaScript》阅读笔记
判断this现在我们可以根据优先级来判断函数在某个调用位置应用的是哪条规则。可以按照下面的顺序来进行判断:1、函数是否在new中调用(new 绑定) ?如果是的话 this 绑定的是新创建的对象。var bar = new foo()2、函数是否通过call、apply (显式绑定)或者硬绑定调用?如果是的话,this 绑定的是指定的对象。var bar = foo.call(obj2)3、函数是否在某个上下文对象中调用(隐式绑定) ?如果是的话,this 绑定的是那个上下文对象。var bar = obj1.foo()4、如果都不是的话,使用默认绑定。如果在严格模式下,就绑定到undefined,否则绑定到全局对象。var bar = foo()可计算属性名ES6中增加了可计算属性名,可以在文字形式中使用[]包裹一个表达式来当作属性名:var prefix = 'zhuzl' var content = { [prefix + '_name']: 'zhuzl', [prefix + '_age']: 34 } content['zhuzl_name'] // zhuzl属性描述符从ES5开始,所有的属性都具备了属性描述符。var myObject = {} Object.defineProperty(myObject, 'a', { value: 2, writable: true, // 是否可以修改值 configurable: true, // 属性描述符是否可配置,该值为单向操作,无法撤消。即便该值为false,还是可以把writable修改为false,但是无法改为true。若值为false,该值不可delete enumerable: true // 控制该属性是否出现在对象的属性枚举列表中。比如说for...in 循环 }) 对象不变性对象常量 结合writable:false和configurable:false可以创建一个真正的常量属性(不可修改、重定义和删除)var myObject = {} Object.defineObject(myObject, 'FAVORITE_NUMBER', { value: 42, writable: false, configurable: false })禁止扩展 如果想禁止一个对象添加新属性并且保留已有属性,可以使用Object.preventExtensions(...)密封 Object.seal(...) 会创建一个“密封”的对象,这个方法实际上会调用Object.preventExtensions(...) 并把所有现有属性标记为 configurable: false。所以密封之后不仅不能添加新属性,也不能重新配置或删除任何现有属性。冻结 Object.freeze(...)会创建一个冻结对象,这个方法实际上会在一个对象上调用Object.seal(...)并把所有“数据访问”属性标记为writable: false。这个方法是应用在对象上的级别最高的不可变性。setter 和 getter通常getter 和 setter 是成对出现的,只定义一个的话通常会产生意料之外的行为。var myObject = {} Object.defineProperty(myObject, 'b', { get: function () {}, set: function () {} }) // OR var myObject = { get a() { return this._a_ }, set a(val) { this._a_ = val * 2 } }遍历for(var k in obj):用来遍历对象的可枚举属性列表,包括prototype原型链上的属性。for(var v of arr):用于遍历数组的值,理论上是通过调用数组内置的@@iterator迭代器对象,然后通过调用迭代器对象的next()方法来手动遍历数组,原理如下:var myArray = [1, 2, 3] for (var v of myArray) { console.log(v) } // 1 // 2 // 3 // ----实际工作模式---- var myArray = [1, 2, 3] var it = myArray[Symbol.iterator]() it.next() // {value: 1, done: false} it.next() // {value: 2, done: false} it.next() // {value: 3, done: false} it.next() // {done: true}数组forEach():会遍历数组中的所有值并忽略函数的返回值数组every():会一直运行直到回调函数返回false(或“假”值)数组some():会一直运行直到回调函数返回true(或“真”值)原型继承function Foo(name) { this.name = name } Foo.prototype.myName = function () { return this.name } function Bar (name, label) { Foo.call(this, name) this.label = label } // 我们创建了一个新的Bar.prototype对象并关联到Foo.prototype // 注意:现在没有Bar.prototype.constructor 了,如果需要这个属性的话可以手工修复一下 Bar.prototype = Object.create(Foo.prototype) Bar.prototype.myLabel = function () { return this.label } var a = new Bar('a', 'obj a') a.myName() // "a" a.myLabel() // "obj a" WARNING:下面这两种方式是常见的错误左房,实际上他们都存在一些问题:// 和你想要的机制不一样。该方式并不会创建一个关联到Bar.prototype的新对象,他只是让Bar.prototype 直接引用Foo.prototype对象。因此当执行类似Bar.prototype.myLable = xxx 的赋值语句时,是直接修改Foo.prototype对象本身。 Bar.prototype = Foo.prototype // 基本上满足需求,但是可能会产生一些副作用。该方式的确会创建一个关联到Foo.prototype的新对象。但是他使用的Foo(...)的“构造函数调用”,如果函数Foo有一些副作用(比如写日志、修改状态、注册到其他对象、给this添加数据属性等),就会影响到Bar()的“后代”。 Bar.prototype = new Foo()TIPS:ES6添加了辅助函数Object.setPrototypeOf(...),可以作为标准且可靠的方式来修改对象关联:// ES6之前使用Bar.prototype = Object.create(Foo.prototype) Bar.prototype = Object.create(Foo.prototype) // ES6 开始可以直接使用修改现有的Bar.prototype Object.setPrototypeOf(Bar.prototype, Foo.prototype)整数的安全范围能够被“安全”呈现的最大整数是2^53-1,即9007199254740991,在ES6中被定义为Number.MAX_SAFE_INTEGER。最小整数是-9007199254740991,在ES6中被定义为Number.MIN_SAFE_INTEGER。toString基本类型值得字符串化规则null -> "null"undefined -> "undefined"true -> "true"普通对象除非自定义,否则toString() (Object.prototype.toString())返回内部属性[[class]]的值,如“[object Object]”如果对象有自己的toString方法,字符串化时会调用该方法并使用其返回值。数组的默认toString()方法经过了重新定义,将返回单元字符串化以后在用“,”链接起来JSON字符串化:JSON.stringify(val, replacer, space),在将对象序列化为字符串时也用到了toString不安全的JSON值:undefined,function,symbol和包含循环引用的对象,JSON.stringify(...)在对象中遇到undefined、function和symbol时会自动将其忽略,在数组中则会返回null(以保证单元位置不变)。如果对象中定义了toJSON()方法,JSON字符串化时会首先调用该方法,然后用它的返回值来进行序列化。toJSON()应该“返回一个能够被字符串化的安全的JSON值”,而不是“返回一个JSON字符串”toNumber非数字值转换数字逻辑:true -> 1false -> 0undefined -> NaNnull -> 0toBoolean以下这些值都是假值:undefinednullfalse0、+0、-0和NaN""除以上假值列表的数据外均为真值~ 运算符(即字位操作“非” ) 和 |(即字位操作“或” )字位运算符只适用于32位整数,运算符会强制操作数使用32位格式,这是通过抽象操作toInt322来实现的。~x 大致等同于 -(x+1), 如 ~42 = ~(42+1) ==> -43。在-(x+1)中唯一能得到0的x值是-1,也就是说如果x为 -1 时,~x会返回假值0,其他情况下则返回真值。根据该规则可以将~和indexOf一起将结果强制转换为真/假值:if (~a.indexOf('xxx')) { // true // 找到匹配的! }~~x: 截除数字值的小数部分
2022年02月04日
33 阅读
0 评论
0 点赞
2021-12-07
Typescript的14个基础语法
一.Ts是什么首先,强类型不允许随意的隐式类型转换,而弱类型是允许的。JavaScript就是经典的弱类型语言。而Typescript可以说是JavaScript的超集,在JS的基础上新增了许多语法特性,使得类型不再可以随意转换,能大大减少开发阶段的错误。二. 基本语法1.声明原始数据类型在变量后面指定一个关键字表示其只能为什么类型。// string类型: const a: string = 'auroras' // number类型: const b: number = 666 // 包括 NAN Infinity // boolean类型: const c: boolean = true // null类型: const d: null = null // undefined类型: const e: undefined = undefined // symbol类型: const h: symbol = Symbol()2.声明Object类型首先,object类型不单单可以指定对象,还可以指定数组或函数:const foo1: object = {}; const foo2: object = []; const foo3: object = function(){};如果只想指定为对象,如下,对象属性都要提前声明好类型:const obj: {name: string,age: number} = { name: '北极光', age:18 }3.1声明数组类型可以指定声明Array且通过<>指定元素类型,比如指定声明元素都为数字的数组:const arr: Array<number> = [1,2,3] // OR const arr: number[] = [1,2,3]3.2声明元组类型就是要提前指定数组里每个元素的类型,严格一一对应:const tuple: [number,string,boolean] = [666,'auraros',true]4.声明枚举类型通过关键字enum声明一个枚举类型,如:enum Status { pedding = 1, resolve = 2, reject = '3' } //访问 console.log(Status.pedding);如果全不写值,默认值为从0开始递增。如果第一个元素为字符类型,就必须全部定义值。如果第一个元素指定为一个数字,后面元素不写值,那值为第一个元素值按位置大小递增的结果。5.函数参数与返回类型函数声明式:指定函数传入参数类型,指定返回值类型,调用时传入参数个数与类型都必须相同:括号里指定每个参数类型,括号右边指定返回值的类型。function fun (name:string,age:number):string{ return 'sss' } fun('auroras',18);如果传入参数不确定传不传,那么可以给参数加个‘?’表明它是可选的:function fun (name:string,age?:number):string{ return 'sss' } fun('auroras');或者给参数添加默认值,那也会成为可选参数:function fun (name:string,age:number=666):string{ return 'sss' } fun('auroras');如果参数个数不确定,可以用扩展运算符加解构赋值表示,当然要传入与指定类型一致的:function fun (name:string,age:number=666,...res:number[]):string{ return 'sss' } fun('auroras',1,2,3);函数表达式:const fun2:(name:string,age:number)=>string = function(name:string,age:number){ return 'sss' }6.任意类型通过指定any关键字表示任意类型,跟原来 js 一样,可以任意赋不同类型的值:let num:any = 1; num = 'a'; num = true;7.类型断言类型断言就是明确的告诉typescript这个变量就是某种类型的,百分之百确定。不用typescript在一些情况下要自己推断某些没有明确定义或者多变的场景是什么类型。可以通过 as+类型 断言它就是某种类型的:const res = 1; const num = res as number;也可以通过 <类型> 形式断言(不推荐):const res = 1; const num = <number>res8.接口基本使用接口可以理解为一种规范,一种契约。可以约束一个对象里应该有哪些成员,这些成员都是怎么样的。通过interface定义一个Post接口,这个接口是一个对象,规则为有一个name属性类型为string,age属性类型为number。interface Post { name:string; age:number }然后比如有一个函数 printPost ,它的参数 post 使用我们定义的 Post 接口的规则,那么调用此函数传参时要传入符合 Post 接口规则的数据。interface Post { name:string; age:number } function printPost(post: Post){ console.log(post.name); console.log(post.age); } printPost({name:'asd',age:666})当然,函数传参时可能有些参数是可选的,那么我们可以给接口也定义可选的成员,通过属性后加一个‘ ? ’指定 可选成员 :interface Post { name:string; age:number; sex?:string; } const auroras: Post = { name:'asd', age: 18 }如果用 readonly 修饰成员,那么这个成员属性在初始化后便不可修改:interface Post { name:string; age:number; sex?:string; readonly like:string } const auroras: Post = { name:'asd', age: 18, like: 'natrue' } auroras.name = 'aaaa'; //保错 auroras.like = 'wind';如果连成员属性名称都不确定,那么可以声明 动态成员 ,要指定成员名字类型与成员值的类型,如:interface Post { [prop:string]:string } const auroras: Post = { name:'asd', like: 'natrue' }9.类基本使用描述一类具体事物的抽象特征。ts增强了es6中class类的相关语法。首先,类的属性使用前必须提前声明好:class Person { name: string; age: number; constructor(name:string,age:number){ this.name = name; this.age = age; } sayHi(msg:string):void { console.log(`hi,${msg},i am ${this.name}`); } }10.类的访问修饰符private 修饰私有属性,只能在类内部访问。 public 修饰公用属性(默认),外部也可访问:class Person { public name: string; private age: number; constructor(name:string,age:number){ this.name = name; this.age = age; } sayHi(msg:string):void { console.log(`hi,${msg},i am ${this.name}`); console.log(this.age); } } const jack = new Person('jack',20); //Person类公有属性可以访问 console.log(jack.name); //Person类私有属性不可以访问 console.log(jack.age);protected 修饰为受保护的,外部也不可访问。但与 private 的区别是若是继承的子类是可以访问的。class Person { public name: string; private age: number; // protected protected gender: boolean; constructor(name:string,age:number){ this.name = name; this.age = age; this.gender = true; } sayHi(msg:string):void { console.log(`hi,${msg},i am ${this.name}`); console.log(this.age); } } class children extends Person{ constructor(name:string,age:number){ super(name,age,); //可以访问 console.log(this.gender); } }11.类只读属性给属性设置 readonly 则为只读属性,该属性初始化后便不可再修改。class Person { public name: string; private age: number; // readonly protected readonly gender: boolean; constructor(name:string,age:number){ this.name = name; this.age = age; this.gender = true; } sayHi(msg:string):void { console.log(`hi,${msg},i am ${this.name}`); console.log(this.age); } }12.类与接口一些类与类之间有些许共同的特征,这些共同的特征可以抽象成为接口。比如 Person 类和 Animal 类,虽然是不同类,但是人和动物都会吃东西和走路等,这些共同的特征可以由接口定义。最后一个特征就定义一个接口。//吃接口 interface Eat { eat(food:string):void } //行进接口 interface Run { run(behavior:string):void } //人 class People implements Eat,Run { eat(food:string){ console.log(`在餐桌上吃${food}`); } run(behavior:string){ console.log(`站着${behavior}`); } } //动物 class Animal implements Eat,Run { eat(food:string){ console.log(`在地上上吃${food}`); } run(behavior:string){ console.log(`爬着${behavior}`); } }13.抽象类约束子类必须有某些成员,有点类似接口,不同的是抽象类可以包含一些具体的实现。比如动物类应该为一个抽象类,它的子类有猫,狗,熊猫等。它们都是动物,也有一些共同的特征。定义一个类为抽象类后,就不能再new实例了,只能被其子类继承。其中 abstract 定义抽象类,类里用 abstract 定义一个抽象方法,子类必须实现抽象方法。abstract class Animal { eat(food:string){ console.log(`在地上吃${food}`); } abstract run (behavior:string):void } //猫 class Dog extends Animal{ run(behavior:string):void{ console.log(behavior); } } const d1 = new Dog(); d1.eat('骨头') d1.run('四脚爬行') //兔子 class rabbit extends Animal{ run(behavior:string):void{ console.log(behavior); } } const r1 = new rabbit(); d1.eat('萝卜') d1.run('蹦蹦跳跳') 14.泛型泛型就是在定义函数,接口或者类的时候没有指定具体类型,等到使用时才指定具体类型。极大程度的复用代码。比如有一个 identity 函数,这个函数会返回任何传入它的值,且传入的类型与返回的类型应该是相同的。如果传入数字,不用泛型的话,这个函数可能是下面这样: function identity(arg:number):number{ return arg }如果传入字符串,这个函数可能是下面这样: function identity(arg:string):string{ return arg }这样的话太麻烦,所以可以使用泛型,一般用大写 T 表示泛型,它可以适用于多个类型,且传入类型与返回类型是相同的。 function identity<T>(arg:T):T{ return arg }
2021年12月07日
164 阅读
0 评论
0 点赞
2021-11-30
简单理解 Typescript 的配置文件 tsconfig.json
TS 使用 tsconfig.json 作为其配置文件,他主要包括两块内容:指定待编译的文件定义编译选项一般来说, tsconfig.json 文件所处的路径就是当前 TS 项目的根路径。tsconfig.json 的配置项众多并且复杂。所有的选项可以参考官方文档:https://www.typescriptlang.org/zh/tsconfig 这里我们分析一个简单示例:{ "compilerOptions": { "module": "commonjs", "noImplicitAny": true, "sourceMap": true, "declaration": true }, "files": [ "app.ts", "foo.ts", ] }其中, compilerOptions 用来配置编译选项, files 用来指定待编译文件。这里的待编译文件是指入口文件,任何被入口文件依赖的文件。也可以使用 include 和 exclude 来指定和排除待编译文件:{ "include": [ "src/**/*" ], "exclude": [ "node_modules", "**/*.spec.ts" ] }下面简述 compilerOptions 中的选项配置,这些配置简单来说就是影响整个编译的过程和编译的结果。module - 生成的 Javascript 模块形式:none、commonjs、amd、system、umd、es6、es2015 或 esnextnoImplicitAny - 存在隐式 any 时抛错 (默认为 false)sourceMap - 生成 map 文件 (默认为 false)declaration - 生成对应的 .d.ts 文件 (默认为 false)
2021年11月30日
310 阅读
0 评论
0 点赞
2021-09-06
如何防止他人恶意调试你的web程序
前言看到社区很多都在讨论 如何调试,如何高级的调试,以及一些调试的奇技淫巧 ,今天我想和大家聊聊, 怎么禁止调试,禁止他人调试我们的程序 为什么会有这篇文章呢,源自一次我寻找盗版电影的遭遇,一次好奇心的驱使下,由于很多这种平台都是只做搬运,不做存储,因为存储盗版电影向他人提供是违法的,特别是那种刚出的新电影! 当时好奇想通过看某站的控制台,想了解一下他们是怎么是通过啥接口,怎么请求,请求来的格式啥样的,抱着这样的好奇心,开始了我的奇妙之旅... 看完本篇文章你将学会我无法断定你能学到什么,但是以下是我希望你能从本篇文章中学到的:1.如何简单的防止你的程序被他人恶意调试2.逆向思维学会如何更好的调试具体实现防止调试的方法,这里我们主要是通过不断 debugger 的方法来疯狂输出断点,让控制台打开后程序就无法正常执行我们都知道 debugger 只有在控制台被打开的时候才会执行,所以后面的所有方法都是围绕着这一特性来进行,废话不多说,我将通过以下几个案例向你们展示道高一尺魔高一丈的道理,先上代码:方法一:(() => { function block() { setInterval(() => { debugger; }, 50); } try { block(); } catch (err) {} })(); 通过上方的代码我们可以看到,在页面中打开控制台后,会有以下结果:需要在这里说明以下几点:程序被 debugger 阻泄了,我们无法像以往一样在 Source Tab 中的对应 js 代码处添加断点调试,无法调试程序的执行逻辑.在程序异常复杂且被混淆后的代码是异常难读的!通常我们会在 source 的左边加上 breakpoint 来让程序每次走到加点的地方停下来,以便让我们查看一些变量的值或是步骤的流程逻辑(如下图所示)我们都知道,第一次打开控制台是看不到 Network tab 中的任何请求的,所以我们想通过 Network tab 来查看网页都做了哪些请求,也是看不到的,当我们打开控制台就会出 debugger 阻挡我们,我们可以通过下面的解决方法来处理,或者是用抓包工具来查看具体的请求大家可以先不看解决方法,想想如果是你,这个时候怎么突破这个屏障呢? 第一次遇到这种情况我也是很懵,不知道咋处理,后面发现问题简直不要太简单,我们可以带着疑问来看: 对于第一个示例,我们如何解决?(绕过它) 答案是: 禁止断点 可以看到很简单,在 Chrome 控制台的 Source Tab 页点击 Deactivate breakpoints 按钮或者按下 Ctrl + f8(如下图所示)。但是对于控制台不熟悉的小伙伴,很难会想到这里去.但是,难道这篇文章就这样结束了?那我可顶不住小伙伴们的 "就这?" 其实,上面的解决方法并没有帮我们解决根本问题,我们需要做的是调试,上面虽然把debugger都去掉了,但是我们也无法在通过点击每一行代码左边的行号添加 breakpoint 了,所以根本性的问题,并没有解决,只是去除了那碍眼的疯狂 debugger,我们还是得另辟蹊径方法二:对对应的代码行,通过添加 logpoint 为 false,然后按回车后刷新网页,发现成功跳过无限 debugger,于是我们就可以愉快的自由调试了~对应的还有一种方法即通过 add script ignore list 来添加需要忽略执行代码行或文件可以看到,我们也可以通过删除 script ignore list 里已添加的忽略代码,恢复初始状态但是,你这么聪明,那人家不得想想对策?对于上面的第一个方法将setInterval(() => {debugger;}, 50);写在一行中,你即使通过添加logpoint为 false,也没用,仍然是疯狂 debugger,即使你可能想到,通过左下角的代码格式化,来格式一下setInterval(() => {debugger;}, 50);将它变成多行的,也是没用的,仍然会在刷新后重新弹 debugger(() => { function block() { setInterval(() => {debugger;}, 50); } try { block(); } catch (err) {} })();对于第二个方法,我们对代码进行如下改造(() => { function block() { setInterval(() => { Function("debugger")(); }, 50); } try { block(); } catch (err) {} })();我们可以通过将debugger改写成Function("debugger")();的形式,来应对;Function 构造器生成的 debugger 会在每一次执行时开启一个临时 js 文件,哈哈~对方表示好无奈于是会有以下结果 这无限套娃,真够狠的,我们要坚信正义最后总会胜利,不能给想非法调试我们程序的人机会,所以我们要把各种情况都考虑周全,可以说这种方法是最恨的,但是这还不算完~ (好家伙~ 想非法调试我程序,那你就得战胜我)强化以上方法 上面的代码由于没有加密混淆,多少可能还是会被别人读一些,那么我们加密混淆看看是啥样的好家伙,你这咋读?eval(function(c,g,a,b,d,e){d=String;if(!"".replace(/^/,String)){for(;a--;)e[a]=b[a]||a;b=[function(f){return e[f]}];d=function(){return"\\w+"};a=1}for(;a--;)b[a]&&(c=c.replace(new RegExp("\\b"+d(a)+"\\b","g"),b[a]));return c}('(()=>{1 0(){2(()=>{3("4")()},5)}6{0()}7(8){}})();',9,9,"block function setInterval Function debugger 50 try catch err".split(" "),0,{}));格式化后的样子我们继续对代码进行改造,让对方尽量的难以识别我们的代码将 Function("debugger").call() 改成 (function(){return false;})["constructor"]("debugger")["call"](); 并且,添加条件,当窗口外部宽高,和内部宽高的差值大于一定的值,我把 body 里的内容全部清空掉,看你还能不能操作我的按钮啊啥的~哈哈哈需要特别说明的是 : 像 toG 的项目或者是一些为了保护自己的版权又或者是一些比较敏感的项目,出于安全的考虑在部署到生产环境后最好是不让别人调试的,当然,前端所做的也就那么一些,需要前后端一起配合,便可以很好的对项目或者数据进行私密的保护 最后: 附上这份未混淆的来之不易的的代码(记得混淆后使用哦~) 一定要记得点赞加关注~原创太不容易了.你可以把它当作你的工具函数,在需要不让别人轻易调试的项目中引用 (当然它仍然有很多漏洞,但是我们可以用类似的逻辑方法,变化出很多更复杂的限制,最暴力的甚至可以当控制台打开后就立马通过window.close();来关闭调试窗口),欢迎大家在评论区留下更好玩的方法~共同学习(() => { function block() { if ( window.outerHeight - window.innerHeight > 200 || window.outerWidth - window.innerWidth > 200 ) { document.body.innerHTML = "检测到非法调试,请关闭后刷新重试!"; } setInterval(() => { (function () { return false; } ["constructor"]("debugger") ["call"]()); }, 50); } try { block(); } catch (err) {} })();推荐一个调试页面的小技巧说了那么多的防止被人调试,那么最后也说一个本人觉得眼前一亮的调试样式的方法通过给 style 标签添加 style="display: block" , contenteditable 两个属性实现在页面中便捷的调试样式复制下方代码到你的 html 文件中,玩一下~<!DOCTYPE html> <body> <div>来调试我吧~</div> <style style="display: block" contenteditable> body { background-color: rgb(140, 209, 230); color: white; } div { background-color: green; width: 300px; height: 300px; line-height: 300px; text-align: center; } </style> </body> 最后我所知道的禁止调试的方法就只有如上所述,但是肯定还有很多好玩的,小伙伴们可以在评论区留言,一起共同学习~最后抛出一个问题, 如何监测控制台是否被打开 (我上面提到的通过window内外高宽的差值其实可以通过独立调试窗口的方法绕过),网上找了一些方法,貌似都没用,感兴趣且有头绪,或者已经有方法的小伙伴可以小伙伴可以在评论下方说说自己的想法,{card-describe title="引用自"}作者:荣顶链接:https://juejin.cn/post/7000784414858805256来源:掘金{/card-describe}
2021年09月06日
73 阅读
0 评论
0 点赞
2021-07-14
在vue-cli3使用sass(scss)定义的全局样式及变量
为统一风格及便于后续维护,在项目中常用的主色调、圆角、阴影相关的样式定义到一个全局变量文件中,常规的使用需要每个Vue组件中进行import,该操作会比较繁琐,经过了解后发现可以基于webpack的loader参数引入全局变量的方式来实现,相关实现过程如下所示:vue.config.jsmodule.exports = { // ... css: { // 为便于在Vue组件中使用,全局引入variables.scss中定义的变量 loaderOptions: { sass: { prependData: `@import "@/styles/variables.scss";` } } } variables.css// base color $blue:#324157; $light-blue:#3A71A8; $red:#C03639; $pink: #E65D6E; $green: #30B08F; $tiffany: #4AB7BD; $yellow:#FEC171; $panGreen: #30B08F; $theme: #004ca1; // ...vue 组件<style lang="scss" scoped> .btn-cursor { cursor:pointer; color: $theme } </style>
2021年07月14日
492 阅读
0 评论
0 点赞
2021-06-30
一个简单的自定义工作流设计器实现
先瞅瞅产品提供的丑陋至极的原型效果再看看经过本人精雕细琢之后的惊艳效果Show me the code流程设计弹出层<template> <el-dialog width="760px" class="hkt-dlg-darkblue" :title="`流程设计:${formData.name}`" :visible.sync="isShow" destroy-on-close :close-on-click-modal="false"> <div class="dialog-wrap" v-loading="loading"> <div style="min-height:50vh"> <div class="info-block"> <el-row class="hkt-block-title"> <el-col :span="12"><div><span class="title-name">流程配置</span></div></el-col> <el-col :span="12" class="text-right"> <el-button type="text" @click="formModalChange('New', null)"><i class="el-icon-plus"></i> 添加审核节点</el-button> </el-col> </el-row> <div class="info-content"> <el-steps direction="vertical" :active="nodes.length"> <el-step v-for="(step, stepIndex) in nodes" class="step-item-wrap" :class="'step-item-signmodel-' + step.signModel" :key="step + stepIndex"> <div slot="title"> <div style="line-height:32px;heihgt:38px;"> <el-row> <el-col :span="12"> <span class="step-name" v-text="step.nodeName"></span> </el-col> <el-col :span="12"> <div class="step-item-oprations text-right"> <el-button size="mini" type="text" @click="stepSort(stepIndex, 'up')" v-if="stepIndex > 0"><i class="el-icon-top"></i> 上移</el-button> <el-button size="mini" type="text"@click="stepSort(stepIndex, 'down')" v-if="stepIndex < nodes.length - 1"><i class="el-icon-bottom"></i> 下移</el-button> <el-button size="mini" type="text" @click="formModalChange('Edit', step, stepIndex)"><i class="el-icon-edit"></i> 修改</el-button> <el-button size="mini" type="text" @click="removeStep(step, stepIndex)"><i class="el-icon-close"></i> 删除</el-button> <el-button size="mini" type="text" @click="addPersons(step, stepIndex)"><i class="el-icon-user"></i> 配置审批人</el-button> </div> </el-col> </el-row> </div> </div> <div slot="description"> <div v-if="step.approveNames.length === 0"> <span class="color-gray">暂未配置审批人员</span> </div> <div v-else class="step-item-content-wrap"> <div class="person-item"> <el-tag v-for="(person, personIndex) in step.approveNames" :key="person" disable-transitions @close="removePerson(stepIndex, personIndex)" closable>{{person}}</el-tag> </div> </div> </div> </el-step> </el-steps> <div v-if="nodes.length === 0"> <h3 style="padding-top:40px;font-weight:normal;color:#dedede;font-size: 22px; text-align:center"><p>当前流程暂未配置审核节点信息,</p><p>请单击右上角的“添加审核节点”按钮进行配置</p></h3> </div> </div> </div> </div> </div> <div slot="footer" class="dialog-footer-wrap text-center"> <el-button @click="isShow = false">取 消</el-button> <el-button type="primary" :loading="submitting" @click="save">{{submitting ? '保存中': '确 定'}}</el-button> </div> <step-form-modal :show.sync="formModalVisible" v-if="formModalVisible" :formData="currentRow" :mode="formMode" @callback="formCallback" /> </el-dialog> </template> <script> import stepFormModal from './stepFormModal.vue' export default { name: 'flowDesignModal', desc: '流程设计器', components: { stepFormModal }, props: { show: { type: Boolean, required: true, default: false }, formData: { // 适用于编辑 type: Object, required: false }, mode: { type: String, required: false } }, data () { return { loading: true, form: { }, nodes: [], formModalVisible: false, currentRow: null, currentStepIndex: null, formMode:'New', // New || Edit submitting: false } }, computed: { isShow: { get () { return this.show }, set (val) { this.$emit('update:show', val) } } }, created() { this.init() }, methods: { init () { if (this.formData) { this.getFlowNodes() // this.form = this.$hktUtils.deepClone(this.formData) } }, stepSort (index, type) { const steps = this.nodes if (type === 'up') { ;[steps[index], steps[index - 1]] = [steps[index - 1], steps[index]] } else { ;[steps[index], steps[index + 1]] = [steps[index + 1], steps[index]] } // 触发页面更新 this.nodes = steps.concat() }, async getFlowNodes () { this.loading = true const res = await this.$request.get(`/v1/manager/flow/node/listByFlowId?flowId=${this.formData.id}`) if (res.success) { this.nodes = res.data } this.loading = false }, // 添加或编辑流程节点 formModalChange (mode, formData, stepIndex) { this.formMode = mode this.currentRow = formData this.currentStepIndex = stepIndex this.formModalVisible = true }, formCallback (item) { // TODO 判断步骤名称是否已存在 if (this.formMode === 'New') { item.flowId = this.formData.id item.approveArray = [] item.approveNames = [] this.nodes.push(item) } else if (this.currentStepIndex >= 0) { this.nodes[this.currentStepIndex] = item this.currentStepIndex = null } }, addPersons (row, stepIndex) { const dialogProps = { multipleFlag: 'Y' } if (row.approveArray && row.approveArray.length > 0) { dialogProps.initRightUsers = row.approveArray.join(',') } window.hktCommon.selectUser(`配置 ${row.nodeName} 节点的审批人`, dialogProps, (res) => { row.approveArray = res.map(item => item.id) row.approveNames = res.map(item => item.name) this.nodes.splice(stepIndex, 1, this.$hktUtils.deepClone(row)) }) }, removePerson(stepIndex, personIndex) { this.nodes[stepIndex].approveArray.splice(personIndex, 1) this.nodes[stepIndex].approveNames.splice(personIndex, 1) }, removeStep(stepItem, stepIndex) { this.$confirm(`本操作不可恢复,确定删除该步骤(${stepItem.nodeName})?`, '提示', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning', }).then(() => { this.nodes.splice(stepIndex, 1) }).catch(() => {}) }, // 保存 async save () { const flowNodes = this.$hktUtils.deepClone(this.nodes) const existNodeNames = []// 用于校验名称是否重复 for (let i = 0; i < flowNodes.length; i++) { const flowNode = flowNodes[i] if (flowNode.approveArray.length === 0) { this.$message({ message: `审核步骤(${flowNode.nodeName})至少需选择一个的审批人`, type: 'warning' }) return } if (existNodeNames.includes(flowNode.nodeName)) { this.$message({ message: `存在相同的审核节点名称:${flowNode.nodeName}`, type: 'warning' }) return } existNodeNames.push(flowNode.nodeName) flowNode.orders = i +1 flowNode.approveIds = flowNode.approveArray.join(',') } this.submitting = true // 执行保存入库相关操作 const res = await this.$request.post(`/v1/manager/flow/node/add`, flowNodes).catch(err => { this.submitting = false }) this.submitting = false if (res && res.success) { this.$emit('callback', flowNodes) this.isShow = false } } } } </script> <style lang="scss" scoped> .form-body-item { background-color:#fff; padding:10px; border-radius: 4px; margin-bottom: 10px; } .form-item-zones { border-bottom: solid 1px #DCDFE6 ; margin-bottom:20px; .block-title { font-weight:bold; display: inline-block; line-height: 36px; font-size:14px; } } .step-item-oprations { display: none; } .step-item-wrap { /deep/ .el-step__main { padding: 0 10px 10px; } } .step-item-wrap:hover { /deep/ .el-step__main { background-color:#F5F7FA; border-radius: 5px; } .step-item-oprations { display: block; } } .step-item-content-wrap { padding: 10px; } .btn-choose { margin-left:10px; vertical-align: middle; } .person-item /deep/ { display: inline-block; .el-tag { vertical-align: middle; position: relative; overflow: initial; margin-bottom:10px; .el-tag__close { padding: 2px; box-sizing: content-box; position:absolute; background-color: #D1E9FF; // display: none; top:-10px; right: -10px; &:hover { background-color: #1890ff; } } &:hover { .el-tag__close { display:block; } } } .el-tag + .el-tag { margin-left: 40px; &:before { content: '或'; position: absolute; left: -26px; color: #666666; } } } // 会签模式步骤的审批人之间是用和 .info-content /deep/ .step-item-signmodel-1 .person-item { .el-tag + .el-tag { &:before { content: '和' } } } .step-name { color:#101010; font-weight: bold; } </style> 步骤编辑弹出层<template> <el-dialog width="460px" class="hkt-dlg-darkblue" :title="(form.id ? '编辑' : '新建') + '步骤'" :visible.sync="isShow" append-to-body destroy-on-close :close-on-click-modal="false"> <div class="dialog-wrap"> <el-form ref="form" :model="form"> <el-form-item label="步骤名称" prop="nodeName" :rules="[{required: true, message: '请填写步骤名称', trigger: 'change'}]"> <el-input v-model="form.nodeName" style="width:250px"></el-input> </el-form-item> <el-form-item label="是否会签模式"> <el-switch v-model="form.signModel" :active-value="1" :inactive-value="2" active-text="是" inactive-text="否"></el-switch> </el-form-item> <el-form-item label="是否可办结"> <el-switch v-model="form.isOver" :active-value="1" :inactive-value="0" active-text="是" inactive-text="否"></el-switch> </el-form-item> </el-form> </div> <div slot="footer" class="dialog-footer-wrap text-center"> <el-button @click="isShow = false">取 消</el-button> <el-button type="primary" @click="save">确 定</el-button> </div> </el-dialog> </template> <script> export default { name: 'stepFormModal', desc: '流程步骤表单', props: { show: { type: Boolean, required: true, default: false }, formData: { // 适用于编辑 type: Object, required: false }, mode: { type: String, required: false } }, data () { return { form: { nodeName: '', signModel: 2, isOver: 0 } } }, computed: { isShow: { get () { return this.show }, set (val) { this.$emit('update:show', val) } } }, created() { this.init() }, methods: { init () { if (this.formData) { this.form = this.$hktUtils.deepClone(this.formData) } }, save () { this.$refs['form'].validate(async (valid) => { if (valid) { this.$emit('callback', this.form) this.isShow = false } }) } } } </script> <style lang="scss" scoped> .form-body-item { background-color:#fff; padding:10px; border-radius: 4px; margin-bottom: 10px; } .form-item-zones { border-bottom: solid 1px #DCDFE6 ; margin-bottom:20px; .block-title { font-weight:bold; display: inline-block; line-height: 36px; font-size:14px; } } .step-item-oprations { display: none; } .step-item-wrap:hover { background-color:#D1E9FF; .step-item-oprations { display: block; } } .step-item-content-wrap { padding: 10px; } .btn-choose { margin-left:10px; vertical-align: middle; } .person-item /deep/ { display: inline-block; .el-tag { vertical-align: middle; position: relative; overflow: initial; .el-tag__close { padding: 2px; box-sizing: content-box; position:absolute; background-color: #D1E9FF; // display: none; top:-10px; right: -10px; &:hover { background-color: #1890ff; } } &:hover { .el-tag__close { display:block; } } } .el-tag + .el-tag { margin-left: 40px; &:before { content: '或'; position: absolute; left: -26px; color: #666666; } } } .step-name { color:#101010; font-weight: bold; } </style> 该组件涉及人员选择相关公共组件的封装,详细见 分享一个在管理系统中一些公共组件的调用方式
2021年06月30日
272 阅读
0 评论
0 点赞
2021-06-30
分享一个在管理系统中一些公共组件的调用方式
公共组件效果查看合同:地图描点:公共资源文件上传:人员选择-多选:人员选择-单选:增加公共组件目录核心代码如下:<template> <div class="common-dialogs-wrap"> <!-- 公共资源上传 --> <resource-modal :title="resourcePickerDlg.dialogTitle" :resourceDialogProps.sync="resourcePickerDlg.resourceDialogProps" v-if="resourcePickerDlg.show" :show.sync="resourcePickerDlg.show" @callback="resourceCallback"></resource-modal> <!-- 公共人员选择 --> <select-user-modal :title="userSelectDlg.dialogTitle" :dialogProps.sync="userSelectDlg.dialogProps" v-if="userSelectDlg.show" :show.sync="userSelectDlg.show" @callback="userSelectedCallback" ></select-user-modal> <!-- 合同详情弹出层 --> <contract-detail-modal :show.sync="contractDetailDlg.show" v-if="contractDetailDlg.show" :detail-id="contractDetailDlg.dialogProps.contractId"></contract-detail-modal> <!-- 地图描点 --> <picker-q-map-position-modal :show.sync="qMaplDlg.show" v-if="qMaplDlg.show" @callback="qMapCallback"></picker-q-map-position-modal> <!-- 预留后续扩展其他公共组件 --> </div> </template> <script> const ResourceModal = () => import('./ResourceModal.vue') const SelectUserModal = () => import('./SelectUserModal.vue') const ContractDetailModal = () => import('@/views/contract-manage/contractList/DetailContractModel.vue') // 合同详情弹出层 const PickerQMapPositionModal = () => import('./PickerQMapPositionModal.vue') // 腾讯地图描点 var hktCommon = {} var uploadCallbackFn = null // 资源上传回调 var userSelectCallback = null // 用户选择回调 var qMapCallbackFn = null // 腾讯地图描点回调 export default { name: 'HktCommon', desc: '华宽通公共组件封装', components: { ResourceModal, SelectUserModal, ContractDetailModal, PickerQMapPositionModal }, data () { return { // 资源上传对话框默认参数 resourceDialogProps: { businessCode: 'resources', // 业务代码,默认为resources resourceType: 'file', // 资源类型,file||image||document||video||audio accept: '', // 接受上传的文件类型 multipleFlag: 'Y' // 是否允许多个 }, resourcePickerDlg: { show: false, dialogTitle: '选择资源', resourceDialogProps: {} }, userSelectDlg: { show: false, dialogTitle: '选择人员', dialogProps: { multipleFlag: 'N', // 是否支持多选 organizationId: '', // 机构ID ignoreUsernames: '', // 不能选择的人员用户名 initRightUsers: '' // 复选上的用户,多个使用逗号分隔 } }, // 合同详情弹出层 contractDetailDlg: { show: false, dialogTitle: '合同信息', dialogProps: { contractId: '' // 合同ID } }, // 合同详情弹出层 qMaplDlg: { show: false, dialogTitle: '地图描点', dialogProps: { } } } }, created () { window.hktCommon = hktCommon this.init() }, methods: { init () { // 初始化公共上传组件 this.initUpload() // 初始化用户选择组件 this.initSelectUser() // 初始化查看合同详情组件 this.initContractDetailModal() // 初始化查看合同详情组件 this.initQMapModal() // TODO:后续可继续扩展其他公共组件 }, initUpload () { // 上传图片 hktCommon.uploadImage = (title, props, callback) => { if (arguments.length === 1 && typeof arguments[0] === 'function') { title = '上传图片' props = { resourceType: 'image' } callback = arguments[0] } if (arguments.length === 2 && typeof arguments[0] === 'string' && typeof arguments[1] === 'function') { props = { resourceType: 'image' } callback = arguments[1] } if (arguments.length === 2 && typeof arguments[0] === 'object' && typeof arguments[1] === 'function') { title = '上传图片' props = arguments[0] callback = arguments[1] } if (!props) { props = { resourceType: 'image' } } if (!props.accept) { props.accept = '.jpg,.png,.gif' } upload('image', title, props, callback) } // 上传视频 hktCommon.uploadVideo = (title, props, callback) => { if (arguments.length === 2 && typeof arguments[0] === 'object' && typeof arguments[1] === 'function') { title = '视频上传' props = arguments[0] callback = arguments[1] } upload('video', title, props, callback) } // 上传音频 hktCommon.uploadAudio = (title, props, callback) => { if (arguments.length === 2 && typeof arguments[0] === 'object' && typeof arguments[1] === 'function') { title = '音频上传' props = arguments[0] callback = arguments[1] } upload('audio', title, props, callback) } // 上传资源 hktCommon.uploadResource = (title, props, callback) => { if (arguments.length === 2 && typeof arguments[0] === 'object' && typeof arguments[1] === 'function') { title = '文件上传' props = arguments[0] callback = arguments[1] } let resourceType = 'file' if (props.resourceType) { resourceType = props.resourceType } upload(resourceType, title, props, callback) } // 定义公共上传方法 const upload = (type, title, props, callback) => { this.resourcePickerDlg.dialogTitle = title const resourceDialogProps = Object.assign({}, this.resourceDialogProps, props) resourceDialogProps.resourceType = type this.resourcePickerDlg.resourceDialogProps = resourceDialogProps this.resourcePickerDlg.show = true if (callback) { uploadCallbackFn = callback } else { uploadCallbackFn = null } } // 完全自主控制上传方式 hktCommon.upload = upload }, initSelectUser() { const self = this /** * 公共选择用户方法 * @param title 对话框标题 * @param props 人员选择配置信息,配置属性见:userSelectDlg.dialogProps * @param callback 选择人员后的回调 */ hktCommon.selectUser = function(title, props, callback) { if (arguments.length === 2 && typeof arguments[0] === 'object' && typeof arguments[1] === 'function') { title = '选择用户' props = arguments[0] callback = arguments[1] } else if (arguments.length === 2 && typeof arguments[0] === 'string' && typeof arguments[1] === 'function') { callback = arguments[1] props = { multipleFlag: 'N', // 是否支持多选 organizationId: '', // 机构ID ignoreUsernames: '' // 不能选择的人员用户名 } } self.userSelectDlg.dialogTitle = title self.userSelectDlg.dialogProps = props self.userSelectDlg.show = true if (callback) { userSelectCallback = callback } else { userSelectCallback = null } } }, // 初始化合同详情界面 initContractDetailModal () { const self = this hktCommon.showContract = function (contractId) { self.contractDetailDlg.dialogProps.contractId = contractId self.contractDetailDlg.show = true } }, // 初始化腾讯地图选择弹出层 initQMapModal () { const self = this hktCommon.pickQMap = function (callback) { self.qMaplDlg.show = true if (callback) { qMapCallbackFn = callback } else { qMapCallbackFn = null } } }, // 选择资源后的回调 resourceCallback (resourceData) { // 可以通过事件机制获取选择后的资源 if (window.eventHub && typeof window.eventHub.$emit === 'function') { window.eventHub.$emit('resourcePicked', resourceData) } if (typeof uploadCallbackFn === 'function') { uploadCallbackFn(resourceData) uploadCallbackFn = null } }, // 选择人员回调 userSelectedCallback(userData) { // 可以通过事件机制获取选择后的资源 if (window.eventHub && typeof window.eventHub.$emit === 'function') { window.eventHub.$emit('userSelected', userData) } if (typeof userSelectCallback === 'function') { userSelectCallback(userData) userSelectCallback = null } }, // 地图描点后的回调 qMapCallback (latLng) { // 可以通过事件机制获取选择后的资源 if (window.eventHub && typeof window.eventHub.$emit === 'function') { window.eventHub.$emit('qMapCallPicked', latLng) } if (typeof qMapCallbackFn === 'function') { qMapCallbackFn(latLng) qMapCallbackFn = null } }, } } </script> main.js主方法中引入公共组件:import HktCommon from './views/hktcommon/HktCommon.vue' // 增加公共组件对象 const commonEl = document.createElement('div') document.body.appendChild(commonEl) new Vue({ name: 'AppCommonRoot', el: commonEl, render: h => h(HktCommon) })在其他组件中调用全局方法:// 上传资源 hktCommon.uploadResource(this.dialogTitle, dialogProps, (resources) => { this.uploadCallback(resources) }) // 选择用户: window.hktCommon.selectUser(`配置 ${row.nodeName} 节点的审批人`, dialogProps, (res) => { row.approveArray = res.map(item => item.id) row.approveNames = res.map(item => item.name) this.nodes.splice(stepIndex, 1, this.$hktUtils.deepClone(row)) }) // 显示合同信息 hktCommon.showContract = function (contractId) { self.contractDetailDlg.dialogProps.contractId = contractId self.contractDetailDlg.show = true } // 地图描点 window.hktCommon.pickQMap((res) => { self.projectForm.areaCode = `${res.latitude},${res.longitude}` })
2021年06月30日
88 阅读
0 评论
0 点赞
2021-06-30
记一次根据经纬度计算距离的项目实践
近期公司接的公租房项目,在小程序模块中有用到离我最近的房源列表的功能,经过项目实践相关实现过程如下:功能演示PC后台配置小区位置:单击地图描点后打开如下图所示的描点对话框,描点选择后单击确定按钮返回经纬度信息:小程序中获取位置后调用接口显示距离:{lamp/}代码实现:管理后台描点Vue组件核心代码<template> <el-dialog width="95%" class="hkt-dlg-darkblue resource-modal" top="5vh" :title="title" :visible.sync="isShow" destroy-on-close :close-on-click-modal="false"> <div class="map-container" ref="mapContainer"></div> <div slot="footer" class="dialog-footer dialog-footer-wrap text-center"> <el-button @click="isShow = false">取 消</el-button> <el-button type="primary" :disabled="!latitude" @click="pickedHandler">{{'确 定'}}</el-button> </div> </el-dialog> </template> <script> export default { name: 'PickerQMapPositionModal', props: { show: { type: Boolean, default: false }, title: { type: String, default: '地图描点' }, dialogProps: { type: Object, reuired: true } }, data () { return { latitude: '', longitude: '' } }, computed: { isShow: { get () { return this.show }, set (val) { this.$emit('update:show', val) } } }, mounted () { this.init() }, methods: { init () { this.initMap() }, async initMap () { // if (!window.TMap) { // } await this.$hktUtils.loadJS('https://map.qq.com/api/gljs?v=1.exp&key=VQ6BZ-ZFSW6-H36SN-E7TZL-QZ6U6-O3F24') var center = new TMap.LatLng(28.239377,112.866161) //定义map变量,调用 TMap.Map() 构造函数创建地图 var map = new TMap.Map(this.$refs.mapContainer, { center: center,//设置地图中心点坐标 zoom: 15, //设置地图缩放级别 // pitch: 43.5, //设置俯仰角 // rotation: 45 //设置地图旋转角度 }); //初始化marker图层 var markerLayer = new TMap.MultiMarker({ id: 'marker-layer', map: map }); map.on("click", (evt) => { if(markerLayer.geometries && markerLayer.geometries.length > 0) { markerLayer.remove(markerLayer.geometries[0].id) } markerLayer.add({ position: evt.latLng }); console.log('evt.latLng', evt.latLng, evt) this.latitude = evt.latLng.lat.toFixed(7) this.longitude = evt.latLng.lng.toFixed(7) }) }, pickedHandler () { this.$emit('callback', { latitude: this.latitude, longitude: this.longitude }) this.isShow = false } } } </script> <style lang="scss" scoped> .map-container { width:100%; height:70vh; } </style> Java端计算距离核心代码package com.hkt.jianfa.rental.housing.manager.wx.util; /** * 根据经纬度计算距离的工具类 * * @author 朱治龙(i@zhuzhilong.cn) * @since 2021-06-29 16:22:18 */ public class DistanceUtil { //地球平均半径 private static final double EARTH_RADIUS = 6378137; //把经纬度转为度(°) private static double rad(double d) { return d * Math.PI / 180.0; } /** * 根据两点间经纬度坐标(double值),计算两点间距离,单位为米 * * @param lng1 第1个坐标的经度值 * @param lat1 第1个坐标的纬度值 * @param lng2 第2个坐标的经度值 * @param lat2 第2个坐标的纬度值 * @return */ public static double getDistance(double lng1, double lat1, double lng2, double lat2) { double radLat1 = rad(lat1); double radLat2 = rad(lat2); double a = radLat1 - radLat2; double b = rad(lng1) - rad(lng2); double s = 2 * Math.asin( Math.sqrt( Math.pow(Math.sin(a / 2), 2) + Math.cos(radLat1) * Math.cos(radLat2) * Math.pow(Math.sin(b / 2), 2) ) ); s = s * EARTH_RADIUS; s = Math.round(s * 10000) / 10000; return s; } /** * 传入逗号分隔的两个经纬度的值计算巨鹿 * * @param position1 第一个经纬度 * @param position2 第二个经纬度 * @return */ public static double getDistance(String position1, String position2) { if (position1.contains(",") && position2.contains(",")) { String[] point1 = position1.split(","); String[] point2 = position2.split(","); return getDistance(Double.parseDouble(point1[1]), Double.parseDouble(point1[0]), Double.parseDouble(point2[1]), Double.parseDouble(point2[0])); } return 0; } /** * 将距离数值转换为前端显示所需的字符串信息 * * @param distance * @return */ public static String decodeDistance(double distance) { if (distance < 1000) { return "<1km"; } else if (distance / 1000 > 100) { return ">100km"; } else { return new Double(distance / 1000).intValue() + "km"; } } } 业务逻辑类中调用DistanceUtil方法计算距离:小程序中获取位置后,将经纬度信息传给后端计算距离及排序核心代码如下:uni.getLocation({ type: 'gcj02', success: (res) => { this.searchForm.sort = 'distance' this.searchForm.location = res.latitude + ',' + res.longitude this.getProjects() }, fail: (err) => { this.getProjects() } })
2021年06月30日
150 阅读
0 评论
3 点赞
2021-06-02
33个JavaScript常用函数封装方法汇总
这是我在实际开发中常用的一些js函数方法,今天总结了一下,以便以后查询,有需要的小伙伴可以参考下。有些人或许会觉得忘了百度就完事儿,No no no!这事儿我真的亲自实践过好多次,百度一次记住了还好,记不住下次碰着了还得找度娘简直是拉低工作效率。1、加载js || css || style const loadRes = function(name, type, fn) { // 加载js || css || style let ref if (type === 'js') { // 外部js ref = document.createElement('script') ref.setAttribute('type', 'text/JavaScript') ref.setAttribute('src', name) } else if (type === 'css') { // 外部css ref = document.createElement('link') ref.setAttribute('rel', 'stylesheet') ref.setAttribute('type', 'text/css') ref.setAttribute('href', name) } else if (type === 'style') { // style ref = document.createElement('style') ref.innerhtml = name } if (typeof ref !== 'undefined') { document.getElementsByTagName('head')[0].appendChild(ref) ref.onload = function() { // 加载完成执行 typeof fn === 'function' && fn() } } }2、获取url参数const getUrlParam = function(name) { // 获取url参数 let reg = new RegExp('(^|&?)' + name + '=([^&]*)(&|$)', 'i') let r = window.location.href.substr(1).match(reg) if (r != null) { return decodeURI(r[2]) } return undefined }3、本地存储const store = { // 本地存储 set: function(name, value, day) { // 设置 let d = new Date() let time = 0 day = (typeof(day) === 'undefined' || !day) ? 1 : day // 时间,默认存储1天 time = d.setHours(d.getHours() + (24 * day)) // 毫秒 localStorage.setItem(name, JSON.stringify({ data: value, time: time })) }, get: function(name) { // 获取 let data = localStorage.getItem(name) if (!data) { return null } let obj = JSON.parse(data) if (new Date().getTime() > obj.time) { // 过期 localStorage.removeItem(name) return null } else { return obj.data } }, clear: function(name) { // 清空 if (name) { // 删除键为name的缓存 localStorage.removeItem(name) } else { // 清空全部 localStorage.clear() } } }4、cookie操作【set,get,del】const cookie = { // cookie操作【set,get,del】 set: function(name, value, day) { let oDate = new Date() oDate.setDate(oDate.getDate() + (day || 30)) document.cookie = name + '=' + value + ';expires=' + oDate + "; path=/;" }, get: function(name) { let str = document.cookie let arr = str.split('; ') for (let i = 0; i < arr.length; i++) { let newArr = arr[i].split('=') if (newArr[0] === name) { return newArr[1] } } }, del: function(name) { this.set(name, '', -1) } }5、Js获取元素样式【支持内联】const getRealStyle = function(obj, styleName) { // Js获取元素样式【支持内联】 var realStyle = null if (obj.currentStyle) { realStyle = obj.currentStyle[styleName] } else if (window.getComputedStyle) { realStyle = window.getComputedStyle(obj, null)[styleName] } return realStyle }6、时间格式化const formatDate = function(fmt, date) { // 时间格式化 【'yyyy-MM-dd hh:mm:ss',时间】 if (typeof date !== 'object') { date = !date ? new Date() : new Date(date) } var o = { 'M+': date.getMonth() + 1, // 月份 'd+': date.getDate(), // 日 'h+': date.getHours(), // 小时 'm+': date.getMinutes(), // 分 's+': date.getSeconds(), // 秒 'q+': Math.floor((date.getMonth() + 3) / 3), // 季度 'S': date.getMilliseconds() // 毫秒 } if (/(y+)/.test(fmt)) { fmt = fmt.replace(RegExp.$1, (date.getFullYear() + '').substr(4 - RegExp.$1.length)) } for (var k in o) { if (new RegExp('(' + k + ')').test(fmt)) { fmt = fmt.replace(RegExp.$1, (RegExp.$1.length === 1) ? (o[k]) : (('00' + o[k]).substr(('' + o[k]).length))) } } return fmt }7、原生ajax操作const ajax = function(conf) { // ajax操作 let url = conf.url, data = conf.data, senData = [], // 封装后的数据 async = conf.async !== undefined ? conf.async : true, // ture为异步请求 type = conf.type || 'get', // 默认请求方式get dataType = conf.dataType || 'json', contenType = conf.contenType || 'application/x-www-form-urlencoded', success = conf.success, error = conf.error, isForm = conf.isForm || false, // 是否formdata header = conf.header || {}, // 头部信息 xhr = '' // 创建ajax引擎对象 if (data == null) { senData = '' } else if (typeof data === 'object' && !isForm) { // 如果data是对象,转换为字符串 for (var k in data) { senData.push(encodeURIComponent(k) + '=' + encodeURIComponent(data[k])) } senData = senData.join('&') } else { senData = data } try { xhr = new ActiveXObject('microsoft.xmlhttp') // IE内核系列浏览器 } catch (e1) { try { xhr = new XMLHttpRequest() // 非IE内核浏览器 } catch (e2) { if (error != null) { error('不支持ajax请求') } } }; xhr.open(type, type !== 'get' ? url : url + '?' + senData, async) if (type !== 'get' && !isForm) { xhr.setRequestHeader('content-type', contenType) } for (var h in header) { xhr.setRequestHeader(h, header[h]) } xhr.send(type !== 'get' ? senData : null) xhr.onreadystatechange = function() { if (xhr.readyState === 4) { if (xhr.status >= 200 && xhr.status < 300) { if (dataType === 'json' && success != null) { let res = '' try { res = eval('(' + xhr.responseText + ')') } catch (e) { console.log(e) } success(res) // 将json字符串转换为js对象 }; } else { if (error != null) { error('通讯失败!' + xhr.status) } } } } }8、fetch请求的封装const fetch = function(url, setting) { // fetch请求的封装 let opts = { // 设置参数的初始值 method: (setting.method || 'GET').toUpperCase(), // 请求方式 headers: setting.headers || {}, // 请求头设置 credentials: setting.credentials || true, // 设置cookie是否一起发送 body: setting.body || {}, mode: setting.mode || 'no-cors', // 可以设置 cors, no-cors, same-origin redirect: setting.redirect || 'follow', // follow, error, manual cache: setting.cache || 'default' // 设置 cache 模式 (default, reload, no-cache) } let dataType = setting.dataType || 'json' // 解析方式 let data = setting.data || '' // 参数 let paramsFormat = function(obj) { // 参数格式 var str = '' for (var i in obj) { str += `${i}=${obj[i]}&` } return str.split('').slice(0, -1).join('') } if (opts.method === 'GET') { url = url + (data ? `?${paramsFormat(data)}` : '') } else { setting.body = data || {} } return new Promise((resolve, reject) => { fetch(url, opts).then(async res => { let data = dataType === 'text' ? await res.text() : dataType === 'blob' ? await res.blob() : await res.json() resolve(data) }).catch(e => { reject(e) }) }) }9、设备判断:android、ios、webconst isDevice = function() { // 判断是android还是ios还是web var ua = navigator.userAgent.toLowerCase() if (ua.match(/iPhone\sOS/i) === 'iphone os' || ua.match(/iPad/i) === 'ipad') { // ios return 'iOS' } if (ua.match(/Android/i) === 'android') { return 'Android' } return 'Web' }10、判断是否为微信const isWx = function() { // 判断是否为微信 var ua = window.navigator.userAgent.toLowerCase() if (ua.match(/MicroMessenger/i) === 'micromessenger') { return true } return false }11、文本复制功能const copyTxt = function(text, fn) { // 复制功能 if (typeof document.execCommand !== 'function') { console.log('复制失败,请长按复制') return } var dom = document.createElement('textarea') dom.value = text dom.setAttribute('style', 'display: block;width: 1px;height: 1px;') document.body.appendChild(dom) dom.select() var result = document.execCommand('copy') document.body.removeChild(dom) if (result) { console.log('复制成功') typeof fn === 'function' && fn() return } if (typeof document.createRange !== 'function') { console.log('复制失败,请长按复制') return } var range = document.createRange() var div = document.createElement('div') div.innerhtml = text div.setAttribute('style', 'height: 1px;fontSize: 1px;overflow: hidden;') document.body.appendChild(div) range.selectNode(div) var selection = window.getSelection() console.log(selection) if (selection.rangeCount > 0) { selection.removeAllRanges() } selection.addRange(range) document.execCommand('copy') typeof fn === 'function' && fn() console.log('复制成功') }12、判断是否是一个数组const isArray = function(arr) { // 判断是否是一个数组 return Object.prototype.toString.call(arr) === '[object Array]' }13、判断两个数组是否相等const arrayEqual = function(arr1, arr2) { //判断两个数组是否相等 if (arr1 === arr2) return true; if (arr1.length != arr2.length) return false; for (let i = 0; i < arr1.length; ++i) { if (arr1[i] !== arr2[i]) return false; } return true; }14、时间与时间戳转换const stamp = { // 时间,时间戳转换 getTime: function(time) { // 时间转10位时间戳 let date = time ? new Date(time) : new Date() return Math.round(date.getTime() / 1000) }, timeToStr: function(time, fmt) { // 10位时间戳转时间 return new Date(time * 1000).pattern(fmt || 'yyyy-MM-dd') } }15、常用正则验证const checkStr = function(str, type) { // 常用正则验证,注意type大小写 switch (type) { case 'phone': // 手机号码 return /^1[3|4|5|6|7|8|9][0-9]{9}$/.test(str) case 'tel': // 座机 return /^(0\d{2,3}-\d{7,8})(-\d{1,4})?$/.test(str) case 'card': // 身份证 return /(^\d{15}$)|(^\d{18}$)|(^\d{17}(\d|X|x)$)/.test(str) case 'pwd': // 密码以字母开头,长度在6~18之间,只能包含字母、数字和下划线 return /^[a-zA-Z]\w{5,17}$/.test(str) case 'postal': // 邮政编码 return /[1-9]\d{5}(?!\d)/.test(str) case 'QQ': // QQ号 return /^[1-9][0-9]{4,9}$/.test(str) case 'email': // 邮箱 return /^[\w-]+(\.[\w-]+)*@[\w-]+(\.[\w-]+)+$/.test(str) case 'money': // 金额(小数点2位) return /^\d*(?:\.\d{0,2})?$/.test(str) case 'URL': // 网址 return /(http|ftp|https):\/\/[\w\-_]+(\.[\w\-_]+)+([\w\-\.,@?^=%&:/~\+#]*[\w\-\@?^=%&/~\+#])?/.test(str) case 'IP': // IP return /((?:(?:25[0-5]|2[0-4]\\d|[01]?\\d?\\d)\\.){3}(?:25[0-5]|2[0-4]\\d|[01]?\\d?\\d))/.test(str) case 'date': // 日期时间 return /^(\d{4})\-(\d{2})\-(\d{2}) (\d{2})(?:\:\d{2}|:(\d{2}):(\d{2}))$/.test(str) || /^(\d{4})\-(\d{2})\-(\d{2})$/.test(str) case 'number': // 数字 return /^[0-9]$/.test(str) case 'english': // 英文 return /^[a-zA-Z]+$/.test(str) case 'chinese': // 中文 return /^[\u4E00-\u9FA5]+$/.test(str) case 'lower': // 小写 return /^[a-z]+$/.test(str) case 'upper': // 大写 return /^[A-Z]+$/.test(str) case 'HTML': // HTML标记 return /<("[^"]*"|'[^']*'|[^'">])*>/.test(str) default: return true } }16、是否为PC端const isPC = function() { // 是否为PC端 let userAgentInfo = navigator.userAgent let Agents = ['Android', 'iPhone', 'SymbianOS', 'Windows Phone', 'iPad', 'iPod'] let flag = true for (let v = 0; v < Agents.length; v++) { if (userAgentInfo.indexOf(Agents[v]) > 0) { flag = false break } } return flag }17、去除字符串空格const trim = function(str, type) { // 去除空格, type: 1-所有空格 2-前后空格 3-前空格 4-后空格 type = type || 1 switch (type) { case 1: return str.replace(/\s+/g, '') case 2: return str.replace(/(^\s*)|(\s*$)/g, '') case 3: return str.replace(/(^\s*)/g, '') case 4: return str.replace(/(\s*$)/g, '') default: return str } }18、字符串大小写转换const changeCase = function(str, type) { // 字符串大小写转换 type: 1:首字母大写 2:首页母小写 3:大小写转换 4:全部大写 5:全部小写 type = type || 4 switch (type) { case 1: return str.replace(/\b\w+\b/g, function(word) { return word.substring(0, 1).toUpperCase() + word.substring(1).toLowerCase() }) case 2: return str.replace(/\b\w+\b/g, function(word) { return word.substring(0, 1).toLowerCase() + word.substring(1).toUpperCase() }) case 3: return str.split('').map(function(word) { if (/[a-z]/.test(word)) { return word.toUpperCase() } else { return word.toLowerCase() } }).join('') case 4: return str.toUpperCase() case 5: return str.toLowerCase() default: return str } }19、过滤html代码const filterTag = function(str) { // 过滤html代码(把<>转换) str = str.replace(/&/ig, '&') str = str.replace(/</ig, '<') str = str.replace(/>/ig, '>') str = str.replace(' ', ' ') return str }20、生成随机数范围const random = function(min, max) { // 生成随机数范围 if (arguments.length === 2) { return Math.floor(min + Math.random() * ((max + 1) - min)) } else { return null } }21、阿拉伯数字转中文大写数字const numberToChinese = function(num) { // 将阿拉伯数字翻译成中文的大写数字 let AA = new Array('零', '一', '二', '三', '四', '五', '六', '七', '八', '九', '十') let BB = new Array('', '十', '百', '仟', '萬', '億', '点', '') let a = ('' + num).replace(/(^0*)/g, '').split('.') let k = 0 let re = '' for (let i = a[0].length - 1; i >= 0; i--) { switch (k) { case 0: re = BB[7] + re break case 4: if (!new RegExp('0{4}//d{' + (a[0].length - i - 1) + '}$').test(a[0])) { re = BB[4] + re } break case 8: re = BB[5] + re BB[7] = BB[5] k = 0 break } if (k % 4 === 2 && a[0].charAt(i + 2) !== 0 && a[0].charAt(i + 1) === 0) { re = AA[0] + re } if (a[0].charAt(i) !== 0) { re = AA[a[0].charAt(i)] + BB[k % 4] + re } k++ } if (a.length > 1) { // 加上小数部分(如果有小数部分) re += BB[6] for (let i = 0; i < a[1].length; i++) { re += AA[a[1].charAt(i)] } } if (re === '一十') { re = '十' } if (re.match(/^一/) && re.length === 3) { re = re.replace('一', '') } return re }22、原生dom操作const dom = { $: function(selector) { let type = selector.substring(0, 1) if (type === '#') { if (document.querySelecotor) return document.querySelector(selector) return document.getElementById(selector.substring(1)) } else if (type === '.') { if (document.querySelecotorAll) return document.querySelectorAll(selector) return document.getElementsByClassName(selector.substring(1)) } else { return document['querySelectorAll' ? 'querySelectorAll' : 'getElementsByTagName'](selector) } }, hasClass: function(ele, name) { /* 检测类名 */ return ele.className.match(new RegExp('(\\s|^)' + name + '(\\s|$)')) }, addClass: function(ele, name) { /* 添加类名 */ if (!this.hasClass(ele, name)) ele.className += ' ' + name }, removeClass: function(ele, name) { /* 删除类名 */ if (this.hasClass(ele, name)) { let reg = new RegExp('(\\s|^)' + name + '(\\s|$)') ele.className = ele.className.replace(reg, '') } }, replaceClass: function(ele, newName, oldName) { /* 替换类名 */ this.removeClass(ele, oldName) this.addClass(ele, newName) }, siblings: function(ele) { /* 获取兄弟节点 */ console.log(ele.parentNode) let chid = ele.parentNode.children, eleMatch = [] for (let i = 0, len = chid.length; i < len; i++) { if (chid[i] !== ele) { eleMatch.push(chid[i]) } } return eleMatch }, getByStyle: function(obj, name) { /* 获取行间样式属性 */ if (obj.currentStyle) { return obj.currentStyle[name] } else { return getComputedStyle(obj, false)[name] } }, domToStirng: function(htmlDOM) { /* DOM转字符串 */ var div = document.createElement('div') div.appendChild(htmlDOM) return div.innerHTML }, stringToDom: function(htmlString) { /* 字符串转DOM */ var div = document.createElement('div') div.innerHTML = htmlString return div.children[0] } }23、判断图片加载完成const imgLoadAll = function(arr, callback) { // 图片加载 let arrImg = [] for (let i = 0; i < arr.length; i++) { let img = new Image() img.src = arr[i] img.onload = function() { arrImg.push(this) if (arrImg.length == arr.length) { callback && callback() } } } }24、音频加载完成操作const loadAudio = function(src, callback) { // 音频加载 var audio = new Audio(src) audio.onloadedmetadata = callback audio.src = src }25、光标所在位置插入字符const insertAtCursor = function(dom, val) { // 光标所在位置插入字符 if (document.selection) { dom.focus() let sel = document.selection.createRange() sel.text = val sel.select() } else if (dom.selectionStart || dom.selectionStart == '0') { let startPos = dom.selectionStart let endPos = dom.selectionEnd let restoreTop = dom.scrollTop dom.value = dom.value.substring(0, startPos) + val + dom.value.substring(endPos, dom.value.length) if (restoreTop > 0) { dom.scrollTop = restoreTop } dom.focus() dom.selectionStart = startPos + val.length dom.selectionEnd = startPos + val.length } else { dom.value += val dom.focus() } }26、图片地址转base64const getBase64 = function(img) { //传入图片路径,返回base64,使用getBase64(url).then(function(base64){},function(err){}); let getBase64Image = function(img, width, height) { //width、height调用时传入具体像素值,控制大小,不传则默认图像大小 let canvas = document.createElement("canvas"); canvas.width = width ? width : img.width; canvas.height = height ? height : img.height; let ctx = canvas.getContext("2d"); ctx.drawImage(img, 0, 0, canvas.width, canvas.height); let dataURL = canvas.toDataURL(); return dataURL; } let image = new Image(); image.crossOrigin = ''; image.src = img; let deferred = $.Deferred(); if (img) { image.onload = function() { deferred.resolve(getBase64Image(image)); } return deferred.promise(); } }27、base64图片下载功能const downloadFile = function(base64, fileName) { //base64图片下载功能 let base64ToBlob = function(code) { let parts = code.split(';base64,'); let contentType = parts[0].split(':')[1]; let raw = window.atob(parts[1]); let rawLength = raw.length; let uInt8Array = new Uint8Array(rawLength); for (let i = 0; i < rawLength; ++i) { uInt8Array[i] = raw.charCodeAt(i); } return new Blob([uInt8Array], { type: contentType }); }; let aLink = document.createElement('a'); let blob = base64ToBlob(base64); //new Blob([content]); let evt = document.createEvent("HTMLEvents"); evt.initEvent("click", true, true); //initEvent不加后两个参数在FF下会报错 事件类型,是否冒泡,是否阻止浏览器的默认行为 aLink.download = fileName; aLink.href = URL.createObjectURL(blob); aLink.click(); }28、浏览器是否支持webP格式图片const isSupportWebP = function() { //判断浏览器是否支持webP格式图片 return !![].map && document.createElement('canvas').toDataURL('image/webp').indexOf('data:image/webp') == 0; }29、url参数转对象const parseQueryString = function(url) { //url参数转对象 url = !url ? window.location.href : url; if (url.indexOf('?') === -1) { return {}; } let search = url[0] === '?' ? url.substr(1) : url.substring(url.lastIndexOf('?') + 1); if (search === '') { return {}; } search = search.split('&'); let query = {}; for (let i = 0; i < search.length; i++) { let pair = search[i].split('='); query[decodeURIComponent(pair[0])] = decodeURIComponent(pair[1] || ''); } return query; }30、对象序列化【对象转url参数】const stringfyQueryString = function(obj) { //对象序列化【对象转url参数】 if (!obj) return ''; let pairs = []; for (let key in obj) { let value = obj[key]; if (value instanceof Array) { for (let i = 0; i < value.length; ++i) { pairs.push(encodeURIComponent(key + '[' + i + ']') + '=' + encodeURIComponent(value[i])); } continue; } pairs.push(encodeURIComponent(key) + '=' + encodeURIComponent(obj[key])); } return pairs.join('&'); }31、H5软键盘缩回、弹起回调const h5Resize = function(downCb, upCb) { //当软件键盘弹起会改变当前 window.innerHeight,监听这个值变化 [downCb 当软键盘弹起后,缩回的回调,upCb 当软键盘弹起的回调] var clientHeight = window.innerHeight; downCb = typeof downCb === 'function' ? downCb : function() {} upCb = typeof upCb === 'function' ? upCb : function() {} window.addEventListener('resize', () => { var height = window.innerHeight; if (height === clientHeight) { downCb(); } if (height < clientHeight) { upCb(); } }); }32、函数防抖const debounce = function(func, wait, immediate) { //函数防抖[func 函数,wait 延迟执行毫秒数,immediate true 表立即执行,false 表非立即执行,立即执行是触发事件后函数会立即执行,然后n秒内不触发事件才能继续执行函数的效果] let timeout; return function() { let context = this; let args = arguments; if (timeout) clearTimeout(timeout); if (immediate) { var callNow = !timeout; timeout = setTimeout(() => { timeout = null; }, wait) if (callNow) func.apply(context, args) } else { timeout = setTimeout(function() { func.apply(context, args) }, wait); } } }33、函数节流const throttle = function(func, wait ,type) { //函数节流 [func 函数 wait 延迟执行毫秒数 type 1 表时间戳版,2 表定时器版] if(type===1){ let previous = 0; }else if(type===2){ let timeout; } return function() { let context = this; let args = arguments; if(type===1){ let now = Date.now(); if (now - previous > wait) { func.apply(context, args); previous = now; } }else if(type===2){ if (!timeout) { timeout = setTimeout(() => { timeout = null; func.apply(context, args) }, wait) } } } }整理至此,请用心记!!!!以上方法可通过该工具自动获取生成哦:http://www.fly63.com/tool/code/来源 | http://www.fly63.com/article/detial/10362
2021年06月02日
205 阅读
0 评论
0 点赞
2020-07-04
JS小技巧
数字转字符串/字符串转数字let num = 15 let s = num + '' // number to string let n = +s // string to number填充数组let filledArray = new Array(10).fill(null).map((item, idx)=> ({'name' : 'value' + idx})) // filledArray = [{"name":"value0"},{"name":"value1"}]对象的动态属性 let dynamic = "value" let user = { id: 1, [dynamic]: "other value" } // user = {id: 1, value: 'other value'}删除重复项let array = [100, 23, 23, 23, 23, 67, 45] let outputArray = Array.from(new Set(array)) //outputArray = [100, 23, 67, 45]数组转对象let arr = ["value1", "value2", "value3"] let arrObject = {...arr} // arrObject = {0: 'value1', 1: 'value2', 2: 'value3'}对象转数组 let n umber = { one: 1, two: 2, } let key = Object.keys(numbers) // key = [ 'one', 'two' ] let value = Object.values(numbers) // value = [ 1, 2 ] let entry = Object.entries(numbers) // entry = [['one' : 1], ['two' : 2]]使用 ^ 检查数字是否不相等let a = 1234 if(a^123) { console.log('not equal') } // 输出:not equal循环对象 const userObj = { shiyu: 32, zhuzl: 36 }; // 方法一 - 获取对象 keys 后循环 Object.keys(userObj).forEach(key => userObj[key]++) console.log(userObj) // { shiyu: 33, zhuzl: 37 } // 方法二:实用 for ..in 循环 for(let key in userObj){ userObj[key]++ } console.log(userObj) // { shiyu: 34, zhuzl: 38 }对象键按插入顺序存储 const obj = { name: "zhuzl", age: 36, address: "湖南省长沙市", profession: "前端开发工程师" } console.log(Object.keys(obj)) // name, age, address, profession
2020年07月04日
20 阅读
0 评论
0 点赞
2020-03-25
浅谈vue中axios的封装
在Vue项目中我们一般都会使用 axios 作为 Ajax 请求库。它是基于promise的http库,他有很多优秀的特性,例如拦截请求和响应、取消请求、转换json、客户端防御cSRF等。所以我们的尤大大也是果断放弃了对其官方库vue-resource的维护,直接推荐我们使用axios库。在实际开发过程我们一般都会将常用的方法做一些封装以便于在项目中调用。工程添加axios依赖yarn add axios -S建立axios封装文件htttp.jsimport axios from 'axios'; import { Message } from 'element-ui'; axios.defaults.timeout = 5000; axios.defaults.baseURL =''; //http request 拦截器 axios.interceptors.request.use( config => { // const token = getCookie('名称');注意使用的时候需要引入cookie方法,推荐js-cookie config.data = JSON.stringify(config.data); config.headers = { 'Content-Type':'application/x-www-form-urlencoded' } // if(token){ // config.params = {'token':token} // } return config; }, error => { return Promise.reject(err); } ); //http response 拦截器 axios.interceptors.response.use( response => { if(response.data.errCode ==2){ router.push({ path:"/login", query:{redirect:router.currentRoute.fullPath}//从哪个页面跳转 }) } return response; }, error => { return Promise.reject(error) } ) /** * 封装get方法 * @param url * @param data * @returns {Promise} */ export function fetch(url,params={}){ return new Promise((resolve,reject) => { axios.get(url,{ params:params }) .then(response => { resolve(response.data); }) .catch(err => { reject(err) }) }) } /** * 封装post请求 * @param url * @param data * @returns {Promise} */ export function post(url,data = {}){ return new Promise((resolve,reject) => { axios.post(url,data) .then(response => { resolve(response.data); },err => { reject(err) }) }) } /** * 封装patch请求 * @param url * @param data * @returns {Promise} */ export function patch(url,data = {}){ return new Promise((resolve,reject) => { axios.patch(url,data) .then(response => { resolve(response.data); },err => { reject(err) }) }) } /** * 封装put请求 * @param url * @param data * @returns {Promise} */ export function put(url,data = {}){ return new Promise((resolve,reject) => { axios.put(url,data) .then(response => { resolve(response.data); },err => { reject(err) }) }) }在入口文件main.js中引入并注册为vue原型方法import axios from 'axios' import {post,fetch,patch,put} from './utils/http.js' //定义全局变量 Vue.prototype.$post=post; Vue.prototype.$fetch=fetch; Vue.prototype.$patch=patch; Vue.prototype.$put=put;在vue组件里直接使用mounted(){ this.$fetch('/api/v2/contents') .then((response) => { console.log(response) }) }, //其余的方法一样
2020年03月25日
192 阅读
0 评论
0 点赞
2020-02-25
总结移动端H5开发常用技巧
HTML 篇常用的meta属性设置meta对于移动端的一些特殊属性,可根据需要自行设置<meta name="screen-orientation" content="portrait"> //Android 禁止屏幕旋转 <meta name="full-screen" content="yes"> //全屏显示 <meta name="browsermode" content="application"> //UC应用模式,使用了application这种应用模式后,页面讲默认全屏,禁止长按菜单,禁止收拾,标准排版,以及强制图片显示。 <meta name="x5-orientation" content="portrait"> //QQ强制竖屏 <meta name="x5-fullscreen" content="true"> //QQ强制全屏 <meta name="x5-page-mode" content="app"> //QQ应用模式电话号码识别在 iOS Safari (其他浏览器和 Android 均不会)上会对那些看起来像是电话号码的数字处理为电话链接,比如:7 位数字,形如:1234567带括号及加号的数字,形如:(+86)123456789双连接线的数字,形如:00-00-0011111 位数字,形如:13800138000关闭识别<meta name="format-detection" content="telephone=no" />开启识别<a href="tel:123456">123456</a>邮箱识别(Android)安卓上会对符合邮箱格式的字符串进行识别,我们可以通过如下的 meta 来管别邮箱的自动识别:<meta content="email=no" name="format-detection" />同样地,我们也可以通过标签属性来开启长按邮箱地址弹出邮件发送的功能:<a mailto:dooyoe@gmail.com">dooyoe@gmail.com</a>CSS 篇0.5px细线移动端 H5 项目越来越多,设计师对于 UI 的要求也越来越高,比如 1px 的边框。在高清屏下,移动端的 1px 会很粗。那么为什么会产生这个问题呢?主要是跟一个东西有关,DPR(devicePixelRatio) 设备像素比,它是默认缩放为 100%的情况下,设备像素和 CSS 像素的比值。目前主流的屏幕 DPR=2(iPhone 8),或者 3(iPhone 8 Plus)。拿 2 倍屏来说,设备的物理像素要实现 1 像素,而 DPR=2,所以 css 像素只能是 0.5。下面介绍最常用的方法/* 底边框 */ .b-border { position: relative; } .b-border:before { content: ''; position: absolute; left: 0; bottom: 0; width: 100%; height: 1px; background: #d9d9d9; -webkit-transform: scaleY(0.5); transform: scaleY(0.5); -webkit-transform-origin: 0 0; transform-origin: 0 0; } /* 上边框 */ .t-border { position: relative; } .t-border:before { content: ''; position: absolute; left: 0; top: 0; width: 100%; height: 1px; background: #d9d9d9; -webkit-transform: scaleY(0.5); transform: scaleY(0.5); -webkit-transform-origin: 0 0; transform-origin: 0 0; } /* 右边框 */ .r-border { position: relative; } .r-border:before { content: ''; position: absolute; right: 0; bottom: 0; width: 1px; height: 100%; background: #d9d9d9; -webkit-transform: scaleX(0.5); transform: scaleX(0.5); -webkit-transform-origin: 0 0; transform-origin: 0 0; } /* 左边框 */ .l-border { position: relative; } .l-border:before { content: ''; position: absolute; left: 0; bottom: 0; width: 1px; height: 100%; background: #d9d9d9; -webkit-transform: scaleX(0.5); transform: scaleX(0.5); -webkit-transform-origin: 0 0; transform-origin: 0 0; } /* 四条边 */ .setBorderAll { position: relative; &:after { content: ' '; position: absolute; top: 0; left: 0; width: 200%; height: 200%; transform: scale(0.5); transform-origin: left top; box-sizing: border-box; border: 1px solid #e5e5e5; border-radius: 4px; } }屏蔽用户选择禁止用户选择页面中的文字或者图片div { -webkit-touch-callout: none; -webkit-user-select: none; -khtml-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; }清除输入框内阴影在 iOS 上,输入框默认有内部阴影,以这样关闭:div { -webkit-appearance: none; }如何禁止保存或拷贝图像代码如下img { -webkit-touch-callout: none; }输入框默认字体颜色设置 input 里面 placeholder 字体的颜色input::-webkit-input-placeholder, textarea::-webkit-input-placeholder { color: #c7c7c7; } input:-moz-placeholder, textarea:-moz-placeholder { color: #c7c7c7; } input:-ms-input-placeholder, textarea:-ms-input-placeholder { color: #c7c7c7; }用户设置字号放大或者缩小导致页面布局错误设置字体禁止缩放body { -webkit-text-size-adjust: 100% !important; text-size-adjust: 100% !important; -moz-text-size-adjust: 100% !important; }android系统中元素被点击时产生边框部分android系统点击一个链接,会出现一个边框或者半透明灰色遮罩, 不同生产商定义出来额效果不一样。去除代码如下a,button,input,textarea{ -webkit-tap-highlight-color: rgba(0,0,0,0) -webkit-user-modify:read-write-plaintext-only; }iOS 滑动不流畅ios 手机上下滑动页面会产生卡顿,手指离开页面,页面立即停止运动。整体表现就是滑动不流畅,没有滑动惯性。 iOS 5.0 以及之后的版本,滑动有定义有两个值 auto 和 touch,默认值为 auto。解决方案在滚动容器上增加滚动 touch 方法.wrapper { -webkit-overflow-scrolling: touch; }设置 overflow 设置外部 overflow 为 hidden,设置内容元素 overflow 为 auto。内部元素超出 body 即产生滚动,超出的部分 body 隐藏。body { overflow-y: hidden; } .wrapper { overflow-y: auto; }JS 篇移动端click屏幕产生200-300 ms的延迟响应移动设备上的web网页是有300ms延迟的,往往会造成按钮点击延迟甚至是点击失效。解决方案:fastclick可以解决在手机上点击事件的300ms延迟zepto的touch模块,tap事件也是为了解决在click的延迟问题触摸事件的响应顺序ontouchstartontouchmoveontouchendonclickaudio 和 video 在 ios 和 andriod 中自动播放这个不是bug,由于自动播放网页中的音频或视频,会给用户带来一些困扰或者不必要的流量消耗,所以苹果系统和安卓系统通常都会禁止自动播放和使用 JS 的触发播放,必须由用户来触发才可以播放。加入自动触发播放的代码$('html').one('touchstart', function() { audio.play() })iOS 上拉边界下拉出现空白手指按住屏幕下拉,屏幕顶部会多出一块白色区域。手指按住屏幕上拉,底部多出一块白色区域。在 iOS 中,手指按住屏幕上下拖动,会触发 touchmove 事件。这个事件触发的对象是整个 webview 容器,容器自然会被拖动,剩下的部分会成空白。解决方案document.body.addEventListener( 'touchmove', function(e) { if (e._isScroller) return // 阻止默认事件 e.preventDefault() }, { passive: false } )ios 日期转换 NAN 的问题将日期字符串的格式符号替换成'/''yyyy-MM-dd'.replace(/-/g, '/')软键盘问题IOS 键盘弹起挡住原来的视图可以通过监听移动端软键盘弹起 Element.scrollIntoViewIfNeeded(Boolean)方法用来将不在浏览器窗口的可见区域内的元素滚动到浏览器窗口的可见区域。 如果该元素已经在浏览器窗口的可见区域内,则不会发生滚动。true,则元素将在其所在滚动区的可视区域中居中对齐。false,则元素将与其所在滚动区的可视区域最近的边缘对齐。 根据可见区域最靠近元素的哪个边缘,元素的顶部将与可见区域的顶部边缘对准,或者元素的底部边缘将与可见区域的底部边缘对准。window.addEventListener('resize', function() { if ( document.activeElement.tagName === 'INPUT' || document.activeElement.tagName === 'TEXTAREA' ) { window.setTimeout(function() { if ('scrollIntoView' in document.activeElement) { document.activeElement.scrollIntoView(false) } else { document.activeElement.scrollIntoViewIfNeeded(false) } }, 0) } })onkeyUp 和 onKeydown 兼容性问题IOS 中 input 键盘事件 keyup、keydown、等支持不是很好, 用 input 监听键盘 keyup 事件,在安卓手机浏览器中没有问题,但是在 ios 手机浏览器中用输入法输入之后,并未立刻相应 keyup 事件IOS12 输入框难以点击获取焦点,弹不出软键盘定位找到问题是 fastclick.js 对 IOS12 的兼容性,可在 fastclick.js 源码或者 main.js 做以下修改FastClick.prototype.focus = function(targetElement) { var length if ( deviceIsIOS && targetElement.setSelectionRange && targetElement.type.indexOf('date') !== 0 && targetElement.type !== 'time' && targetElement.type !== 'month' ) { length = targetElement.value.length targetElement.setSelectionRange(length, length) targetElement.focus() } else { targetElement.focus() } }IOS 键盘收起时页面没用回落,底部会留白通过监听键盘回落时间滚动到原来的位置window.addEventListener('focusout', function() { window.scrollTo(0, 0) }) //input输入框弹起软键盘的解决方案。 var bfscrolltop = document.body.scrollTop $('input') .focus(function() { document.body.scrollTop = document.body.scrollHeight //console.log(document.body.scrollTop); }) .blur(function() { document.body.scrollTop = bfscrolltop //console.log(document.body.scrollTop); })IOS 下 fixed 失效的原因软键盘唤起后,页面的 fixed 元素将失效,变成了 absolute,所以当页面超过一屏且滚动时,失效的 fixed 元素就会跟随滚动了。不仅限于 type=text 的输入框,凡是软键盘(比如时间日期选择、select 选择等等)被唤起,都会遇到同样地问题。解决方法: 不让页面滚动,而是让主体部分自己滚动,主体部分高度设为 100%,overflow:scroll<body> <div class='warper'> <div class='main'></div> <div> <div class="fix-bottom"></div> </body>.warper { position: absolute; width: 100%; left: 0; right: 0; top: 0; bottom: 0; overflow-y: scroll; -webkit-overflow-scrolling: touch; /* 解决ios滑动不流畅问题 */ } .fix-bottom { position: fixed; bottom: 0; width: 100%; }作者:lzg9527链接:https://juejin.im/post/5e4a0162f265da57133b2005来源:掘金
2020年02月25日
98 阅读
0 评论
0 点赞
2018-04-29
简易版DOM封装
在无障碍工具项目的实际开发中,为了方便的获取页面元素,对页面DOM操作进行了轻量级封装,虽没有jQuery强大,但也可满足大部分使用场景了。相关核心代码如下所示: var getDom = function (el) { if (!el) { return el } if (typeof el === 'string') { return document.getElementById(el.replace(/^#/, '')) } if (!el.nodeName && el.length && el[0].nodeName) { // 如果是一个jquery对象 return el[0] } return el } var Dom = {} Dom.hasClass = function (elem, className) { var el = getDom(elem) className = className.replace(/\-/, '\\-') return new RegExp('(?:^|\\s)' + className + '(?:\\s|$)').test(el.className) } Dom.addClass = function (elem, className) { var el = getDom(elem) if (!Dom.hasClass(el, className)) { el.className = el.className ? el.className + ' ' + className : className } return el } Dom.removeClass = function (elem, className) { var el = getDom(elem) className = className.replace(/\-/, '\\-') var classToRemove = new RegExp('(^|\\s)' + className + '(?=\\s|$)', 'i') el.className = el.className .replace(classToRemove, '') .replace(/^\s+|\s+$/g, '') return el } Dom.events = {} Dom.idSeed = 0 Dom.addEvent = function (el, type, className, handler) { var id = el.id || (el.id = 'id' + ++Dom.idSeed) if (typeof className === 'function' && !handler) { handler = className className = undefined } var delegate = function (evt) { return handler.call(el, evt) } if (className) { className = className.replace(/\./, '') delegate = function (evt) { evt = evt || window.event var node = evt.target || evt.srcElement while (node && node != el) { if (Dom.hasClass(node, className)) { return handler.call(node, evt) } node = node.parentNode } } } delegate.origHandler = handler delegate.delegateSelector = className if (!Dom.events[id]) { Dom.events[id] = {} } if (!Dom.events[id][type]) { Dom.events[id][type] = [] var callFuncList = function (evt) { if (Dom.events[id][type]) { for (var i = 0; i < Dom.events[id][type].length; i++) { var fn = Dom.events[id][type][i] fn(evt) } } } if (el.addEventListener) { el.addEventListener(type, callFuncList) } else { el.attachEvent('on' + type, callFuncList) } } Dom.events[id][type].push(delegate) } Dom.removeEvent = function (el, type, className, handler) { var id = el.id || (el.id = 'id' + ++Dom.idSeed) if (typeof className === 'function' && !handler) { handler = className className = undefined } if (!Dom.events[id]) { return false } if (type && !Dom.events[id][type]) { return false } if (!handler && !className) { if (!type) { Dom.events[id] = {} } else { Dom.events[id][type] = [] } return true } for (var i = Dom.events[id][type].length - 1; i >= 0; i--) { var fn = Dom.events[id][type][i] if ( fn.origHandler == handler && (!className || fn.delegateSelector == className) ) { Dom.events[id][type].splice(i, 1) } } } Dom.attr = function (el, name, value) { if (arguments.length === 1) { var map = {} var attributes = el.attributes var aLength = attributes.length for (var a = 0; a < aLength; a++) { map[attributes[a].name.toLowerCase()] = attributes[a].value } return map } if (arguments.length === 2) { return el.getAttribute(name) } el.setAttribute(name, value) }
2018年04月29日
154 阅读
0 评论
0 点赞
2017-12-02
向客户解释为什么PC端网页要提供宽为 960或1200的设计稿
一个网页的尺寸和浏览器及显示器的尺寸大为相关,我们不可能满足所有用户的显示器尺寸,我们能做的是让绝大多数用户在访问网站时没有横向滚动条。需要统计客户的显示器分辨率,以百度近六个月的浏览器统计数据为例:1920x1080 44.5% 1366x768 10.19% 1440x900 8.27% 1536x864 7.22% 1600x900 6.43% 1280x720 4.31% 1024x768 3.27% 其他 15.81%从统计来看-要让绝大多数(约99%)的PC用户浏览网站没有横向滚动条,网页宽在1024以下,可以是960像素,960是2、3、4、5、6的公约数,980是2、4、5、6、7的公约数。-要页面美观大气,兼顾大部分(约80%)PC用户浏览网站没有横向滚动条,网页宽在1366以下为佳,可以是1170/1200/1260像素。1170是2、3、5、6的公约数。1200是2、3、4、5、6的公约数。1260是2、3、4、5、6、7的公约数。建议在不做响应式设计的情况下,提供宽1200分辨率的设计稿,并以像素为单位标注尺寸在做响应式设计的情况下,分别提供宽360(手机)、宽750(平板)、宽960(横向平板或窄屏PC)、宽1200(PC),四种尺寸下的设计稿。并以像素为单位标注尺寸以上宽指的是页面主体内容宽,在PS里设计时画布要更宽一些用于示意两侧留白效果 360/400 750/800 960/1260 1200/1600 1800/2400
2017年12月02日
68 阅读
0 评论
0 点赞
2016-06-07
链接应该在新窗口打开吗
从易用性的观点来说,强制在新窗口打开链接,违反了一个用户界面设计的基础原则:应该让用户对他们正在交互的界面有控制权。 一个友好并且实际有效的界面设计,在用户做操作的时候,总是能让他们按自己的意志做出决定。当用户在使用界面元素的时候,他们必须知道、理解、并且能预料到什么将会发生。这才是以用户为中心的设计。 有经验的用户,非常强烈的希望由他们来操控整个系统,系统对他们的行为产生回应。 我自己的浏览习惯是,使用多标签式浏览器,找到要浏览的信息列表页,例如论坛的一个版块,或搜索引擎返回的结果页,然后连续拖拽好几个自己感兴趣的链接让页面在新标签非激活状态打开,然后切换到最选装载完成的标签来查看内容。 强制新窗口打开链接的主要问题是,没有强制新窗口打开的链接用户可以通过右键菜单或按shift键(在一些浏览器里可以是拖拽)这种比较容易的方法来在新窗口打开,而强制在新窗口打开的链接要让用户在本身窗口打开却不容易(可以拖动链接到地址栏来在当前窗口打开链接),所以强制新窗口打开链接,超越了用户自己的决定,剥夺了用户的控制权。 对于浏览网站比较熟练的国内用户来说有几个特点: 1、就算是浏览目的很明确的浏览者,也未必能立即找到完全满足自己需要的信息,搜寻和比较必不可少; 2、目前的网速和国内的网页体积来说,网页很少能够即点即开般在一两秒钟内打开,所以许多人习惯一次点开多个页面,让等待页面加载的时间集中在一起。 3、非常讨厌意料之外的弹出窗口。 我记得在2000年,我刚接触网络的时候,机器配置都比较差,网速却又很慢,一方面我不能打开太多窗口以免占用大多资源让机器反应变慢,另一方面我又不时地打开新窗口,以便同时加载多个页面,选择最先加载完成的页面浏览。 为了比较准确地控制窗口的数量,我希望每个链接都能够由我来控制是在自身窗口打开或是新窗口打开。 我的意见是,谨慎地使用新开窗口,并且新开窗口要给予适当的暗示。一个链接是不是在新窗口打开,尽量交给用户来决定。如果他们想在新窗口打开他们会自己去做,不要低估他们的智力帮他们去做决定什么的。 当你的鼠标移到一个链接上的时候,浏览器并不会提示你是自身窗口打开或是新窗口打开。 1、要么给新窗口打开的链接一种特殊的颜色或图标;采用ICON通知访问者外部链接是一种很常见的做法。使用css属性选择器或javascript都可以实现对链接的筛选、外观修改。 2、大部分链接让用户自己按shift键(在一些浏览器里是拖拽)来决定在新窗口打开链接。 考虑到有一部分刚接触网络的用户,使用浏览器还不熟练,甚至也不知道使用shift键(或拖拽)来新开窗口,所以在一些必需的情况下还是应该强制在新窗口打开链接。 在以下几种情况下,强制在新窗口打开链接是比较合适的: 1、链接指向一个本域名之外的网站:如友情链接一 般使用新窗口,新的网站新的窗口,基本上是可预料的。 2、提供帮助类的链接:如一个购物页面上对支付方式的帮助说明可以新开窗口,如果内容不多的话使用弹出层比新窗口更好。 3、页面跳转有可能打断一个正在进行的进程:如在注册页面上指向免责条款、版权声明等页面的链接,页面跳转会导致用户正在填写的注册信息丢失。 4、链接指向一个非HTML文件。例如指向一个pdf文件的链接最好使用新窗口,也许这个pdf会在新窗口直接浏览,也许会弹出文件下载对话框。
2016年06月07日
157 阅读
0 评论
0 点赞
2016-06-07
IE6/7下的console.log()
相信很多人和我一样,在firefox下常常用console.log()输出一些对象或变量的值,在ie6/7里常常用alert()弹出一些对象或变量的值,来作调试。有时候忘记了删除console.log()语句(或alert语句),结果在ie6/7(或没有开启firebug控制台的firefox下也会)下弹出了错误信息。 我常常在自己的common.js里加入以下代码来容错:if(!window.console){ window.console={} window.console.log=function(){return;} } 当然,能够在ie下使用console.log的调试功能当然是最好的,哪怕只是最简单的一个变量值的输出也比alert友好一些,于是有了下面这个简易版的console.log<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="X-UA-Compatible" content="IE=EmulateIE7" /> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <title>ie下的console.log</title> <style> body {background: #ffffff; color: #444;} a{ color: #08e; text-decoration: none; border: 0; background-color: transparent;} body,div,q,iframe,form,h5{ margin: 0; padding: 0;} a img,fieldset { border: none 0; } body,td,textarea { word-break: break-all; word-wrap: break-word; line-height:1.5;} body,input,textarea,select,button { margin: 0; font-size: 12px; font-family: Tahoma, SimSun, sans-serif;} div,p,table,th,td { font-size:1em; font-family:inherit; line-height:inherit;} </style> <script type="text/javascript"> var debug=true; var $id = function (id) {//避免与jQuery的$函数冲突 return typeof id == "string" ? document.getElementById(id) : id; }; if(!$){var $=$id;} if(!window.console){ window.console={} window.console.cache=[]; window.console.constr=function(_value){ if(!_value)return; var result = []; if (_value instanceof Function){ result.push(_value); }else if(_value!=undefined&&Boolean(_value.nodeName)&&Boolean(_value.nodeType)){ result.push(_value.nodeName.toLowerCase()); result.push(_value.getAttribute("id")?"id="+_value.getAttribute("id"):""); result.push(_value.getAttribute("className")?"class="+_value.getAttribute("className"):""); return "<"+result.join(" ")+">"; }else if(_value instanceof Array){ for(var i=0; i< _value.length; i++) result.push(_value[i]); return "["+result.join(", ")+"]"; }else if(typeof _value == "object"){ for (var p in _value){ if(_value.hasOwnProperty(p) && p!='prototype'){ result.push("'"+p+"':"+_value[p]); } }; return "{"+result.join(", ")+"}"; }else if(typeof _value == 'string'){ return "\""+_value+"\""; }else if(typeof _value == 'number' && isFinite(_value)){ result.push(_value); }else{ result.push(_value); } return result.join(""); } window.console.log=function(outputValue){ if(!debug)return; if(!outputValue)return null; var bgColor=bgColor||"#fff"; consoleDiv =$id("_console"); if(!consoleDiv){ consoleDiv=document.createElement("div"); consoleDiv.id="_console"; consoleDiv.style.cssText="position:absolute; z-index:9999; left:0%;top:"+Math.max(document.documentElement.scrollTop, document.body.scrollTop)+"px; width:62%; background-color:#fff; border:1px solid #359; opacity:0.9; filter:alpha(opacity=90); padding:4px;" consoleDiv.innerHTML='<div id="_consoleHead" style="background-color:#cde; height:20px; color:#000; font-size:12px; line-height:20px; cursor:move;"><a style="color:#123; float:right; text-decoration:none; margin:1px 2px 0;" href="javascript:$id(\'_console\').style.display=\'none\';void(0);">[关闭]</a><a style="color:#123; float:right; text-decoration:none; margin:1px 2px 0;" href="javascript:$id(\'_consoleBody\').innerHTML=\'\';void(0);">[清空]</a></div>'; consoleDivBody=document.createElement("div"); consoleDivBody.id="_consoleBody"; consoleDivBody.style.cssText="font-size:12px; line-height:1.5;color:#333; width:100%; max-height:150px; overflow:auto;" consoleDivBody.innerHTML=''; consoleDiv.appendChild(consoleDivBody); document.getElementsByTagName("BODY")[0].appendChild(consoleDiv); if(Drag) Drag.init(consoleDiv,consoleDiv);//注册拖拽方法,可以使用自己的拖拽方法来代替,以减少代码量 } consoleDiv.style.display=""; var consoleDivTop=consoleDiv.style.top.replace(/\D/gi,""); if(consoleDivTop<Math.max(document.documentElement.scrollTop, document.body.scrollTop)||consoleDivTop>Math.max(document.documentElement.scrollTop, document.body.scrollTop)+(document.compatMode == "BackCompat"?document.body.clientHeight:document.documentElement.clientHeight)) consoleDiv.style.top=Math.max(document.documentElement.scrollTop, document.body.scrollTop)+"px"; var newItem=document.createElement("div"); newItem.style.cssText="border-top:1px solid #cde; padding:3px;font-family:'Courier New'; font-size:13px; background-color:"+bgColor; var content = []; for(var i=0, len=arguments.length; i<len; i++){ content.push( window.console.constr(arguments[i]) ); } newItem.innerHTML= content.join(" "); $id("_consoleBody").appendChild(newItem); $id("_consoleBody").scrollTop=9999; }; } /***小巧的拖拽类***/ var Drag={ "obj":null, "init":function(handle, dragBody, e){ if (e == null) { handle.onmousedown=Drag.start; } handle.root = dragBody; if(isNaN(parseInt(handle.root.style.left)))handle.root.style.left="0px"; if(isNaN(parseInt(handle.root.style.top)))handle.root.style.top="0px"; handle.root.onDragStart=new Function(); handle.root.onDragEnd=new Function(); handle.root.onDrag=new Function(); if (e !=null) { var handle=Drag.obj=handle; e=Drag.fixe(e); var top=parseInt(handle.root.style.top); var left=parseInt(handle.root.style.left); handle.root.onDragStart(left,top,e.pageX,e.pageY); handle.lastMouseX=e.pageX; handle.lastMouseY=e.pageY; document.onmousemove=Drag.drag; document.onmouseup=Drag.end; } }, "start":function(e){ var handle=Drag.obj=this; e=Drag.fixEvent(e); var top=parseInt(handle.root.style.top); var left=parseInt(handle.root.style.left); //alert(left) handle.root.onDragStart(left,top,e.pageX,e.pageY); handle.lastMouseX=e.pageX; handle.lastMouseY=e.pageY; document.onmousemove=Drag.drag; document.onmouseup=Drag.end; return false; }, "drag":function(e){ e=Drag.fixEvent(e); var handle=Drag.obj; var mouseY=e.pageY; var mouseX=e.pageX; var top=parseInt(handle.root.style.top); var left=parseInt(handle.root.style.left); if(document.all){Drag.obj.setCapture();}else{e.preventDefault();};//作用是将所有鼠标事件捕获到handle对象,对于firefox,以用preventDefault来取消事件的默认动作: var currentLeft,currentTop; currentLeft=left+mouseX-handle.lastMouseX; currentTop=top+(mouseY-handle.lastMouseY); handle.root.style.left=currentLeft +"px"; handle.root.style.top=currentTop+"px"; handle.lastMouseX=mouseX; handle.lastMouseY=mouseY; handle.root.onDrag(currentLeft,currentTop,e.pageX,e.pageY); return false; }, "end":function(){ if(document.all){Drag.obj.releaseCapture();};//取消所有鼠标事件捕获到handle对象 document.onmousemove=null; document.onmouseup=null; Drag.obj.root.onDragEnd(parseInt(Drag.obj.root.style.left),parseInt(Drag.obj.root.style.top)); Drag.obj=null; }, "fixEvent":function(e){//格式化事件参数对象 var sl = Math.max(document.documentElement.scrollLeft, document.body.scrollLeft); var st = Math.max(document.documentElement.scrollTop, document.body.scrollTop); if(typeof e=="undefined")e=window.event; if(typeof e.layerX=="undefined")e.layerX=e.offsetX; if(typeof e.layerY=="undefined")e.layerY=e.offsetY; if(typeof e.pageX == "undefined")e.pageX = e.clientX + sl - document.body.clientLeft; if(typeof e.pageY == "undefined")e.pageY = e.clientY + st - document.body.clientTop; return e; } }; /***测试***/ function sometext(ele,n){ var strArr=["可","以","清","心","也"]; var writeStr="" for(i=0;i<n;i++){ index=parseInt(Math.random()*5); for(j=0;j<5;j++){ str=index+j>4?index+j-5:index+j; writeStr+=strArr[str]; } } $(ele).innerHTML=writeStr; } function test1(){ console.log("这是一个js对象:",{w:100,h:50,area:function(){return this.w*this.h}}) } function test2(){ console.log("这是一个dom对象:",$id("div1")) } function test3(){ var arr=function(w,h){return w*h;} console.log("这是一个js函数:",arr) } function test4(){ var arr=[1,2.2,"a","abc",true,false,new Date(),/<[^>]*>/,function(){alert(0);}] console.log("这是一个数组:",arr) } </script> </head> <body> <div id="div1" class="style1"></div> <p><input type="button" value="返回一个object" onclick="test1()" /> <input type="button" value="返回一个element" onclick="test2()" /> <input type="button" value="返回一个function" onclick="test3()" /> <input type="button" value="返回一个arrary" onclick="test4()" /> </p> <div id="div2" class="style1"></div> <script>sometext("div1",200);sometext("div2",200);</script> </body> </html>如果不要自带的drag方法(大家的js库里一般都有自己的drag方法吧)的话,代码大约60行,方便调试,也不会让你的js库变得臃肿。
2016年06月07日
67 阅读
0 评论
0 点赞
2016-05-29
Web前端常用的JavaScript方法封装
1、输入一个值,返回其数据类型function type(para) { return Object.prototype.toString.call(para) }2、数组去重function unique1(arr) { return [...new Set(arr)] } function unique2(arr) { var obj = {}; return arr.filter(ele => { if (!obj[ele]) { obj[ele] = true; return true; } }) } function unique3(arr) { var result = []; arr.forEach(ele => { if (result.indexOf(ele) == -1) { result.push(ele) } }) return result; }3、字符串去重String.prototype.unique = function () { var obj = {}, str = '', len = this.length; for (var i = 0; i < len; i++) { if (!obj[this[i]]) { str += this[i]; obj[this[i]] = true; } } return str; } //去除连续的字符串 function uniq(str) { return str.replace(/(\w)\1+/g, '$1') }4、深拷贝 浅拷贝//深克隆(深克隆不考虑函数) function deepClone(obj, result) { var result = result || {}; for (var prop in obj) { if (obj.hasOwnProperty(prop)) { if (typeof obj[prop] == 'object' && obj[prop] !== null) { // 引用值(obj/array)且不为null if (Object.prototype.toString.call(obj[prop]) == '[object Object]') { // 对象 result[prop] = {}; } else { // 数组 result[prop] = []; } deepClone(obj[prop], result[prop]) } else { // 原始值或func result[prop] = obj[prop] } } } return result; } // 深浅克隆是针对引用值 function deepClone(target) { if (typeof (target) !== 'object') { return target; } var result; if (Object.prototype.toString.call(target) == '[object Array]') { // 数组 result = [] } else { // 对象 result = {}; } for (var prop in target) { if (target.hasOwnProperty(prop)) { result[prop] = deepClone(target[prop]) } } return result; } // 无法复制函数 var o1 = jsON.parse(jsON.stringify(obj1));5、reverse底层原理和扩展// 改变原数组 Array.prototype.myReverse = function () { var len = this.length; for (var i = 0; i < len; i++) { var temp = this[i]; this[i] = this[len - 1 - i]; this[len - 1 - i] = temp; } return this; }6、圣杯模式的继承function inherit(Target, Origin) { function F() {}; F.prototype = Origin.prototype; Target.prototype = new F(); Target.prototype.constructor = Target; // 最终的原型指向 Target.prop.uber = Origin.prototype; }7、找出字符串中第一次只出现一次的字母String.prototype.firstAppear = function () { var obj = {}, len = this.length; for (var i = 0; i < len; i++) { if (obj[this[i]]) { obj[this[i]]++; } else { obj[this[i]] = 1; } } for (var prop in obj) { if (obj[prop] == 1) { return prop; } } }8、找元素的第n级父元素function parents(ele, n) { while (ele && n) { ele = ele.parentElement ? ele.parentElement : ele.parentNode; n--; } return ele; }9、返回元素的第n个兄弟节点function retSibling(e, n) { while (e && n) { if (n > 0) { if (e.nextElementSibling) { e = e.nextElementSibling; } else { for (e = e.nextSibling; e && e.nodeType !== 1; e = e.nextSibling); } n--; } else { if (e.previousElementSibling) { e = e.previousElementSibling; } else { for (e = e.previousElementSibling; e && e.nodeType !== 1; e = e.previousElementSibling); } n++; } } return e; }10、封装mychildren,解决浏览器的兼容问题function myChildren(e) { var children = e.childNodes, arr = [], len = children.length; for (var i = 0; i < len; i++) { if (children[i].nodeType === 1) { arr.push(children[i]) } } return arr; }11、判断元素有没有子元素function hasChildren(e) { var children = e.childNodes, len = children.length; for (var i = 0; i < len; i++) { if (children[i].nodeType === 1) { return true; } } return false; }12、我一个元素插入到另一个元素的后面Element.prototype.insertAfter = function (target, elen) { var nextElen = elen.nextElenmentSibling; if (nextElen == null) { this.appendChild(target); } else { this.insertBefore(target, nextElen); } }13、返回当前的时间(年月日时分秒)function getDateTime() { var date = new Date(), year = date.getFullYear(), month = date.getMonth() + 1, day = date.getDate(), hour = date.getHours() + 1, minute = date.getMinutes(), second = date.getSeconds(); month = checkTime(month); day = checkTime(day); hour = checkTime(hour); minute = checkTime(minute); second = checkTime(second); function checkTime(i) { if (i < 10) { i = "0" + i; } return i; } return "" + year + "年" + month + "月" + day + "日" + hour + "时" + minute + "分" + second + "秒" }14、获得滚动条的滚动距离function getScrollOffset() { if (window.pageXOffset) { return { x: window.pageXOffset, y: window.pageYOffset } } else { return { x: document.body.scrollLeft + document.documentElement.scrollLeft, y: document.body.scrollTop + document.documentElement.scrollTop } } }15、获得视口的尺寸function getViewportOffset() { if (window.innerWidth) { return { w: window.innerWidth, h: window.innerHeight } } else { // ie8及其以下 if (document.compatMode === "BackCompat") { // 怪异模式 return { w: document.body.clientWidth, h: document.body.clientHeight } } else { // 标准模式 return { w: document.documentElement.clientWidth, h: document.documentElement.clientHeight } } } }16、获取任一元素的任意属性function getStyle(elem, prop) { return window.getComputedStyle ? window.getComputedStyle(elem, null)[prop] : elem.currentStyle[prop] }17、绑定事件的兼容代码function addEvent(elem, type, handle) { if (elem.addEventListener) { //非ie和非ie9 elem.addEventListener(type, handle, false); } else if (elem.attachEvent) { //ie6到ie8 elem.attachEvent('on' + type, function () { handle.call(elem); }) } else { elem['on' + type] = handle; } }18、解绑事件function removeEvent(elem, type, handle) { if (elem.removeEventListener) { //非ie和非ie9 elem.removeEventListener(type, handle, false); } else if (elem.detachEvent) { //ie6到ie8 elem.detachEvent('on' + type, handle); } else { elem['on' + type] = null; } }19、取消冒泡的兼容代码function stopBubble(e) { if (e && e.stopPropagation) { e.stopPropagation(); } else { window.event.cancelBubble = true; } }20、检验字符串是否是回文function isPalina(str) { if (Object.prototype.toString.call(str) !== '[object String]') { return false; } var len = str.length; for (var i = 0; i < len / 2; i++) { if (str[i] != str[len - 1 - i]) { return false; } } return true; }21、检验字符串是否是回文function isPalindrome(str) { str = str.replace(/\W/g, '').toLowerCase(); console.log(str) return (str == str.split('').reverse().join('')) }22、兼容getElementsByClassName方法Element.prototype.getElementsByClassName = Document.prototype.getElementsByClassName = function (_className) { var allDomArray = document.getElementsByTagName('*'); var lastDomArray = []; function trimSpace(strClass) { var reg = /\s+/g; return strClass.replace(reg, ' ').trim() } for (var i = 0; i < allDomArray.length; i++) { var classArray = trimSpace(allDomArray[i].className).split(' '); for (var j = 0; j < classArray.length; j++) { if (classArray[j] == _className) { lastDomArray.push(allDomArray[i]); break; } } } return lastDomArray; }23、运动函数function animate(obj, json, callback) { clearInterval(obj.timer); var speed, current; obj.timer = setInterval(function () { var lock = true; for (var prop in json) { if (prop == 'opacity') { current = parseFloat(window.getComputedStyle(obj, null)[prop]) * 100; } else { current = parseInt(window.getComputedStyle(obj, null)[prop]); } speed = (json[prop] - current) / 7; speed = speed > 0 ? Math.ceil(speed) : Math.floor(speed); if (prop == 'opacity') { obj.style[prop] = (current + speed) / 100; } else { obj.style[prop] = current + speed + 'px'; } if (current != json[prop]) { lock = false; } } if (lock) { clearInterval(obj.timer); typeof callback == 'function' ? callback() : ''; } }, 30) }24、弹性运动function ElasticMovement(obj, target) { clearInterval(obj.timer); var iSpeed = 40, a, u = 0.8; obj.timer = setInterval(function () { a = (target - obj.offsetLeft) / 8; iSpeed = iSpeed + a; iSpeed = iSpeed * u; if (Math.abs(iSpeed) <= 1 && Math.abs(a) <= 1) { console.log('over') clearInterval(obj.timer); obj.style.left = target + 'px'; } else { obj.style.left = obj.offsetLeft + iSpeed + 'px'; } }, 30); }25、封装自己的forEach方法Array.prototype.myForEach = function (func, obj) { var len = this.length; var _this = arguments[1] ? arguments[1] : window; // var _this=arguments[1]||window; for (var i = 0; i < len; i++) { func.call(_this, this[i], i, this) } }26、封装自己的filter方法Array.prototype.myFilter = function (func, obj) { var len = this.length; var arr = []; var _this = arguments[1] || window; for (var i = 0; i < len; i++) { func.call(_this, this[i], i, this) && arr.push(this[i]); } return arr; }27、数组map方法Array.prototype.myMap = function (func) { var arr = []; var len = this.length; var _this = arguments[1] || window; for (var i = 0; i < len; i++) { arr.push(func.call(_this, this[i], i, this)); } return arr; }28、数组every方法Array.prototype.myEvery = function (func) { var flag = true; var len = this.length; var _this = arguments[1] || window; for (var i = 0; i < len; i++) { if (func.apply(_this, [this[i], i, this]) == false) { flag = false; break; } } return flag; }29、数组reduce方法Array.prototype.myReduce = function (func, initialValue) { var len = this.length, nextValue, i; if (!initialValue) { // 没有传第二个参数 nextValue = this[0]; i = 1; } else { // 传了第二个参数 nextValue = initialValue; i = 0; } for (; i < len; i++) { nextValue = func(nextValue, this[i], i, this); } return nextValue; }30、获取url中的参数function getWindonHref() { var sHref = window.location.href; var args = sHref.split('?'); if (args[0] === sHref) { return ''; } var hrefarr = args[1].split('#')[0].split('&'); var obj = {}; for (var i = 0; i < hrefarr.length; i++) { hrefarr[i] = hrefarr[i].split('='); obj[hrefarr[i][0]] = hrefarr[i][1]; } return obj; }31、数组排序// 快排 [left] + min + [right] function quickArr(arr) { if (arr.length <= 1) { return arr; } var left = [], right = []; var pIndex = Math.floor(arr.length / 2); var p = arr.splice(pIndex, 1)[0]; for (var i = 0; i < arr.length; i++) { if (arr[i] <= p) { left.push(arr[i]); } else { right.push(arr[i]); } } // 递归 return quickArr(left).concat([p], quickArr(right)); } // 冒泡 function bubbleSort(arr) { for (var i = 0; i < arr.length - 1; i++) { for (var j = i + 1; j < arr.length; j++) { if (arr[i] > arr[j]) { var temp = arr[i]; arr[i] = arr[j]; arr[j] = temp; } } } return arr; } function bubbleSort(arr) { var len = arr.length; for (var i = 0; i < len - 1; i++) { for (var j = 0; j < len - 1 - i; j++) { if (arr[j] > arr[j + 1]) { var temp = arr[j]; arr[j] = arr[j + 1]; arr[j + 1] = temp; } } } return arr; }32、遍历Dom树// 给定页面上的DOM元素,将访问元素本身及其所有后代(不仅仅是它的直接子元素) // 对于每个访问的元素,函数讲元素传递给提供的回调函数 function traverse(element, callback) { callback(element); var list = element.children; for (var i = 0; i < list.length; i++) { traverse(list[i], callback); } }33、原生js封装ajaxfunction ajax(method, url, callback, data, flag) { var xhr; flag = flag || true; method = method.toUpperCase(); if (window.XMLHttpRequest) { xhr = new XMLHttpRequest(); } else { xhr = new ActiveXObject('Microsoft.XMLHttp'); } xhr.onreadystatechange = function () { if (xhr.readyState == 4 && xhr.status == 200) { console.log(2) callback(xhr.responseText); } } if (method == 'GET') { var date = new Date(), timer = date.getTime(); xhr.open('GET', url + '?' + data + '&timer' + timer, flag); xhr.send() } else if (method == 'POST') { xhr.open('POST', url, flag); xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); xhr.send(data); } }34、异步加载scriptfunction loadScript(url, callback) { var oscript = document.createElement('script'); if (oscript.readyState) { // ie8及以下版本 oscript.onreadystatechange = function () { if (oscript.readyState === 'complete' || oscript.readyState === 'loaded') { callback(); } } } else { oscript.onload = function () { callback() }; } oscript.src = url; document.body.appendChild(oscript); }35、cookie管理var cookie = { set: function (name, value, time) { document.cookie = name + '=' + value + '; max-age=' + time; return this; }, remove: function (name) { return this.setCookie(name, '', -1); }, get: function (name, callback) { var allCookieArr = document.cookie.split('; '); for (var i = 0; i < allCookieArr.length; i++) { var itemCookieArr = allCookieArr[i].split('='); if (itemCookieArr[0] === name) { return itemCookieArr[1] } } return undefined; } }36、实现bind()方法Function.prototype.myBind = function (target) { var target = target || window; var _args1 = [].slice.call(arguments, 1); var self = this; var temp = function () {}; var F = function () { var _args2 = [].slice.call(arguments, 0); var parasArr = _args1.concat(_args2); return self.apply(this instanceof temp ? this : target, parasArr) } temp.prototype = self.prototype; F.prototype = new temp(); return F; }37、实现call()方法Function.prototype.myCall = function () { var ctx = arguments[0] || window; ctx.fn = this; var args = []; for (var i = 1; i < arguments.length; i++) { args.push(arguments[i]) } var result = ctx.fn(...args); delete ctx.fn; return result; }38、实现apply()方法Function.prototype.myApply = function () { var ctx = arguments[0] || window; ctx.fn = this; if (!arguments[1]) { var result = ctx.fn(); delete ctx.fn; return result; } var result = ctx.fn(...arguments[1]); delete ctx.fn; return result; }39、防抖function debounce(handle, delay) { var timer = null; return function () { var _self = this, _args = arguments; clearTimeout(timer); timer = setTimeout(function () { handle.apply(_self, _args) }, delay) } }40、节流function throttle(handler, wait) { var lastTime = 0; return function (e) { var nowTime = new Date().getTime(); if (nowTime - lastTime > wait) { handler.apply(this, arguments); lastTime = nowTime; } } }41、requestAnimFrame兼容性方法window.requestAnimFrame = (function () { return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || function (callback) { window.setTimeout(callback, 1000 / 60); }; })();42、cancelAnimFrame兼容性方法window.cancelAnimFrame = (function () { return window.cancelAnimationFrame || window.webkitCancelAnimationFrame || window.mozCancelAnimationFrame || function (id) { window.clearTimeout(id); }; })();43、jsonp底层方法function jsonp(url, callback) { var oscript = document.createElement('script'); if (oscript.readyState) { // ie8及以下版本 oscript.onreadystatechange = function () { if (oscript.readyState === 'complete' || oscript.readyState === 'loaded') { callback(); } } } else { oscript.onload = function () { callback() }; } oscript.src = url; document.body.appendChild(oscript); }44、获取url上的参数function getUrlParam(sUrl, sKey) { var result = {}; sUrl.replace(/(\w+)=(\w+)(?=[&|#])/g, function (ele, key, val) { if (!result[key]) { result[key] = val; } else { var temp = result[key]; result[key] = [].concat(temp, val); } }) if (!sKey) { return result; } else { return result[sKey] || ''; } }45、格式化时间function formatDate(t, str) { var obj = { yyyy: t.getFullYear(), yy: ("" + t.getFullYear()).slice(-2), M: t.getMonth() + 1, MM: ("0" + (t.getMonth() + 1)).slice(-2), d: t.getDate(), dd: ("0" + t.getDate()).slice(-2), H: t.getHours(), HH: ("0" + t.getHours()).slice(-2), h: t.getHours() % 12, hh: ("0" + t.getHours() % 12).slice(-2), m: t.getMinutes(), mm: ("0" + t.getMinutes()).slice(-2), s: t.getSeconds(), ss: ("0" + t.getSeconds()).slice(-2), w: ['日', '一', '二', '三', '四', '五', '六'][t.getDay()] }; return str.replace(/([a-z]+)/ig, function ($1) { return obj[$1] }); }46、验证邮箱的正则表达式function isAvailableEmail(sEmail) { var reg = /^([\w+\.])+@\w+([.]\w+)+$/ return reg.test(sEmail) }47、函数柯里化是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数且返回结果的新函数的技术function curryIt(fn) { var length = fn.length, args = []; var result = function (arg) { args.push(arg); length--; if (length <= 0) { return fn.apply(this, args); } else { return result; } } return result; }48、大数相加function sumBigNumber(a, b) { var res = '', //结果 temp = 0; //按位加的结果及进位 a = a.split(''); b = b.split(''); while (a.length || b.length || temp) { //~~按位非 1.类型转换,转换成数字 2.~~undefined==0 temp += ~~a.pop() + ~~b.pop(); res = (temp % 10) + res; temp = temp > 9; } return res.replace(/^0+/, ''); }49、单例模式function getSingle(func) { var result; return function () { if (!result) { result = new func(arguments); } return result; } }50、前端随机字符串var uuid = Date.now().toString(36) + Math.random().toString(36).substr(4)51、检测浏览器是否支持transformlet isSuportsTransform = false; (function () { if (typeof CSS === 'object' && typeof CSS.supports === 'function') { isSuportsTransform = CSS.supports('transform:scale(1.2)'); } else if('transform' in document.body.style){ isSuportsTransform = true } })();52、获取当前js文件的路径 var getCurrentScript = function (base) { if (document.currentScript) { return document.currentScript.src // FF,Chrome } var stack try { a.b.c() // 强制报错,以便捕获e.stack } catch (e) { // safari的错误对象只有line,sourceId,sourceURL stack = e.stack if (!stack && window.opera) { // opera 9没有e.stack,但有e.Backtrace,但不能直接取得,需要对e对象转字符串进行抽取 stack = (String(e).match(/of linked script \S+/g) || []).join(' ') } } if (stack) { stack = stack.split(/[@ ]/g).pop() // 取得最后一行,最后一个空格或@之后的部分 stack = stack[0] === '(' ? stack.slice(1, -1) : stack.replace(/\s/, '') // 去掉换行符 return stack.replace(/(:\d+)?:\d+$/i, '') // 去掉行号与或许存在的出错字符起始位置 } var nodes = (base ? document : head).getElementsByTagName('script') // 只在head标签中寻找 for (var i = nodes.length, node; (node = nodes[--i]);) { if (node.readyState === 'interactive') { return node.src } } var node = nodes[nodes.length - 1] return node.hasAttribute ? node.src : node.getAttribute('src', 4) } var jsPath = getCurrentScript(true) var cssPath = jsPath.substr(0, jsPath.lastIndexOf('/')) + '/xxxx.css' // 获取同目录的css文件
2016年05月29日
55 阅读
0 评论
0 点赞
2011-01-28
2011年履职意愿反馈(朱治龙)
一、请描述您具备哪些突出的工作能力。1、较强的学习能力。在技术上乐于接收并学习新技术,在学习的过程中不断地充实自己。在工作中学习能力是很重要的,尤其是做技术这一行,不能老拿三五年前的技术老底吃老本行,IT行业是一个与时俱进、日新月异的行业,只有具备充足的精神和十足的学习劲头才能立于行业的风尖浪头。2、具备一定的项目研发能力,对java相关主流框架技术有一定实际运用经验。3、有多年web前端技术运用经验。4、具备一定的项目管理相关的经验。这跟之前在北京所从事的工作有密不可分的关联。二、请说明2011年您愿意在银信公司具体承担哪一方面(或哪些方面)的工作,您的工作将帮助公司解决哪些问题?带来哪些利益?主要的工作可能还是项目的具体实施相关的工作,通过2010年的积累,相信在以后的工作中,类似的实施工作能较为轻松的胜任。并且在后期根据项目需要进行一些小型项目的开发工作。在2011年我还可以承担公司内部相关前沿技术的学习推广工作。通过学习、钻研,然后将所学的知识授之与众,实为人生之一乐事。三、请描述您2011年的个人职业目标以及它们和公司经营管理目标(根据您自己的理解)之间的关系。我的个人职业目标:努力为公司创造更多的价值。在实际的技术工作中我感觉自己是一个不那么善于表达的人,但这并不说明我缺乏自己对工作的主观想法。一直以来我都是一个乐于奉献的人,很多时候认为自己多付出些也是应该的。个人职位方面相关事项服从领导安排。四、您认为公司应该采取哪些措施帮助您实现个人目标?在项目不算繁忙的时间里,可以建议同事自发的学习一些基于工作上需要的东西。然后再进行相关学习经验的分享。尤其是技术方面,一个人闭门造车式的学习是没有多大长进的,只有通过分享、交流并进行实际应用才能最大化的将所学的技术为我所用。
2011年01月28日
9 阅读
0 评论
0 点赞
1
2