├── .babelrc ├── .eslintrc ├── .gitignore ├── .npmignore ├── .npmrc ├── .postcssrc ├── CHANGELOG.md ├── cypress.json ├── docs ├── depoly.md ├── plugin.md ├── snapshot.md ├── snapshot.png ├── snapshot_application.png ├── snapshot_console.png ├── snapshot_element.png ├── snapshot_element2.png ├── snapshot_element3.png ├── snapshot_network.png ├── snapshot_plugin.png ├── snapshot_plugin_settings1.png └── snapshot_plugin_settings2.png ├── gulpfile.js ├── jest.config.js ├── package-lock.json ├── package.json ├── public ├── favicon.ico ├── index.html ├── index_cdn.html ├── mint-ui.min.css ├── mint-ui.min.js ├── style_import1.css ├── style_import2.css ├── style_link.css ├── test_application.js ├── test_console.js ├── test_network.js ├── test_plugin.js └── vue.js ├── readme.md ├── src ├── App.vue ├── assets │ └── icons │ │ ├── 2xx.png │ │ ├── 3xx.png │ │ ├── 4xx.png │ │ ├── 5xx.png │ │ ├── add-disable.png │ │ ├── add.png │ │ ├── ban.png │ │ ├── ban_disable.png │ │ ├── chrome_logo.png │ │ ├── close-disable.png │ │ ├── close.png │ │ ├── collapse.png │ │ ├── edit-disable.png │ │ ├── edit.png │ │ ├── expand.png │ │ ├── refresh.png │ │ ├── save-disable.png │ │ ├── save.png │ │ ├── setting.png │ │ └── transparent_bg.png ├── components │ ├── VFootBar.vue │ ├── VHighlightView.vue │ ├── VIcon.vue │ ├── VJSONViewer │ │ ├── JSONTextBlock.vue │ │ ├── JSONTextInlineBlock.vue │ │ ├── VJSONViewer.vue │ │ └── index.js │ ├── VTabBar.vue │ ├── VTabBarItem.vue │ └── index.js ├── constants │ ├── PanelType.js │ └── index.js ├── main.js ├── panels │ ├── application │ │ ├── ApplicationPanel.vue │ │ ├── TabStorage.vue │ │ └── XStorage.js │ ├── console │ │ ├── ConsolePanel.vue │ │ ├── Message.vue │ │ ├── TextBlock.vue │ │ └── TextInlineBlock.vue │ ├── element │ │ ├── BoxModel.vue │ │ ├── ElementPanel.vue │ │ ├── NodeLink.vue │ │ ├── NodeView.vue │ │ ├── StyleColorValue.vue │ │ ├── StyleProperty.vue │ │ ├── StyleRule.vue │ │ ├── TabComputed.vue │ │ ├── TabStyles.vue │ │ └── Tag.vue │ ├── index.js │ ├── network │ │ ├── HttpStatus.js │ │ ├── NetworkPanel.vue │ │ ├── NetworkRequest.vue │ │ ├── RequestType.js │ │ ├── TabHeaders.vue │ │ ├── TabPreview.vue │ │ └── TabResponse.vue │ └── settings │ │ └── SettingsPanel.vue ├── plugins │ ├── Plugin.js │ ├── index.js │ ├── pluginEvents.js │ └── pluginManager.js ├── polyfill.js ├── styles │ ├── _global.scss │ ├── _mixins.scss │ ├── _triangles.scss │ └── _variables.scss └── utils │ ├── EventBus.js │ ├── Logger.js │ ├── TaskScheduler.js │ ├── consoleHooks.js │ ├── filters.js │ ├── index.js │ ├── miscs.js │ └── style.js ├── tests ├── api │ ├── data │ │ ├── response.css │ │ ├── response.gif │ │ ├── response.html │ │ ├── response.jpg │ │ ├── response.js │ │ ├── response.json │ │ ├── response.png │ │ ├── response.svg │ │ └── response.txt │ └── index.js ├── e2e │ ├── .eslintrc │ ├── plugins │ │ └── index.js │ ├── specs │ │ └── test.js │ └── support │ │ ├── commands.js │ │ └── index.js └── unit │ ├── .eslintrc │ └── HelloWorld.spec.js ├── tsconfig.json └── vue.config.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "@vue/app" 4 | ], 5 | "plugins": [ 6 | [ 7 | "component", 8 | { 9 | "libraryName": "mint-ui", 10 | "style": true 11 | } 12 | ] 13 | ] 14 | } -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "extends": [ 4 | "plugin:vue/essential", 5 | "eslint:recommended" 6 | ], 7 | "env": { 8 | "browser": true, 9 | "node": true 10 | }, 11 | "globals": { 12 | }, 13 | "rules": { 14 | "no-console": "off", 15 | "no-case-declarations": "off" 16 | } 17 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /dist 4 | /demo 5 | 6 | /tests/e2e/videos/ 7 | /tests/e2e/screenshots/ 8 | 9 | # local env files 10 | .env.local 11 | .env.*.local 12 | 13 | # Log files 14 | npm-debug.log* 15 | yarn-debug.log* 16 | yarn-error.log* 17 | 18 | # Editor directories and files 19 | .idea 20 | .vscode 21 | *.suo 22 | *.ntvs* 23 | *.njsproj 24 | *.sln 25 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | * 2 | */ 3 | !dist/*.js 4 | !package*.json -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | git-tag-version=false -------------------------------------------------------------------------------- /.postcssrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": { 3 | "autoprefixer": {} 4 | } 5 | } -------------------------------------------------------------------------------- /cypress.json: -------------------------------------------------------------------------------- 1 | { 2 | "pluginsFile": "tests/e2e/plugins/index.js" 3 | } 4 | -------------------------------------------------------------------------------- /docs/depoly.md: -------------------------------------------------------------------------------- 1 | ## 部署 2 | 3 | 发布到 npm 仓库: 4 | 5 | ```bash 6 | npm run semantic-release 7 | ``` 8 | 9 | 更新 github.io 在线示例: 10 | 11 | ```bash 12 | npm run depoly 13 | ``` 14 | 15 | 本地调试 16 | 17 | ```js 18 | const script = document.createElement("script"); 19 | script.src = "http://localhost:8081/app.js"; 20 | script.onload = function() { 21 | new window.WebConsole(); 22 | }; 23 | document.body.appendChild(script); 24 | ``` 25 | -------------------------------------------------------------------------------- /docs/plugin.md: -------------------------------------------------------------------------------- 1 | # web-console 插件开发 2 | 3 | 通过插件可以增强 web-console 的能力,扩大使用场景。web-console 插件基于 Vue 组件开发,提供了丰富的 API、生命周期方法、内置组件、自定义偏好设置,同时提供了一个初始的插件开发项目模板方便开发、测试和发布。 4 | 5 | ## 一个简单的插件示例 6 | 7 | 下面是一个简单的插件示例。 8 | 9 | 首先,创建一个文件 MyPlugin.js 用于定义插件。 10 | 11 | ```js 12 | export default function(WebConsole, options) { 13 | return new WebConsole.Plugin({ 14 | id: "myPlugin", 15 | name: "我的插件", 16 | component: { 17 | render(h) { 18 | return h("h1", null, "这是我的第一个插件"); 19 | } 20 | } 21 | }); 22 | } 23 | ``` 24 | 25 | 然后通过`WebConsole.use()`方法注册插件,当 web-console 运行起来后,插件会自动加载到主面板。 26 | 27 | ```js 28 | // 使用插件 29 | import WebConsole from "@whinc/web-console"; 30 | import MyPlugin from "my-plugin"; 31 | 32 | WebConsole.use(MyPlugin); 33 | new WebConsole(); 34 | ``` 35 | 36 | 运行截图如下: 37 | 38 | ![](snapshot_plugin.png) 39 | 40 | ## 创建插件 41 | 42 | web-console 插件是一个返回`WebConsole.Plugin`实例的函数,函数的第一个参数是`WebConsole`对象,第二个参数是传递给插件的参数。 43 | 44 | ```js 45 | function MyPlugin(WebConsole, options) { 46 | return new WebConsole.Plugin({ 47 | /*...*/ 48 | }); 49 | } 50 | ``` 51 | 52 | `WebConsole.Plugin`类是所有插件的基类,返回的插件实例必须继承自它,它的构造函数接受一个配置参数,支持的字段及取值如下: 53 | 54 | | 字段 | 类型 | 必填 | 备注 | 55 | | --------- | ------------ | ----- | ------------------------------------ | 56 | | id | string | true | 不能与已安装的插件 id 相同 | 57 | | name | string | false | 插件名称,用于显示,缺省时与 id 相同 | 58 | | component | VueComponent | true | 插件主面板,是一个 Vue 组件 | 59 | | settings | Object | false | 增加到设置面板的设置项 | 60 | 61 | > 参数配置中的 component 注入了一些 web-console 组件,可以直接使用。(后续补充关于这些内置组件的文档) 62 | 63 | ## 注册插件 64 | 65 | 通过`WebConsole.use()`方法注册插件。 66 | 67 | ```js 68 | WebConsole.use(MyPlugin, {/*...*/}) 69 | 70 | new WebConsole(}) 71 | ``` 72 | 73 | > 注意:插件必须在 WebConsole 实例化之前注册,否则不生效。 74 | 75 | ## 插件生命周期 76 | 77 | 目前插件支持如下生命周期方法,这些插件方法在`component`所赋值的组件中可用。 78 | 79 | | 生命周期方法 | 执行时机 | 备注 | 80 | | --------------------------------------------------- | ---------------------------- | ----------------------------- | 81 | | `onWebConsoleReady(hostProxy)` | 主面板首次渲染完成时 | 可访问 DOM 和配置,仅执行一次 | 82 | | `onWebConsoleShow(hostProxy)` | 主面板从不可见变为**可见**时 | | 83 | | `onWebConsoleHide(hostProxy)` | 主面板从可见变为**不可见**时 | 84 | | `onWebConsoleTabChanged(hostProxy, newTab, oldTab)` | 切换不同子面板时 | 85 | | `onWebConsoleSettingsLoaded(hostProxy, settings)` | 偏好设置首次加载时 | 86 | | `onWebConsoleSettingsChanged(hostProxy, settings)` | 偏好设置变化时 | 87 | 88 | 使用示例: 89 | 90 | ```js 91 | export default function(WebConsole, options) { 92 | return new WebConsole.Plugin({ 93 | id: "myPlugin", 94 | name: "我的插件", 95 | component: { 96 | render(h) { 97 | return h("h1", null, "这是我的第一个插件"); 98 | }, 99 | methods: { 100 | onWebConsoleShow() { 101 | console.log("Show"); 102 | }, 103 | onWebConsoleHide() { 104 | console.log("Hide"); 105 | } 106 | } 107 | } 108 | }); 109 | } 110 | ``` 111 | 112 | ## 插件偏好设置 113 | 114 | web-console 有一个集中的偏好设置界面(点击主面板右上角的齿轮进入),插件可以在其中添加自己的偏好设置项,添加方式是在创建插件时指定`settings`属性,该属性接受一个数组,数组每个元素是一个设置描述对象。 115 | 116 | 支持的设置描述对象如下(还会支持更多): 117 | 118 | ```js 119 | // 复选框 120 | { 121 | type: 'checkbox', 122 | desc: '是否开启', 123 | name: 'isOpen', 124 | value: false 125 | } 126 | 127 | // 下拉选择框 128 | { 129 | type: "select", 130 | desc: "选择颜色", 131 | name: 'color', 132 | value: 'red', 133 | options: [ 134 | { text: "红色", value: 'red' }, 135 | { text: "绿色", value: 'green' }, 136 | { text: "蓝色", value: 'blue' }, 137 | ] 138 | } 139 | ``` 140 | 141 | 使用示例: 142 | 143 | ```js 144 | export default function(WebConsole, options) { 145 | return new WebConsole.Plugin({ 146 | id: "myPlugin", 147 | name: "我的插件", 148 | component: { 149 | render(h) { 150 | return h("h1", null, "这是我的第一个插件"); 151 | }, 152 | methods: { 153 | onWebConsoleSettingsLoaded(hostProxy, settings) { 154 | console.log("加载设置:", settings); 155 | }, 156 | onWebConsoleSettingsChanged(hostProxy, settings) { 157 | console.log("设置变化:", settings); 158 | } 159 | } 160 | }, 161 | settings: [ 162 | { 163 | type: "checkbox", 164 | desc: "是否选中", 165 | name: "isChecked", 166 | value: false 167 | } 168 | ] 169 | }); 170 | } 171 | ``` 172 | 173 | 运行截图: 174 | 175 | ![](snapshot_plugin_settings1.png) 176 | 177 | ![](snapshot_plugin_settings2.png) 178 | -------------------------------------------------------------------------------- /docs/snapshot.md: -------------------------------------------------------------------------------- 1 | # Element 面板 2 | 3 | ![element](snapshot_element.png) 4 | 5 | ![element](snapshot_element2.png) 6 | 7 | ![element](snapshot_element3.png) 8 | 9 | # Console 面板 10 | 11 | ![console](snapshot_console.png) 12 | 13 | # Network 面板 14 | 15 | ![network](snapshot_network.png) 16 | 17 | # Application 面板 18 | 19 | ![application](snapshot_application.png) 20 | -------------------------------------------------------------------------------- /docs/snapshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/whinc/web-console/207247e3144ccbcab19cb3bd74d996eb2461c4c4/docs/snapshot.png -------------------------------------------------------------------------------- /docs/snapshot_application.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/whinc/web-console/207247e3144ccbcab19cb3bd74d996eb2461c4c4/docs/snapshot_application.png -------------------------------------------------------------------------------- /docs/snapshot_console.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/whinc/web-console/207247e3144ccbcab19cb3bd74d996eb2461c4c4/docs/snapshot_console.png -------------------------------------------------------------------------------- /docs/snapshot_element.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/whinc/web-console/207247e3144ccbcab19cb3bd74d996eb2461c4c4/docs/snapshot_element.png -------------------------------------------------------------------------------- /docs/snapshot_element2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/whinc/web-console/207247e3144ccbcab19cb3bd74d996eb2461c4c4/docs/snapshot_element2.png -------------------------------------------------------------------------------- /docs/snapshot_element3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/whinc/web-console/207247e3144ccbcab19cb3bd74d996eb2461c4c4/docs/snapshot_element3.png -------------------------------------------------------------------------------- /docs/snapshot_network.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/whinc/web-console/207247e3144ccbcab19cb3bd74d996eb2461c4c4/docs/snapshot_network.png -------------------------------------------------------------------------------- /docs/snapshot_plugin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/whinc/web-console/207247e3144ccbcab19cb3bd74d996eb2461c4c4/docs/snapshot_plugin.png -------------------------------------------------------------------------------- /docs/snapshot_plugin_settings1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/whinc/web-console/207247e3144ccbcab19cb3bd74d996eb2461c4c4/docs/snapshot_plugin_settings1.png -------------------------------------------------------------------------------- /docs/snapshot_plugin_settings2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/whinc/web-console/207247e3144ccbcab19cb3bd74d996eb2461c4c4/docs/snapshot_plugin_settings2.png -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | const pkgInfo = require("./package.json"); 2 | const child_process = require("child_process"); 3 | const process = require("process"); 4 | var gulp = require("gulp"); 5 | 6 | gulp.task("default", function(cb) { 7 | // 将你的默认的任务代码放在这 8 | }); 9 | 10 | let version = "prerelease"; 11 | 12 | gulp.task("version", cb => { 13 | const oldVersion = pkgInfo.version; 14 | console.log(oldVersion); 15 | console.log(process.argv, ";", process.argv0); 16 | child_process.exec("npm version " + version, (err, stdout, stderr) => { 17 | if (err) cb(err); 18 | console.log(stdout); 19 | }); 20 | }); 21 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | moduleFileExtensions: [ 3 | 'js', 4 | 'jsx', 5 | 'json', 6 | 'vue' 7 | ], 8 | transform: { 9 | '^.+\\.vue$': 'vue-jest', 10 | '^.+\\.jsx?$': 'babel-jest' 11 | }, 12 | moduleNameMapper: { 13 | '^@/(.*)$': '/src/$1' 14 | }, 15 | snapshotSerializers: [ 16 | 'jest-serializer-vue' 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@whinc/web-console", 3 | "version": "0.7.2", 4 | "author": "whincwu@163.com", 5 | "license": "MIT", 6 | "keywords": [ 7 | "console", 8 | "vConsole", 9 | "web-console", 10 | "webConsole", 11 | "log" 12 | ], 13 | "repository": "https://github.com/whinc/web-console", 14 | "main": "dist/web-console.umd.min.js", 15 | "scripts": { 16 | "serve": "vue-cli-service serve", 17 | "build:lib": "vue-cli-service build --target lib --name web-console --dest dist src/main.js", 18 | "depoly:lib": "npm run lint && npm run build:lib && cross-env NPM_TOKEN=$NPM_TOKEN semantic-release && npm run changelog", 19 | "build:demo": "vue-cli-service build --target app --dest demo", 20 | "depoly:demo": "npm run build:demo && npx gh-pages -d demo", 21 | "lint": "vue-cli-service lint src/**/*.js", 22 | "changelog": "conventional-changelog -p angular -i CHANGELOG.md -s -r 0", 23 | "report": "vue-cli-service build --report", 24 | "test:unit": "vue-cli-service test:unit", 25 | "test:api": "cd ./tests/api && nodemon index.js", 26 | "e2e": "vue-cli-service e2e" 27 | }, 28 | "dependencies": { 29 | "cookie_js": "^1.2.2", 30 | "highlight.js": "^9.14.2", 31 | "lodash.clonedeep": "^4.5.0", 32 | "mint-ui": "^2.2.13", 33 | "specificity": "^0.4.1", 34 | "url-search-params": "^1.1.0", 35 | "vue": "^2.5.22", 36 | "vue-infinite-scroll": "^2.0.2" 37 | }, 38 | "devDependencies": { 39 | "@commitlint/cli": "^7.5.0", 40 | "@commitlint/config-conventional": "^7.5.0", 41 | "@vue/cli-plugin-babel": "^3.4.1", 42 | "@vue/cli-plugin-e2e-cypress": "^3.4.1", 43 | "@vue/cli-plugin-eslint": "^3.4.1", 44 | "@vue/cli-plugin-unit-jest": "^3.5.1", 45 | "@vue/cli-service": "^3.4.1", 46 | "@vue/test-utils": "^1.0.0-beta.28", 47 | "babel-core": "^7.0.0-0", 48 | "babel-jest": "^22.4.4", 49 | "babel-plugin-component": "^1.1.1", 50 | "conventional-changelog-angular": "^5.0.3", 51 | "conventional-changelog-cli": "^2.0.12", 52 | "cross-env": "^5.2.0", 53 | "cz-conventional-changelog": "^2.1.0", 54 | "gh-pages": "^1.2.0", 55 | "gulp": "^4.0.0", 56 | "husky": "^1.3.1", 57 | "i": "^0.3.6", 58 | "node-sass": "^4.11.0", 59 | "nodemon": "^1.18.9", 60 | "prettier": "1.14.2", 61 | "pretty-quick": "^1.10.0", 62 | "sass-loader": "^6.0.6", 63 | "semantic-release": "^15.13.3", 64 | "semantic-release-cli": "^4.1.1", 65 | "vue-template-compiler": "^2.5.22" 66 | }, 67 | "browserslist": [ 68 | "> 1%", 69 | "last 2 versions", 70 | "not ie <= 8" 71 | ], 72 | "husky": { 73 | "hooks": { 74 | "pre-commit": "pretty-quick --staged", 75 | "commit-msg": "commitlint -E HUSKY_GIT_PARAMS" 76 | } 77 | }, 78 | "prettier": { 79 | "printWidth": 120 80 | }, 81 | "commitlint": { 82 | "extends": [ 83 | "@commitlint/config-conventional" 84 | ] 85 | }, 86 | "release": { 87 | "ci": false, 88 | "plugins": [ 89 | "@semantic-release/commit-analyzer", 90 | "@semantic-release/release-notes-generator", 91 | "@semantic-release/npm" 92 | ] 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/whinc/web-console/207247e3144ccbcab19cb3bd74d996eb2461c4c4/public/favicon.ico -------------------------------------------------------------------------------- /public/index_cdn.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 测试CDN下载 7 | 8 | 9 | 10 | 11 | 14 | 15 | -------------------------------------------------------------------------------- /public/style_import1.css: -------------------------------------------------------------------------------- 1 | @import url("style_import2.css"); 2 | 3 | /* 测试元素审查 */ 4 | #element { 5 | border-color: green; 6 | } 7 | .xyz { 8 | border: 2px dashed blue; 9 | padding: 2px; 10 | } 11 | -------------------------------------------------------------------------------- /public/style_import2.css: -------------------------------------------------------------------------------- 1 | /* 测试元素审查 */ 2 | #element { 3 | border-color: yellow; 4 | } 5 | .xyz { 6 | border: 3px dotted yellow; 7 | padding: 3px; 8 | } 9 | -------------------------------------------------------------------------------- /public/style_link.css: -------------------------------------------------------------------------------- 1 | /* 测试元素审查 */ 2 | #element { 3 | background-color: white; 4 | } 5 | .xyz { 6 | border: 1px solid red; 7 | padding: 1px; 8 | } 9 | -------------------------------------------------------------------------------- /public/test_application.js: -------------------------------------------------------------------------------- 1 | window.$application = (function() { 2 | function testWriteCookie(n = 1) { 3 | for (var i = 0; i < n; ++i) { 4 | const obj = { timestamp: Date.now() }; 5 | document.cookie = i + "=" + JSON.stringify(obj); 6 | } 7 | } 8 | 9 | function testWriteLocalStorage(n = 1) { 10 | for (var i = 0; i < n; ++i) { 11 | const obj = { timestamp: Date.now() }; 12 | window.localStorage.setItem(i, JSON.stringify(obj)); 13 | } 14 | } 15 | 16 | function testWriteSessionStorage(n = 1) { 17 | for (var i = 0; i < n; ++i) { 18 | const obj = { timestamp: Date.now() }; 19 | window.sessionStorage.setItem(i, JSON.stringify(obj)); 20 | } 21 | } 22 | 23 | return { 24 | testWriteCookie, 25 | testWriteSessionStorage, 26 | testWriteLocalStorage 27 | }; 28 | })(); 29 | -------------------------------------------------------------------------------- /public/test_console.js: -------------------------------------------------------------------------------- 1 | window.$console = (function() { 2 | function testLogLevel() { 3 | console.log("log"); 4 | console.info("info"); 5 | console.debug("debug"); 6 | console.warn("warn"); 7 | console.error("error"); 8 | } 9 | 10 | function testLogParams() { 11 | console.log(100, true, undefined, null, { a: 100 }); 12 | } 13 | 14 | // 测试log格式化输出 15 | function testLogFormat(value, format) { 16 | console.log("console.log(%" + format + ", %s)", JSON.stringify(value)); 17 | console.log(format, value); 18 | } 19 | 20 | function testIntervalLog(interval = 1000) { 21 | console.log(Date.now()); 22 | setTimeout(testIntervalLog.bind(null, arguments), interval); 23 | } 24 | 25 | function testException() { 26 | // 测试直接输出 Error 对象 27 | console.error(new ReferenceError("reference error")); 28 | 29 | // 测试未捕获的全局异常 30 | setTimeout(function() { 31 | throw new Error("This is a uncaught exception"); 32 | }); 33 | 34 | // 测试未处理的 rejected Promise 35 | Promise.reject("uncaught rejected promise"); 36 | } 37 | 38 | // 打印对象 39 | function testObject() { 40 | let obj2 = { 41 | // 测试数值类型 42 | a: 1, 43 | a5: { a: 1, b: 2, c: { a: 1, b: 2 } }, 44 | // 测试数组 45 | a3: [1, 2], 46 | a4: [1, 2, [1, 2, [1, 2, 3], 3], 3], 47 | // 测试属性值很长时的展示 48 | a1: "10101010101010101010101010101010101010101010101001010101", 49 | a2: 10101010101010101010110101010101010110101011010101011010101, 50 | // 测试字符串类型 51 | b: "b", 52 | // 测试转义字符展示 53 | b1: '"b1"', 54 | c: true, 55 | d: undefined, 56 | e: null, 57 | f: Symbol(), 58 | g: { 59 | a: 1, 60 | f1: () => { 61 | return 1; 62 | }, 63 | f2: function() { 64 | return 2; 65 | }, 66 | b: "b", 67 | c: { 68 | a: 1, 69 | b: "b" 70 | }, 71 | d: undefined, 72 | e: null 73 | }, 74 | [Symbol()]: 10, 75 | [Symbol("age")]: 28, 76 | f1: function() {}, 77 | f2: () => {}, 78 | f3: function say() {}, 79 | Z: "Z", 80 | M: "m", 81 | z: "z", 82 | // 测试排序规则 83 | __a: "public __a", 84 | _a: "public _a", 85 | $a: "public $a" 86 | }; 87 | Object.defineProperties(obj2, { 88 | // value 89 | h: { 90 | enumerable: true, 91 | configurable: true, 92 | value: 10, 93 | writable: false 94 | }, 95 | // value 96 | i: { 97 | enumerable: false, 98 | configurable: false, 99 | value: 10, 100 | writable: true 101 | }, 102 | // getter and setter 103 | j: { 104 | get: () => { 105 | return Math.random() + ""; 106 | }, 107 | set: v => {} 108 | }, 109 | J: { 110 | get: () => { 111 | return "" + Math.random(); 112 | }, 113 | set: v => {} 114 | }, 115 | // only getter 116 | k: { 117 | get: function() { 118 | return { 119 | a: 1, 120 | b: "b" 121 | }; 122 | } 123 | }, 124 | K: { 125 | get: function() { 126 | return [ 127 | { 128 | a: 1, 129 | b: "b" 130 | } 131 | ]; 132 | } 133 | }, 134 | // only setter 135 | l: { 136 | set: function(v) {} 137 | }, 138 | [Symbol("a")]: { 139 | value: "1", 140 | enumerable: false 141 | }, 142 | // 测试属性排序规则 143 | __b: { 144 | value: "private __b", 145 | enumerable: false 146 | }, 147 | _b: { 148 | value: "private _b", 149 | enumerable: false 150 | }, 151 | $b: { 152 | value: "private $b", 153 | enumerable: false 154 | } 155 | }); 156 | Object.defineProperties(obj2.g, { 157 | h: { 158 | enumerable: true, 159 | configurable: true, 160 | value: 10 161 | }, 162 | i: { 163 | enumerable: false, 164 | configurable: false, 165 | value: 10 166 | }, 167 | j: { 168 | get: () => { 169 | return Math.random(); 170 | }, 171 | set: v => {} 172 | }, 173 | k: { 174 | get: function() { 175 | return Math.random(); 176 | }, 177 | set: function() {} 178 | } 179 | }); 180 | Object.defineProperties(obj2.g.c, { 181 | c: { 182 | enumerable: false, 183 | configurable: false, 184 | value: 10 185 | }, 186 | d: { 187 | get: function() { 188 | return Math.random(); 189 | }, 190 | set: function() {} 191 | } 192 | }); 193 | console.log("对象全属性:", obj2); 194 | console.log("三种函数:", { 195 | f1: function() {}, 196 | f2: () => {}, 197 | f3: function say() {} 198 | }); 199 | var arr = []; 200 | arr["10"] = "10"; 201 | arr["29"] = "29"; 202 | arr[" "] = " "; // 0x20 203 | arr[" "] = " "; 204 | arr[" a"] = " a"; 205 | arr[" a"] = " a"; // 空白符处理 206 | arr["!"] = "!"; // 0x21 207 | arr["0"] = "0"; // 0x30 208 | arr["1"] = "1"; // 0x31 209 | arr["2"] = "2"; // 0x31 210 | arr[":"] = ":"; // 0x3A 211 | arr["A"] = "A"; // 0x41 212 | arr["B"] = "B"; // 0x42 213 | arr["["] = "["; // 0x5B 214 | arr["a"] = "a"; // 0x61 215 | arr["b"] = "b"; // 0x62 216 | arr["{"] = "{"; // 0x7B 217 | console.log("数组 key 排序:", arr); 218 | 219 | const thumbObj = { 220 | extra: `{"num_total":300,"num_left":157,"discount":"{\"create_time\":1531970051000,\"discount_data\":\"[{\\\"amount_min\\\":\\\"100\\\",\\\"amount_cut\\\":\\\"10\\\"},{\\\"amount_min\\\":\\\"200\\\",\\\"amount_cut\\\":\\\"25\\\"},{\\\"amount_min\\\":\\\"300\\\",\\\"amount_cut\\\":\\\"50\\\"}]\",\"discount_id\":3,\"discount_name\":\"满减-yanger\",\"discount_prod\":\"[\\\"WM-180101001\\\",\\\"WM-180101002\\\",\\\"WM-180101004\\\",\\\"WM-180101006\\\",\\\"WM-180101008\\\",\\\"WM-180525102\\\",\\\"WM-180101017\\\"]\",\"discount_provider\":\"0\",\"discount_status\":\"1\",\"discount_type\":\"1\",\"update_time\":1540458898000}"}`, 221 | isChange: false 222 | }; 223 | const thumbObj2 = [ 224 | `{"num_total":300,"num_left":157,"discount":"{\"create_time\":1531970051000,\"discount_data\":\"[{\\\"amount_min\\\":\\\"100\\\",\\\"amount_cut\\\":\\\"10\\\"},{\\\"amount_min\\\":\\\"200\\\",\\\"amount_cut\\\":\\\"25\\\"},{\\\"amount_min\\\":\\\"300\\\",\\\"amount_cut\\\":\\\"50\\\"}]\",\"discount_id\":3,\"discount_name\":\"满减-yanger\",\"discount_prod\":\"[\\\"WM-180101001\\\",\\\"WM-180101002\\\",\\\"WM-180101004\\\",\\\"WM-180101006\\\",\\\"WM-180101008\\\",\\\"WM-180525102\\\",\\\"WM-180101017\\\"]\",\"discount_provider\":\"0\",\"discount_status\":\"1\",\"discount_type\":\"1\",\"update_time\":1540458898000}"}` 225 | ]; 226 | thumbObj2.push(thumbObj); 227 | console.log("测试对象缩略信息:", thumbObj, thumbObj2); 228 | } 229 | 230 | // 格式化输出 231 | function testFormat() { 232 | var numInt = 1; 233 | var numFloat = 1.23; 234 | var str = "hello"; 235 | var obj = { 236 | a: 1, 237 | b: 2, 238 | c: { 239 | d: 3 240 | } 241 | }; 242 | var arr = [1, 2, [3, obj]]; 243 | // 测试占位符的支持类型 244 | testLogFormat(numInt, "%i"); 245 | testLogFormat(numFloat, "%i"); 246 | testLogFormat(numInt, "%d"); 247 | testLogFormat(numFloat, "%d"); 248 | testLogFormat(numInt, "%f"); 249 | testLogFormat(numFloat, "%f"); 250 | testLogFormat(obj, "%s"); 251 | testLogFormat(obj, "%o"); 252 | testLogFormat(arr, "%o"); 253 | testLogFormat(obj, "%O"); 254 | testLogFormat(arr, "%O"); 255 | // 测试占位符缺少对应的参数 256 | console.log('console.log("-%s%i%d%f%o%O%c-")'); 257 | console.log("-%s%i%d%f%o%O%c-"); 258 | /* 测试占位符之间的空白符 */ 259 | // 期望输出 "a1b2c 3" 260 | console.log("a%sb%sc", 1, 2, 3); 261 | // 测试参数多余占位符 262 | console.log('console.log("-%s-", 99, {a: "1"})'); 263 | console.log("-%s-", 99, { a: 1 }); 264 | // 测试占位符和参数的高亮 265 | console.log("console.log('%s-%s-%s-%s-%s-%s-%s', 100, true, null, undefined, '100', {}, [])"); 266 | console.log("%s-%s-%s-%s-%s-%s-%s", 100, true, null, undefined, "100", {}, []); 267 | // 测试占位符 %c 的展示 268 | console.log( 269 | `%c111 222 %c 333 %c %c %O %o %i %f %s %%`, 270 | "color: white; background-color: rgba(0, 116, 217, 0.69); padding: 2px 5px; border-radius: 2px", 271 | "color: #0074D9", 272 | "", 273 | "color: white; background-color: rgba(255, 65, 54, 0.69); padding: 2px 5px; margin-right: 5px; border-radius: 2px", 274 | { a: 444 }, 275 | { b: 555 }, 276 | 666, 277 | 777, 278 | "888" 279 | ); 280 | } 281 | 282 | return { 283 | testLogLevel: testLogLevel, 284 | testLogParams: testLogParams, 285 | testFormat: testFormat, 286 | testException: testException, 287 | testIntervalLog: testIntervalLog, 288 | testObject: testObject 289 | }; 290 | })(); 291 | -------------------------------------------------------------------------------- /public/test_network.js: -------------------------------------------------------------------------------- 1 | window.$network = (function() { 2 | var baseURL = "http://localhost:8090"; 3 | var nodeApi = "https://nodejs.org/dist/latest-v8.x/docs/api/index.json"; 4 | 5 | function testXMLHttpRequest() {} 6 | 7 | function testFetch() { 8 | if (typeof window.fetch !== "function") { 9 | console.warn("current browser version don't support fetch api"); 10 | return; 11 | } 12 | 13 | var url = baseURL + "/get_status/" + 200; 14 | 15 | // 不带选项 16 | var options = { 17 | method: "GET", 18 | headers: { 19 | "Content-Type": "text/plain" 20 | } 21 | }; 22 | fetch(url); 23 | fetch(url, options); 24 | 25 | fetch(new Request(url)); 26 | fetch(new Request(url, options)); 27 | 28 | fetch(new Request(url, options), { 29 | headers: { 30 | "Content-Type": "text/html" 31 | } 32 | }); 33 | } 34 | 35 | // 测试 HTTP 状态码 36 | function testHTTPStatus() { 37 | request({ url: nodeApi }); 38 | [100, 200, 300, 400, 500].forEach(function(status) { 39 | request({ url: baseURL + "/get_status/" + status }); 40 | }); 41 | } 42 | 43 | /** 44 | * 测试请求参数 45 | */ 46 | function testRequestParams(type = "get") { 47 | var email = "xx@yy.com"; 48 | var password = "zz"; 49 | 50 | switch (type) { 51 | case "get": 52 | // GET 53 | request({ url: baseURL + "/get?email=" + email + "&password=" + password + "&a=&b" }); 54 | break; 55 | case "post:raw": 56 | request({ 57 | url: baseURL + "/post", 58 | method: "POST", 59 | data: "hello" 60 | }); 61 | break; 62 | case "post:raw:text": 63 | request({ 64 | url: baseURL + "/post", 65 | method: "POST", 66 | requestHeaders: { 67 | "Content-Type": "text/plain" 68 | }, 69 | data: "hello" 70 | }); 71 | break; 72 | case "post:raw:json": 73 | request({ 74 | url: baseURL + "/post", 75 | method: "POST", 76 | requestHeaders: { 77 | "Content-Type": "application/json;charset=UTF-8" 78 | }, 79 | data: '{"email": aa}' 80 | }); 81 | break; 82 | case "post:x-www-form-urlencoded": 83 | request({ 84 | url: baseURL + "/post", 85 | method: "POST", 86 | requestHeaders: { 87 | "Content-Type": "application/x-www-form-urlencoded" 88 | }, 89 | data: "email=" + encodeURIComponent(email) + "&password=" + encodeURIComponent(password) 90 | }); 91 | break; 92 | case "post:form-data": 93 | var formData = new FormData(); 94 | formData.append("email", email); 95 | formData.append("password", password); 96 | 97 | request({ 98 | url: baseURL + "/post", 99 | method: "POST", 100 | data: formData 101 | }); 102 | break; 103 | } 104 | } 105 | 106 | // 测试响应数据类型 107 | // 参考 http://devdocs.io/http/basics_of_http/mime_types 108 | function testResponseData() { 109 | const mimeTypeList = [ 110 | "application/json", 111 | "application/javascript", 112 | "text/css", 113 | "text/plain", 114 | "text/html", 115 | "image/jpeg", 116 | "image/png", 117 | "image/gif", 118 | "image/svg+xml" 119 | ]; 120 | 121 | mimeTypeList.forEach(mimeType => { 122 | request({ url: baseURL + "/get_data/?mime_type=" + encodeURIComponent(mimeType) }); 123 | }); 124 | } 125 | 126 | return { 127 | testHTTPStatus: testHTTPStatus, 128 | testResponseData: testResponseData, 129 | testRequestParams: testRequestParams, 130 | testFetch: testFetch, 131 | testXMLHttpRequest: testXMLHttpRequest 132 | }; 133 | })(); 134 | 135 | function request(options) { 136 | options = options || {}; 137 | var url = options.url; 138 | var method = options.method || "GET"; 139 | var data = options.data || null; 140 | var requestHeaders = options.requestHeaders || {}; 141 | 142 | /* XMLHttpRequest */ 143 | var xhr = new window.XMLHttpRequest(); 144 | xhr.onreadystatechange = function() { 145 | // console.log("readyState:", this.readyState); 146 | if (this.readyState === XMLHttpRequest.DONE) { 147 | // console.log(this.getResponseHeader("content-type")); 148 | // console.log(this.response); 149 | } 150 | }; 151 | xhr.open(method, url); 152 | Object.keys(requestHeaders).forEach(key => { 153 | xhr.setRequestHeader(key, requestHeaders[key]); 154 | }); 155 | xhr.send(data); 156 | 157 | /* fetch */ 158 | if (typeof window.fetch === "function") { 159 | fetch(url, { 160 | method: method, 161 | body: data, 162 | headers: requestHeaders 163 | }); 164 | } else { 165 | console.warn('current brwoser don\'t support "fetch"'); 166 | } 167 | } 168 | -------------------------------------------------------------------------------- /public/test_plugin.js: -------------------------------------------------------------------------------- 1 | function plugin1(WebConsole) { 2 | return new WebConsole.Plugin({ 3 | id: "plugin1", 4 | name: "插件1", 5 | component: function() { 6 | var plugin = this; 7 | var tag = plugin.toString(); 8 | return { 9 | data: function() { 10 | return { 11 | text: "plugin1 panel", 12 | activeTab: "A" 13 | }; 14 | }, 15 | methods: { 16 | onWebConsoleReady: function(hostProxy) { 17 | this.hostProxy = hostProxy; 18 | console.log(tag, "onWebConsoleReady"); 19 | }, 20 | onWebConsoleShow: function(hostProxy) { 21 | console.log(tag, "onWebConsoleShow"); 22 | }, 23 | onWebConsoleHide: function(hostProxy) { 24 | console.log(tag, "onWebConsoleHide"); 25 | }, 26 | onWebConsoleTabChanged: function(hostProxy, newVal, oldVal) { 27 | console.log(tag, "onWebConsoleTabChanged", newVal); 28 | }, 29 | onWebConsoleSettingsLoaded: function(hostProxy, settings) { 30 | console.log(tag, "onWebConsoleSettingsLoaded", settings); 31 | }, 32 | onWebConsoleSettingsChanged: function(hostProxy, settings) { 33 | console.log(tag, "onWebConsoleSettingsChanged", settings); 34 | }, 35 | hidePanel: function() { 36 | this.hostProxy.hidePanel(); 37 | }, 38 | printSettings: function() { 39 | console.log("settings:", this.hostProxy.getSettings()); 40 | } 41 | }, 42 | render: function(h) { 43 | var vm = this; 44 | return h( 45 | "div", 46 | { 47 | style: { 48 | display: "flex", 49 | "flex-direction": "column" 50 | } 51 | }, 52 | [ 53 | h( 54 | "div", 55 | { 56 | style: { 57 | flex: "1" 58 | } 59 | }, 60 | [ 61 | h( 62 | "button", 63 | { 64 | on: { 65 | click: this.hidePanel 66 | } 67 | }, 68 | "hide panel" 69 | ), 70 | h( 71 | "button", 72 | { 73 | style: { 74 | "margin-left": "10px" 75 | }, 76 | on: { 77 | click: this.printSettings 78 | } 79 | }, 80 | "print settings" 81 | ), 82 | h( 83 | "div", 84 | { 85 | style: { 86 | "margin-top": "20px", 87 | display: "flex", 88 | "flex-direction": "row" 89 | } 90 | }, 91 | ["setting", "close", "refresh", "ban", "edit", "save", "add", "cancel", "expand", "collapse"].map( 92 | name => 93 | h("VIcon", { 94 | style: { 95 | "margin-left": "10px", 96 | width: "20px", 97 | height: "20px" 98 | }, 99 | props: { 100 | name: name 101 | } 102 | }) 103 | ) 104 | ) 105 | ] 106 | ), 107 | h("VJSONViewer", { 108 | props: { 109 | value: { a: 1, b: "2", c: { a: 1, b: "2" } } 110 | } 111 | }), 112 | h("VHighlightView", { 113 | props: { 114 | code: "var a = 1;\nvar b 2;\nconsole.log('a + b =', a + b)", 115 | language: "javascript" 116 | } 117 | }), 118 | h( 119 | "VTabBar", 120 | { 121 | props: { 122 | value: vm.activeTab, 123 | equalWidth: true 124 | }, 125 | on: { 126 | input: function(id) { 127 | vm.activeTab = id; 128 | } 129 | } 130 | }, 131 | [ 132 | h("VTabBarItem", { props: { id: "A" } }, "Tab1"), 133 | h("VTabBarItem", { props: { id: "B" } }, "Tab2"), 134 | h("VTabBarItem", { props: { id: "C" } }, "Tab3"), 135 | h("VTabBarItem", { props: { id: "D" } }, "Tab4") 136 | ] 137 | ), 138 | h("v-foot-bar", { 139 | props: { 140 | buttons: [ 141 | { 142 | text: "Hide", 143 | click: function() { 144 | vm.hostProxy.hidePanel(); 145 | } 146 | } 147 | ] 148 | } 149 | }) // v-foot-bar 150 | ] 151 | ); // div 152 | } 153 | }; 154 | }, 155 | settings: [ 156 | { 157 | type: "checkbox", 158 | name: "plugin0", 159 | value: false, 160 | desc: "test plugin0" 161 | } 162 | ] 163 | }); 164 | } 165 | 166 | function plugin2(WebConsole) { 167 | return new WebConsole.Plugin({ 168 | id: "plugin2", 169 | name: "插件2", 170 | component: { 171 | data: function() { 172 | return { 173 | text: "plugin2 panel" 174 | }; 175 | }, 176 | render: function(h) { 177 | return h("div", {}, this.text); 178 | } 179 | }, 180 | settings: function() { 181 | return [ 182 | { 183 | type: "checkbox", 184 | name: this.id, 185 | value: false, 186 | desc: "test " + this.id 187 | } 188 | ]; 189 | } 190 | }); 191 | } 192 | 193 | window.$plugins = [plugin1, plugin2]; 194 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # web-console 2 | 3 | ![Github release](https://img.shields.io/npm/v/@whinc/web-console.svg) 4 | ![总下载量](https://img.shields.io/npm/dt/@whinc/web-console.svg) 5 | ![月下载量](https://img.shields.io/npm/dm/@whinc/web-console.svg) 6 | ![周下载量](https://img.shields.io/npm/dw/@whinc/web-console.svg) 7 | ![npm bundle size (minified + gzip)](https://img.shields.io/bundlephobia/minzip/@whinc/web-console.svg) 8 | ![LINCENSE](https://img.shields.io/github/license/mashape/apistatus.svg) 9 | 10 | web-console 是一款基于 H5 开发的移动端 Web 调试工具。其高度还原了 Chrome DevTools 的功能和交互,支持 webpack 打包和` 83 | 84 | ``` 85 | 86 | 通过下面代码初始化 87 | 88 | ```js 89 | new WebConsole(config); 90 | ``` 91 | 92 | ## API 93 | 94 | `WebConsole`构造参数如下: 95 | 96 | | 字段 | 类型 | 必填 | 备注 | 97 | | --------- | ------------ | ----- | ------------------------------------ | 98 | | panelVisible | bool | false | 是否自动弹窗主面板 | 99 | | activeTab | string | 'console' | 默认激活的 Tab 面板,支持'element', 'console', 'network', 'application', 以及插件的 id | 100 | | entryStyle | string | 'button' | 入口样式,支持两种'button'和'icon' | 101 | 102 | > 后续补充更多的配置参数和 API 接口 103 | 104 | ## 插件开发 105 | 106 | web-console 提供一些开箱即用的功能,如果这些无法满足你的需求,你还可以通过 web-console 提供的插件机制,添加第三方编写的插件来扩展功能。 107 | 108 | 可参考下面资源: 109 | 110 | - [插件开发文档](./docs/plugin.md) 111 | - [插件项目模板](https://github.com/whinc/web-console-plugin) 112 | 113 | ## 更新日志 114 | 115 | [CHANGELOG](CHANGELOG.md) 116 | 117 | ## 相似项目 118 | 119 | **Web** 120 | - [vConsole](https://github.com/Tencent/vConsole) A lightweight, extendable front-end developer tool for mobile web page. 121 | - [eruda](https://github.com/liriliri/eruda) Console for mobile browsers 122 | 123 | **Native** 124 | - [wt-console](https://github.com/WeBankFinTech/wt-console) A lightweight, extendable react-native developer and tester tool 125 | 126 | ## License 127 | 128 | MIT 129 | -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 56 | 57 | 241 | 242 | 279 | -------------------------------------------------------------------------------- /src/assets/icons/2xx.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/whinc/web-console/207247e3144ccbcab19cb3bd74d996eb2461c4c4/src/assets/icons/2xx.png -------------------------------------------------------------------------------- /src/assets/icons/3xx.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/whinc/web-console/207247e3144ccbcab19cb3bd74d996eb2461c4c4/src/assets/icons/3xx.png -------------------------------------------------------------------------------- /src/assets/icons/4xx.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/whinc/web-console/207247e3144ccbcab19cb3bd74d996eb2461c4c4/src/assets/icons/4xx.png -------------------------------------------------------------------------------- /src/assets/icons/5xx.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/whinc/web-console/207247e3144ccbcab19cb3bd74d996eb2461c4c4/src/assets/icons/5xx.png -------------------------------------------------------------------------------- /src/assets/icons/add-disable.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/whinc/web-console/207247e3144ccbcab19cb3bd74d996eb2461c4c4/src/assets/icons/add-disable.png -------------------------------------------------------------------------------- /src/assets/icons/add.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/whinc/web-console/207247e3144ccbcab19cb3bd74d996eb2461c4c4/src/assets/icons/add.png -------------------------------------------------------------------------------- /src/assets/icons/ban.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/whinc/web-console/207247e3144ccbcab19cb3bd74d996eb2461c4c4/src/assets/icons/ban.png -------------------------------------------------------------------------------- /src/assets/icons/ban_disable.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/whinc/web-console/207247e3144ccbcab19cb3bd74d996eb2461c4c4/src/assets/icons/ban_disable.png -------------------------------------------------------------------------------- /src/assets/icons/chrome_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/whinc/web-console/207247e3144ccbcab19cb3bd74d996eb2461c4c4/src/assets/icons/chrome_logo.png -------------------------------------------------------------------------------- /src/assets/icons/close-disable.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/whinc/web-console/207247e3144ccbcab19cb3bd74d996eb2461c4c4/src/assets/icons/close-disable.png -------------------------------------------------------------------------------- /src/assets/icons/close.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/whinc/web-console/207247e3144ccbcab19cb3bd74d996eb2461c4c4/src/assets/icons/close.png -------------------------------------------------------------------------------- /src/assets/icons/collapse.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/whinc/web-console/207247e3144ccbcab19cb3bd74d996eb2461c4c4/src/assets/icons/collapse.png -------------------------------------------------------------------------------- /src/assets/icons/edit-disable.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/whinc/web-console/207247e3144ccbcab19cb3bd74d996eb2461c4c4/src/assets/icons/edit-disable.png -------------------------------------------------------------------------------- /src/assets/icons/edit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/whinc/web-console/207247e3144ccbcab19cb3bd74d996eb2461c4c4/src/assets/icons/edit.png -------------------------------------------------------------------------------- /src/assets/icons/expand.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/whinc/web-console/207247e3144ccbcab19cb3bd74d996eb2461c4c4/src/assets/icons/expand.png -------------------------------------------------------------------------------- /src/assets/icons/refresh.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/whinc/web-console/207247e3144ccbcab19cb3bd74d996eb2461c4c4/src/assets/icons/refresh.png -------------------------------------------------------------------------------- /src/assets/icons/save-disable.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/whinc/web-console/207247e3144ccbcab19cb3bd74d996eb2461c4c4/src/assets/icons/save-disable.png -------------------------------------------------------------------------------- /src/assets/icons/save.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/whinc/web-console/207247e3144ccbcab19cb3bd74d996eb2461c4c4/src/assets/icons/save.png -------------------------------------------------------------------------------- /src/assets/icons/setting.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/whinc/web-console/207247e3144ccbcab19cb3bd74d996eb2461c4c4/src/assets/icons/setting.png -------------------------------------------------------------------------------- /src/assets/icons/transparent_bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/whinc/web-console/207247e3144ccbcab19cb3bd74d996eb2461c4c4/src/assets/icons/transparent_bg.png -------------------------------------------------------------------------------- /src/components/VFootBar.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 45 | 46 | 47 | 79 | -------------------------------------------------------------------------------- /src/components/VHighlightView.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 54 | 55 | 62 | 63 | 221 | -------------------------------------------------------------------------------- /src/components/VIcon.vue: -------------------------------------------------------------------------------- 1 | 27 | 28 | 55 | 56 | 66 | -------------------------------------------------------------------------------- /src/components/VJSONViewer/JSONTextBlock.vue: -------------------------------------------------------------------------------- 1 | 31 | 32 | 206 | 207 | 260 | -------------------------------------------------------------------------------- /src/components/VJSONViewer/JSONTextInlineBlock.vue: -------------------------------------------------------------------------------- 1 | 50 | 51 | 121 | 122 | 149 | -------------------------------------------------------------------------------- /src/components/VJSONViewer/VJSONViewer.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 35 | 36 | 41 | -------------------------------------------------------------------------------- /src/components/VJSONViewer/index.js: -------------------------------------------------------------------------------- 1 | export { default } from "./VJSONViewer.vue"; 2 | -------------------------------------------------------------------------------- /src/components/VTabBar.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 45 | 46 | 73 | -------------------------------------------------------------------------------- /src/components/VTabBarItem.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 29 | 30 | 72 | -------------------------------------------------------------------------------- /src/components/index.js: -------------------------------------------------------------------------------- 1 | export { default as VTabBar } from "./VTabBar"; 2 | export { default as VTabBarItem } from "./VTabBarItem"; 3 | export { default as VFootBar } from "./VFootBar"; 4 | export { default as VJSONViewer } from "./VJSONViewer"; 5 | export { default as VIcon } from "./VIcon"; 6 | export { default as VHighlightView } from "./VHighlightView"; 7 | -------------------------------------------------------------------------------- /src/constants/PanelType.js: -------------------------------------------------------------------------------- 1 | export default Object.freeze( 2 | Object.setPrototypeOf( 3 | { 4 | ELEMENT: "element", 5 | CONSOLE: "console", 6 | APPLICATION: "application", 7 | NETWORK: "network" 8 | }, 9 | { 10 | text(type) { 11 | switch (type) { 12 | case this.ELEMENT: 13 | return "Element"; 14 | case this.CONSOLE: 15 | return "Console"; 16 | case this.APPLICATION: 17 | return "Application"; 18 | case this.NETWORK: 19 | return "Network"; 20 | default: 21 | return type; 22 | } 23 | } 24 | } 25 | ) 26 | ); 27 | -------------------------------------------------------------------------------- /src/constants/index.js: -------------------------------------------------------------------------------- 1 | export { default as PanelType } from "./PanelType"; 2 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | import Vue from "vue"; 2 | import App from "./App.vue"; 3 | import "./polyfill"; 4 | import { consoleHooks, filters, isFunction } from "@/utils"; 5 | import { pluginManager, Plugin } from "@/plugins"; 6 | import { PanelType } from "@/constants"; 7 | import InfiniteScroll from "vue-infinite-scroll"; 8 | import "./styles/_global.scss"; 9 | 10 | // register filters 11 | Object.keys(filters).forEach(name => { 12 | Vue.filter(name, filters[name]); 13 | }); 14 | 15 | // Fix: 避免重复注册插件(从 mint-ui 库引入和从 vue-infinite-scroll 引入是同一个插件的两个不同实例) 16 | const installedPlugins = Vue._installedPlugins || []; 17 | const foundIndex = installedPlugins.findIndex(plugin => isFunction(plugin.bind) && isFunction(plugin.unbind)); 18 | if (foundIndex === -1) { 19 | Vue.use(InfiniteScroll); 20 | } else { 21 | console.warn('"vue-infinite-scroll" has been registered'); 22 | } 23 | 24 | // const logger = new Logger("[main.js]"); 25 | 26 | // 放在可滚动容器上,在滚动触顶或触底时,可以阻止背景层滚动 27 | Vue.directive("prevent-bkg-scroll", { 28 | bind(el) { 29 | let preventMove = false; 30 | el.addEventListener("touchstart", function() { 31 | // logger.log('touchstart scrollTop: %s, clientHeight: %s, scrollHeight: %s', el.scrollTop, el.clientHeight, el.scrollHeight) 32 | if (el.scrollTop <= 0) { 33 | // 滚动到顶部时将其设置为 1,避免触顶后不能向下滚动(IOS 上 scrollTop 会出现负数) 34 | el.scrollTop = 1; 35 | // 如果内容不足一屏,则禁用 touchmove 36 | if (el.scrollTop === 0) { 37 | preventMove = true; 38 | } 39 | } else if (el.scrollTop + el.clientHeight >= el.scrollHeight) { 40 | // 滚动到底部时,将滚动距离设置为最大可滚动距离 - 1,避免触底后不能向上滚动 41 | el.scrollTop = el.scrollHeight - el.clientHeight - 1; 42 | // 如果内容不足一屏 43 | if (el.scrollTop + el.clientHeight === el.scrollHeight) { 44 | preventMove = true; 45 | } 46 | } 47 | }); 48 | el.addEventListener("touchmove", function(e) { 49 | if (preventMove) { 50 | e.preventDefault(); 51 | } 52 | }); 53 | el.addEventListener("touchend", function() { 54 | preventMove = false; 55 | }); 56 | } 57 | }); 58 | 59 | Vue.config.productionTip = false; 60 | 61 | // WebConsole 只能创建一个实例 62 | let _webConsole = null; 63 | 64 | class WebConsole { 65 | // 注册插件 66 | static use(pluginCreator, options) { 67 | if (_webConsole) { 68 | console.warn("Plugin must be registered before creating WebConsole"); 69 | return; 70 | } 71 | 72 | if (!isFunction(pluginCreator)) { 73 | console.warn("Invalid plugin:", pluginCreator); 74 | return; 75 | } 76 | const plugin = pluginCreator.call(null, WebConsole, options); 77 | pluginManager.addPlugin(plugin); 78 | } 79 | 80 | constructor(options) { 81 | if (_webConsole) { 82 | console.warn("WebConsole has been initialized, return previous instance: %O", _webConsole); 83 | return _webConsole; 84 | } 85 | 86 | _webConsole = this; 87 | this._isLoaded = false; 88 | this._load(this._processOptions(options)); 89 | } 90 | 91 | _processOptions(options) { 92 | const defaultOptions = { 93 | panelVisible: false, 94 | activeTab: "console", 95 | entryStyle: "button" 96 | }; 97 | 98 | const supportTab = Object.keys(PanelType).map(key => PanelType[key]); 99 | 100 | let activeTab = options.activeTab; 101 | if (supportTab.indexOf(activeTab) === -1 && !pluginManager.hasPlugin(activeTab)) { 102 | activeTab = defaultOptions.activeTab; 103 | } 104 | 105 | return { 106 | panelVisible: options.panelVisible || defaultOptions.panelVisible, 107 | entryStyle: options.entryStyle || defaultOptions.entryStyle, 108 | activeTab: activeTab 109 | }; 110 | } 111 | 112 | _load(options = {}) { 113 | if (!this._isLoaded && (document.readyState === "interactive" || document.readyState === "complete")) { 114 | this._isLoaded = true; 115 | const root = document.createElement("div"); 116 | (document.documentElement || document.body).appendChild(root); 117 | 118 | const vm = new Vue({ 119 | el: root, 120 | render: h => 121 | h(App, { 122 | props: { 123 | initPanelVisible: options.panelVisible, 124 | initActiveTab: options.activeTab, 125 | initEntryStyle: options.entryStyle 126 | } 127 | }) 128 | }); 129 | } else { 130 | document.addEventListener("readystatechange", this._load.bind(this, ...arguments)); 131 | } 132 | } 133 | } 134 | 135 | WebConsole.Plugin = Plugin; 136 | 137 | if (!window.WebConsole) { 138 | window.WebConsole = WebConsole; 139 | consoleHooks.install(); 140 | } else { 141 | console.error("Inject WebConsole into window failed"); 142 | } 143 | export default WebConsole; 144 | -------------------------------------------------------------------------------- /src/panels/application/ApplicationPanel.vue: -------------------------------------------------------------------------------- 1 | 24 | 25 | 109 | 110 | 136 | -------------------------------------------------------------------------------- /src/panels/application/XStorage.js: -------------------------------------------------------------------------------- 1 | import { Logger, cloneDeep } from "@/utils"; 2 | import { cookie } from "cookie_js"; 3 | 4 | const logger = new Logger("XStorage"); 5 | 6 | class CookieStorage { 7 | constructor() { 8 | this._keys = []; 9 | this.refresh(); 10 | } 11 | 12 | refresh() { 13 | const cookies = cookie.all(); 14 | this._keys = Object.keys(cookies); 15 | } 16 | 17 | get length() { 18 | return this._keys.length; 19 | } 20 | 21 | key(n) { 22 | return this._keys[n]; 23 | } 24 | 25 | getItem(key) { 26 | return cookie.get(key); 27 | } 28 | 29 | setItem(key, value) { 30 | cookie.set(key, value); 31 | this.refresh(); 32 | } 33 | 34 | removeItem(key) { 35 | cookie.remove(key); 36 | this.refresh(); 37 | } 38 | 39 | clear() { 40 | cookie.empty(); 41 | this.refresh(); 42 | } 43 | } 44 | 45 | /** 46 | * 统一 localStorage/sessionStorage/cookie 三种存储器接口 47 | * 48 | * XStorage 49 | * | 50 | * | <- XStorage.refresh() 51 | * | 52 | * Filtered Data Source 53 | * | 54 | * | <- XStorage._refreshFilteredDataSource() 55 | * | 56 | * Data Source 57 | * | 58 | * | <- XStorage._refreshDataSource() 59 | * | 60 | * CookieStorage | SessionStorage | LocalStorage 61 | * | 62 | * | <- CookieStorage.refresh() 63 | * | 64 | * cookie 65 | */ 66 | class XStorage { 67 | // 分页大小 68 | static get PAGE_SIZE() { 69 | return 20; 70 | } 71 | 72 | constructor(type) { 73 | if (type === "localStorage" || type === "sessionStorage") { 74 | this._storage = window[type]; 75 | } else if (type === "cookieStorage") { 76 | this._storage = new CookieStorage(); 77 | } else { 78 | logger.error("unsupport storage type:", type); 79 | } 80 | 81 | // data source 82 | this._ds = []; 83 | // filterd data source 84 | this._fds = []; 85 | // filter keyword 86 | this._filter = ""; 87 | // 下次加载的开始位置 88 | this._cursor = 0; 89 | 90 | this.refresh(); 91 | } 92 | 93 | setFilter(filter) { 94 | const isChange = filter !== this._filter; 95 | 96 | if (typeof filter === "string" && filter) { 97 | this._filter = filter; 98 | } else { 99 | this._filter = ""; 100 | } 101 | 102 | if (isChange) { 103 | this._refreshFilteredDataSource(); 104 | } 105 | } 106 | 107 | refresh() { 108 | // 在 XStorage 和 cookie 之间增加了一层 CookieStorage 109 | // 当 cookie 发生变化后需要手动刷新 CookieStorage 110 | if (this._storage instanceof CookieStorage) { 111 | this._storage.refresh(); 112 | } 113 | 114 | this._cursor = 0; 115 | this._refreshDataSource(); 116 | } 117 | 118 | // 从指定位置开始,加载一页数据 119 | loadItems(startIndex, pageSize = XStorage.PAGE_SIZE) { 120 | return cloneDeep(this._fds.slice(startIndex, startIndex + pageSize)); 121 | } 122 | 123 | // 从上一次加载结束位置开始,加载一页数据 124 | loadMoreItems() { 125 | logger.log("loadMoreItems"); 126 | const items = this.loadItems(this._cursor); 127 | this._cursor += items.length; 128 | return items; 129 | } 130 | 131 | hasMore() { 132 | return this._cursor < this._fds.length; 133 | } 134 | 135 | get length() { 136 | return this._fds.length; 137 | } 138 | 139 | getItem(key) { 140 | return this._storage.getItem(key); 141 | } 142 | 143 | setItem(key, value) { 144 | this._storage.setItem(key, value); 145 | /** 146 | * 更新存在两种情况: 147 | * 1) 更新已存在项,这种情况不需要刷新数据源,只要更新数据源中指定项即可 148 | * 2) 新增项,这种情况必须刷新数据源,保证读出的数据源列表保持自然顺序 149 | * 150 | * 针对第 2 中情况进行示例说明,假设分页大小为 2 并且 localStorage 是倒序读取(storage 读取顺序是浏览器决定的,这里假设这种读取方式) 151 | * data source UI data action 152 | * [A, B, C, D] [D, C] 修改 D -> E 153 | * {E, A, B, C} [E, C] 加载下一页 154 | * {E, A, B, C} [E, C, A, E] 155 | * 由于修改后导致读取顺序变化,分页加载的数据不再准确 156 | */ 157 | const item = this._ds.find(item => item.key === key); 158 | if (item) { 159 | // 更新已存在项 160 | item.value = value; 161 | const item2 = this._fds.find(item => item.key === key); 162 | if (item2) { 163 | item2.value = value; 164 | } 165 | } else { 166 | // 新增项 167 | this._refreshDataSource(); 168 | } 169 | } 170 | 171 | removeItem(key) { 172 | this._storage.removeItem(key); 173 | 174 | /** 175 | * 为了提高性能并保留已读取出来的数据,不直接刷新数据源,而是从数据源中删除指定项 176 | * 删除不会影响已读取数据源中数据的排列顺序 177 | */ 178 | const foundIndex = this._ds.findIndex(item => item.key === key); 179 | if (foundIndex !== -1) { 180 | this._ds.splice(foundIndex, 1); 181 | const foundIndex2 = this._fds.findIndex(item => item.key === key); 182 | if (foundIndex2 !== -1) { 183 | this._fds.splice(foundIndex2, 1); 184 | } 185 | // 当前游标前移 1 格 186 | this._cursor -= 1; 187 | } 188 | // logger.log('removeItem', this) 189 | } 190 | 191 | clear() { 192 | this._storage.clear(); 193 | this._ds = []; 194 | this._fds = []; 195 | this._cursor = 0; 196 | } 197 | 198 | // 更新数据源 199 | _refreshDataSource() { 200 | logger.log("_refreshDataSource", this); 201 | let key, value; 202 | const storage = this._storage; 203 | const length = this._storage.length; 204 | this._ds = []; 205 | for (let j = 0; j < length; ++j) { 206 | key = storage.key(j); 207 | value = storage.getItem(key); 208 | this._ds.push({ key, value }); 209 | } 210 | this._refreshFilteredDataSource(); 211 | } 212 | 213 | // 更新过滤数据源 214 | _refreshFilteredDataSource() { 215 | logger.log("_refreshFilteredDataSource", this); 216 | const filter = this._filter; 217 | const ds = this._ds; 218 | this._fds = []; 219 | if (filter) { 220 | // 过滤出 key 或 value 中包含关键字的项 221 | this._fds = ds.filter(({ key, value }) => key.indexOf(filter) >= 0 || value.indexOf(filter) >= 0); 222 | } else { 223 | this._fds = ds; 224 | } 225 | } 226 | } 227 | 228 | window.CookieStorage = CookieStorage; 229 | window.XStorage = XStorage; 230 | export default XStorage; 231 | -------------------------------------------------------------------------------- /src/panels/console/ConsolePanel.vue: -------------------------------------------------------------------------------- 1 | 31 | 32 | 242 | 243 | 282 | -------------------------------------------------------------------------------- /src/panels/console/TextInlineBlock.vue: -------------------------------------------------------------------------------- 1 | 83 | 84 | 241 | 242 | 272 | -------------------------------------------------------------------------------- /src/panels/element/BoxModel.vue: -------------------------------------------------------------------------------- 1 | 48 | 49 | 83 | 84 | 140 | -------------------------------------------------------------------------------- /src/panels/element/ElementPanel.vue: -------------------------------------------------------------------------------- 1 | 29 | 30 | 197 | 198 | 228 | -------------------------------------------------------------------------------- /src/panels/element/NodeLink.vue: -------------------------------------------------------------------------------- 1 | 28 | 29 | 64 | 65 | 91 | -------------------------------------------------------------------------------- /src/panels/element/NodeView.vue: -------------------------------------------------------------------------------- 1 | 84 | 85 | 218 | 219 | 276 | -------------------------------------------------------------------------------- /src/panels/element/StyleColorValue.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 19 | 20 | 32 | -------------------------------------------------------------------------------- /src/panels/element/StyleProperty.vue: -------------------------------------------------------------------------------- 1 | 30 | 31 | 94 | 95 | 143 | -------------------------------------------------------------------------------- /src/panels/element/StyleRule.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | 166 | 167 | 203 | -------------------------------------------------------------------------------- /src/panels/element/TabComputed.vue: -------------------------------------------------------------------------------- 1 | 41 | 42 | 177 | 178 | 179 | 296 | -------------------------------------------------------------------------------- /src/panels/element/TabStyles.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | 137 | 138 | 170 | -------------------------------------------------------------------------------- /src/panels/element/Tag.vue: -------------------------------------------------------------------------------- 1 | 28 | 29 | 86 | 87 | 113 | -------------------------------------------------------------------------------- /src/panels/index.js: -------------------------------------------------------------------------------- 1 | export { default as SettingsPanel } from "./settings/SettingsPanel.vue"; 2 | export { default as NetworkPanel } from "./network/NetworkPanel.vue"; 3 | export { default as ConsolePanel } from "./console/ConsolePanel"; 4 | export { default as ApplicationPanel } from "./application/ApplicationPanel"; 5 | export { default as ElementPanel } from "./element/ElementPanel.vue"; 6 | -------------------------------------------------------------------------------- /src/panels/network/HttpStatus.js: -------------------------------------------------------------------------------- 1 | export default { 2 | "100": "Continue", 3 | "101": "Switching Protocols", 4 | "102": "Processing", 5 | "200": "OK", 6 | "201": "Created", 7 | "202": "Accepted", 8 | "203": "Non-Authoritative Information", 9 | "204": "No Content", 10 | "205": "Reset Content", 11 | "206": "Partial Content", 12 | "207": "Multi-Status", 13 | "208": "Already Reported", 14 | "226": "IM Used", 15 | "300": "Multiple Choices", 16 | "301": "Moved Permanently", 17 | "302": "Found", 18 | "303": "See Other", 19 | "304": "Not Modified", 20 | "305": "Use Proxy", 21 | "307": "Temporary Redirect", 22 | "308": "Permanent Redirect", 23 | "400": "Bad Request", 24 | "401": "Unauthorized", 25 | "402": "Payment Required", 26 | "403": "Forbidden", 27 | "404": "Not Found", 28 | "405": "Method Not Allowed", 29 | "406": "Not Acceptable", 30 | "407": "Proxy Authentication Required", 31 | "408": "Request Timeout", 32 | "409": "Conflict", 33 | "410": "Gone", 34 | "411": "Length Required", 35 | "412": "Precondition Failed", 36 | "413": "Payload Too Large", 37 | "414": "URI Too Long", 38 | "415": "Unsupported Media Type", 39 | "416": "Range Not Satisfiable", 40 | "417": "Expectation Failed", 41 | "418": "I'm a teapot", 42 | "421": "Misdirected Request", 43 | "422": "Unprocessable Entity", 44 | "423": "Locked", 45 | "424": "Failed Dependency", 46 | "425": "Unordered Collection", 47 | "426": "Upgrade Required", 48 | "428": "Precondition Required", 49 | "429": "Too Many Requests", 50 | "431": "Request Header Fields Too Large", 51 | "451": "Unavailable For Legal Reasons", 52 | "500": "Internal Server Error", 53 | "501": "Not Implemented", 54 | "502": "Bad Gateway", 55 | "503": "Service Unavailable", 56 | "504": "Gateway Timeout", 57 | "505": "HTTP Version Not Supported", 58 | "506": "Variant Also Negotiates", 59 | "507": "Insufficient Storage", 60 | "508": "Loop Detected", 61 | "509": "Bandwidth Limit Exceeded", 62 | "510": "Not Extended", 63 | "511": "Network Authentication Required" 64 | }; 65 | -------------------------------------------------------------------------------- /src/panels/network/NetworkRequest.vue: -------------------------------------------------------------------------------- 1 | 31 | 32 | 80 | 81 | 127 | -------------------------------------------------------------------------------- /src/panels/network/RequestType.js: -------------------------------------------------------------------------------- 1 | // 请求类型 2 | export default Object.freeze({ 3 | XHR: "xhr", 4 | FETCH: "fetch" 5 | }); 6 | -------------------------------------------------------------------------------- /src/panels/network/TabHeaders.vue: -------------------------------------------------------------------------------- 1 | 67 | 68 | 176 | 177 | 220 | -------------------------------------------------------------------------------- /src/panels/network/TabPreview.vue: -------------------------------------------------------------------------------- 1 |