美文网首页前端大杂烩
从零开始用Axios撸一个Postman Chrome浏览器插件

从零开始用Axios撸一个Postman Chrome浏览器插件

作者: 写写而已 | 来源:发表于2019-10-16 21:41 被阅读0次
    image.png
    image.png

    自己在学习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
    看到这篇文章的小伙伴们加油,砥砺前行,在编程的路上越走越顺,用双手创造美好的生活!

    相关文章

      网友评论

        本文标题:从零开始用Axios撸一个Postman Chrome浏览器插件

        本文链接:https://www.haomeiwen.com/subject/eocpmctx.html