自己在学习Golang Web开发的时候,碰到了post请求和get请求,既要写页面又要开发接口,稀里糊涂的实在头大,所以突发奇想自己动手搞一个简易postman吧!
为了追求速度,所以直接用了基于Vue的饿了么ElementUI,接口请求用的是Axios,使用<pre></pre>标签直接格式化json,虽然不怎么好看,但是够用,凡事讲个够用,哈哈,再根据自己项目优化,岂不美哉。
好了废话不多说,上代码!
postman.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Magic Postman</title>
<link rel="stylesheet" href="css/style.css">
<link rel="stylesheet" href="css/element-ui@2.12.0.css">
<link rel="icon" type="img/png" href="img/icon-128.png">
</head>
<body>
<div id="main" class="flex" style="height: 100%;">
<!-- template 防止首次打开抖动出现{{}} -->
<template>
<!-- 左侧边栏 -->
<aside class="postman-aside shrink0 flex-view">
<div class="flex1 scroll-y">
<el-menu default-active="2" class="el-menu-vertical-demo">
<el-submenu index="常用">
<template slot="title">
<i class="el-icon-folder-opened"></i>
<span>常用</span>
</template>
<el-menu-item @click="handleTabsEdit(false, 'add', api)" v-for="(api, index) in apisObject['常用']" :key="index" :index="api.randomIndex">{{api.alias||api.url}}</el-menu-item>
</el-submenu>
<el-submenu :index="item" v-for="(item, index) in groupList" :key="index">
<template slot="title">
<i class="el-icon-folder-opened"></i>
<span>{{item}}</span>
</template>
<el-menu-item @click="handleTabsEdit(false, 'add', api)" v-for="(api, index) in apisObject[item]" :key="index" :index="api.randomIndex">{{api.alias||api.url}}</el-menu-item>
</el-submenu>
</el-menu>
</div>
<!-- 左侧边栏底部 -->
<footer class="postman-footer center">
<el-button type="text" @click="groupFuncType = 2;dialog.group = true" icon="el-icon-setting">组管理</el-button>
<el-button type="text" @click="dialog.introduce = true" icon="el-icon-info">说明</el-button>
</footer>
</aside>
<!-- 右侧主功能 -->
<section class="postman-main flex1 mlr12 mtb12">
<el-tabs v-model="editableTabsValue" type="card" editable @edit="handleTabsEdit">
<el-tab-pane :key="item.name" v-for="(item, index) in editableTabs" :label="item.name" :name="item.name">
<div class="flex">
<!-- 请求类型 -->
<el-select v-model="item.method" style="min-width: 100px;">
<el-option v-for="(item, index) in methods" :key="index" :label="item" :value="item">
</el-option>
</el-select>
<!-- 发送 -->
<el-input @keyup.native.enter="send(item)" v-focus placeholder="请输入要请求的链接地址" v-model="item.url" clearable class="flex1 mlr12"></el-input>
<el-dropdown split-button type="primary" style="min-width: 121px;" @click="send(item)" :loading="item.loading">
<i class="el-icon-s-promotion"></i> Send <el-dropdown-menu slot="dropdown">
<!-- handleCommand -->
<span @click="handleCommand('常用', item)">
<el-dropdown-item>保存到“常用”</el-dropdown-item>
</span>
<span v-for="(name, index) in groupList" :key="index" @click="handleCommand(name, item)">
<el-dropdown-item>保存到“{{name}}”</el-dropdown-item>
</span>
<span @click="handleCommand('新分组', item)">
<el-dropdown-item>保存为...</el-dropdown-item>
</span>
</el-dropdown-menu>
</el-dropdown>
</div>
<div class="flex mtb12">
<div class="flex1 fxmiddle">
<el-radio-group v-model="item.reqDisplayType">
<el-radio label="key">键对值</el-radio>
<el-radio label="json">JSON文本</el-radio>
</el-radio-group>
</div>
<div>
<el-button type="text" @click="setCookie" disabled="true">配置Cookie</el-button>
</div>
</div>
<ul class="">
<li v-if="item.reqDisplayType == 'key'">
<ul>
<li class="flex mb12" v-for="(p, index) in item.params" :key="index">
<el-input placeholder="请输入Key" @keyup.native="editParams(item.params, 1)" v-model.trim="p.key" clearable style="width: 230px;" class=""></el-input>
<el-input placeholder="请输入Value,或者粘贴对象到此处试试。提示:复杂json请使用json文本模式" v-model="p.value" clearable class="flex1 mlr12" @paste.native="paste(event, item)"></el-input>
<div class="center">
<el-button type="danger" :disabled="item.params.length===1" icon="el-icon-delete" size="mini" circle @click="editParams(item.params, 0, index)"></el-button>
</div>
</li>
</ul>
</li>
<li v-else>
<el-input v-model="item.data" placeholder="JSON文本.....复杂json我也能搞定!" type="textarea" :autosize="{ minRows: 4, maxRows: 15}"></el-input>
</li>
</ul>
<div class="mtb12">
<!-- <el-input placeholder="返回....." type="textarea" rows="10"><pre>{{item.response}}</pre></el-input> -->
<div contenteditable="true" class="response-box">
<pre>{{item.response}}</pre>
</div>
</div>
</el-tab-pane>
</el-tabs>
</section>
<!-- 分组设置 -->
<el-dialog :title="groupFuncType === 2 ? '组管理' : '请选择一个分组'" :visible.sync="dialog.group" width="600px">
<ul>
<li class="flex">
<el-radio v-model="selectGroup" label="">
<el-input v-focus @keyup.native.enter="createNewGroup()" v-model.trim="newGroupName" style="width: 525px;" placeholder="若新建分组,请在这里输入新分组名称,回车键确认" :maxlength="16" clearable class="flex1 ml12"></el-input>
</el-radio>
</li>
<li class="flex mt12">
<div class="flex1 fxmiddle">
<el-radio v-model="selectGroup" label="常用"><span class="ml12">常用</span></el-radio>
</div>
<div class="center">
<el-button type="danger" @click="createNewGroup('常用')" icon="el-icon-delete" size="mini" circle></el-button>
</div>
</li>
<li class="flex mt12" v-for="(item, index) in groupList" :key="index">
<div class="flex1 fxmiddle">
<el-radio v-model="selectGroup" :label="item"><span class="ml12">{{item}}</span></el-radio>
</div>
<div class="center">
<el-button type="danger" icon="el-icon-delete" size="mini" circle @click="createNewGroup(item, index)"></el-button>
</div>
</li>
</ul>
<span slot="footer" class="dialog-footer">
<el-button type="primary" @click="saveAs" :disabled="!selectGroup||groupFuncType === 2">确 定</el-button>
</span>
</el-dialog>
<!-- 说明 -->
<el-dialog title="说明以及待开发" :visible.sync="dialog.introduce" width="600px">
<ul class="">
<li class="middle flex mb12">
<img src="img/icon-96.png" alt="">
<p class="padtop10">Magic Postman ver 1.0.0</p>
</li>
<li class="flex mt12">
<el-checkbox><span class="ml12">自定义携带cookie功能待开发,碰到此类网站,可以先登录,登录后随意跨域调试</span></el-checkbox>
</li>
<li class="flex mt12">
<el-checkbox><span class="ml12">删除组内接口,给接口设定别名后保存待开发</span></el-checkbox>
</li>
<li class="flex mt12">
<el-checkbox><span class="ml12">特殊情况未捕获可以F12打开调试面板查看</span></el-checkbox>
</li>
<li class="flex mt12">
<el-checkbox><span class="ml12">页面UI优化,比如链接遮挡,样式丑陋等</span></el-checkbox>
</li>
</ul>
</el-dialog>
</template>
</div>
</body>
<script type="text/javascript" src="js/vue.min.js"></script>
<script src="js/element-ui@2.12.0.js"></script>
<script type="text/javascript" src="js/vue.directive.js"></script>
<script type="text/javascript" src="js/axios@0.19.0.js"></script>
<script type="text/javascript" src="js/postman.js"></script>
</html>
js/postman.js如下
/* cookie */
axios.defaults.withCredentials = true
let app = new Vue({
el: "#main",
data: {
/* 支持的方式 */
methods: ['GET', 'POST', 'PUT', 'DELETE'],
/* 打开的Tabs */
editableTabs: [],
editableTabsValue: '1',
/* 初始化显示api配置 */
config: {
method: 'GET',
name: '',
url: '',
reqDisplayType: 'key',
params: [{
key: '',
value: ''
}],
data: '{ "":"" }',
response: '{}',
loading: false
},
/* 弹窗控制层 */
dialog: {
group: false,
introduce: false
},
/* Api分组相关 开始 */
groupList: [],
selectGroup: '',
tabIndex: 0,
userApiGroup: [{ name: '常用', list: [] }],
newGroupName: '',
apisObject: {},
/* 组管理2,保存1 */
groupFuncType: 0,
/* Api分组相关结束 */
},
methods: {
/* 发送请求 */
send(item) {
if (!item.url) { return }
if(!item.url.includes('http://')&&!item.url.includes('https://')){
return this.$message.warning('链接好像不怎么对~')
}
let config = { ...item }
let temp = {}
if (item.reqDisplayType === 'key') {
for (let m of item.params) {
if (m.key !== '') {
temp[m.key] = m.value
}
}
} else {
temp = JSON.parse(item.data)
}
/* 删除key为空的对象 */
for (let i in temp) {
if (i === '') {
delete temp[i]
}
}
/* 处理Axios,get方法使用params,post方法用data */
config.data = temp
if (config.method === "GET") {
config.params = config.data
delete config.data
}
delete config.reqDisplayType
delete config.response
console.log(config)
item.loading = true
axios(config).then(res => {
item.response = res.data
item.loading = false
}).catch(err => {
let res = err.response
item.response = res.data || { status: res.status, statusText: res.statusText }
console.log(err.response)
item.loading = false
})
},
/* tabs 增加tab和删除tab操作 */
handleTabsEdit(targetName, action, api) {
if (action === 'add') {
let newTabName = 'New Tab ' + (++this.tabIndex + '')
if (api) {
let temp = { ...api }
temp.name = newTabName
temp.response = '{}'
this.editableTabs.push(temp)
} else {
this.config.name = newTabName
this.editableTabs.push({ ...this.config })
}
this.editableTabsValue = newTabName
}
if (action === 'remove') {
let tabs = this.editableTabs;
if (tabs.length <= 1) {
return this.$message.warning('做人留一线,日后好相见!')
}
let activeName = this.editableTabsValue
if (activeName === targetName) {
tabs.forEach((tab, index) => {
if (tab.name === targetName) {
let nextTab = tabs[index + 1] || tabs[index - 1]
if (nextTab) {
activeName = nextTab.name
}
}
});
}
this.editableTabsValue = activeName
this.editableTabs = tabs.filter(tab => tab.name !== targetName)
}
},
/* 键对值操作,自动新增一条空行, []params, action: (1新增,0删除),index: 操作的行*/
editParams(items, action, index) {
let length = items.length
if (action) {
let empty = items.filter(m => m.key === '')
/* 有空key就不新增行 */
if (!empty.length) {
items.push({ key: '', value: '' })
}
} else {
/* 如果是最后一个不删除,只清空 */
if (length === 1) {
items[0].key = items[0].value = ''
} else {
items.splice(index, 1)
}
}
},
/* 处理粘贴操作 */
paste(event, item) {
let clipboardData = event.clipboardData || window.clipboardData
if (clipboardData) {
let txt = clipboardData.getData('Text')
txt = txt.replace(/\/n/g, '').replace(/\/r/g, '')
if (txt) {
try {
/* 只处理是对象的情况,使用eval是方式{"key": ''}, 中key没有引号包裹无法转换 */
txt = eval('(' + txt + ')')
let arr = []
if (txt.constructor === Object) {
for (let i in txt) {
arr.push({
key: i,
value: txt[i]
})
}
setTimeout(() => {
item.params = arr
}, 100)
}
} catch (e) {}
}
}
},
/* str要转化的内容, type:函数接收的类型 */
str2json(str, type) {
try {
let m = JSON.parse(str)
if (type === 'Object') {
return m.constructor === Object ? m : ''
} else if (type === 'Array') {
return m.constructor === Array ? m : ''
} else {
return m
}
} catch (e) {
console.log(e)
return ''
}
},
/* 自定义携带的cookie待开发 */
setCookie() {},
/* 保存到分组 */
saveAs() {
this.handleCommand(this.selectGroup, this.tempApiConfig)
},
/* 另存下拉点击事件 name:组名,api:当前展开的api*/
handleCommand(name, api) {
if (name === '新分组') {
this.groupFuncType = 1
this.dialog.group = true
this.tempApiConfig = api
} else {
if (!api.url) {
return this.$message.warning('无法另存空链接')
}
// 处理当前Api信息
let item = { ...api }
delete item.response
delete item.name
delete item.loading
item.randomIndex = Math.random()
let oldApis = this.apisObject[name] || []
oldApis.push(item)
localStorage.setItem('_UserApis_' + name, JSON.stringify(oldApis))
this.getGroupList()
this.mapAllGroup()
this.dialog.group = false
}
},
/* 新建或删除分组 item:分组名,index:行号*/
createNewGroup(item, index) {
if (item) {
/* 删除操作 */
localStorage.clear('_UserApis_' + item)
if (item === '常用') {
this.$message.success('组内所有api已清空')
this.apisObject['常用'] = []
} else {
this.groupList.splice(index, 1)
}
} else {
/* 创建 */
let ngm = this.newGroupName
let gl = this.groupList
if (!ngm) { return }
if (gl.includes(ngm) || ngm === '常用' || ngm === '新分组') {
return this.$message.warning(`组名:${ngm} 已存在,或不可用`)
} else {
this.groupList.push(ngm)
}
this.selectGroup = ngm
this.newGroupName = ''
}
localStorage.setItem('_UserGroups', JSON.stringify(this.groupList))
this.getGroupList()
},
/* 从本地读取组并赋值 */
getGroupList() {
let groups = this.str2json(localStorage.getItem('_UserGroups'), 'Array')
this.groupList = groups || []
},
/* 根据组名获取所有的api */
getApisByGroup(name) {
let apis = this.str2json(localStorage.getItem('_UserApis_' + name), 'Array')
if (apis) {
this.apisObject[name] = apis
}
},
/* 遍历所有组并获取api */
mapAllGroup() {
this.getApisByGroup('常用')
this.groupList.forEach(item => {
this.getApisByGroup(item)
})
}
},
computed: {},
created() {
/* 创建一个tab */
this.handleTabsEdit(false, 'add')
/* 获取所有组 */
this.getGroupList()
/* 遍历所有组并获取api */
this.mapAllGroup()
}
})
代码中我也尽可能增加了注释,最关键的是manifest.json的配置,关于browser_action,permissions,以及对popup添加添加点击事件,对插件鼠标右键添加事件
/*--------------右键菜单添加--------------*/
chrome.contextMenus.create({
title: '接口调试工具',
onclick() {
window.open('../postman.html')
}
}, function (e) {
})
/* 为图标添加点击事件 */
chrome.browserAction.onClicked.addListener(function() {
window.open('../postman.html')
})
manifest.json解释了权限,在chrome插件中使用vue,做了详细备注
{
"background": {
/* 只执行一次,用来写入到鼠标右键 */
"scripts": ["js/background.js"]
},
"author": "TEEMO",
"description": "基于Vue,Axios的简易Postman插件,UI基于Eleme 2.1.2.0",
"icons": {
"128": "img/icon-128.png",
"16": "img/icon-16.png",
"32": "img/icon-32.png",
"48": "img/icon-48.png",
"96": "img/icon-96.png"
},
"manifest_version": 2,
"name": "Magic Postman",
"offline_enabled": true,
"browser_action": {
"default_icon": "img/icon-96.png",
/* "default_popup": "postman.html", */
"default_title": "Magic Postman"
},
/* ***重要*** 配置安全选项,配置后才可以使用Vue*/
"content_security_policy": "style-src 'self' 'unsafe-inline';script-src 'self' 'unsafe-eval'; object-src 'self' ; media-src 'self' filesystem:",
/* 插件要获取的浏览器用户权限 */
"permissions": [
"background",
"contextMenus",
"geolocation",
"management",
"topSites",
"bookmarks",
"unlimitedStorage",
"topSites",
"identity",
/* ***重要*** 允许所有http,https请求可以跨域 */
"http://*/", "https://*/",
"chrome://favicon/",
"history",
"alarms",
"notifications",
"tabs",
"storage",
"activeTab",
"declarativeContent",
"webNavigation",
"webRequest",
"webRequestBlocking",
"cookies"
],
"version": "1.0.0"
}
最终的代码请看https://gitlab.com/qiaen/magic-postman
看到这篇文章的小伙伴们加油,砥砺前行,在编程的路上越走越顺,用双手创造美好的生活!
有疑问加站长微信联系(非本文作者)