暂时未有相关云产品技术能力~
越来越多的前端项目现在需要这个操作,其操作的原因很简单,你的项目可能跑在小尺寸分辨率的电脑上,也有可能在大尺寸的会议平板上,更有甚者是在LED上。那么如何让你的项目根据屏幕分辨率的大小而自动变化,修改页面展示字体以及调整尺寸呢?按照我们所需功能,我们还是先来对一下流程及需求。第一:我们要求页面一打开就获取屏幕大小,通过计算设置尺寸及字体大小。第二:我们当页面分辨率修改后(拖动、或者分辨率转换)我们也需要设置其尺寸和大小。鉴于以上两种需求。我们可以提炼出。1. 我们需要一个监测屏幕分辨率的方法。2. 我们的页面尺寸需要使用rem来进行编写。3. 我们需要根据屏幕情况来动态设置font-size下来我们来编写代码:我们新建一个文件index.js 并将这个文件引入到 index.html 中在这个index.js中我们先放置一个空的自执行函数// 项目根目录新建文件 util.js 放置一个自执行函数 (() => { // 内容 })()然后在这个自执行函数里我们再写一个函数方法,这个方法来动态设置字体大小(() => { // 设置字体大小 function calcFontSize() { const zoom = window.screen.width / 1920; const size = zoom * 16; document.documentElement.style.fontSize = size + 'px'; console.log('设置字体大小', size); } })()上述代码中,我们首先获取屏幕的大小,以1920 为主。1920分辨率下默认font-size设置为16px。至于为什么要设置 font-size。那是因为rem的缘故,此处不了解的可以自行百度。设置完设置字体代码后,我们完成了上述需求的第三个,则还需要第一个跟第二个。那下来我们再写一个方法用来监测电脑分辨率。window.onload(() => { // 设置字体大小 function calcFontSize() { const zoom = window.screen.width / 1920; const size = zoom * 16; document.documentElement.style.fontSize = size + 'px'; console.log('设置字体大小', size); } window.onload = () => { } })()上述中window.onload 方法的意思是用于在网页加载完毕后立刻执行的操作,即当 HTML 文档加载完毕后,立刻执行某个方法。也就是页面初始化的时候去加载。在这里面我们需要昨个操作,也就是页面一加载完的时候,我们需要去给window的 onresize 绑定一个方法。 window.onload = () => { console.log("页面加载完成") let resize_timer = null; const resize = () => { } window.onresize = resize; }window.onresize的意思是,当监测到屏幕分辨率变的时候,所以我们需要一个操作就是,页面一加载完成,我们给window.onresize 绑定一个方法。这个方法只绑定一次,而不需要每次都去绑定。这样的话,当每次屏幕分辨率改变的时候,都会去执行 resize 这个方法。那么这个方法里我们应该些什么呢?那就是调用我们第一步做的 calcFontSize 这个方法,修改整个document的font-size 属性。 window.onload = () => { console.log("页面加载完成") let resize_timer = null; const resize = () => { calcFontSize(); } window.onresize = resize; }目前的完整代码如下:(() => { // 设置字体大小 function calcFontSize() { const zoom = window.screen.width / 1920; const size = zoom * 16; document.documentElement.style.fontSize = size + 'px'; console.log('设置字体大小', size); } calcFontSize(); window.onload = () => { console.log("页面加载完成") const resize = () => { calcFontSize(); } window.onresize = resize; } })()我们去页面上查看,会发现当我们拖动文档流的时候,字体并不会重新设置。如下我拖了两次都没有触发修改。同样是12.8.只有当我们改完电脑屏幕分辨率后,它才会重新设置。就比如,我从笔记本的屏幕拖动项目到外接显示器上。它就会响应重新设置 font-size。如下: 当我拖到外接显示器后,它自动的出发了 calcFontSize 方法重新配置了字体大小。 到这里我们的主功能就设置结束了。但是我们发现每次触发resize后。我们的resize被执行了两次?这是怎么回事呢?这是因为,每次window.onresize 都会去执行 resize,而在屏幕分辨率发生变化的时候,window.onresize 和浏览器的自身实现有关系.不同的浏览器和操作系统实现可能不一样,目前谷歌是执行了两次,有是时候是一次。所以导致页面变化有点闪动。那怎么办呢?那就是去增加settimeout 来达到短时间只触发一次的效果。(() => { // 设置字体大小 function calcFontSize() { const zoom = window.screen.width / 1920; const size = zoom * 16; document.documentElement.style.fontSize = size + 'px'; console.log('设置字体大小', size); } window.onload = () => { console.log("页面加载完成") let resize_timer = null; const resize = () => { if (resize_timer) { clearTimeout(resize_timer); } resize_timer = setTimeout(() => { resize_timer = null; calcFontSize(); }, 500); } window.onresize = resize; } })()上述代码中,我们利用防抖的方式来使用setTimeout 达到了让 resize 在短时间内只执行一次的效果。再次尝试后发现,页面功能一切正常。页面组件样式大小也达到了我们的要求。
1.首先在我们需要在nw官务下载exe的包【该压缩文件为打包运行程序】因为运维实施对代码的不敏感,估此次打包采用的是可视化打包。解压nw压缩文件 加压后得到下列文件。2.手动在该文件夹内建立package.json 文件。并将下方的内容填写进去 双击运行该文件下nw.exe软件,可以进行软件测试使用。如果能打开软件内网页,则证明该地址正确。我们则可以进行软件封装。电脑下载软件 NIS Edit 软件。安装完毕后打开 NIS Edit ,点击文件。选择新建脚本 选择向导。 点击下一步既可以进入应用程序基本信息页面,填入公司的基本信息及软件的版本信息等等,填写完毕点击下一步。在当前页面中选择程序所要安装的语言版本。同时可以设置安装程序显示的图标。【瘀斑版本我们选择 SimpleChinese 】接下来进入页面未自定义授权文件,可以在这里新建一个txt或者rft文件并写入一些软件的授权信息。所设置的授权文件信息会在安装的时候显示出来并且让用户选择接受协议。其他保留默认配置。即可继续点击下一步。这一步其实是最重要的,就是选择程序文件。将下图中的组名称双击修改成【主程序必选】,描述信息可写可不写。中间红框里的的两个文件都进行删除。允许用户选择安装的组件,然后点击上方的树形图按钮选择目录。既我们刚才所解压并且修改的 nw文件。再下一步的时候我们这里是全部选择默认选项。不进行修改。第九步则是修改自定义的卸载程序图标。我们采用默认的不予修改。 勾选三个勾。然后利用nisEdit进行编译。执行打包过程。最后即可生成所要安装的软件。
调用.dll文件与调用.exe稍有类似 ,类似的是可以在主进程里调用,但是同时也可以在渲染进程中进行调用。同时调用.dll我们则需要用到electron的一个库 electron-edge-js .才可以进行。为了规范,我们还是统一在主进程 main.js 中进行.dll调用。假如,现在我们有个.dll程序已经编写好了,我们这时候需要调用它怎么办。一定是先从DOM(渲染进程)发送通知到主进程(main.js)中,由主进程接收到后,我们再进行.dll的文件调用。鉴于上章我们说了从主进程接收通知来调用.exe,大家已经知道了渲染进程跟主进程的通信及接收。那我们这次就说如何在渲染进程中调用.dll吧以上述为例,我们看下代码:我们首先是需要安装 : electron-edge-js 然后引入到项目中:这时候我们根据官方示例,使用edge.func 来生成一个调用方法啊。上面代码中的 assemblyFile指的是你的 .dll 文件存放路径。(这里可能会存在路径找不到问题,需要绝对路径)typeName的意思则是: 命名空间.对象名methodName就很见名思意了:你这个对应.dll下的的方法名这样的话我们就生成了一个调用.dll的执行方法。下来我们就只需要调用即可。 invoke3 这个方法第一个为要传递给.dll的入参,因为我们没有,所以不用传递,第二个则为一个函数,它返回两个值,第一个值为调用错误。第二个则为调用后的返回参数。我们根据情况进行判断即可。至于上面我们说有可能路径会出现报错问题。大家可以这样解决,如果是在渲染进程中,大家的存放 .dll 的文件尽可能地放在项目的根目录,这样不管是开发环境还是编译环境我们都可以直接通过 ./ceshi/ceshi.dll 文件进行找到它。如果是在main.js 主进程调用的话,这时候大家就需要注意了。需要判断开发环境与生产环境,分别来进行获取.dll 路径。如:
在实际的electron项目开发过程中,为了快读开发,有一些第三方软件例如:截图软件直接调用了现成的软件。那么electron如何调用.exe并传递相应参数呢?在网上找了一堆说的都是下载什么包,做什么操作,云里雾里。这里我就给大家说一下怎么去操作这个。1. 使用 child_process 插件child_process 是node的一个重要模块,熟悉shell脚本的同学,可以用它来完成很多有意思的事情,比如文件压缩、增量部署等,nodejs创建子进程有四种方法,分别是spawn、fork、exec、execFile。而我们本次则使用的是它的第三种办法:exec2. child_process.exec 创建一个shell,然后在shell里执行命令。执行完成后,将stdout、stderr作为参数传入回调方法。例子如下:执行成功,error为null;执行失败,error为Error实例。error.code为错误码,stdout、stderr为标准输出、标准错误。默认是字符串,除非options.encoding为buffer首先,我们需要在electron项目的主进程,main.js中引入这个模块 然后它的使用也必须是在主进程中进行,因为electron的主进程是支持node操作的。 如上代码,我们从渲染进程给主进程发送指令,并携带了一些参数,如上:ip,端口,电话号码,密码等参数。当主进程接收到后,调用openScreenshot 方法打开 ScreenCapture.exe 软件并将参数传递进去如上代码,我 们获取到了 存放ScreenCapture.exe的文件目录及地址,并设置参数。然后使用.replace 进行替换,获取到了 ScreenCapture.exe 文件的path。最后使用 exec 方法进行调用即可。 在使用exec的时候一定要注意,cwd 指的是当前.exe存放目录,一定要配置正确,否则可能软件能调通,但是工作环境会出现一些问题,导致.exe软件内部出现一些问题。至于为什么.exe软件后面要跟参数,这个是需要大家去跟客户端程序员去对接的,看需不需要跟参数,每个参数都是什么,一定要记住,顺序是以客户端程序员排列的顺序为主。这样他那拿到的就是一个对象。
vue项目大家都了解,开发用 npm run dev/npm run serve。而要上线则必须是先将项目打包编译 npm run build 之后成为了普通的静态网页才可上线进行部署及发布。同样这时候我们也已经将代码全部写好了。如果说要改里面的某个值或者修改请求地址我们应该怎么办呢?其实这个问题,我们也可以将它改成,vue/react项目打包编译后如何再修改其配置。打包之后的项目目录如上是一个静态网页。当我们如果想直接修改js的某个变量的话,打开js 我们会发现项目已经被编译为普通的js,我们甚至连文件都找不到。而里面的内容也被webpack编译为其它字符,找不到我们所要改的那个变量。这时候怎么办呢?其实聪明的伙伴估计已经想到了。那就是让内容直接去读取第三方存储。例如:localStorage,cookies或者某个json文件等。也就是说,不管我们编译不编译,我们都让代码去读取第三方存储,获取我们的配置。这点我们其实经常碰到。就比如项目的某个配置文件。那么怎么做呢?我们需要新建一个文件,放置在项目的public文件夹中,因为vue项目打包编译的时候,默认这个文件是不会被编译的。(如果你们有单独设置了webpack的编译则另说)类似于我在当前页面创建一个文件叫:config.js文件这个文件中,我们声明导出一个接口地址,并将它挂载在window之上。下来我们需要在全局的index.html 中引入这个js文件。众所周知,public下的index.html就是当前vue项目的主html文件。所以我们的目标就是在项目每次打开的时候去运行这个js,将接口写入window之上。这样的话,我们在每次打开这个项目的时候,都会去先执行config.js这个文件,读取文件配置然后挂载在window属性值上。剩下的就是简单了。在全局配置請求地址的地方引入window.myURL.URL 即可。这样,我们就完成了项目的配置,我们先将config.js文件改成然后使用 npm run build 进行编译打包后上线部署。这时候我们打开项目在控制台上获取下window看看配置文件地址是否生效。结果是生效的。当我们再次修改接口地址为当前最新地址。我们再次尝试。同样也是成功的。而这时候接口地址也走向了最新地址。我们也可以在控制台的 network中看到。页面一打开后第一时间加载了 config 改变了全局的接口地址。以上就是我们项目打包后如何再次修改请求接口的方法。同时借用这个方法,我们也可以做项目配置。例如:控制文件上传大小,等操作。都是类似的操作。
NFC相信大家都很熟悉,现实中经常使用的门禁卡,公交卡,地铁卡,饭卡等都是采用NFC功能,那么你知道吗,NFC也可以用微信小程序来实现。使用微信小程序可以读取/写入让手机成为一个刷卡器,也可以使用微信小程序模拟一个主机卡,来刷开门禁/饭卡等等。本章就带大家来一起看看微信小程序的NFC有何不同!一、什么是NFCNFC是一种采用13.56MHz频带的近距离无线通讯技术,虽然通讯距离仅为10cm左右,不过和非接触式IC卡技术一样,我们只需要“触碰一下”即可在不同的电子产品之间交换数据。与非接触IC卡不同,NFC与非接触式IC卡不同,NFC可进行双向通信。只要是支持NFC的产品和IC卡,就可以读出或写入数据。还可在手机等便携产品间进行通信。数据传输速度不高,有106kbit/秒、212kbit/秒、424kbit/秒以及848kbit/秒四种速度可供选择。NFC介绍二、NFC可以做什么NFC具有“卡模拟”、“读写器模拟”以及“产品间通信(P2P)”三种功能。1. 卡模拟:举例来说,我们可以用手机来模拟门禁卡,公交卡,饭卡等等。但是要注意的是,它是模拟出来的,也就是说并不实体存在的一个卡。2. 读写器模拟:指的就是,你可以模拟一个读写器,别人用NFC卡来你这里刷卡,你就可以获取其卡片上的信息。3. 通讯讲就是数据通信,使用NFC卡在刷的时候都会进行数据交换。用来做一些事情,比如:配置网络,设置信息,传输文件等等。三、微信小程序的NFCNFC | 微信开放文档我们打开文档可以看到,对于NFC,微信给它的名字叫:近场通讯首先一定要强调的是,因为苹果手机权限的缘故,所以NFC在微信小程序中只支持:Android微信将NFC分为了三部分HCE(基于主机的卡模拟),也就是将安卓手机模拟成实体的智能卡。我们可以通过模拟的智能卡来刷对应的读卡器,给读卡器传递数据。支持NFC读写,也就是将手机作为读卡器来使用。也就是我们可以将一些实体的NFC卡通过贴在手机上从而实现读取卡内容。NFC标签打开小程序。指的就是我们可以通过NFC卡片触碰手机,快速唤起小程序页面的能力。1. HCE(基于主机的卡模拟)的使用场景:使用手机做门禁卡/公交卡/地铁卡使用手机给读卡器传递数据(配网、登记)2. NFC读写的使用场景:使用手机做刷卡器,来获取NFC卡的信息。3. NFC标签打开小程序的使用场景设备的快速配网文件快速传输等快捷控制官网文档写的也很齐全了,大家可以根据自己的需求选择不同的场景来进行使用。本章我就选择了HCE(基于主机的卡模拟)场景。四、使用步骤1.研究APIwx.stopHCE(Object object) | 微信开放文档微信官方文档中,卡模拟一共有六个API。分别为:wx.stopHCE 关闭NFC模块。wx.startHCE 初始化NFCwx.sendHCEMessage 发送NFC消息wx.onHCEMessage 监听接收NFC设备消息事件。wx.offHCEMessage 移除接收 NFC 设备消息事件的监听函数wx.getHCEState 判断当前设备是否支持 HCE 能力。再次特别要强调的是,NFC仅在Android系统下支持。2.使用方法有过开发经验的同学其实比较清楚。以上API 很明显就是一个生命周期。从开始到销毁。我们该如何去操作这个NFCapi呢?1. 首先调用 wx.getHCEState, 判断设备是否支持NFC2. 调用 wx.startHCE(OBJECT) 初始化手机的NFC模块;3. 初始化完成后,调用 wx.onHCEMessage 监听芯片响应的消息;4. 点击页面上的“询卡”按钮,调用 wx.sendHCEMessage发送询卡指令;5. 这时 wx.onHCEMessage(应该可以收到带有uid信息的芯片响应数据;6. 业务处理。7. 全部操作完成后之后,调用 wx.stopHCE停止手机的NFC模块好了,废话不多说,直接进入我们的正式项目。一:新项目我们需要利用微信开发者工具,来新建一个我们的项目。APPID大家如果有的话就用自己的,没有的话则使用测试号即可。二:设置简单页面及对应的js三:根据上述我们理清的 NFC生命周期顺序来搭建我们的NFC项目。顺序自然为:页面打开初始化的时候就需要 getHCEState 判断设备是否支持NFC。不支持则需要做兼容。当返回值的errCode为 0 的时候则一切正常,是支持NFC的。同时,为了更有效的避免大家判断code,所以我们可以将官网的 getHCEState返回code 声明出来,以便后面使用。四:需要开始初始化StartHCE(初始化NFC,将手机初始化为一个主机模拟卡) wx.startHCE接收一个参数为 aid_list。官方解释意思为,需要注册到系统AID列表中,AID列表其实就是每个刷卡器的唯一标识,不清楚的就问下你们的安卓或者默认填写:F22222222。与其它API 类似,它也有自己的返回值,默认0为正常成功的。五:onHCEMEssage 监听在说这里之前,网上许多朋友碰到的类似,wx.onHCEMessage,不管怎么调都没有返回值?不知道有没有伙伴仔细看我上面的话,初始化完成后,onHCEMessage其实就要开始监听。至于为什么没有返回值。是因为,wx.onHCEMessage是一个监听。它需要读卡器来响应它,也就是说要给需要读卡器(设备)通过render 指令 给它发消息,这时候它才能拿得到值。 它有三个返回值,但是我们只一般取第一个也就是 messageType。messageType的值 = 1 时,也就是说,读卡器给我们响应了,说它接收到了NFC。这时候我们就可以调用 sendHCEMessage 来给读卡器发送消息了。messageType的值 = 2 时,就是说,手机已经从读卡器上拿开了。 所以我们在这里只判断 messageType的值为1. 当等于1 的时候我们就可以开始发消息了。六:使用wx.sendHCEMessage 发送NFC消息细心的朋友会发现,发个消息不就是 wx.sendHCEMessage就够了。为啥我的有一大堆的ArrayBuffer 甚至还有comm.这是因为文档有标注:wx.sendHCEMessage 的data 必须是一个二进制数据。也就是我们不能将普通的JSON,字符串等数据 传递给它。否则会报错。那这时候我们需要怎么办呢?转换!我在项目中的comm 就是一个封装的 转换文件。内容如下:comm.jsconst formatTime = date => { const year = date.getFullYear() const month = date.getMonth() + 1 const day = date.getDate() const hour = date.getHours() const minute = date.getMinutes() const second = date.getSeconds() return [year, month, day].map(formatNumber).join('/') + ' ' + [hour, minute, second].map(formatNumber).join(':') } /** * 生成指定长度随机数 */ function genRandom(n) { let a = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; //生成的随机数的集合 let res = []; for (let i = 0; i < n; i++) { let index = parseInt(Math.random() * (a.length)); //生成一个的随机索引,索引值的范围随数组a的长度而变化 res.push(a[index]); a.splice(index, 1) //已选用的数,从数组a中移除, 实现去重复 } return res.join(''); } /** * 字符串转换为时间 * @param {String} src 字符串 */ function strToDate(dateObj){ dateObj = dateObj.replace(/T/g, ' ').replace(/\.[\d]{3}Z/, '').replace(/(-)/g, '/') dateObj = dateObj.slice(0, dateObj.indexOf(".")) return new Date(dateObj) } function isFunctinMethod(name) { if (name != undefined && typeof name === 'function') { return true } return false } const formatNumber = n => { n = n.toString() return n[1] ? n : '0' + n } // ArrayBuffer转16进度字符串 function ab2hex(buffer) { var hexArr = Array.prototype.map.call( new Uint8Array(buffer), function (bit) { return ('00' + bit.toString(16)).slice(-2) } ) return hexArr.join(''); } //十六进制字符串转字节数组 function hex2Bytes(str) { var pos = 0; var len = str.length; if (len % 2 != 0) { return null; } len /= 2; var hexA = new Array(); for (var i = 0; i < len; i++) { var s = str.substr(pos, 2); var v = parseInt(s, 16); hexA.push(v); pos += 2; } return hexA; } function hex2ArrayBuffer(hex){ var pos = 0; var len = hex.length; if (len % 2 != 0) { return null; } len /= 2; var buffer = new ArrayBuffer(len) var dataview=new DataView(buffer) for (var i = 0; i < len; i++) { var s = hex.substr(pos, 2); var v = parseInt(s, 16); dataview.setInt16(i,v) pos += 2; } return buffer } /** * string转16进制 */ function stringToHex(str) { var val = ""; for (var i = 0; i < str.length; i++) { if (val == "") val = str.charCodeAt(i).toString(16); else val += str.charCodeAt(i).toString(16); } return val; } /** * 16进制转string */ function hexCharCodeToStr(hexCharCodeStr) { var trimedStr = hexCharCodeStr.trim(); var rawStr = trimedStr.substr(0, 2).toLowerCase() === "0x" ? trimedStr.substr(2) : trimedStr; var len = rawStr.length; if (len % 2 !== 0) { alert("Illegal Format ASCII Code!"); return ""; } var curCharCode; var resultStr = []; for (var i = 0; i < len; i = i + 2) { curCharCode = parseInt(rawStr.substr(i, 2), 16); // ASCII Code Value resultStr.push(String.fromCharCode(curCharCode)); } return resultStr.join(""); } function pad(num, n) { var len = num.toString().length; while (len < n) { num = "0" + num; len++; } return num; } function strToHexCharCode(str) { if (str === "") return ""; var hexCharCode = []; hexCharCode.push("0x"); for (var i = 0; i < str.length; i++) { hexCharCode.push((str.charCodeAt(i)).toString(16)); } return hexCharCode.join(""); } /** * string转byte数组 */ function stringToByteArray(str) { var bytes = new Array(); var len, c; len = str.length; for (var i = 0; i < len; i++) { c = str.charCodeAt(i); if (c >= 0x010000 && c <= 0x10FFFF) { bytes.push(((c >> 18) & 0x07) | 0xF0); bytes.push(((c >> 12) & 0x3F) | 0x80); bytes.push(((c >> 6) & 0x3F) | 0x80); bytes.push((c & 0x3F) | 0x80); } else if (c >= 0x000800 && c <= 0x00FFFF) { bytes.push(((c >> 12) & 0x0F) | 0xE0); bytes.push(((c >> 6) & 0x3F) | 0x80); bytes.push((c & 0x3F) | 0x80); } else if (c >= 0x000080 && c <= 0x0007FF) { bytes.push(((c >> 6) & 0x1F) | 0xC0); bytes.push((c & 0x3F) | 0x80); } else { bytes.push(c & 0xFF); } } return bytes; } /** * byte数组转string */ function byteToString(bytearr) { if (typeof arr === 'string') { return arr; } var str = '', _arr = arr; for (var i = 0; i < _arr.length; i++) { var one = _arr[i].toString(2), v = one.match(/^1+?(?=0)/); if (v && one.length == 8) { var bytesLength = v[0].length; var store = _arr[i].toString(2).slice(7 - bytesLength); for (var st = 1; st < bytesLength; st++) { store += _arr[st + i].toString(2).slice(2); } str += String.fromCharCode(parseInt(store, 2)); i += bytesLength - 1; } else { str += String.fromCharCode(_arr[i]); } } return str; } /** * 二进制转10 */ function bariny2Ten(byte){ return parseInt(byte, 2) } function bariny2Hex(a){ return parseInt(a, 16) } /** * 10/16进制转2进制 */ function ten2Bariny(ten){ return ten.toString(2) } function str2Hex(str){ return parseInt(str, 10).toString(16) } /** * 16进制转2进制 */ function hex2bariny(hex){ return parseInt(hex, 16).toString(2) } module.exports = { formatTime: formatTime, isFunctinMethod: isFunctinMethod, ab2hex: ab2hex, hex2Bytes: hex2Bytes, stringToByteArray: stringToByteArray, byteToString: byteToString, hex2ArrayBuffer: hex2ArrayBuffer, bariny2Ten: bariny2Ten, bariny2Hex: bariny2Hex, ten2Bariny: ten2Bariny, str2Hex: str2Hex, hex2bariny: hex2bariny, genRandom: genRandom, stringToHex: stringToHex, hexToString: hexCharCodeToStr, pad: pad }它里面包括了 string 转 字节数组等常用转换方法。至于 ArrayBuffer 以及 DataView 等方法,相信各位也都知道,就不在这里做详解。不了解的可以在CSDN上搜索学习。由于我在这里给后台需要传递的是一个JSON,所以我就将JSON转换为字符串。然后再将字符串转换为字节数组,传递给了消息。comm就是 上述封装的转化文件。comm.js stringToByteArray 方法就是 comm.js 文件中封装的 string转 字节数组的方法。当我们完成这一切后使用“真机调试”前往对应刷卡器跟前刷卡即可!会发现前端已经成功了,并且读卡器也已经在控制台上打印出了我们传递的数据。至此,我们的项目就结束了。总结以上就是今天分享给大家的微信小程序HCE模拟主机卡的功能。但是其中不缺乏有一些坑。我列出来供大家参考!1. wx.onHCEMessage 没有返回值。这个可能是我见过最多的问题了,其实它没有返回值的原因就是,读卡器没有 返回信息。通俗点讲就是,你手机开启了NFC,当你挨着读卡器的时候,手机不知道你有没有挨上读卡器,所以需要读卡器给你说一句,你挨上我了,可以发消息了。所以这块的处理是需要对应的客户端开发来处理的,小程序端已经结束了。如果客户端开发不会的话,在此处放一个大佬的文章,可以让客户端开发参考下。微信小程序 NFC HCE卡模拟+AndroidNFC读取优必果2. 我的手机刷卡没反应。是我哪里写错了吗?如果你是按照上面的步骤同时结合官网文档来进行的,那么就基本没错。如果刷卡没反应,并且这时候客户端已经是处理好了的。那么只有一个可能,手机兼容性不够。你换个安卓手机试试。(PS:我试了 vivo跟华为。vivo 有两个手机不支持。华为目前使用的一款是支持的)、3. aid_list 是什么,怎么找呢?其实最简单的就是找你们客户端开发,他们有办法找到你的读卡器的 aid, 默认一般都是F222222222 。如果不是那就找一找你们的客户端开发,让他帮你找一下。好了,文章就到这里。后面大家若是在开发过程有疑问,欢迎提问、私信我。
动态渲染.vue文件其实存在于很多地方,例如近期做的表单设计器就是其中一个,生成vue代码后,应用在其它地方。要求下载完vue文件在其它项目中引入即可使用。那么动态渲染.vue项目如何去做呢?1. 我们需要一个模板页面,这个页面用来处理传递进来的代码,我们最后通过之前有说过的 extend 以及$mount 来进行实例化挂载。那么,我们先第一步,创建一个动态渲染.vue的模板页面。<!-- display.vue --> <template> <div ref="display"></div> </template> <script>export default { props: { code: { type: String, default: '' } }, data () { return { html: '', js: '', css: '' } }, } </script>在上述模板中,我们传入了一个code,这个code 指的就是外部传递给这个页面的动态.vue文件的内容。那么,第二步我们则就是来处理传递进来的.vue文件里的内容(代码)。2. 处理传入的.vue 文件内容export default { methods: { getSource (source, type) { const regex = new RegExp(`<${type}[^>]*>`); let openingTag = source.match(regex); if (!openingTag) return ''; else openingTag = openingTag[0]; return source.slice(source.indexOf(openingTag) + openingTag.length, source.lastIndexOf(`</${type}>`)); }, splitCode () { const script = this.getSource(this.code, 'script').replace(/export default/, 'return '); const style = this.getSource(this.code, 'style'); const template = '<div id="app">' + this.getSource(this.code, 'template') + '</div>'; this.js = script; this.css = style; this.html = template; }, } }在上述代码中,我们声明了两个方法,一个是 splitCode ,我们用来分隔传递进来的需要动态渲染的.vue文件的内容。在分割内容的时候为了方便,我们则单独封装了一个方法 getSource方法。这个方法接收两个参数。source:.vue 文件代码,即 props: code;type:分割的部分,也就是 template、script、style。所以上述的splitCode的方法的目的就是,来分割出我们所需要的三个内容,分别为:模板文件(html)template,js ,css。分割后,返回的内容不再包含 等标签,直接是对应的内容,在 splitCode 方法中,把分割好的代码分别赋值给 data 中声明的 html、js、css。有两个细节需要注意:1. vue的 <script> 部分一般都是以export default 开始的,但是在上述splitCode中,我们把它替换成了return。这里我们需要注意,分割完的代码,仍然是字符串。2. 在分割的 外层套了一个 <div id="app"> ,这是为了容错,有时使用者传递的 code 可能会忘记在外层包一个节点,没有根节点的组件,是会报错的。当我们准备好以上之后,就可以使用extend渲染组件了,但是在这之前,我们必须要注意到当前的this.js 是字符串,而extend的说法,接收的选项必须是一个对象类型。那么这时候我们就必须要先把this.js 转为一个对象。我们在这里使用new Function 来进行转化,如下:const sum = new Function('a', 'b', 'return a + b') console.log(2, 6) // 8new Function 的语法:new Function ([arg1[, arg2[, ...argN]],] functionBody)arg1, arg2, ... argN 是被函数使用的参数名称,functionBody 是一个含有包括函数定义的 JavaScript 语句的字符串。也就是说,示例中的字符串 return a + b 被当做语句执行了。this.js 中是将 export default 替换为 return 的,如果将 this.js 传入 new Function 里,那么 this.js 就执行了,这时因为有 return,返回的就是一个对象类型的 this.js 了。如果你还不是很理解 new Function,可以自行搜索其使用方法。除了 new Function,你熟悉的 eval 函数也可以使用,它与 new Function 功能类似。所以目前的代码如下:<template> <div ref="display"></div> </template> <script>import Vue from 'vue'; export default { data () { return { component: null } }, methods: { renderCode () { this.splitCode(); if (this.html !== '' && this.js !== '') { const parseStrToFunc = new Function(this.js)(); parseStrToFunc.template = this.html; const Component = Vue.extend( parseStrToFunc ); this.component = new Component().$mount(); this.$refs.display.appendChild(this.component.$el); } } }, mounted () { this.renderCode(); } } </script>extend 构造的实例通过 $mount 渲染后,挂载到了组件唯一的一个节点 <div ref="display">上。到现在为止,html和 js 都有了,还剩下css,加载css 没有什么其它方法,就是创建一个<style>标签,然后把css 放进去,再插入到前面的 <head> 中,这样css就被游览器给成功解析了,为了便于后面 this.code 变化或组件销毁时移除动态创建的 <style> 的标签,我们给每个style 标签都增加了一个随机ID 表示。新建工具random_str.js 文件,并写入以下内容:// 生成随机字符串 export default function (len = 32) { const $chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890'; const maxPos = $chars.length; let str = ''; for (let i = 0; i < len; i++) { str += $chars.charAt(Math.floor(Math.random() * maxPos)); } return str; }这个方法是从指定的 a-zA-Z0-9 中随机生成 32 位的字符串。我们这时候可以补全之前的代码:import randomStr from '../../utils/random_str.js'; export default { data () { return { id: randomStr() } }, methods: { renderCode () { if (this.html !== '' && this.js !== '') { // ... if (this.css !== '') { const style = document.createElement('style'); style.type = 'text/css'; style.id = this.id; style.innerHTML = this.css; document.getElementsByTagName('head')[0].appendChild(style); } } } } }当 Display 组件销毁时,也要手动销毁 extend 创建的实例以及上面的 css:export default { methods: { destroyCode () { const $target = document.getElementById(this.id); if ($target) $target.parentNode.removeChild($target); if (this.component) { this.$refs.display.removeChild(this.component.$el); this.component.$destroy(); this.component = null; } } }, beforeDestroy () { this.destroyCode(); } }当 this.code 更新时,整个过程要重新来一次,所以要对 code 进行 watch 监听:// display.vue,部分代码省略 export default { watch: { code () { this.destroyCode(); this.renderCode(); } } }以上就是我们动态渲染.vue文件的整个过程了。此处各位伙伴还需要注意的是版本问题。在vue2时候,有独立构建和运行时构建两种版本可以选择。但是vue cli3 的时候,使用了vue.runtime.js 不允许编译template模板。因为我们在vue.extend构造实例的时候,用了 template 选项。所以会报错。所以,解决办法就是,对vue.config 做以修改。// vue.config.js module.exports = { runtimeCompiler: true }而它的意思就是,是否使用包含运行时编译器的Vue构建版本,设置为true后就可以在vue组件中使用 template选项了。
上一章节我们说到,创建一个vue实例的时候,都会有一个选项el,来指定实例的根节点,如果不写el选项,那组件就处于未挂载状态。Vue.extend 的作用,就是基于 Vue 构造器,创建一个“子类”,它的参数跟 new Vue 的基本一样,但 data 要跟组件一样,是个函数,再配合 $mount ,就可以让组件渲染,并且挂载到任意指定的节点上,比如 body。import Vue from 'vue'; const AlertComponent = Vue.extend({ template: '<div>{{ message }}</div>', data () { return { message: 'Hello, Aresn' }; }, });这一步,我们创建了一个构造器,这个过程就可以解决异步获取 template 模板的问题,下面要手动渲染组件,并把它挂载到 body 下:const component = new AlertComponent().$mount();这一步,我们调用了 $mount 方法对组件进行了手动渲染,但它仅仅是被渲染好了,并没有挂载到节点上,也就显示不了组件。此时的 component 已经是一个标准的 Vue 组件实例,因此它的 $el 属性也可以被访问:document.body.appendChild(component.$el);当然,除了 body,你还可以挂载到其它节点上。$mount 也有一些快捷的挂载方式,以下两种都是可以的:// 在 $mount 里写参数来指定挂载的节点 new AlertComponent().$mount('#app'); // 不用 $mount,直接在创建实例时指定 el 选项 new AlertComponent({ el: '#app' });实现同样的效果,除了用 extend 外,也可以直接创建 Vue 实例,并且用一个 Render 函数来渲染一个 .vue 文件:import Vue from 'vue'; import Notification from './notification.vue'; const props = {}; // 这里可以传入一些组件的 props 选项 const Instance = new Vue({ render (h) { return h(Notification, { props: props }); } }); const component = Instance.$mount(); document.body.appendChild(component.$el);这样既可以使用 .vue 来写复杂的组件(毕竟在 template 里堆字符串很痛苦),还可以根据需要传入适当的 props。渲染后,如果想操作 Render 的 Notification 实例,也是很简单的:const notification = Instance.$children[0];因为 Instance 下只 Render 了 Notification 一个子组件,所以可以用 $children[0] 访问到。需要注意的是,我们是用 $mount 手动渲染的组件,如果要销毁,也要用 $destroy 来手动销毁实例,必要时,也可以用 removeChild 把节点从 DOM 中移除。
前言断断续续,接近一个月时间,我们终于到了最后一个章节,部署上线。回想之前我们从刚开始的拉取代码,到修改代码,新增页面,调用接口,维护菜单一直到现在的上线部署,十篇文章,虽然不多,但是基本上都涵盖了我们快速开发的要点,伙伴们,这篇终于到结尾了。跟着我的步骤,完成这篇文章,我们基本上可以下次很灵活的使用 Ant Design Pro 来进行开发了。快点一起来看看吧一、开发步骤:依旧如此,在开发前我们再次需要看一遍官方文档,以免在开发过程中出现错误。1. 打开ant design pro的官网文档。找到构建与部署。有过开发经验的朋友大概都懂,一般来说,开发我们需要使用命令npm run start / npm run dev / npm run serve这三种其中的某一个,如果还是不对,那就要打开项目根目录下的package.json 查看 script 字段看看里面是否有修改。而部署的时候一样,我们不能直接将整个项目使用开发的手段跑到服务器上。而是我们需要先进行打包编译。之后将编译过的文件(html,css,js)等文件部署到服务器上才行。也就是说,我们打包编译后,其实部署的是一个静态文件。 官方给的命令是 npm run build下来我们跟着尝试下构建编译。打包后的文件夹里的内容:很明显的可以看到是我们最为普通的 html,css js,以及图片文件。这里我们就打包结束。2 上线部署 上线部署我们与构建一样,也从文档开始看起,文档里说,pro默认提供了mock数据,而在此之前,我们的处理其实以及通过命令行将mock关闭了。所以按照官方的说法来说,我们只需要将整个dist复制到我们的cdn或者静态服务器,那就结束了,其中index.html 就是我们的服务器入口。但同时,我们在这里也需要处理一些东西,如:路由。相信一些同学会发现,部署上线后都正常,结果一刷新页面404 了?怎么办!官方有给出文档。这是什么原因呢? hashHistory 使用如 https://cdn.com/#/users/123 这样的 URL,取井号后面的字符作为路径。browserHistory 则直接使用 https://cdn.com/users/123 这样的 URL。使用 hashHistory 时浏览器访问到的始终都是根目录下 index.html。使用 browserHistory 则需要服务器做好处理 URL 的准备,处理应用启动最初的 / 这样的请求应该没问题,但当用户来回跳转并在 /users/123 刷新时,服务器就会收到来自 /users/123 的请求,这时你需要配置服务器能处理这个 URL 返回正确的 index.html,否则就会出现 404 找不到该页面的情况。如果没有对服务器端的控制权限,建议在配置中开启 exportStatic,这样编译后的 dist 目录会对每一个路由都生成一个 index.html,从而每个路由都能支持 deeplink 直接访问。强烈推荐使用默认的 browserHistory。同时,可能有的小伙伴还会问,如果部署到非根目录的文件夹下。那我如何配置呢?修改线上请求地址 以及朋友们最后经常问我的,pro上线,需要改正式的请求地址,它的请求地址在哪改。我怎么改了config 也没作用?对,那是不起作用的。ant design pro上线部署后,正式请求地址与开发的配置可不同,正式地址是存放在其它地方的。如:官网给我们给了nginx,spring boot ,express ,egg 等四种情况。其中就以我们最常见的nginx 的配置:server { listen 80; # gzip config gzip on; gzip_min_length 1k; gzip_comp_level 9; gzip_types text/plain application/javascript application/x-javascript text/css application/xml text/javascript application/x-httpd-php image/jpeg image/gif image/png; gzip_vary on; gzip_disable "MSIE [1-6]\."; root /usr/share/nginx/html; location / { # 用于配合 browserHistory使用 try_files $uri $uri/index.html /index.html; # 如果有资源,建议使用 https + http2,配合按需加载可以获得更好的体验 # rewrite ^/(.*)$ https://preview.pro.ant.design/$1 permanent; } location /api { proxy_pass https://ant-design-pro.netlify.com; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Real-IP $remote_addr; } }在这个配置中 location / 是用来配合router的browserHistory 来解决刷新页面后 404问题的。而 location / api。则是正式我们的项目用到的正式环境的请求地址。 所以伙伴们明白了吧。在正式上线时候,我们不需要在项目中配置环境地址,相反我们只需要打包即可。但是重要的是,我们需要类似于NGINX 等web 容器。并且需要给它写上我们的请求地址。这样我们在项目正式上线后才能获取到这个地址,并且用来应用!总结:到目前为止,我们的ant design pro课程就正式结束了,笔者是站在使用者的角度,将一个模板从0开始拉取一直到完成项目后上线,所有需要踩得坑都走了一遍,相信如果你是认真追读的,到目前为止,你也可以利用 ant design pro 来做你的后台开发模板了。当然了,技术之深不是我一两句能够交代结束的。本次也只是带大家入门。后期,还会给大家出部分课程,带大家走进 ant design pro的底层封装原理。感兴趣的伙伴记得留下赞哦~
前言上章我们说了,如何搭建一个自己想要的后台页面。那本章我们就来说一说,这个页面的接口调用,以及如果在这个页面中完成新增与编辑功能。一、开发前:开发前我们先来看一下本章我们的目标是什么样的呢?如下:1. 我们需要完成列表数据展示。2. 我们需要完成顶部所属医院的筛选3. 我们需要完成创建科室4. 我们需要完成修改科室。在点击修改的时候需要赋值给表单。当修改完成后,表格需要立即刷新。二:开发步骤1. 对接后台api。我们需要链接项目到实际的api请求地址。确保我们项目可与获取到token等有效信息。(此处不会的可与翻看之前的,我们有具体讲解过如何对接后台接口)。2. 对接完后,我们需要给页面增加初始化调用机制,也就是页面一打开的时候就获取表格数据。这里要强调的是与之前有所不一致,因为,之前我们都是放在生命周期里,而本次我们使用的是已经被封装好的组件中,所以我们需要使用ProTable的request 方法来进行数据请求。官网对 ProTable的 request的介绍是这样说的通过官网介绍,我们可以知道,request就是我们获取表格dataSource的办法没有之一。它其中也包含了分页信息,loading,查询表单的值等信息。因为我们这里使用的是自定义筛选表单,所以查询表单在此处跟我们没有多大意义。所以此处的代码我们将如上。其中,PageIndex 与 PageSize 为查询时候传递给后台的分页条件。pageRequest 为分页查询的时候传递给后台其它数据 如:查询 、搜索等其它数据。因为官网有说个,我们在获取数据后必须要返回一个对象,这个对象中必须包括:data(表格数据)、success(Boolean,是否成功),total(查询到的数据总数)。而且只有当其中的success 返回true,并且data 不为空,total 不为0的时候,才会有数据。其实这里的request 就相当于我们页面打开的时候发了一次请求,调用了 getOfficePageList 这个接口。并且传递了相应参数。 3. 新增/编辑表单完成了页面数据后,我们就需要新增与编辑操作了。一般来说,新增与编辑是相同的,只不过新增的时候要求都为空,而编辑的时候,需要将现有数据赋值给表单。所以我们先完成新增操作。编辑也就基本完成了。在office/component 下新建index.tsx 文件。在这个文件中我们引入 ModalForm(浮层组件) 这个组件。同时在浮层组件中,我们需要引入 ProForm。表单组件,毕竟后续的增改我们都是需要表单的。<ModalForm > <ProForm.Group> <div style={{ width: '700px' }}> <ProFormSelect width="md" name="hospitalID" label="所属医院" rules={[{ required: true, message: '请选择所属医院' }]} options={ hospitalList } /> </div> <ProFormText width="md" name="officeName" label="科室名称" tooltip="最长为 50 位" placeholder="请输入科室名称" rules={[ { required: true, message: '请输入1~50位科室名称' }, { min: 1, max: 50, message: '长度在 1 到 50 个字符', }, ]} /> </ProForm.Group> </ModalForm>修改完成大概如上,其中所属医院下拉框中的 options的值,是我调接口获取的。大家可以随便写。不一定这里的参数要跟我一样。当如上完成后,下来我们则需要给ModalForm (浮层组件)添加它的所需属性如:title,visible,onFinish,onVisibleChange 等。其中,title就是 弹框的 名称 visible 则为是否展示弹框的值。默认为false (不展示)onFinish 为点击确定按钮触发的事件,在这里我们添加调用保存或者修改的后台接口。onVisibleChange 为 visible 改变时候触发。<ModalForm formRef={restFormRef} title={formTitle} modalProps={{ forceRender: true, }} visible={modalVisit} onFinish={async (values) => { const dataobj = { ...data, ...values, }; const res = await postOffice(dataobj); if (res.status === 200) { message.success('提交成功'); setQueryData(new Date().getTime()); return true; } else { return false; } }} onVisibleChange={setModalVisit} > <ProForm.Group> <div style={{ width: '700px' }}> <ProFormSelect width="md" name="hospitalID" label="所属医院" rules={[{ required: true, message: '请选择所属医院' }]} options={ hospitalList } /> </div> <ProFormText width="md" name="officeName" label="科室名称" tooltip="最长为 50 位" placeholder="请输入科室名称" rules={[ { required: true, message: '请输入1~50位科室名称' }, { min: 1, max: 50, message: '长度在 1 到 50 个字符', }, ]} /> </ProForm.Group> </ModalForm>修改完后,我们的代码就长如上的样子。在onFinish 中我们增加了新增后台接口调用方法。这时候还有个问题就是,当我们没新增一个信息的时候,主页面的table需要新增一条信息,按照我们往常的操作,我们需要再调用一次查询分页接口就行,但是如今我们没办法,那应该怎么搞呢?答案是: 有的搞。在ProTable 中有个属性为 params,官网是这样解释的,当它的值发生变化后,request 会重新执行。 所以这也就是为什么,上面代码中,我在新增完成后执行了一个方法setQueryData(new Date().getTime());setQueryData 这个方法就是由主ProTable页面传递给 浮层表单这个组件中的。而它的使用则是在 ProTable的 params中。 所以这下伙计们懂了吧。当我们新增完后点击保存,这时候我们需要触发一个方法改变params的值,这样ProTable的request 就可以重新触发,我们拿到最新值。这里当然了 大家可以任意放置东西哈,不一定跟我一样这么写。只要确保数据发生变化即可。好了,到目前为止,弹框打开到保存我们都结束了,现在就差最后一个,如何打开弹框。毕竟到目前为止我们一直没有说打开弹框的事情。所以在最后我们说一下弹框如何打开。因为我喜欢用ReactHook。所以这里我则使用ref的方式来打开这个组件。 <HospItalModal ref={cardRef} modalVisit={modalVisible} setModalVisit={setmodalVisible} setQueryData={setQueryData} />首先我们在主页面中引入浮层弹框组件。同时传入三个值,一个属性与两个方法,属性 modalVisible 为是否打开弹框,setmodalVisible 操作 modalVisible 打开与关闭方法,最后一个setQueryData 则为 ProTable 的params的值,用来出发主表格发生改变。做完这个后,我们需要在浮层弹框组件中再进行处理。1. 引入 forwardRef 将在主页面的ref 传递下去。const Modal = forwardRef( ( { setQueryData: setQueryData, modalVisit: modalVisit, setModalVisit: setModalVisit, hospitalList: hospitalList }: Props, ref, ) => { // ... })2. 在浮层组件中,使用 useImperativeHandle 来接收ref组件及调用后的方法。3. 我们在主页面中主要声明 浮层组件的ref,并在新增时候进行调用。如上,在主页面通过ref 调用的 current下的add方法,则就是 浮层组件中的 add 方法。我们这时候就完成了页面功能的新增。剩下的编辑我就不写了,大体与新增一致,只不过多了渲染一部分。这时候大家多动脑自行编写,有疑问可以随时留言给我。总结:本章节,跟大家从上个章节遗留的tabel数据获取到页面数据的新增,以及浮层组件的建立,使用ref 调用子组件的操作等。基本带大家熟悉了一大圈,到目前为止,属于干活的ant design pro 的基本使用内容已经告一段落了。下一章就给大家说一说最热门关心的,ant design pro如何部署上线问题。好了。大家如果还有疑问,欢迎随时留言讨论。
前言前面,我们从第一章的初识拉取项目一直到本章节的正式完成自己的 ant design pro的第一个页面,一路心酸,但是伙伴们能够看到这里也证明大家很优秀,至少坑被我们踩的差不多了,下来我们就跟随脚步一起写我们的第一个完成的页面吧~一、开发前:如图,是本章我们需要开发的页面。是不是很好看呢,这就是使用了ant design pro的效果,proComponents 已经封装了许多好看的控件等我们来用,所以我们轻轻松松就可以搭建如上的样子。我们来一起走走试试搭建吧。在开始之前,我们开始老样子,需要看一下必备文档:所用到的组件。众所周知, 在ant design pro中,我们使用了若干个组件及框架,就比如:ProComponents 模板组件,它被称为开箱即用,无需我们再次封装。所以我们在刚开始的时候就需要阅读 ProComponents 文档,来熟悉所需要的组件。ProLayout ProForm ProTable ProCard1. ProLayout关于ProLayout 我们其实已经在之前有接触了,它是一个高级布局组件,我们的ant design pro 就是基于 ProLayout 的布局组件搭建成我们喜欢的这个样子的。如果大家忘了,可以返回看一下,src/index.tsx 中的 Layout方法即可。它就是 ProLayout 。2. ProFormProForm 在原来的 Form 的基础上增加一些语法糖和更多的布局设置,帮助我们快速的开发一个表单。同时添加一些默认行为,让我们的表单默认好用。我们只需要简单配置几步即可生成我们想要的样子。 3. ProTableProTable 的诞生是为了解决项目中需要写很多 table 的样板代码的问题,所以在其中做了封装了很多常用的逻辑。这些封装可以简单的分类为预设行为与预设逻辑。依托于 ProForm 的能力,ProForm 拥有多种形态,可以切换查询表单类型,设置变形成为一个简单的 Form 表单,执行新建等功能。就比如我们刚开始的时候那个页面的底部就是使用的ProTable 来创建的。如图,红框中全部是由ProTable 来完成的。所以我们本章的重点也就是,ProTable 以及 ProForm。同时我们还需要再接触一个新的组件。就是官网文档的 浮层表单。它在我们点击某个按钮的时候弹出,漂浮在上层。我们本次需要用它来实现我们的新增以及编辑功能。二:开发流程1. 新建pages页面我们需要先在项目的src/pages/创建一个页面,我们起名为:Office,并创建其页面的业务组件component 以及页面的主入口 index.tsx 2. 修改路由配置,增加让其显示出这个页面。我们需要跟第一章时候一样,在全局的config/routes 中增加这个路由(此处如果有开启了国际化的同学)需要根据之前国际化那章节进行对应的页面增加哦。如果大家都没错的话,那么打开的页面应该是个空白的。 我在此处为了辨识度,增加了两个字。3 . 搭建页面如刚开始页面那样,我们下来是需要搭建页面的。先放置一个ProTable表格,这个是最好处理的。我们从 ProComponent 文档 找到 ProTable 复制它的【无查询表格】这个示例代码,保存后页面上就是我们目前这个样子。 此处要说一下为什么要使用无查询表格,是因为这里我打算分开写,自定义搜索查询条件,这个是许多朋友在实际开发中碰到的,所以这里分开写自定义查询,让大家走个流程看看。搞完table后,我们接下来再处理自定义查询,我们继续打开ProComponent文档,找到 筛选表单 复制它的示例代码,粘贴到我们的代码中在 ProTable之上。同时修改了筛选表单里的两个组件。修改之后在页面上的展示如下 这时候,有人要问了,例如刚开始那也的顶部筛选表单是白色的如何做呢?那个,简单。加个css样式就行 给筛选表单套个DIV 加个样式即可。再次保存后页面就如下: 基本上与我们刚开始想要的样子差不了多少了,剩下我们只需要修改下各个字段,去除不想要的查看日志、导出数据等字段。像表格上面这三个按钮在这里直接可以找到。我们删除或者替换即可。一切修改完成后,我们想要的页面就已经出来了。与刚开始的那样是否一致呢?总结:怎么样,是不是很简单,一个页面的搭建就是这样出来的,因为使用了现成的组件,所以需要东西都不需要我们操心,直接放置进来即可。本章就到这里,下一章节就给大家说一下,如何接通后台数据,以及弹框表单进行新增与删除。
前言哈喽,朋友们好久不见,这两周忙着实际工作的事情,所以没有更新,不知不觉我们已经针对ant design pro的研究已经到了第七章。越来越深入的了解,让我们可能都有点跟不上,但是在此还是希望伙伴们能够沉下心,细心的跟敲,我们把这些代码吃透吃够就行。本章第七章,我们来一起看看 Ant design pro 如何动态加载左侧菜单。一、开发之前先了解知识?我们本次使用的是 Ant design pro的admin 模板来进行二次开发的,所以我们在开始之前需要了解熟读自己的代码确定当前我们的使用布局是ant design ui 的 还是 proComponents 的。因为之前我们在联调后台接口的时候有介绍过基础的结构,所以 我们打开代码 src/app.tsx 就可以明确看到,我们使用的是 layout 这个组件。而在它的顶部,已经给我们注明了,ProLayout 以及对应文档。所以故而可知,项目使用的是 ProLayout 这个组件。我们打开对应文档继续深入。 二、使用步骤1.基础查看 我们可以任意的打开一个代码演示来进行查看,这里就留给伙伴们自己学习。而我们直奔本章的要点,如何从服务器加载menu并展示。我们在一进去这个文档的右侧有个菜单。我们可以在菜单上找到我们本章的重点章节。 点击后进行跳转。跳转后我们会发现,它有两个段落,一个是普通的获取menu,而另一个则是,获取menu后也同时使用icon。也就是页面上菜单前面的小图标。让我们选择,我们必然选择的是第二种,从服务器加载menu,并且使用icon图标。2.代码演示如下,是官方文档的从服务器加载menu,并给它赋值icon的代码。import { HeartOutlined, SmileOutlined } from '@ant-design/icons'; import type { MenuDataItem } from '@ant-design/pro-components'; import { PageContainer, ProLayout } from '@ant-design/pro-components'; const IconMap = { smile: <SmileOutlined />, heart: <HeartOutlined />, }; const defaultMenus = [ { path: '/', name: 'welcome', icon: 'smile', routes: [ { path: '/welcome', name: 'one', icon: 'smile', routes: [ { path: '/welcome/welcome', name: 'two', icon: 'smile', exact: true, }, ], }, ], }, { path: '/demo', name: 'demo', icon: 'heart', }, ]; const loopMenuItem = (menus: any[]): MenuDataItem[] => menus.map(({ icon, routes, ...item }) => ({ ...item, icon: icon && IconMap[icon as string], children: routes && loopMenuItem(routes), })); export default () => ( <ProLayout style={{ minHeight: 500, }} fixSiderbar location={{ pathname: '/welcome/welcome', }} menu={{ request: async () => loopMenuItem(defaultMenus) }} > <PageContainer content="欢迎使用"> <div style={{ height: '120vh', minHeight: 600, }} > Hello World </div> </PageContainer> </ProLayout> );由上面代码我们可以看得出。1. 我们需要一个Icon的枚举。2. 我们需要获取router的列表。3. 我们需要在路由的列表上增加你想展示的icon 字段,并某个可以对应的值。4. 循环给router列表的每一项赋值实际的icon图标。那我们下来按照上面的四步进行走一走试试。看能否添加成功呢?3.实际操作1. 我们在项目的Layout 方法下 需要增加menu对象,它接收一个参数为request。官方文档是这么解释的。它是用来在页面初始化的时候,获取后台菜单数据的一个请求方法。并且在这个方法没有返回的时候,它会自动修改loading的状态。所以在项目中,我们需要定义request这个字段来请求后台的菜单数据。 因为官网的文档上,router的列表直接是定义好的,并且与项目中的结构使用的是一致的,但是在实际情况中,有可能我们后台接口的字段定义是与 antdesignpro的router 需要的字段定义的不一致,所以在这里我们需要增加一步操作,就是转译,将后台字段改为我们前端项目中router能识别出来的字段。 如上我们在获取到后台的菜单数据后,需要将这个菜单数据的字段做以修改处理,改成我们项目的router能识别的字段。 具体方法如上,因为后台也是个无限递归的结构,所以前端也用递归来进行处理。最后将它们继续放置在一个数组中,形成项目可以识别出来的结构。这一步结束后,不要以为就结束了,大家记得我们还得定义ICON,按照文档的要求,我们首先得定义icon的枚举。所以我就声明了个对象,这个对象都是一些我自己在antdesign ui中找的一些icon图标。这时候根据文档,我们需要一个方法,这个方法是循环router列表,我们将对应的router的每一个item的icon 替换成我们上述声明好的IconMap 这个实际的icon。注意:router中的icon 是我在后台定义好的。每一个icon对应的都是我上面 IconMap 这个对象中的Key。所以我们的完成的request方法为:具体的loopMenuItem方法为:到此我们从服务端动态获取 menu,并且赋值 icon的操作就结束了。同时我们需要注意的是,params 这个字段它的意思其实就是,每当我们的用户信息改变时候,都会重新获取一遍后台的菜单数据。这里大家可以改成初始化时候你们获取数据中的任意一个字段,根据它来判断即可。而它的使用意义就是,例如:在线切换用户角色后,角色ID变化,则重新获取服务端最新的菜单列表数据。如果你在这里定义的某个值没有变化,则不会去重新触发获取服务端菜单数据的这个接口。总结其实经过这七章下来,大家可能有所感触,其实关于ant design pro的使用很简单,仔细阅读文档,我们能够找到大多数的解决方法,如果实在找不到,大家也不要着急,因为antdesign pro 也是基于 antdesign ui 的再次封装行程。实在不行我们就查看它的声明来找其解决的办法即可。
前言在之前的处理中,我们的项目 模板到目前为止已经基本具备了大多数功能,并且较为清晰,从本章开始,我们就要着重于实际开发,来解决伙伴们在日常开发中碰到的坑以及麻烦的事情,最大程度上的提高开发效率与开发规范。本章我们就来说一下前后端分离后,前端如何定义请求开发接口。什么是请求接口在项目中,前后端交互,我们被称为前端调用后端的请求接口,我们需要先看后台有哪些接口,事先维护进我们的项目中,并且我们需要清楚的告知具体的API它是用来做什么的,有什么功能,尤其是我们现在使用的是 Typescript,我们则需要知道具体的字段信息来声明我们页面的interface 接口。老的做法在ts不流行的时候我们一贯的做法可能是,前后端定义一个接口名,参数,请求类型,然后前端将这个接口维护进自己的项目,如果说这个项目大一点需要几百个上千个接口,那么们在前端项目中就需要维护这么上百上千个接口,并且需要著名每个接口的请求方式,参数,接口名等等。如上就是之前在某个老项目中,前端定义的接口请求。 (Ps: 此处只供参考,没有截图具体类型。代码补全哦~)前端需要根据后台给出的Swagger 来将一个个的接口手动输入到项目中,然后在去对应页面去调用使用。新做法在新的做法中,前端接入了Typescript强类型,所以我们在之前老的接口上还需要给接口加上类型,参数类型已经返回类型等,所以对前端来说,又是一个很高的维护成本。并且还需要维护大量的接口文件。做为一个优秀的前端工程师来说,我们应该着重于提高开发效率,将精力应该着重放在业务之上,所以对这类重复的事情,我们应该想办法的快速搞起来,不应该占据我们的大量时间。那么这个我们怎么办呢?答案就是:openApi。为什么有openapiopenapi 即开放 API,也称开放平台。 所谓的开放 API(OpenAPI)是服务型网站常见的一种应用,网站的服务商将自己的网站服务封装成一系列 API(Application Programming Interface,应用编程接口)开放出去,供第三方开发者使用,这种行为就叫做开放网站的 API,所开放的 API 就被称作 OpenAPI(开放 API )。如它的名字一样,打开api,也就是说它对我们前端来说,就是一个列出所有api的一个网站,如果你对接的后台是java,c#等,那我们最常见的就是。swagger。后端接入完成 swagger 之后,我们可以访问 swagger 生成的文档。它清楚的列出了我们所需要的接口请求地址,接口请求类型,接口名,并且针对不同的实现类也划分好了接口的标题。并且我们还可以在线直接输入参数用来进行接口请求,查看请求后返回的数据。并且访问页面我们可以拿到一个 openapi 的规范文件。(出于安全,我隐藏了自己的地址)openapi 在前端的作用是什么呢?openapi在前端,其实是umi 封装的一个小功能,它的作用是将你 swagger的 openapi 规范文件导入到项目中,并且在项目中实现 接口自动引入,类型自动生成等。也就是说,你把openapi的规范文件地址复制到 我们项目的 openapi 中,我们就可以一键将你后台中所有的接口引入进来,并且自动创建对应类型,并且自动声明类型。也就是说,它是一个提升你开发效率的。神器!我们杜绝了重复性的将接口引入到我们的项目中。如下图,都是自动生成并引入的。心动了吗?我们来一起看看如何看看 在我们的pro项目中如何使用 openapi如何使用后端接入完成 swagger 之后,我们可以访问 swagger 生成的文档,一般来说都是 http://localhost:8080/swagger-ui.html,访问页面我们可以拿到一个 openapi 的规范文件。如图的位置其次我们需要在我们项目中的 config 文件中修改config.ts 配置。,以 pro 的 openapi 为例,我们配置一下openAPI: { requestLibPath: "import { request } from 'umi'", // 这里使用 copy 的 url schemaPath: "https://gw.alipayobjects.com/os/antfincdn/M%24jrzTTYJN/oneapi.json", mock: false, } // requestLibPath 这里我们需要保证引入request,我们可以是自己封装的 request,也可以是 umi 自己的request。但是这里必须要保证,引入request,否则,我们在项目中的请求将可能无法执行。 // mock 这里指的是 如果配置为 true 则会自动生成一些mock文件,虽然质量不如我们写的,但是在开发中没啥问题,生成的 mock 文件在项目根路径下的 mock 文件中,生成的 mock 数据每次都不同,如果要调试可以随意修改,只有执行 npm run openapi 才会进行修改。 // schemaPath 这里指的是 我们的openapi 规则文件的地址,一般我们取上述 swagger 中的openapi地址。也可以取本地某个 openapi规范化文件。当我们这些都配置完成之后。我们就可以执行项目自带的执行openapi的命令。npm openapi然后系统会自动的帮我们在项目目录生成对应的 请求文件。并且也会自动的帮我们声明这些请求接口的 定义类型。同时,swagger中的注释也会给我们引入进来。在 pro项目中,这个接口会默认生成在 src/service/ .. 之下。如果说,你的swagger 有很多个不同的分类如:这时候为了区分不同的业务。所以你在配置config的时候一定要注意传递一个参数叫: projectName 用来区分不同的接口然后你的项目中,就会是我这种样子,自动区分了对应的文件夹这下如果你想用的话,就直接在页面使用即可。并且也为你定义好了声明文件。也可以自由使用这些声明文件哦。最后要注意的是:openapi 是umi 团队开发且重度绑定了umi,如果你对当前的实现觉得不合适,也可以自行参考工具的实现思路,然后在其之上进行造轮子,配置适合你自己的 openapi。
在第三章节中,我们联调后台管理系统,因为后台服务接口没出来,所以我们使用的mock模拟数据来联调,这时候假如我们的正式后台接口出来了,我们的pro模板应当如何联调正式的后台管理系统呢?关闭mock数据正式联调后台接口的时候,我们要做的第一步就是关闭mock数据。官方文档是这样介绍的。下来我们根据官方的介绍走一遍流程看看是不是跟官方介绍一致。我们打开根目录的 package.json文件。修改script。"start": "cross-env MOCK=none UMI_ENV=dev umi dev",在start命令中增加 MOCK=node验证mock是否关闭。修改完第一步后,我们保存下代码重启项目。然后再次请求 login 登录进行尝试。会发现点击登录没反应了。那也就是说,我们的mock数据已经被关闭了。修改请求地址下来我们则需要修改请求地址。先来看一下官方对请求地址的说明现在市面上所有的脚手架都提供了 proxy 的能力,底层基于 http-proxy-middleware, 这个包可以把所有符合正则匹配的请求转发到某个地址,这个配置可以将所有 /api 开头的请求转发到 http://www.example.org/ ,并且附带所有的参数,包括头信息和 cookie。有一点需要注意的是,在浏览器控制台里看到的仍然是 http://localhost:3000/api/xxx ,转化的步骤是在 node.js 中完成。在pro中,是用proxy更加简单在config.ts中配置即可。我们打开根目录下的 src/proxy.ts 文件可与看到配置。它的使用是在config.ts中进行使用的。默认接收一个参数,这个参数为当前项目的环境。默认是dev环境。我们修改 config/proxy.ts文件的地址来进行尝试。如上我修改了 dev的环境地址,为了安全起见,地址我隐藏了。大家可与修改这里为你们的正式请求地址。修改完成后,我们可与保存下代码再次进行尝试登录。会发现,接口已经正式生效了。至此,配置修改后台请求接口地址的工作就已完成了。但是细心的朋友发现了,当我点了登录后它没有变。还是在这个页面。是的。这个还需要接下来配置几个地方。也就是我们所说的运行时配置运行时配置getInitialState插件打开app.ts中,我们可与看到一个方法 getInitialState 。官方说这个插件叫做 初始化插件,它获取项目的初始化数据,例如(最新用户信息/菜单权限等)。它初始化的数据会缓存下来,我们可以在任何组件中使用useModel进行使用。为此,我们可以在全局搜一下 useModel来查看它的使用方法。项目的 getInitialState 方法getInitialState 的使用也就是说,我们在初始化的时候 getInitialState 的时候获取了最新用户信息,并且将它保存了下来,后续我们可以在任意组件中通过const {initialState} = useModel('@initialState')来进行获取这些信息并使用。此处要注意的是, getInitialState 在请求的时候 会堵塞页面加载。所以接口若是返回慢,页面也也会一直被堵塞。这时候官方又提供了一个新的 api initialStateConfig 用来解决此问题。initialStateConfig 是 getInitialState 的补充配置, getInitialState 支持异步设置,在初始化没有完成之前会堵塞页面加载。这时候我们可以利用 initialStateConfig 这个方法来配置一个loading 代替空白页面。项目中如下:当我们配置完如上之后会发现,我们也就是说知道了该在哪里去跳转页面。也就是登陆完成后并且获取到用户信息的时候跳转。我们来修改下代码。app.tsx 。我们增加了一个console.log 来代替页面跳转,然后我们保存下,进行验证。当我们再次 登录 点击确定后会发现,页面没有变化?这是为什么呢?我们查看下登录页面的代码。原来是因为登录接口出的问题,登录接口判断的是 status == ‘ok’,而我们的接口返回的是所以无法执行 initialState 这个useModel,从而获取用户信息。我们按照官方文档来修改下接口的返回值,并统一一下。【官方描述】当后端接口不满足该规范的时候你需要通过该配置把后端接口数据转换为该格式,该配置只是用于错误处理,不会影响最终传递给页面的数据格式。也就是说,我们的后台接口返回值与前端的定义不一样,所以我们需要配置一个config来进行修改返回值。按照文档修改后如上,然后我们再次修改 登录接口的返回值判断再次保存后查看页面是否返回正确。好了,接口返回终于没问题了。那我们再看下获取用户信息是否正确。额,报错了。原因是没有token。怎么回事呢?其实这里大多数朋友都能想得到。每次在一个后台管理系统中,需要token的接口占据大多数,但是也有不需要token的接口,也就是登录,我们在登录的时候获取到了token,但是却没有保存下来,在其它接口请求的时候加入到请求头中,所以导致没有token无法请求。所以我们需要根据官方配置,使用 请求前拦截来处理增加上token。我们照着文档写一个上述代码的意思就是,当登录成功后,我们会保存token到全局,然后在请求其它接口的时候,我们判断是否有token,有的话就拿出来,增加到请求头上。options 则是我们在全局api接口定义处的 options。做完以上后我们保存查看项目已经成功登录。如果看到这里,你会以为这就配置结束了?不还没有。就比如说,我们默认所有接口返回的都是 200. 那如果接口返回的不是200 呢?我们需要在每个接口请求下都判断 status == 200 吗?答案是否定的。这时候就需要我们的最后一个请求配置。响应后拦截在网络请求响应的 .then 或 catch 处理前拦截处理,使用方法基本和 requestInterceptors 相同。也就是说,我们在这里可以统一去处理接口返回的状态,就比如。200 我们就正常处理。非200 状态,我们需要定义弹出框、提示、或者是401 我们需要退到登录页。这里我们需要用到一个配置就是 responseInterceptors 。来我们一起走一遍流程。上述代码中,我拦截了接口响应。首先判断接口状态是否是200,如果不是200 ,那就证明我的接口请求有问题。有问题我们就抛出异常: 您的网络发生异常,无法连接服务器!如果接口状态正常,那么这时候我们就继续需要判断,接口内部定义的状态码。也就是说是后台给我们的接口状态码是不是200.上述只判断了非200 .如果全部正常则正常抛出到页面。我们首先判断401与 501 没有权限的时候或者登录过期的时候,我们清除token,抛出提示:用户已过期,请重新登陆。并返回到登陆页面。其它状态非200 非 401 非501 的我们统一认为接口有问题。将后台给我们的异常抛出即可。到此。我们的项目已经能够具备正常功能,能够利用模拟数据开发,能够接入后台接口进行开发,能够引入国际化进行开发,能够新增页面配置路由。已经具备了一个项目初始化的形态。伙伴们,你们的跟我的一致吗?如果不一致就赶紧看看是否有所不一样的存在。下一章节,我们将一起重整项目,去除不必要的东西,留下一个纯开发模板。
接上文,在一个项目中,除过基础的增加页面之外,我们紧跟着就是要数据联调,可是当后台还没有给出正式接口的时候,前端难道要停止开发等待后台接口吗?答案是否定的!这时候前端需要的是mock数据。也就是传说中的假数据,只不过mock数据是基于接口请求并且返回的。前端模拟后台返回数据,后续切到线上后也只需要修改请求地址即可。而不用去修改其它地方。在pro中,mock有两种定义方式,并且默认pro 是开启mock数据的。一般是不需要配置。是在根目录的mock中接入是在src/pages中的mock.ts中进行配置。我们查看项目目录可以发现,项目模板默认使用的是第一种方式:在根目录的mock中接入。 一个标准的mock由三部分组成。我们以login登录接口为例:红色块为第一部分,指的是网络请求的method 配置。也就是说,get,post,delete,put等请求方式。为黄色块。它指的是网络请求的地址,一般我们会使用统一前缀,这样方便修改代理的使用。第三部分则为绿色块,指的是mock接口的返回数据,一般都是json。或者是配置一个function,function有三个参数,req,res,url,具体的其实是与 express 相同。其中需要注意的是,所有数据都必须通过res.send 来进行返回。 我们在项目中看一下,项目是如何使用mock数据的(以登录接口为例)。一:查找接口。一般我们在开发的时候都喜欢将接口文件地址统一提取成一个单独的文件,这样方便后期修改,而不用去触动页面逻辑。所以我们可以查看下登录页面的请求接口。 可以看到在登录页面中,引入了一个login接口。这个接口被单独提取出去放在了根目录下的 services/ant-design-pro/api这个文件中。我们可以继续深入查看当前接口。 进入当前文件后可以看到,该接口的实际请求地址为:api/login/account 这个地址。其余的大家都清楚,我就不再做一一表述。我们看下当前请求接口:api/login/account 在mock的返回数据是怎样的。我们打开根目录下的mock/user.ts 这个文件进行搜索 api/login/account这个请求地址。可以看到就是我们刚才示例的mock接口 。二:验证接口在我们找当当前接口后还不行,我们下来是需要验证当前接口。怎么验证呢?当然是启动项目去尝试。就必须,我们要验证登录接口,那么我们就启动项目找到登录,进行登录,然后查看接口返回值是否一直。我们先修改当前的mock数据,增加自定义返回值两个。isAdmin 与 userInfo 2. 保存后我们运行项目进行登录,输入参数与mock数据中一样 username为 admin ,password为 ant.design。并且查看登录接口是否与mock数据一致。 3. 第三步,就是查看返回值,看是否有我们增加两项的返回值。 由此可见,当前mock数据就是我们在登录页面中请求了 login 接口后返回的数据。综上所述,mock数据是前端在后台正式接口还没出来之前的模拟数据,前端模拟后台数据返回,可以自由定义返回数据,使前端不停歇的继续开发,大大提高了开发效率。并且在前后端分离的现在,前后端只需要定义好数据接口,其实就可以不用去管,我们只需要在正式上线时候切换到正式环境即可,其余的都可以不用修改。本章目前就到这里,如果说你的项目使用的是正式后台请求接口,那么下一章,我们来看看,pro如何正式联调后台接口!
接上章,我们到第一章结束已经成功的拉取了项目并运行了项目。这时候我们会说,如果我们要开始二次开发,那么我们需要改什么呢?一:如何新增一个页面1. 新增页面与我们往常开发Vue 或者 React 类似,我们需要在文件夹目录的src/pages/下新建一个页面的文件夹。例如:2. 我在src下的pages文件夹中新建了一个页面文件夹home,并且在文件夹中建了新的tsx以及less文件。用来代表当前页面的页面内容文件。并且我给这个页面写入了一个内容,它会显示一行文字:这里是Home页面。 3. 我们也可以引入新建的less文件。Ant Design Pro 默认使用的是Css Modules。我们可以在页面中这样去使用。我们在less文件中写入了一个样式。并且我们在Home页面tsx中进行引入,然后以modules的方式将它赋值给div的classname关于CssModule 的官方解释是:所有的 class 的名称和动画的名称默认属于本地作用域的 CSS 文件。所以 CSS Modules 不是一个官方的规范,也不是浏览器的一种机制,它是一种构建步骤中的一个进程。(构建通常需要 webpack 或者 browserify 的帮助)。通过构建工具的帮助,可以将 class 的名字或者选择器的名字作用域化。(类似命名空间化)。通过构建工具来使指定class达到scope的过程。CssModules的优势则是:解决全局命名冲突问题 css modules只关心组件本身 命名唯一模块化 可以使用composes来引入自身模块中的样式以及另一个模块的样式解决嵌套层次过深的问题 使用扁平化的类名4. 页面新建完成后,我们则需要开始配置让页面展示出来,也就是配置页面路由。我们打开项目目录的config/routers.ts文件。可以看到这里就是我们的项目路由地址。 我们根据下方 list.table-list 的table路由配置一个页面,看看是否能够生效呢? 答案是,生效了。当我们保存路由地址,回到页面进行刷新后,发现左侧菜单出来了一个菜单 当我们点击后,会发现路由也跳转到了我们刚才写入的页面。至此我们的新增页面就到此结束了。伙伴们快看看,你们的跟我的是否有所不一致呢? 二:如何使用国际化?不知道有没有细心的伙伴注意到,整个config/routers.ts 中,没有任何一个页面名字叫 查询表格那页面上的 查询表格 这个name是怎么出来的呢?官方是这样说的:路由配置完成后,访问页面即可看到效果,如果需要在菜单中显示,需要配置 name,icon,hideChildrenInMenu等来辅助生成菜单。其实name为配置菜单的name,如果配置了国际化后,name则为国际化的key。所以,在上述路由中,name 为: list.table-list。 其实它是国际化的key。也就是说,这个路由是开启了国际化的。并且,我们在页面上的右上角使用国际化切换语言为英文后,其它菜单都变了,但就是我们新增的 home页面没有变化。没有改变为英文。 其实,这里就牵扯到我们今天要说的第二个内容,国际化。 关于 Ant Design Pro的国际化,想看官方文档的伙伴可以直接访问官方文档进行查看哦【国际化 - Ant Design Pro】不详查看官方文档的,那就跟我一步一步的往下走。我们来给我们的Home 页面菜单添加上国际化(多语言切换)。1. 首先我们需要打开 config/config.ts 文件,我们需要配置国际化。2. 其次,我们需要在src/locales中增加相应的 国际化key。例如:我们在locales的zh-CN (中文)的menu.ts,以及 en-US(英文)的menu.ts中增加我们的Home页面国际化key与值。 如: zh-CN(中文中添加)home页面的key : menu.list.home。以及 home对应的中文值:我的主页 en-US(英文中添加)home页面的key:menu.list.home。以及home对应英文的值:My Home Pages一定要注意的是,如果你想要修改哪一部分的显示语言为国际化,那么在对应国际化中的key一定要是相同的。例如: 下述中文menu的key 与英文menu的key 需要是一样的。3 下来我们只需要打开新增页面时候的config/routers.ts 这个文件,修改我们之前的 home页面路由名字为我们的 国际化key:menu.list.home 即可。修改为(此处不用与menu.ts中一样使用全部的key: menu.list.home)因为这里已经是封装过的,menu会自动拼接,所以我们只需要省略menu即可。在这里直接使用 list.home即可 4. 我们保存刷新下页面进行查看,因为在config中默认我们是中文,所以home页面菜单名字则会改成我们在zh-CN的menu.ts中设置的值为 我的主页。当我们切换成英文后。菜单则会显示成 我们在 en-US 的menu.ts 中定义好的 值: My Home Pages到此菜单的国际化就算是修改完了。但是这里会有伙伴想问,那我如果要修改页面的值为国际化我应该怎么做呢?第三:如果在页面上使用国际化展示内容此处我们以welcome页面的 欢迎使用 为例:(中英文切换)1. 我们需要与菜单配置国际化时候类似,在src/locales 下的文件夹中,找到我们要切换的英文文件夹—— en-US/pages.ts 这个 文件。这个文件就是用来表示,页面上内容的英文显示的。我们定义一个统一的国际化的key与值,如: 我们起的key叫: pages.welcome.link。英文的值为 Welcome。同样。我们在对应的中文文件夹 —— zh-CN/pages.ts 这个文件中,新增中文译文的key与值。 key与英文的key保持一致: pages.welcome.link.值为: 欢迎使用。2. 下来我们则需要进入到 welcome 这个页面中,使用 umi 自带的国际化组件(默认自带)FormattedMessage 来直接使用即可。FormattedMessage 组件的id 就是你定义好的 国际化的key。例如: 3. 我们保存下内容。进入页面刷新后进行切换。会发现已经改变。四: 使用方法切换国际化同样。在页面除过直接使用 FormattedMessage 组件外,我们也可以使用方法进行国际化切换。1. 我们需要从 umi 中引入useIntl 这个钩子函数import { useIntl } from 'umi';2. 其次,实例化这个钩子函数。const intl = useIntl();3. 在页面/方法 中可以直接使用. 与 FormattedMessage 组件相比,它可以存在与方法中。同样,我们保存下,去页面刷新试试。 显示依旧正确。这个就是使用 Ant Design Pro的国际化。最后,笔者说一句,笔者也是从官网文档学习过来的。不喜勿喷。
前言这段时间接到了一个突击项目,要在一周内完成一个某医院的癫痫管理平台,由于之前一直使用vue进行开发,本次想换个技术栈,则选择使用了react进行开发。在开发的同时为了快速则选择了react技术栈中较火的 Ant Design UI库来接入。当然了,只使用UI库还不够,我需要快速开发,则对应的开发模板不能少,既然都使用了 Ant Deisgn ,那自然的开发模板选择了 Ant Design 官方出品的 Ant Design Pro 后台管理系统模板。在本次使用中也有一些收获与踩坑,特此放上了,让各位伙伴避免踩坑。 一:开发前步骤。一般来说,在正式开发前,需要看一下业务需求来判断下业务开发中所需要的技术栈,毕竟是后台管理系统类,所以个人默认,Ant Design 中全有。直接进行开发即可。(1)拉取Ant Design Pro 项目模板。根据官方(Ant Design Pro)文档显示。在使用Ant Design Pro之前,我们需要安装Ant Design Pro官方脚手架来拉取项目。所以第一步先安装脚手架。npm i @ant-design/pro-cli -g安装完成之后,我们需要再运行来使用 安装好的脚手架 创建我们的 Ant Design Pro 模板项目pro create myapp在运行之后,cmd会显示让你选择本次使用的umi版本。关于umi 大家可以了解下,也是阿里出品的一个PC应用的库,会给大家省去不少的烦心事。 我选择的是umi@3版本。因为umi@3版本中,我可以选择基础模板,它只提供了框架的基本运行内容。可以用来做二次开发。而complete 模板则包含所有的区块,不太适合我们进行二次开发 我们选择完umi@3 后,再选择 simple 基础模板。则脚手架会自动帮我们创建一个适合二次开发的基础模板。文件夹里我们看到的目录如下:(2)运行拉取项目 以上就是创建我们的基础模板了。umi会默认帮我们引入React,antd,lodash,moment,react-dom,mockjs,typescript等一系列用到的库。比我们自己手动创建更加方便。这时候我们就可以开始cd到当前目录下。使用npm install来拉取对应的包依赖。拉取完成后,使用npm satrt项目会自动启动,按照项目启动的地址,打开它即可看到我们的初始项目。 运行地址 http://localhost:8001 打开后页面如下至此,项目拉取结束,我们可以使用页面上展示的默认账号密码进行登录查看基础功能。登录后可以看到,基础模板默认左侧只有三个菜单,一个欢迎页面(默认主页)一个管理页(权限区别页)以及一个普通的表格展示页。这就是当前默认模板的所有内容。我们再回来看一下项目目录。【官方文档也已经写出来了:文件夹结构 - Ant Design Pro】 至此,我们的ant-design-pro项目第一部分,拉取antdesignpro 模板就已经结束了。小伙伴可以对照文章,或者照着官网的文档进行操作,如果有不一样的,也欢迎留言给我。
随时前端的发展,在大团队下前端的规范性及开发便携性以及如何提升研发效能是前端开发的首要问题。如何提升研发效能呢?其实这种说法在大厂中比较常见。一个部门下有若干个前端团队,如果每一个团队都有不同的项目组件库,项目框架,http请求方法,不同的埋点方法或者工具方法,那在后期如果要联动开发是很难的事情。这时候就有了一个说法:前端开发脚手架!它的优点不言而喻:a **自动化:**项目重复代码拷贝/git操作/发布上线操作b **标准化:**项目创建/git flow /发布流程/ 回滚流程c **数据化:**研发过程系统化,数据化,使得研发过程可量化。使用者的角度是如何看待前端脚手架呢?它本质上是一个操作系统客户端,通过命令行执行。如:vue createvue-test-app它对于使用者就是一行命令,但是它可以做什么呢?它会帮助我们在本地创建一个vue项目,项目名为vue-test-app。实际上还有比这个场景更加复杂的命令。不过今天只是带大家入门。让大家看看,如果从0开始,在自己本地开发 一个前端脚手架并发布到npm 呢。1. 首先在本地创建一个npm项目。 2. 在本地新建文件夹。vue-test 3. 创建好文件夹后,使用cmd进入该创建好的文件夹。 4. 使用npm init 全部Y 生成一个默认的package.json模板 在package.json 中需要增加一个bin配置,声明这个bin的名称以及对应文件地址。5. 在当前文件夹下新建一个文件夹目录bin目录。6. 在新建的这个bin目录下新建一个index.js文件。 7. 编辑这个index.js 文件,在文件中添加 console.log("vue-test") 8. 并且在当前文件的首开始,添加一行 #!/usr/bin/env node9. 将脚手架发布到npm(此处需要大家先行在npm进行注册。) 使用npm login 进行npm登陆。 然后使用 npm publish 发布 (此处报错,建议百度。一般都是名称问题) 10. 推送成功后,在自己电脑本地则可以使用 npm install vue-test -g 进行安装 11. 安装完成后,则可以使用 vue-test (package.json 中bin配置的那个名称)来进行验证以上就是一个最基础的前端本地开发脚手架以及发布到npm的一些列流程。喜欢麻烦点个赞~~~ 后面会陆续出如何搭建一个脚手架提升研发效能。
js 使用AES加解密在项目中安装 crypto.js。npm install crypto-js2.在项目中新建文件夹 utils3.新建工具类文件4.引入crypto-js文件import CryptoJS from 'crypto-js'5.初始化16位密钥 及 16位iv(密钥偏移量)6.解密方法export const Decrypt = (word) => { let encryptedHexStr = CryptoJS.enc.Hex.parse(word); let srcs = CryptoJS.enc.Base64.stringify(encryptedHexStr); let decrypt = CryptoJS.AES.decrypt(srcs, key, { iv: iv, mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.Pkcs7 }); let decryptedStr = decrypt.toString(CryptoJS.enc.Utf8); return decryptedStr.toString(); }7.加密方法export const Encrypt = (word) => { let srcs = CryptoJS.enc.Utf8.parse(word); let encrypted = CryptoJS.AES.encrypt(srcs, key, { iv: iv, mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.Pkcs7 }); return encrypted.ciphertext.toString().toUpperCase(); }加密 数字 123456789代码中测试测试:至于为什么在工具上方选择不是初始化的选项。主要是因为,代码中明显可以看到加密使用的是UTF-8,并且模式使用的是CBC填充padding 则使用的是PKCS7解密时候可以看到输出使用的是HEX,模式依旧是CBC 填充是PKCS7 。转换字符为UTF8故此,验证完毕。
element-admin 动态加载菜单报错:Cannot find module '@/views/ShopConfig/ShopLis并且控制台显示Critical dependency: the request of a dependency is an expression解决方法只有一个那就是将你之前的 () => import(`@/views${url}`) 将这个 改成 resolve => require([`@/views${url}`], resolve) 即可解决真的,代码不难,难就难到各种环境问题,不支持。我难啊
在写java的时候,各种问题层出不穷。特别是对于新手来说。 头一次写java 项目,遇到问题后就只能百度搜索查询。java开发中不常碰到的问题,例如:引入包的版本问题。版本不兼容问题。我们是没发一次性知道的。只有在做某些特定时期的时候才能知晓。就比如,笔者也是一个java新手,头一次的java项目中就遇到了如下问题:项目使用 SpringBoot + Mybatis-plus用到了LocalDateTime 类型。然而在转换时候报错Error attempting to get column 'XXX' from result set. Cause: java.sql.这时候我们的解题思路是什么呢?既然是类型问题,我们就必须找找类型的事。查看数据库中的时间字段,发现类型是:datetime。而我们的实际项目中使用的则是 LocalDatetime类型。当我们确定后再尝试,会发现导致查询报错的就是这个类型问题。那么如何解决这个问题呢?最简单的就是更换druid。也就是集成了druid数据源的问题。通过查看mybatis-plus更新文档得知。mybatis-plus 3.1.1+ 版本使用了新版的jdbc,LocalDateTime等日期类型处理方式给做了一定的升级、但是我们集成进来的druid在1.1.21版本之前不支持LocalDateTime。如上图所示,3.1.1版本之后做个升级处理因此我们断定:是因为com.alibaba 的版本问题。解决方法:切换版本号到1.1.22 即可消除问题 <dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> <version>1.1.22</version> </dependency> 2.让mybatis-plus版本保持在3.1.0.3.如果非要使用最新版本的mybatis-plus,我们就必须换掉druid数据源。采用spring boot 默认数据源。让它保持于mybatis-plus 的版本保持紧跟即可。
2022年10月