二月初想一想這個月還得搗鼓一篇文章,也沒啥好的想法那仍是記錄一下畢設的一些思路吧。前端
一些擴展的重要功能將在這裏一點點從零開始進行思考。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()) }) });
還有一些功能須要等後端開發的時候再記錄思路,所以這部分先到這裏。