二月初想一想这个月还得捣鼓一篇文章,也没啥好的想法那仍是记录一下毕设的一些思路吧。前端
一些扩展的重要功能将在这里一点点从零开始进行思考。git
项目导入导出的构想是在设定特点功能时候想到的,主要是用于不一样服务器上若是部署了平台,若是想要本身私下部署测试,那么从新创建项目,而后再一个个配置接口路径,配置返回数据是一件很麻烦的事情。为了之后用(自)户(己)用的顺畅,想了一键各类版本的功能,其中就包括项目的导入导出。github
所以在设计数据结构的时候,我将最终返回结果数据设计成以下形式json
{ project:{ projectId:, ... interfaceList: [] } }
也就是前端本地缓存的数据就能够直接导出。redux
固然导出能够把一些信息先过滤处理一遍,好比项目Id ,接口 Id。后端
因而导出能够为一个纯 Json 文本。api
而后倒入时候须要验证 Json 格式,其实就是须要作个遍历,看看该有的属性有没有,没有的话就报错不进行数据导入。数组
并且导入的时候须要作个分类,是导入到项目示例仍是我的/团队项目。缓存
常见的是打包下载 zip。这个大概须要后端处理,所以先找找有没有前端处理的。服务器
搜了一下方案,再结合 github 上一些项目的源码,能够简单的写出一个导出模块
export function exportFile(data: string, filename: string, type: string) { var typeList = { json: 'application/json;charset=utf-8', markdown: 'text/markdown;charset=utf-8', doc: 'application/mswordcharset=utf-8', } // 建立隐藏的可下载连接 var eleLink = document.createElement('a'); eleLink.download = filename; eleLink.style.display = 'none'; // 字符内容转变成blob地址 var blob ; blob= new Blob(['\uFEFF' + data],{type: typeList[type]}); eleLink.href = URL.createObjectURL(blob); document.body.appendChild(eleLink); eleLink.click(); document.body.removeChild(eleLink); }
调用方式很简单,就是传入三个参数:
exportFile(JSON.stringify(this.state.currentProjectData), 'default.md', 'markdwon')
固然咱们如今得到的数据是没有进行处理的,咱们须要对数据作个过滤,剔除一些关键信息以及不必的数据。
假设如今的数据是这样的
{ "_id": "project001", "projectName": "演示项目 - REST接口示例超长字符串测试asd123", "projectUrl": "/project001", "projectDesc": "项目描述", "version": "v1.0", "transferUrl": "http://haoqiao.me/api/project", "status": "transfer", "type": "demo", "teamMember": [ { "_id": "user001", "username": "2333", "role": "前端工程师", "avatar": "https://zos.alipayobjects.com/rmsportal/ODTLcjxAfvqbxHnVXCYX.png" }, { "_id": "user002", "username": "宋青树", "role": "后端工程师", "avatar": "https://zos.alipayobjects.com/rmsportal/ODTLcjxAfvqbxHnVXCYX.png" } ], "interfaceList": [ { "_id": "interface001", "interfaceName": "获取", "url": "/getAll", "method": "get", "desc": "接口描述", "mode": "{data: 1 || 2}" }, { "_id": "interface002", "interfaceName": "增长", "url": "/add", "method": "post", "desc": "接口描述", "mode": "{data: 1 || 2}" }, { "_id": "interface003", "interfaceName": "删除", "url": "/delete", "method": "delete", "desc": "接口描述", "mode": "{data: 1 || 2}" }, { "_id": "interface004", "interfaceName": "更新", "url": "/update", "method": "put", "desc": "接口描述", "mode": "{data: 1 || 2}" } ] }
咱们须要将里面全部的 _id(字段)
, teamMember(数组)
去除。这里你们想一想若是是本身要怎么处理?
其实很是简单,只要你对原生的 JSON.stringify
比较熟悉,你就知道它的完整定义以下
JSON.stringify(value, replacer?, space?)
replacer
是一个过滤函数或则一个数组包含要被 stringify
的属性名。若是没有定义,默认全部属性都被 stringify
。
能够作一个遍历器,遍历Json里的属性名。而后内部作个剔除。
好比这样
filterData = (json: any) =>{ console.log(json) let expectArr = ['_id', 'teamMember'] let filterArr = [] let result = '' for( let key in json){ if (expectArr.indexOf(key) === -1){ filterArr.push(key) } result = JSON.stringify(this.state.currentProjectData, filterArr) console.log(result) return result }
但这样只能拿到第一层的属性名,如何拿到嵌套的数组里的Json的属性呢?
咱们只须要再作个判断就能够了
filterData = (json: any) =>{ console.log(json) let expectArr = ['_id', 'teamMember'] let filterArr = [] let result = '' for( let key in json){ if (expectArr.indexOf(key) === -1){ filterArr.push(key) // 若是是嵌套数组,并且数组内有数据 if(Object.prototype.toString.call(json[key]) == "[object Array]" && json[key].length > 0){ for( let item in json[key][0]){ // 一样对里面的json数据进行属性字段过滤 if (expectArr.indexOf(item) === -1){ filterArr.push(item) } } } } } result = JSON.stringify(this.state.currentProjectData, filterArr) console.log(result) return result }
这样就把该有的属性筛选出来了。而后作个转换就能过滤只剩须要的数据。
数据清理后就变成以下格式
{ "_id": "project001", "projectName": "演示项目 - REST接口示例超长字符串测试asd123", "projectUrl": "/project001", "projectDesc": "项目描述", "version": "v1.0", "transferUrl": "http://haoqiao.me/api/project", "status": "transfer", "type": "demo", "teamMember": [ { "_id": "user001", "username": "2333", "role": "前端工程师", "avatar": "https://zos.alipayobjects.com/rmsportal/ODTLcjxAfvqbxHnVXCYX.png" }, { "_id": "user002", "username": "宋青树", "role": "后端工程师", "avatar": "https://zos.alipayobjects.com/rmsportal/ODTLcjxAfvqbxHnVXCYX.png" } ], "interfaceList": [ { "_id": "interface001", "interfaceName": "获取", "url": "/getAll", "method": "get", "desc": "接口描述", "mode": "{data: 1 || 2}" }, { "_id": "interface002", "interfaceName": "增长", "url": "/add", "method": "post", "desc": "接口描述", "mode": "{data: 1 || 2}" }, { "_id": "interface003", "interfaceName": "删除", "url": "/delete", "method": "delete", "desc": "接口描述", "mode": "{data: 1 || 2}" }, { "_id": "interface004", "interfaceName": "更新", "url": "/update", "method": "put", "desc": "接口描述", "mode": "{data: 1 || 2}" } ] }
以后是要考虑导入项目,我首先想到点击上传 JSON 格式文件而后读取里面的内容,而后验证数据再让后台将其导入到指定表中。
这里面发生了一些事情,好比我确定不但愿用户真的把json文件上传到服务器上,我以为这玩意前端确定能解析和解决,可是咱们确定仍是须要一个上传的按钮和UI交互。
这里我直接用了 antd
的 upload
组件,它里面有个方法就是 beforeUpload
, 只要咱们在这个函数里直接返回 false
那么是不会真正触发上传动做的,可是咱们又能够拿到本地的 File
,能够用 HTML5
的新方法 FileReader
来帮助读取内容。
咱们的组件能够这么修改
const uploadProps = { name: 'file', action: '', showUploadList: false, beforeUpload: (file: any) => { const isJSON = file.type === 'application/json'; if (!isJSON) { Message.error('只容许上传JSON格式文件!'); } const isLt2M = file.size / 1024 / 1024 < 2; if (!isLt2M) { Message.error('JSON文件大小必须小于 2MB!'); } var reader = new FileReader(); // 读取操做都是由FileReader完成的 var that = this reader.readAsText(file); reader.onload = function(){//读取完毕从中取值 const json = this.result if(isJson(json) && that.state.uploadJsonData.length === 0){ that.setState({ uploadProject:true, uploadJsonData: json }) Message.success('Json文件上传识别成功!'); } } return false; }}, onChange: (info: any) => { }, }; <Dragger {...uploadProps}> <p className="ant-upload-drag-icon"> <Icon type="inbox" /> </p> <p className="ant-upload-text">点击上传JSON文件或者拖拽上传JSON文件</p> </Dragger>
来看下实际的交互效果
这样咱们就打通了项目的导入导出功能的交互。以后就是接口对接一下就好了。
这两个功能其实很相似,主要是用于帮助用户可以复制已经存在的接口或者项目。
好比我已经以前创建了一套系统的接口,包括了增删减改。我下一个系统和这套系统很相似,可能只需改几个字段就能够用了。
咱们固然能够利用导入和导入功能,可是在系统内部咱们最好有一键迁移的方式,那就是克隆。
克隆咱们须要注意,首先是接口克隆,假设咱们接口定义的格式以下:
_id(pin): "interface005" interfaceName(pin): "注册" url(pin): "/reg" method(pin): "post" desc(pin): "接口描述" mode(pin): "{data: 1 || 2}"
而后我为了方便定义的项目 Model 里面包含了接口 Model。
也就是我只需把 接口 Id,和 项目 Id 传给后台,让后台作一个查询接口内容,而后新建接口把查询到的内容插入到指定 Id 就能够了。
这很简单。主要部分是 UI 这块,不过经过对数据流的管理也是花时间就能解决的事情。以下图:
以后是克隆项目这块,
咱们首先已经知道项目 Model 里面包含了 接口 Model,所以咱们克隆整个项目实际上是须要将整个接口提取出来,团队成员是须要剔除的,由于新克隆项目应该是只有建立者,所以咱们须要把 用户 Id 也从前端传过去,固然也可不传,经过 Jwt 对 token 解析也能识别用户信息。
主要是后端拿到信息以后它的思路应该是先查询这个项目的信息,而后提取部分信息建立新项目, 而后遍历原有项目的接口列表,批量建立接口。
基本上中后台的应用都会有我的信息管理这项,有的用表单,有的拆分。
其他数据都好搞定,无非是传参的问题,先后端约定的问题。
固然比较麻烦的实际上是头像的更改。
假设你注册的时候默认分配给一个用户头像,而后再我的信息里用户想要更改。
这时候问题来了。更改头像实际上是一个交互问题,你确定不能让用户一步步操做。而是一步到位,符合要求的图片上传以后,拿到上传后的图片地址。而后更改本地的数据。还须要在后台自动更新数据。
这里是用 redux 维护了一个本地的前端数据层,全部显示的变动显示操做须要对其进行更新。
而后是 reducer 里监听了动做, 定义为 UPDATE_LOCALXXX
的动做是用于提交数据后等待后台处理完毕后返回成功,而后将本地的数据进行更新。
显而易见这个动做是异步操做。若是不少个相似操做须要管理咱们代码写起来确定会很乱。所以我在技术评估阶段引入了 rxjs
。
经过其特色时间线的管理,就很容易了。如下是简单的示例代码
export const userUpdate = (action$: any) => action$.ofType(UPDATE_USER) .mergeMap((action: any) => { return fetch.post(updateUser, action.data) // 登陆验证状况 .map((response: any) => { console.log(response); if (response.state.code === 1) { updateUserSuccess(response.state.msg); return updateLocalUser(action.data); } else { console.log('token error') updateUserError(response.state.msg); return nothing(); } }) // 只有服务器崩溃才捕捉错误 .catch((e: any): any => { // console.log(e) return Observable.of(({ type: USER_LOGINERROR })).startWith(loadingError()) }) });
还有一些功能须要等后端开发的时候再记录思路,所以这部分先到这里。