初始化my文件

This commit is contained in:
yziiy
2025-09-26 14:50:30 +08:00
parent 29c69f49fa
commit cae9c4c8fc
1497 changed files with 248229 additions and 0 deletions

View File

@@ -0,0 +1,6 @@
个人可免费商用
企业可免费商用
如果你只是想分享本组件库及其教程或者知识点,请保留版权链接 dev.api0.cn
不可以复制本工具库的任何文档至第三方站点,以此形成第三方文档库。这样会扰乱正常官网的声誉,因为文档是不定期的经常更新。
贡献者提供的组件一并适用第12点。
请尊重作者的辛苦,本工具库耗费大量时间维护。

View File

@@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@@ -0,0 +1,108 @@
# UniDevTools - UniApp调试工具
><b>在线文档 - 完整版使用教程:<br>
><a href="https://dev.api0.cn" target="_blank">https://dev.api0.cn</a>
----
不知道大家是否和我一样已经受够了使用Uniapp开发APP时需要插USB线连着HbuilderX才能看到console打印各项指标数据全是黑盒开发APP时无法选择调试节点等各类头疼问题。Uniapp官方一直没有一个标准的调试工具为什么不能像Chrome调试工具一样调试App呢为此我们开发了一个插件把这些痛点问题依次解决
UniDevTools是一个UniApp工具库包含console打印日志、request请求记录、storage缓存管理、vuex状态管理、框架报错记录、文件管理等多功能调试工具合集:
- `Tools` 常用工具(重启、请求构建、跳转指定页面、注入VConsole、Eruda)、自定义工具页
- `Error` 全局报错拦截vue模板报错、uniapp框架报错
- `Console` 打印日志记录
- `Network` request请求记录、重发请求
- `JsRunner` 执行js代码、支持App和H5端
- `Storage` 缓存管理支持localStorage、cookie、sessionStorage
- `Pages` 路由页面管理、日活时间记录
- `Vuex` 状态管理,支持`Vuex`、`Pinia`、`globalData`
- `Logs` 框架运行日志、框架api调用日志、自定义上报的日志记录
- `Info` 当前设备参数、App启动参数、运行时信息、权限列表
- `UniBus` uni框架事件总线调用记录
- `FileSys` 本地文件管理系统
- `Setting` DevTools工具设置、清空全部缓存、导出全部日志
兼容框架:
| Vue2+js+vuex | Vue3+ts+vuex(pinia)|
| -- | -- |
| √ | √ |
兼容平台:
| H5 | APP | 微信小程序 | APP-NVUE | 其他小程序 | UniAppX |
| -- | -- | -- | -- | -- | -- |
| √| √ | √ | √(大部分功能支持) | 未测试 | × (待办中) |
><b>本工具支持在<span style="color: red;">生产环境</span>中使用</b><br>
><span style="font-size: 13px;">生产环境使用时建议隐藏调试浮窗,可设置通过特定方法进入调试页</span>
## 下载运行本示例项目使用说明:
>注意该项目使用Cli模式搭建请勿导入HBuilderX中运行<br>
>开始前请先准备好环境:`node18` + `pnpm`
1. 克隆项目到本地:
```cmd
git clone https://github.com/1615958039/UniDevTools.git
```
2. 进入项目
```cmd
cd ./UniDevTools
```
3. 安装依赖
```
pnpm i
```
4. 启动预览H5
```
pnpm run dev:h5
```
说明示例基于UniApp+Vue3+ts+ViteUI框架为TM-UI3.1
## 把调试工具引入自己项目:
1. 下载最新源码包`v3.5.0`
>GitHub: [releases](https://github.com/1615958039/UniDevTools/releases) <br>
>Gitee: [releases](https://gitee.com/t1zf/UniAppDevTools/releases/) <br>
>备用:[v3.5.0_r.zip](https://dev.api0.cn/releases/v3.5.0_r.zip)
1. 引入项目(请移步至在线文档)[https://dev.api0.cn/guide/install](https://dev.api0.cn/guide/install)
----
### 示例项目
>《斗图助手APP》表情包制作工具<br>
>该项目基于Uniapp+Vue2已集成DevTools工具<br>
>打开APP后在搜索页输入` __devtools__ `即可打开调试弹窗查看请求和日志数据
<div class="qrCodeList" style="display: flex;flex-direction: row; align-items: center;gap: 30px;flex-wrap: wrap;">
<div class="codeItem">
<img src="https://dev.api0.cn/qrCode/iosAppStore.png" style="width: 160px;height: 160px;">
<div class="codeTitle">苹果AppStore</div>
</div>
<br><br><br>
<div class="codeItem">
<img src="https://dev.api0.cn/qrCode/androidYYB.png" style="width: 160px;height: 160px;">
<div class="codeTitle">安卓应用宝</div>
</div>
<br><br><br>
<div class="codeItem">
<img src="https://dev.api0.cn/qrCode/wxmp.jpg" style="width: 160px;height: 160px;">
<div class="codeTitle">微信小程序</div>
</div>
<br><br><br>
<div class="codeItem">
<img src="https://dev.api0.cn/qrCode/web.png" style="width: 160px;height: 160px;">
<div class="codeTitle">H5</div>
</div>
</div>

View File

@@ -0,0 +1,18 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0" />
<title></title>
<!--preload-links-->
<!--app-context-->
</head>
<body>
<div id="app"><!--app-html--></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>

11402
uni_modules/UniDevTools/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,81 @@
{
"name": "UniDevTools开发调试神器支持vue2和3支持vuex和pinia",
"version": "3.6",
"scripts": {
"dabao": "node buildTmui/build.js 3.1.09",
"dev:app": "uni -p app",
"dev:custom": "uni -p",
"dev:h5": "uni",
"dev:h5:ssr": "uni --ssr",
"dev:mp-alipay": "uni -p mp-alipay",
"dev:mp-baidu": "uni -p mp-baidu",
"dev:mp-kuaishou": "uni -p mp-kuaishou",
"dev:mp-lark": "uni -p mp-lark",
"dev:mp-qq": "uni -p mp-qq",
"dev:mp-toutiao": "uni -p mp-toutiao",
"dev:mp-weixin": "uni -p mp-weixin",
"dev:quickapp-webview": "uni -p quickapp-webview",
"dev:quickapp-webview-huawei": "uni -p quickapp-webview-huawei",
"dev:quickapp-webview-union": "uni -p quickapp-webview-union",
"build:app": "uni build -p app",
"build:custom": "uni build -p",
"build:h5": "uni build",
"build:h5:ssr": "uni build --ssr",
"build:mp-alipay": "uni build -p mp-alipay",
"build:mp-baidu": "uni build -p mp-baidu",
"build:mp-kuaishou": "uni build -p mp-kuaishou",
"build:mp-lark": "uni build -p mp-lark",
"build:mp-qq": "uni build -p mp-qq",
"build:mp-toutiao": "uni build -p mp-toutiao",
"build:mp-weixin": "uni build -p mp-weixin",
"build:quickapp-webview": "uni build -p quickapp-webview",
"build:quickapp-webview-huawei": "uni build -p quickapp-webview-huawei",
"build:quickapp-webview-union": "uni build -p quickapp-webview-union"
},
"dependencies": {
"@dcloudio/uni-app": "3.0.0-3090520231028001",
"@dcloudio/uni-app-plus": "3.0.0-3090520231028001",
"@dcloudio/uni-components": "3.0.0-3090520231028001",
"@dcloudio/uni-h5": "3.0.0-3090520231028001",
"@dcloudio/uni-helper-json": "^1.0.13",
"@dcloudio/uni-mp-alipay": "3.0.0-3090520231028001",
"@dcloudio/uni-mp-baidu": "3.0.0-3090520231028001",
"@dcloudio/uni-mp-kuaishou": "3.0.0-3090520231028001",
"@dcloudio/uni-mp-lark": "3.0.0-3090520231028001",
"@dcloudio/uni-mp-qq": "3.0.0-3090520231028001",
"@dcloudio/uni-mp-toutiao": "3.0.0-3090520231028001",
"@dcloudio/uni-mp-weixin": "3.0.0-3090520231028001",
"@dcloudio/uni-quickapp-webview": "3.0.0-3090520231028001",
"echarts": "5.4.2",
"pinia": "2.0.36",
"sass": "^1.77.6",
"vue": "3.2.47",
"vue-i18n": "9.2.2"
},
"devDependencies": {
"@dcloudio/types": "3.3.2",
"@dcloudio/uni-automator": "3.0.0-3090520231028001",
"@dcloudio/uni-cli-shared": "3.0.0-3090520231028001",
"@dcloudio/vite-plugin-uni": "3.0.0-3090520231028001",
"@types/node": "^17.0.35",
"autoprefixer": "10.4.14",
"unplugin-vue-components": "0.24.1",
"vite": "4.2.1"
},
"id": "jie-UniDevTools",
"displayName": "UniDevTools开发调试神器支持vue2和3支持vuex和pinia",
"description": "UniDevTools跨平台多功能免费调试工具包含console打印日志、request请求记录、storage缓存管理、vuex状态管理、框架报错记录、文件管理等多功能调试工具合集",
"keywords": [
"console",
"调试工具",
"vconsole",
"debug",
"devtools"
],
"dcloudext": {
"category": [
"uni-app前端模板",
"uni-app前端项目模板"
]
}
}

7415
uni_modules/UniDevTools/pnpm-lock.yaml generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,9 @@
const {
uniPostcssPlugin
} = require('@dcloudio/uni-cli-shared')
module.exports = {
plugins: [
uniPostcssPlugin(),
require('autoprefixer')()
]
}

View File

@@ -0,0 +1,74 @@
<template>
</template>
<script lang="ts">
export default {
globalData: {
test: {
a: 1,
b: [1,2,3,4]
},
b: "红红火火恍恍惚惚",
c: Math.random(),
t: new Date().getTime(),
testArray: [
Math.random(),
[Math.random(),Math.random(),Math.random(),Math.random(),],
Math.random(),Math.random(),Math.random(),
{
[Math.random()]: 123
},
{
a: [
{
b: [
{
c: [
{
d: [
{
e: [
]
}
]
}
]
}
]
}
]
}
],
}
}
</script>
<script lang="ts" setup>
import { onError, onLaunch } from '@dcloudio/uni-app';
onError((err)=>{
try {
// 挂载devTools全局报错拦截
uni.$dev.errorReport(err, "at App.vue onError", "oe");
} catch (error) {}
})
onLaunch((ctx) => {
try {
// 挂载APP启动日志提交
uni.$dev.logReport("appOnLaunch>" + JSON.stringify(ctx));
} catch (error) {}
})
</script>
<style>
/* #ifdef APP-PLUS-NVUE */
@import './tmui/scss/nvue.css';
/* #endif */
/* #ifndef APP-PLUS-NVUE */
@import './tmui/scss/noNvue.css';
/* #endif */
</style>

View File

@@ -0,0 +1,3 @@
{
"prompt": "template"
}

View File

@@ -0,0 +1,435 @@
<template>
<view class="demoIndex">
<view class="hello">Hello</view>
<tm-card title="DevTools">
<template #status>
<view @click="goUrl('https://github.com/1615958039/UniDevTools')">
<tm-text label="GitHub" :font-size="26" color="orange"></tm-text>
</view>
</template>
<template #content>
<view class="vertical">
<view @click="goUrl('https://dev.api0.cn')">
<text class="row">在线文档<text style="color: #ff2d55">https://dev.api0.cn</text></text>
</view>
<view>
<tm-text
label="这是一个开发给UniApp框架兼容Vue2、Vue3的调试工具库支持在线上生产环境中使用"
:font-size="26"
color="cyan"
></tm-text>
</view>
<view>
<tm-text label="只需要简单几步即可在你的项目中引入本工具,详情请移步在线文档" :font-size="26" color="orange"></tm-text>
</view>
</view>
</template>
<template #action>
<view class="flex flex-row flex-row-center-end flex-1">
<tm-button
@click="goUrl('https://dev.api0.cn')"
:round="24"
color="red"
label="在线文档"
:font-size="24"
:width="180"
:height="64"
></tm-button>
<tm-button @click="goQun" :round="24" :margin="[24, 0]" label="QQ群" :font-size="24" :width="120" :height="64"></tm-button>
</view>
</template>
</tm-card>
<tm-card title="Start 开始">
<template #content>
<view class="btnList">
<tm-button label="一键生成全部测数据" @click="rand()" :width="290" size="small" :round="20"></tm-button>
<tm-button label="打开Dev弹窗" @click="open" :width="220" color="blue" size="small" :round="20"></tm-button>
</view>
</template>
</tm-card>
<tm-card title="Error 错误拦截示例">
<template #content>
<view class="btnList">
<tm-button label="常规js运行报错" @click="showError" :width="290" color="orange" size="small" :round="20"></tm-button>
<tm-button label="vue渲染报错" @click="showTemplateError" :width="290" color="red" size="small" :round="20"></tm-button>
<template v-if="isShowTemplateError">
<view :test="testModel" />
</template>
<tm-text label="打开调试页后可看到具体报错日志" :font-size="20" color="red"></tm-text>
</view>
</template>
</tm-card>
<tm-card title="Console 打印示例">
<template #content>
<view class="btnList">
<tm-button label="打印普通对象" @click="print('def')" :width="290" size="small" :round="20"></tm-button>
<tm-button label="console.info()" @click="print('info')" :width="290" color="grey" size="small" :round="20"></tm-button>
<tm-button label="console.warn()" @click="print('warn')" :width="290" color="orange" size="small" :round="20"></tm-button>
<tm-button label="console.error()" @click="print('error')" :width="290" color="red" size="small" :round="20"></tm-button>
<tm-button label="打印数组" @click="print('array')" color="red" size="small" :round="20"></tm-button>
<tm-button label="打印对象" @click="print('object')" color="pink" size="small" :round="20"></tm-button>
<tm-button label="打印JSON字符串" @click="print('json')" color="orange" :width="220" size="small" :round="20"></tm-button>
</view>
</template>
</tm-card>
<tm-card title="Storage 本地缓存">
<template #content>
<view class="btnList">
<tm-button label="随机添加一个KEY" @click="storage('add')" :width="290" size="small" :round="20"></tm-button>
</view>
</template>
</tm-card>
<tm-card title="Network 网络请求">
<template #content>
<view class="btnList">
<tm-button label="发送GET请求" @click="sendReq('get')" :width="290" size="small" :round="20"></tm-button>
<tm-button label="POST" @click="sendReq('post')" :width="290" color="red" size="small" :round="20"></tm-button>
</view>
</template>
</tm-card>
<tm-card title="Pinia 全局变量">
<template #content>
<view class="vertical">
<tm-text :label="'Pinia: test.model = ' + testStore.model" :font-size="24" color="red"></tm-text>
<tm-button label="更改model的值" @click="changeModel" :width="290" size="small" :round="20"></tm-button>
</view>
</template>
</tm-card>
<tm-card title="EventBus 事件总线监听">
<template #content>
<view class="vertical">
<tm-button label="生成测试数据" @click="eventBusTest" :width="290" size="small" :round="20"></tm-button>
</view>
</template>
</tm-card>
<view class="divider">
<tm-divider :border="8" label="分割线(下方为tm-ui示例项目)"></tm-divider>
</view>
<tm-notification placement="top" ref="msg" label="消息提醒"></tm-notification>
</view>
</template>
<script lang="ts" setup>
import { useTestStore } from '@/stores/test'
import { ref } from 'vue'
const msg = ref()
/**
* 测试Pinia
*/
const testStore = useTestStore()
const isShowTemplateError = ref(false)
let isCanTips = true
function tips(data = '') {
if (!isCanTips) return
// @ts-ignore
msg.value.show({
label: data
})
}
/**
* 打开调试弹窗
*/
function open() {
// @ts-ignore
uni.$dev.show()
}
/**
* 报错示例
*/
function showError(e = '') {
const ee = () => {
// @ts-ignore
aaaa()
}
try {
ee()
} catch (error: any) {
tips('运行成功:' + error?.message ?? 'js运行出错')
}
ee()
}
function showTemplateError() {
tips('操作成功请进入调试工具Error中查看')
isShowTemplateError.value = true
setTimeout(() => {
isShowTemplateError.value = false
}, 1000)
}
/**
* console 打印
*/
function print(type = '') {
let obj = {
a: 1,
b: 2,
c: 3,
d: [{ e: 1, f: [{ s: '0' }] }],
[Math.random()]: Math.random(),
[Math.random()]: Math.random(),
[Math.random()]: Math.random(),
[Math.random()]: Math.random(),
[Math.random()]: Math.random(),
[Math.random()]: Math.random(),
[Math.random()]: Math.random(),
[Math.random()]: Math.random(),
[Math.random()]: Math.random(),
[Math.random()]: Math.random(),
[Math.random()]: Math.random(),
[Math.random()]: Math.random(),
[Math.random()]: Math.random(),
[Math.random()]: Math.random(),
[Math.random()]: Math.random(),
[Math.random()]: Math.random(),
testFun: (args: any) => {
console.log('args', args)
},
other: null as any,
set: new Set(),
map: new Map(),
Symbol: Symbol()
}
try {
if (Blob) {
obj.other = new Blob()
}
} catch (error) {}
switch (type) {
case 'def':
console.log('示例打印:' + Math.random())
break
case 'info':
console.info('这是一条info打印:' + new Date().getTime())
break
case 'warn':
console.warn('这是一条warn打印:' + new Date().getTime())
break
case 'error':
console.error('这是一条error打印:' + new Date().getTime())
break
case 'array':
console.log(['array1', 'array2', 'array3', 'array4', 'array5'])
break
case 'object':
console.log(obj)
break
case 'json':
console.log(JSON.stringify(['json字符串', '1', '2', obj]))
break
default:
break
}
tips('操作成功!')
}
/**
* 发送请求
*/
function sendReq(type = '') {
switch (type) {
case 'get':
uni.request({
method: 'GET',
url: 'https://api.oioweb.cn/api/SimpWords',
success(res) {
console.log('GET 请求成功! res=>', res)
// @ts-ignore
tips(res.data.result.content)
},
fail() {
tips('请求失败')
}
})
break
case 'post':
uni.request({
method: 'POST',
url: 'https://api.oioweb.cn/api/qq/getQQLevelInfo',
data: {
qq: Number((Math.random() * 1000000000).toString().replace('0.', '')).toFixed(0)
},
header: {
'content-type': 'application/x-www-form-urlencoded; charset=UTF-8'
},
success(res) {
console.log('POST 请求成功! res=>', res)
// @ts-ignore
tips(res.data.msg)
},
fail() {
tips('请求失败')
}
})
break
default:
break
}
}
/**
* 缓存管理
*/
function storage(type = '') {
switch (type) {
case 'add':
uni.setStorageSync('test_' + Math.random(), Math.random())
break
default:
break
}
tips('操作成功!')
}
function changeModel() {
testStore.model = Math.random().toString()
tips('操作成功!')
}
function eventBusTest() {
let eventName = 'test_' + Math.random()
uni.$on(eventName, (opts) => {})
uni.$emit(eventName, Math.random())
uni.$emit(eventName, { t: new Date().toString() })
uni.$emit(eventName, { s: [new Date().getTime()] })
uni.$off(eventName)
uni.$once(eventName, () => {})
uni.$emit(eventName, Math.random())
tips('操作成功!')
}
function rand() {
isCanTips = false
uni.showLoading({
title: '模拟数据生成中'
})
try {
print('def')
print('info')
print('warn')
print('error')
print('array')
print('object')
print('json')
print('def')
print('info')
print('warn')
print('error')
print('array')
print('object')
print('json')
storage('add')
storage('add')
sendReq('get')
sendReq('post')
sendReq('get')
sendReq('post')
changeModel()
changeModel()
eventBusTest()
eventBusTest()
} catch (error) {}
setTimeout(() => {
isCanTips = true
uni.hideLoading()
uni.showToast({
icon: 'success',
title: '随机数据生成成功!'
})
}, 1500)
showTemplateError()
showError()
}
/**
* 跳转指定URL
*/
function goUrl(url = '') {
// #ifdef H5
window.open(url)
// #endif
// #ifdef MP
uni.setClipboardData({
data: url
})
// #endif
// #ifdef APP-PLUS
plus.runtime.openURL(url)
// #endif
}
/**
* 跳转QQ群
*/
function goQun() {
// #ifdef H5 || APP-PLUS
goUrl('https://qm.qq.com/q/2mf5R4Ar7q')
// #endif
// #ifdef MP
uni.setClipboardData({
data: '894584115',
success() {
uni.showToast({
icon: 'success',
title: '已复制QQ群号'
})
}
})
// #endif
}
if (uni.getStorageSync('hasTestData') != '1') {
setTimeout(() => {
uni.setStorageSync('hasTestData', '1')
rand()
}, 200)
}
</script>
<style lang="scss">
.demoIndex {
margin-top: 100rpx;
}
.divider {
margin-top: 100rpx;
padding: 100rpx 0;
}
.btnList {
width: 650rpx;
display: flex;
flex-direction: row;
flex-wrap: wrap;
align-items: center;
gap: 10rpx;
}
.vertical {
display: flex;
flex-direction: column;
gap: 10rpx;
}
.hello {
width: 100%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
font-size: 120rpx;
padding-bottom: 50rpx;
}
</style>

View File

@@ -0,0 +1,62 @@
let config = {
status: true, //调试工具总开关
route: "/devTools/page/index", // 调试页面的路由,不建议更改
bubble: { //调试弹窗气泡设置
status: true, // 气泡标签是否显示,生产环境建议关闭
text: "DevTools", // 气泡上展示的文字
color: "#ffffff", // 气泡文字颜色
bgColor: "rgba(250, 53, 52,0.7)", // 气泡背景颜色
},
// 注意: 以下配置不建议更改
pageStatistics: {// 页面统计开关
status: true, // 统计状态开关
size: 1024 * 100, // 缓存上限单位byte
dayOnlineRowMax: 30, // 活跃数据缓存天数
},
console: { //console日志配置
status: true, //功能总开关
isOutput: true, //打印的日志是否对外输出到浏览器调试界面,建议在生产环境时关闭
cache: {
status: true, //是否启用本地缓存
size: 512 * 1024, //缓存上限单位byte
rowSize: 1024 * 4,//单条记录缓存上限单位byte
},
},
uniBus: { // uni event bus 监听设置
status: true,
cache: {
status: true,
size: 1024 * 512, // bus调用日志上限 byte
rowSize: 1024 * 10,
countMaxSize: 1024 * 10, // bus统计上限 byte
},
},
error: { //报错拦截配置
status: true,
cache: {
status: true,
size: 512 * 1024,
rowSize: 1024 * 4,
},
},
network: { //请求拦截配置
status: true,
cache: {
status: true,
size: 512 * 1024,
rowSize: 1024 * 4,
},
},
logs: { //运行日志
status: true,
cache: {
status: true,
size: 512 * 1024,
rowSize: 1024 * 4,
},
},
};
export default config;

View File

@@ -0,0 +1,156 @@
<template>
<view
v-if="isMp && options && options.status && options.bubble.status"
class="mpDevBubble"
:style="{
left: `${tagConfig.x}px`,
top: `${tagConfig.y}px`,
'background-color': options.bubble.bgColor,
'box-shadow': `0px 0px 6px ${options.bubble.bgColor}`,
}"
@touchstart.stop="touchstart"
@touchmove.stop="touchmove"
@touchend.stop="touchend"
>
<text
class="mpDevBubbleText"
:style="{
color: options.bubble.color,
'font-size': '20rpx',
}"
>
{{ options.bubble.text }}
</text>
</view>
</template>
<script>
import devOptions from "../libs/devOptions";
let options = devOptions.getOptions();
let sysInfo = uni.getSystemInfoSync();
let tagConfig = uni.getStorageSync("devTools_tagConfig");
if (!tagConfig) {
tagConfig = {};
}
tagConfig = Object.assign(
{
x: sysInfo.screenWidth - 150,
y: sysInfo.screenHeight - 240,
},
tagConfig
);
// 拖动范围限制
let dragLimit = {
min: { x: 0, y: 0 },
max: {
x: sysInfo.screenWidth - 70,
y: sysInfo.screenHeight - 24,
},
};
tagConfig.x = Math.min(Math.max(tagConfig.x, dragLimit.min.x), dragLimit.max.x);
tagConfig.y = Math.min(Math.max(tagConfig.y, dragLimit.min.y), dragLimit.max.y);
let isTouch = false;
let touchStartPoint = {
clientX: 0,
clientY: 0,
tagX: tagConfig.x,
tagY: tagConfig.y,
hasMove: false,
};
let isMp = false;
// #ifdef MP
isMp = true;
// #endif
export default {
data() {
return {
isMp,
/**
* 标签参数
*/
options,
/**
* 标签坐标信息配置
*/
tagConfig,
};
},
mounted() {
// console.log("调试开始zzzzzzzzzzzzzzzz");
},
methods: {
touchstart(e) {
if (isTouch) return;
if (e.preventDefault) {
e.preventDefault();
}
let clientX = e.touches[0].clientX;
let clientY = e.touches[0].clientY;
touchStartPoint.clientX = clientX;
touchStartPoint.clientY = clientY;
touchStartPoint.tagX = tagConfig.x;
touchStartPoint.tagY = tagConfig.y;
touchStartPoint.hasMove = false;
isTouch = true;
},
touchmove(e) {
if (!isTouch) return;
if (e.preventDefault) {
e.preventDefault();
}
let clientX = e.touches[0].clientX;
let clientY = e.touches[0].clientY;
touchStartPoint.hasMove = true;
let offsetX = touchStartPoint.clientX - clientX;
let offsetY = touchStartPoint.clientY - clientY;
let tx = touchStartPoint.tagX - offsetX;
let ty = touchStartPoint.tagY - offsetY;
tx = Math.min(Math.max(tx, dragLimit.min.x), dragLimit.max.x);
ty = Math.min(Math.max(ty, dragLimit.min.y), dragLimit.max.y);
tagConfig.x = tx;
tagConfig.y = ty;
this.tagConfig.x = tx;
this.tagConfig.y = ty;
},
touchend(e) {
if (!isTouch) return;
if (e.preventDefault) {
e.preventDefault();
}
isTouch = false;
uni.setStorageSync("devTools_tagConfig", tagConfig);
if (!touchStartPoint.hasMove) {
let pages = getCurrentPages();
let route = options.route.substring(1, options.route.length - 2);
if (pages[pages.length - 1].route == route) {
// 已经处于debug页面不响应点击事件
return;
}
this.$devTools.show();
}
},
},
};
</script>
<style lang="scss" scoped>
.mpDevBubble {
box-sizing: border-box;
position: fixed;
z-index: 9999999;
width: 70px;
height: 24px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 4px;
border-radius: 6px;
font-size: 10px;
}
</style>

View File

@@ -0,0 +1,142 @@
/**
*! 创建h5页面上拖动的气泡标签
*/
function createH5Bubble(options, devTools) {
let tagConfig = localStorage.getItem("devTools_tagConfig");
if (!tagConfig) {
tagConfig = {}
} else {
tagConfig = JSON.parse(tagConfig)
}
tagConfig = Object.assign({
show: options.bubble.status,
x: window.innerWidth - 90,
y: window.innerHeight - 90,
}, tagConfig);
tagConfig.show = options.bubble.status;
// 拖动范围限制
let dragLimit = {
min: { x: 0, y: 0, },
max: {
x: window.innerWidth - 70,
y: window.innerHeight - 24,
}
}
tagConfig.x = Math.min(Math.max(tagConfig.x, dragLimit.min.x), dragLimit.max.x)
tagConfig.y = Math.min(Math.max(tagConfig.y, dragLimit.min.y), dragLimit.max.y)
let tag = document.createElement("div");
tag.style = `
box-sizing: border-box;
position: fixed;
z-index: 9999999;
left: ${tagConfig.x}px;
top: ${tagConfig.y}px;
width: 70px;
height: 24px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 4px;
border-radius: 6px;
background-color: ${options.bubble.bgColor};
color: ${options.bubble.color};
font-size: 10px;
cursor: grab;
box-shadow: 0px 0px 6px ${options.bubble.bgColor};
backdrop-filter: blur(1px);
`;
tag.innerHTML = options.bubble.text;
tag.setAttribute("id", "debugTag")
if (tagConfig.show) {
document.body.appendChild(tag)
}
/**
* 标签单击事件
*/
function tagClick() {
let pages = getCurrentPages()
let route = options.route.substring(1, options.route.length - 2);
if (pages[pages.length - 1].route == route) {
// 已经处于debug页面不响应点击事件
return;
}
devTools.show()
}
let isTouch = false;
let touchStartPoint = {
clientX: 0,
clientY: 0,
tagX: tagConfig.x,
tagY: tagConfig.y,
hasMove: false,
}
function touchStart(e) {
if (isTouch) return;
if (e.preventDefault) {
e.preventDefault()
}
let clientX = e.clientX ? e.clientX : e.targetTouches[0].clientX;
let clientY = e.clientX ? e.clientY : e.targetTouches[0].clientY;
touchStartPoint.clientX = clientX;
touchStartPoint.clientY = clientY;
touchStartPoint.tagX = tagConfig.x;
touchStartPoint.tagY = tagConfig.y;
touchStartPoint.hasMove = false;
isTouch = true;
}
function touchMove(e) {
if (!isTouch) return;
if (e.preventDefault) {
e.preventDefault()
}
let clientX = e.clientX ? e.clientX : e.targetTouches[0].clientX;
let clientY = e.clientX ? e.clientY : e.targetTouches[0].clientY;
touchStartPoint.hasMove = true;
let offsetX = touchStartPoint.clientX - clientX;
let offsetY = touchStartPoint.clientY - clientY;
let tx = touchStartPoint.tagX - offsetX;
let ty = touchStartPoint.tagY - offsetY;
tx = Math.min(Math.max(tx, dragLimit.min.x), dragLimit.max.x)
ty = Math.min(Math.max(ty, dragLimit.min.y), dragLimit.max.y)
tag.style.left = `${tx}px`;
tag.style.top = `${ty}px`;
tagConfig.x = tx;
tagConfig.y = ty;
}
function touchEnd(e) {
if (!isTouch) return;
if (e.preventDefault) {
e.preventDefault()
}
isTouch = false;
localStorage.setItem("devTools_tagConfig", JSON.stringify(tagConfig))
if (!touchStartPoint.hasMove) {
tagClick()
}
}
tag.addEventListener("touchstart", touchStart)
tag.addEventListener("touchmove", touchMove)
tag.addEventListener("touchend", touchEnd)
tag.addEventListener("mousedown", touchStart)
document.addEventListener("mousemove", touchMove)
document.addEventListener("mouseup", touchEnd)
localStorage.setItem("devTools_tagConfig", JSON.stringify(tagConfig))
}
export default createH5Bubble;

View File

@@ -0,0 +1,129 @@
import devOptions from "./devOptions"
/**
* dev工具缓存管理
*/
export default {
/**
* 存储的键开始名称
*/
cacheKey: "devTools_v3_",
options: null,
/**
* 临时缓存对象
*/
tempCache: {
errorReport: [],
logReport: [],
console: [],
request: [],
uniBus: [],
},
/**
* 临时数据存放
*/
tempData: {},
/**
* 向缓存内写入数据
*/
set(key, value) {
try {
if (['errorReport', 'logReport', 'console', 'request', 'uniBus'].indexOf(key) != -1) {
let setting = this.getLongListSetting(key)
if (!setting.status) return;
if (!setting.cache.status) {
// !不使用缓存
this.tempCache[key] = value;
return;
}
}
key = `${this.cacheKey}${key}`
// #ifdef APP-NVUE
let pages = getCurrentPages()
if (pages[pages.length - 1].route == "devTools/page/index") {
// devtools 页面直接走设置缓存
return uni.setStorageSync(key, value);
}
// #endif
this.tempData[key] = value;
} catch (error) {
console.log("devCache.set error", error);
}
},
/**
* 同步读取缓存数据
*/
get(key) {
try {
if (['errorReport', 'logReport', 'console', 'request', 'uniBus'].indexOf(key) != -1) {
let setting = this.getLongListSetting(key)
if (!setting.status) return [];
if (!setting.cache.status) {
// !不使用缓存
return this.tempCache[key];
}
}
key = `${this.cacheKey}${key}`
// #ifdef APP-NVUE
let pages = getCurrentPages()
if (pages[pages.length - 1].route == "devTools/page/index") {
// devtools 页面直接走设置缓存
return uni.getStorageSync(key);
}
// #endif
if (this.tempData.hasOwnProperty(key)) {
return this.tempData[key];
} else {
let value = uni.getStorageSync(key);
this.tempData[key] = value;
return value;
}
} catch (error) {
console.log("devCache.get error", error);
return "";
}
},
getLongListSetting(key) {
let optionsKey = {
errorReport: 'error',
logReport: 'logs',
console: 'console',
request: 'network',
uniBus: 'uniBus',
}
if (this.options) return this.options[optionsKey[key]];
this.options = devOptions.getOptions()
return this.options[optionsKey[key]];
},
/**
* 同步本地缓存
*/
syncLocalCache() {
let that = this;
setTimeout(() => {
try {
let waitSetKeys = Object.keys(that.tempData);
for (let i = 0; i < waitSetKeys.length; i++) {
const key = waitSetKeys[i];
uni.setStorage({
key,
data: that.tempData[key],
success() {
// console.log("set " + key + " success,length=" + that.tempData[key].length);
delete that.tempData[key];
}
});
}
} catch (error) {
console.log("devCache error: ", error);
}
setTimeout(() => {
that.syncLocalCache();
}, 500);
}, Math.round(Math.random() * 3 * 1000) + 2000);
},
}

View File

@@ -0,0 +1,191 @@
import devCache from "./devCache";
/**
* 设置各端大小 kb
*/
const defSize = (h5, app, mp) => {
let r = 0;
// #ifdef H5
r = h5;
// #endif
// #ifdef MP
r = mp;
// #endif
// #ifdef APP-PLUS || APP-NVUE
r = app;
// #endif
return Math.ceil(r * 1024);
}
/**
* 获取配置
*/
export default {
/**
* 配置缓存key
*/
cacheKey: "options_v8",
/**
* 默认配置项
*/
defaultOptions: {
version: 3.6,
status: false, //调试工具总开关
route: "/devTools/page/index", // 调试页面的路由,不建议更改
bubble: { //调试弹窗气泡设置
status: false, // 气泡标签是否显示,生产环境建议关闭
text: "调试工具", // 气泡上展示的文字
color: "#ffffff", // 气泡文字颜色
bgColor: "rgba(250, 53, 52,0.7)", // 气泡背景颜色
},
console: {
status: true, // 开关
isOutput: true, //打印的日志是否对外输出到浏览器调试界面,建议在生产环境时开启
cache: {
status: true, //是否启用console缓存
size: defSize(512, 1024 * 2, 512),
rowSize: defSize(5.12, 20, 10),
},
},
error: {
status: true,
cache: {
status: true,
size: defSize(512, 1024 * 2, 512),
rowSize: defSize(5.12, 20, 10),
},
},
network: {
status: true,
cache: {
status: true,
size: defSize(512, 1024 * 2, 512),
rowSize: defSize(5.12, 20, 10),
},
},
logs: {
status: true,
cache: {
status: true,
size: defSize(512, 1024 * 2, 512),
rowSize: defSize(0.4, 0.4, 0.4),
},
},
// 页面统计开关
pageStatistics: {
status: true, // 统计状态开关
size: defSize(200, 1024 * 2, 512),
// #ifdef H5
dayOnlineRowMax: 30, // 日活跃时间的保存条数
// #endif
// #ifdef APP-PLUS || APP-NVUE
dayOnlineRowMax: 90, // 日活跃时间的保存条数
// #endif
// #ifdef MP-WEIXIN
dayOnlineRowMax: 60, // 日活跃时间的保存条数
// #endif
},
// uni event bus 监听设置
uniBus: {
status: true,
cache: {
status: true,
size: defSize(512, 1024 * 2, 512),
rowSize: defSize(5.12, 20, 10),
countMaxSize: defSize(512, 1024 * 2, 512), // bus统计上限 kb
},
},
},
/**
* 获取配置信息
*/
getOptions() {
try {
let options = devCache.get(this.cacheKey)
if (!options) {
return {
status: false, //默认关闭调试工具
}
}
let r = String(options.route)
// ! 增加 devRoute 参数
options.devRoute = r.substring(1, r.length)
return options;
} catch (error) {
console.log("devOptions.getOptions error: ", error);
return {
status: false, //默认关闭调试工具
}
}
},
/**
* 保存配置项
*/
setOptions(options) {
try {
if (!options) {
options = this.defaultOptions;
}
if (options.status) {
if (
!options.route
|| typeof options.route != "string"
|| options.route.indexOf("/") != 0
) {
return this.outputError(`devTools 调试工具配置出错: [route] 参数配置错误!`)
}
}
let data = deepMerge(this.defaultOptions, options)
devCache.set(this.cacheKey, data)
} catch (error) {
console.log("devOptions.setOptions error: ", error);
}
},
/**
* 弹出错误信息
*/
outputError(msg) {
console.log('%c' + msg, `
padding: 4px;
background-color: red;
color: #fff;
font-size: 15px;
`)
},
}
/**
* 深度合并对象
*/
function deepMerge(target, ...sources) {
try {
if (!sources.length) return target; // 如果没有源对象则直接返回目标对象
const source = sources[0];
for (let key in source) {
if (source.hasOwnProperty(key)) {
if (typeof source[key] === 'object' && typeof target[key] !== undefined) {
target[key] = deepMerge({}, target[key], source[key]); // 若属性值为对象类型且目标对象已存在该属性则递归调用deepMerge函数进行合并
} else {
target[key] = source[key]; // 否则将源对象的属性赋值到目标对象上
}
}
}
return deepMerge(target, ...sources.slice(1)); // 处理完第一个源对象后再次调用deepMerge函数处理其他源对象
} catch (error) {
console.log("deepMerge error", error);
return {}
}
}

View File

@@ -0,0 +1,117 @@
/**
* 绘制调试工具
*/
/**
* 入口文件
*/
function init(options, devTools) {
let sysInfo = uni.getSystemInfoSync()
let tagConfig = uni.getStorageSync("devTools_tagConfig");
if (!tagConfig) {
tagConfig = {}
}
tagConfig = Object.assign({
show: options.bubble.status,
x: sysInfo.screenWidth - 90,
y: sysInfo.screenHeight - 90,
}, tagConfig)
tagConfig.show = options.bubble.status;
// 拖动范围限制
let dragLimit = {
min: { x: 0, y: 0, },
max: {
x: sysInfo.screenWidth - 70,
y: sysInfo.screenHeight - 24,
}
}
let view = new plus.nativeObj.View('debugTag', {
top: tagConfig.y + 'px',
left: tagConfig.x + 'px',
height: '24px',
width: '70px',
backgroundColor: options.bubble.bgColor,
});
view.drawText(options.bubble.text, {}, {
size: '12px',
color: options.bubble.color,
weight: 'bold'
});
if (tagConfig.show) {
view.show();
}
let isTouch = false;
let touchStart = {
l: 0,
t: 0,
x: 0,
y: 0,
time: 0,
hasMove: false,
}
view.addEventListener("touchstart", (e) => {
isTouch = true;
touchStart.l = e.clientX;
touchStart.t = e.clientY;
touchStart.time = new Date().getTime();
touchStart.hasMove = false;
})
view.addEventListener("touchmove", (e) => {
if (!isTouch) return;
if (!touchStart.hasMove) {
touchStart.hasMove = true;
}
let x = e.screenX - touchStart.l;
let y = e.screenY - touchStart.t;
x = Math.min(Math.max(x, dragLimit.min.x), dragLimit.max.x)
y = Math.min(Math.max(y, dragLimit.min.y), dragLimit.max.y)
view.setStyle({
top: y + 'px',
left: x + 'px',
})
touchStart.x = x;
touchStart.y = y;
})
view.addEventListener("touchend", (e) => {
isTouch = false;
if (
!touchStart.hasMove
|| touchStart.time > (new Date().getTime() - 300)
) {// 单击事件
let pages = getCurrentPages()
let route = options.route.substring(1, options.route.length - 2);
if (pages[pages.length - 1].route == route) {
// 已经处于debug页面不响应点击事件
return;
}
devTools.show()
} else { //拖拽结束事件
tagConfig.x = touchStart.x;
tagConfig.y = touchStart.y;
uni.setStorageSync("devTools_tagConfig", tagConfig)
}
})
uni.setStorageSync("devTools_tagConfig", tagConfig)
}
export default init;

View File

@@ -0,0 +1,64 @@
import devCache from "./devCache";
import devOptions from "./devOptions";
import jsonCompress from "./jsonCompress";
/**
* ! vue报错捕获
*/
/**
* * vue错误日志上报
* @param {'ve'|'vw'|'oe'|'n'} type 错误类型
*/
function errorReport(msg, trace, type = "n") {
try {
if (!msg) return false;
if (msg instanceof Error) {
msg = msg.message;
}
let options = devOptions.getOptions()
if (!options.error.status) return;
let page = "未知";
try {
let pages = getCurrentPages()
let item = pages[pages.length - 1];
if (item && item.route) {
page = item.route;
}
} catch (error) { }
let logs = devCache.get("errorReport");
if (!logs) logs = [];
if (logs.length >= options.error.cache.rowMax) {
logs = logs.splice(0, options.error.cache.rowMax)
}
msg = String(msg)
msg = jsonCompress.compressObject(msg, options.error.cache.rowSize / 2)
trace = String(trace)
trace = jsonCompress.compressObject(trace, options.error.cache.rowSize / 2)
logs.unshift({
t: new Date().getTime(),
m: msg,
tr: trace,
p: page,
type,
});
console.error("__ignoreReport__", msg, trace)
logs = jsonCompress.compressArray(logs, "end", options.error.cache.size)
devCache.set("errorReport", logs)
} catch (error) {
console.log("errorReport error: ", error);
}
}
export default errorReport;

View File

@@ -0,0 +1,237 @@
/**
* json压缩工具
*/
export default {
/**
* 压缩js对象成json字符串并控制json字节大小多余部分裁剪
*/
compressObject(obj = {}, maxSize = 1024 * 10.24) {
try {
let t = new Date().getTime()
if (obj === undefined || obj === null) return obj;
if (typeof obj == "string") {
return this.truncateStrBySize(obj, maxSize);
}
if (typeof obj != "object") {
return obj;
}
if (maxSize < 2) return {};
let addEndOut = false;
if (maxSize > 50) {
let objSize = this.calculateStringByteSize(obj);
if (objSize > maxSize) {
maxSize = maxSize - 50;
addEndOut = true;
}
}
let sizeCount = 2;
let str = this.safeJsonStringify(obj, (key, value) => {
let keySize = this.calculateStringByteSize(key)
if (typeof value == "object") {
if (sizeCount + keySize + 6 > maxSize) {
return;
}
sizeCount = sizeCount + keySize + 6;
return value;
}
let valueSize = this.calculateStringByteSize(value)
let rowSize = keySize + valueSize + 6;
if (rowSize + sizeCount > maxSize) return;
sizeCount = sizeCount + rowSize;
return value;
})
let outPut = JSON.parse(str)
if (addEndOut) {
if (Array.isArray(outPut)) {
outPut.push('(已截断其余部分)')
} else if (typeof outPut == "object") {
outPut["*注意"] = "(已截断其余部分)";
}
}
// console.log("compressObject use time: " + (new Date().getTime() - t));
return outPut;
} catch (error) {
console.log("compressObject error", error);
return "";
}
},
/**
* 压缩数组不超过特定大小
* @param {any[]} [arr=[]] 需要处理的数组
* @param {string} [delType='start'] 数组超出后删除的开始位置
* @param {number} [maxSize=1024 * 972] 数组最大字节数
*/
compressArray(arr = [], delType = "start", maxSize = 1024 * 972) {
let t = new Date().getTime()
try {
if (!arr || arr.length == 0 || !arr[0]) return [];
let i = 0;
while (true) {
i = i + 1;
if (i > 999999) return arr;
if (!arr || arr.length == 0) {
return [];
}
if (this.calculateStringByteSize(arr) <= maxSize) {
// consoleLog("compressArray t=>" + (new Date().getTime() - t) + " i=>" + i)
return arr;
}
if (delType == "start") {
arr.splice(0, 1);
} else {
arr.splice(arr.length - 1, 1);
}
}
} catch (error) {
console.log("compressArray error", error);
return [];
}
},
/**
* 计算对象或字符串占用的字节大小传入对象将自动转json
*/
calculateStringByteSize(str) {
try {
let type = typeof str;
if (
type == "bigint"
|| type == "number"
|| type == "boolean"
) {
return str.toString().length;
} else if (type == "function") {
str = str.toString().length
} else if (str === null || str === undefined) {
return 0;
} else {
try {
str = this.safeJsonStringify(str);
if (str && str.hasOwnProperty) {
return str.length;
} else {
return 1024 * 20;
}
} catch (error) {
console.log("calculateStringByteSize error", error);
return 1024 * 20;
}
}
let size = 0;
for (let i = 0; i < str.length; i++) {
const charCode = str.charCodeAt(i);
if (charCode < 0x0080) {
size += 1;
} else if (charCode < 0x0800) {
size += 2;
} else if (charCode >= 0xD800 && charCode <= 0xDFFF) {
size += 4;
i++;
} else {
size += 3;
}
}
return size;
} catch (error) {
console.log("calculateStringByteSize error", error);
return 1024 * 1024;
}
},
/**
* 安全的js对象转字符串
*/
safeJsonStringify(obj, handleValue) {
if (!obj) return "{}";
try {
if (handleValue) {
return JSON.stringify(obj, (key, value) => {
return handleValue(key, value)
})
} else {
return JSON.stringify(obj, (key, value) => {
return value;
})
}
} catch (error) {
// 尝试解析json失败可能是变量循环引用的问题继续尝试增加WeakSet解析
}
try {
let seen = new WeakSet();
let jsonStr = JSON.stringify(obj, (key, value) => {
if (typeof value == "object") {
try {
if (value instanceof File) {
value = "js:File";
}
if (value && value.constructor && value.constructor.name && typeof value.constructor.name == "string") {
let className = value.constructor.name;
if (className == "VueComponent") {
return "js:Object:VueComponent";
}
}
} catch (error) { }
}
if (typeof value == "function") {
try {
value = value.toString();
} catch (error) {
value = "js:function";
}
}
if (typeof value === "object" && value !== null) {
// 处理循环引用问题
if (seen.has(value)) {
return;
}
seen.add(value);
}
if (handleValue && typeof handleValue == "function") {
try {
return handleValue(key, value);
} catch (error) {
console.log("handleValue error", error);
}
return;
}
return value;
});
seen = null;
return jsonStr;
} catch (error) {
return "{}";
}
},
/**
* 根据限制的字节大小截取字符串
*/
truncateStrBySize(str = "", size = 20 * 1024) {
try {
if (size < 1) return "";
if (this.calculateStringByteSize(str) <= size) return str;
let endStr = "";
if (size > 30) {
endStr = "(已截断多余部分)"
size = size - 30;
}
let low = 0, high = str.length, mid;
while (low < high) {
mid = Math.floor((low + high) / 2);
let currentSize = this.calculateStringByteSize(str.substring(0, mid));
if (currentSize > size) {
// 如果大于限制值,减小高边界
high = mid;
} else {
// 如果小于或等于限制值,增加低边界
low = mid + 1;
}
}
// 返回截断的字符串注意low-1是因为low是最后一次检查超出大小时的位置
return str.substring(0, low - 1) + endStr;
} catch (error) {
console.log("truncateStrBySize error", error);
return "";
}
}
}

View File

@@ -0,0 +1,67 @@
import devCache from "./devCache";
import devOptions from "./devOptions";
import jsonCompress from "./jsonCompress";
/**
* ! 运行日志提交工具
*/
/**
* 日志上报
*/
function logReport(msg) {
try {
if (!msg) return false;
let options = devOptions.getOptions()
if (!options.status) {
console.error("日志上报失败dev工具未启用 msg:" + msg);
return;
}
if (!options.logs.status) {
console.error("日志上报失败dev logs未启用 msg:" + msg);
return;
}
try {
let pages = getCurrentPages()
if (pages[pages.length - 1].route == options.devRoute) {
// 不记录调试工具报出的日志
return false;
}
} catch (error) { }
if (typeof msg == "object") {
try {
msg = JSON.stringify(msg)
} catch (error) {
msg = "logReport:error"
}
}
let log = {
t: new Date().getTime(),
m: "",
}
let logSize = jsonCompress.calculateStringByteSize(log)
msg = String(msg);
msg = jsonCompress.compressObject(msg, options.logs.cache.rowSize - logSize)
log.m = msg;
let logs = devCache.get("logReport");
if (!logs) logs = [];
logs.unshift(log);
logs = jsonCompress.compressArray(logs, "end", options.logs.cache.size)
devCache.set("logReport", logs)
} catch (error) {
console.log("logReport error", error);
}
}
export default logReport;

View File

@@ -0,0 +1,74 @@
/**
* !页面统计:访问次数、停留时长
*/
import devCache from "./devCache"
import devOptions from "./devOptions";
import jsonCompress from "./jsonCompress";
import { timeFormat } from "./timeFormat";
/**
* 页面注销时提交
*/
function pageStatisticsReport(
route, activeTime,
) {
try {
if (!route) return false;
let options = devOptions.getOptions()
if (!options.pageStatistics.status) return; //! 配置文件关闭页面统计
let logs = devCache.get("pageCount");
if (!logs) logs = [];
let pageIndex = logs.findIndex(x => x.route == route)
if (pageIndex == -1) {
logs.push({
route,
activeTimeCount: activeTime,
})
} else {
logs[pageIndex].activeTimeCount = activeTime + logs[pageIndex].activeTimeCount;
}
logs = jsonCompress.compressArray(logs, "end", options.pageStatistics.size)
devCache.set("pageCount", logs)
let now = new Date().getTime();
let date = timeFormat(now, "yyyy-mm-dd");
let dayOnline = devCache.get("dayOnline");
if (!dayOnline) dayOnline = [];
let i = dayOnline.findIndex(x => x.date == date);
if (i == -1) {
dayOnline.unshift({
date,
activeTimeCount: activeTime,
page: [
{
r: route,
t: activeTime,
}
]
})
} else {
dayOnline[i].activeTimeCount = dayOnline[i].activeTimeCount + activeTime;
let pi = dayOnline[i].page.findIndex(x => x.r == route);
if (pi == -1) {
dayOnline[i].page.push({
r: route,
t: activeTime,
})
} else {
dayOnline[i].page[pi].t = dayOnline[i].page[pi].t + activeTime;
}
}
if (dayOnline.length > options.pageStatistics.dayOnlineRowMax) {
dayOnline = dayOnline.splice(0, options.pageStatistics.dayOnlineRowMax)
}
dayOnline = jsonCompress.compressArray(dayOnline, "end", options.pageStatistics.size)
devCache.set("dayOnline", dayOnline)
} catch (error) {
console.log("pageStatistics error", error);
}
}
export default pageStatisticsReport;

View File

@@ -0,0 +1,94 @@
// padStart 的 polyfill因为某些机型或情况还无法支持es7的padStart比如电脑版的微信小程序
// 所以这里做一个兼容polyfill的兼容处理
try {
if (!String.prototype.padStart) {
// 为了方便表示这里 fillString 用了ES6 的默认参数,不影响理解
String.prototype.padStart = function (maxLength, fillString = ' ') {
if (Object.prototype.toString.call(fillString) !== "[object String]") throw new TypeError(
'fillString must be String')
let str = this
// 返回 String(str) 这里是为了使返回的值是字符串字面量,在控制台中更符合直觉
if (str.length >= maxLength) return String(str)
let fillLength = maxLength - str.length,
times = Math.ceil(fillLength / fillString.length)
while (times >>= 1) {
fillString += fillString
if (times === 1) {
fillString += fillString
}
}
return fillString.slice(0, fillLength) + str;
}
}
} catch (error) {
console.log("timeFormat fillString error", error);
}
// 其他更多是格式化有如下:
// yyyy:mm:dd|yyyy:mm|yyyy年mm月dd日|yyyy年mm月dd日 hh时MM分等,可自定义组合
export function timeFormat(dateTime = null, fmt = 'yyyy-mm-dd hh:MM:ss') {
try {
// 如果为null,则格式化当前时间
if (!dateTime) dateTime = Number(new Date());
// 如果dateTime长度为10或者13则为秒和毫秒的时间戳如果超过13位则为其他的时间格式
if (dateTime.toString().length == 10) dateTime *= 1000;
let date = new Date(dateTime);
let ret;
let opt = {
"y+": date.getFullYear().toString(), // 年
"m+": (date.getMonth() + 1).toString(), // 月
"d+": date.getDate().toString(), // 日
"h+": date.getHours().toString(), // 时
"M+": date.getMinutes().toString(), // 分
"s+": date.getSeconds().toString() // 秒
// 有其他格式化字符需求可以继续添加,必须转化成字符串
};
for (let k in opt) {
ret = new RegExp("(" + k + ")").exec(fmt);
if (ret) {
fmt = fmt.replace(ret[1], (ret[1].length == 1) ? (opt[k]) : (opt[k].padStart(ret[1].length, "0")))
};
};
return fmt;
} catch (error) {
console.log("timeFormat error", error);
return "unknown error"
}
}
export function timeFromNow(timestamp) {
try {
const now = new Date().getTime();
let diff = timestamp - now;
// 确定是过去还是未来
const suffix = diff > 0 ? "后" : "前";
diff = Math.abs(diff);
// 计算时间差异
const seconds = Math.floor(diff / 1000);
const minutes = Math.floor(seconds / 60);
const hours = Math.floor(minutes / 60);
const days = Math.floor(hours / 24);
const months = Math.floor(days / 30);
const years = Math.floor(days / 365);
// 根据时间差异返回相应的字符串
if (seconds < 60) {
return `${seconds}${suffix}`;
} else if (minutes < 60) {
return `${minutes}分钟${suffix}`;
} else if (hours < 24) {
return `${hours}小时${suffix}`;
} else if (days < 30) {
return `${days}${suffix}`;
} else if (months < 12) {
return `${months}个月${suffix}`;
} else {
return `${years}${suffix}`;
}
} catch (error) {
console.log("timeFromNow error", error);
}
}

View File

@@ -0,0 +1,430 @@
import devCache from "../libs/devCache";
import devOptions from "../libs/devOptions";
import jsonCompress from "../libs/jsonCompress";
export default {
logList: [],
options: null,
/**
* 挂载打印拦截器
*/
install() {
let that = this;
this.options = devOptions.getOptions()
if (!this.options.console.status) return;
this.logList = devCache.get("console");
if (!this.logList) this.logList = [];
this.syncReqData(); //同步缓存
if (uni.__log__) {
// ! VUE3在app端时有这个特殊的方法
that.mountUniConsole()
} else {
that.mountJsConsole()
}
//! 删除指定记录
uni.$on("devTools_delConsoleItem", (item) => {
let i = that.logList.findIndex(
(x) => {
let t = JSON.stringify(x.list)
return t == JSON.stringify(item.list) &&
x.time == item.time &&
x.page == item.page &&
x.type == item.type
}
);
if (i != -1) {
that.logList.splice(i, 1);
}
that.saveData()
});
//! 清空console日志
uni.$on("devTools_delConsoleAll", () => {
that.logList = []
that.saveData()
});
},
/**
* 同步请求信息到缓存数据中
*/
syncReqData() {
let that = this;
setTimeout(() => {
try {
that.saveData()
} catch (error) {
console.log("console.syncReqData error", error);
}
that.syncReqData()
}, 3000);
},
/**
* 同步数据到缓存
*/
saveData() {
let that = this;
that.logList = jsonCompress.compressArray(that.logList, 'end', that.options.console.cache.size)
devCache.set("console", that.logList)
},
/**
* 挂载监听js自带的console函数
*/
mountJsConsole() {
let that = this;
try {
let l = console.log;
try {
globalThis.consoleLog = function () {
console.log(...arguments)
};
} catch (error) { }
try {
window.consoleLog = function () {
console.log(...arguments)
};
} catch (error) { }
console.log = function () {
replaceConsole("log", arguments)
};
let e = console.error;
function _error() {
try {
let args = [...arguments]
if (
args[0]
&& typeof args[0] == "string"
&& (
args[0] == "__ignoreReport__" //! 忽略错误日志上报
|| args[0].indexOf("__ignoreReport__") == 0
)
) {
let _args = []
if (args.length > 1) {
for (let i = 0; i < args.length; i++) {
if (i != 0) {
_args.push(args[i])
}
}
} else {
_args[0] = args[0];
_args[0] = _args[0].replace("__ignoreReport__", "");
}
if (that.options.console.isOutput) {
e(..._args)
}
return;
}
replaceConsole("error", args)
} catch (error) {
e("监听console.error出错", error)
}
}
console.error = _error;
let w = console.warn;
console.warn = function () {
replaceConsole("warn", arguments)
};
let i = console.info;
console.info = function () {
replaceConsole("info", arguments)
};
/**
* 替换系统打印函数
*/
function replaceConsole(type, args) {
try {
let data = []
if (args && args.length > 0) {
let argList = args;
// #ifdef APP-PLUS
if (args.length == 1) {
argList = args[0].split("---COMMA---");
let endItem = argList[argList.length - 1];
if (
endItem
&& typeof endItem == "string"
&& endItem.indexOf(" at ") > -1
&& endItem.indexOf(":") > -1
) { // 可能包含路径信息
let endList = endItem.split(" at ");
if (endList.length == 2) {
argList.pop()
argList.push(endList[0])
argList.push("at " + endList[1])
}
}
argList = argList.map((item, index) => {
try {
if (typeof item == "string") {
if (item.indexOf("---BEGIN") > -1) {
let isJson = item.indexOf("---BEGIN:JSON---") > -1;
item = item.replace(/---BEGIN:.*?---/g, '')
item = item.replace(/---END:.*?---/g, '')
if (isJson) {
item = JSON.parse(item);
}
} else if (item == "---NULL---") {
item = null;
} else if (item == "---UNDEFINED---") {
item = undefined;
}
}
} catch (error) {
console.log("replaceConsole 尝试解析对象出错:", error);
}
return item;
})
}
// #endif
let oneSize = that.options.console.cache.rowSize / argList.length;
for (let i = 0; i < argList.length; i++) {
let row = jsonCompress.compressObject(argList[i], oneSize)
data.push(row)
}
} else {
data = []
}
let page = "未知";
try {
let pages = getCurrentPages()
let item = pages[pages.length - 1];
if (item && item.route) {
page = item.route;
}
} catch (error) { }
that.logList.unshift({
list: data,
time: new Date().getTime(),
page,
type,
})
if (that.options.console.isOutput) {
if (type == "error") {
e(...args)
} else if (type == "warn") {
w(...args)
} else if (type == "info") {
i(...args)
} else {
l(...args)
}
}
} catch (error) {
if (that.options.console.isOutput) {
e("监听console出错", error)
}
}
}
} catch (error) {
console.log("console.install error", error);
}
},
/**
* 挂载监听uni自带的打印函数
*/
mountUniConsole() {
let that = this;
try {
let uniSysConsole = uni.__log__;
try {
globalThis.consoleLog = function () {
uni.__log__("log", "未知来源", ...arguments)
};
} catch (error) { }
try {
window.consoleLog = function () {
uni.__log__("log", "未知来源", ...arguments)
};
} catch (error) { }
uni.__log__ = function (type, line, ...args) {
try {
// 处理特殊情况 "__ignoreReport__" 忽略错误日志上报
if (type == "error") {
if (
args[0]
&& typeof args[0] == "string"
&& (
args[0] == "__ignoreReport__" //! 忽略错误日志上报
|| args[0].indexOf("__ignoreReport__") == 0
)
) {
let _args = []
if (args.length > 1) {
for (let i = 0; i < args.length; i++) {
if (i != 0) {
_args.push(args[i])
}
}
} else {
_args[0] = args[0];
_args[0] = _args[0].replace("__ignoreReport__", "");
}
if (that.options.console.isOutput) {
uniSysConsole(type, line, ..._args)
}
return;
}
}
let data = []
if (args && args.length > 0) {
let argList = args;
let oneSize = that.options.console.cache.rowSize / argList.length;
for (let i = 0; i < argList.length; i++) {
let row = jsonCompress.compressObject(argList[i], oneSize)
data.push(row)
}
} else {
data = []
}
let page = "未知";
try {
let pages = getCurrentPages()
let item = pages[pages.length - 1];
if (item && item.route) {
page = item.route;
}
} catch (error) { }
data.push(line)
that.logList.unshift({
list: data,
time: new Date().getTime(),
page,
type,
})
if (that.options.console.isOutput) {
uniSysConsole(type, line, ...data)
}
} catch (error) {
if (that.options.console.isOutput) {
uniSysConsole("error", "监听console出错", error)
}
}
}
/**
* 替换系统打印函数
*/
function replaceConsole(type, args) {
try {
let data = []
if (args && args.length > 0) {
let argList = args;
// #ifdef APP-PLUS
if (args.length == 1) {
argList = args[0].split("---COMMA---");
let endItem = argList[argList.length - 1];
if (
endItem
&& typeof endItem == "string"
&& endItem.indexOf(" at ") > -1
&& endItem.indexOf(":") > -1
) { // 可能包含路径信息
let endList = endItem.split(" at ");
if (endList.length == 2) {
argList.pop()
argList.push(endList[0])
argList.push("at " + endList[1])
}
}
argList = argList.map((item, index) => {
try {
if (typeof item == "string") {
if (item.indexOf("---BEGIN") > -1) {
let isJson = item.indexOf("---BEGIN:JSON---") > -1;
item = item.replace(/---BEGIN:.*?---/g, '')
item = item.replace(/---END:.*?---/g, '')
if (isJson) {
item = JSON.parse(item);
}
} else if (item == "---NULL---") {
item = null;
} else if (item == "---UNDEFINED---") {
item = undefined;
}
}
} catch (error) {
console.log("replaceConsole 尝试解析对象出错:", error);
}
return item;
})
}
// #endif
let oneSize = that.options.console.cache.rowSize / argList.length;
for (let i = 0; i < argList.length; i++) {
let row = jsonCompress.compressObject(argList[i], oneSize)
data.push(row)
}
} else {
data = []
}
let page = "未知";
try {
let pages = getCurrentPages()
let item = pages[pages.length - 1];
if (item && item.route) {
page = item.route;
}
} catch (error) { }
that.logList.unshift({
list: data,
time: new Date().getTime(),
page,
type,
})
if (that.options.console.isOutput) {
if (type == "error") {
e(...args)
} else if (type == "warn") {
w(...args)
} else if (type == "info") {
i(...args)
} else {
l(...args)
}
}
} catch (error) {
if (that.options.console.isOutput) {
e("监听console出错", error)
}
}
}
} catch (error) {
console.log("console.install error", error);
}
},
}

View File

@@ -0,0 +1,36 @@
import devCache from "../libs/devCache";
import console from "./console";
import request from "./request";
import storage from "./storage";
import uniBus from "./uniBus";
import uniListen from "./uniListen";
/**
* dev调试工具初始化
*/
export default function devToolsProxyInstall(options) {
try {
if (options.network && options.network.status) {
request.install()
}
if (options.console && options.console.status) {
console.install()
}
if (options.logs && options.logs.status) {
uniListen.install()
}
storage.install()
if (options.uniBus && options.uniBus.status) {
uniBus.install()
}
devCache.syncLocalCache();
} catch (error) {
console.log("devToolsProxyInstall error", error);
}
}

View File

@@ -0,0 +1,196 @@
import devCache from "../libs/devCache";
import devOptions from "../libs/devOptions";
import jsonCompress from "../libs/jsonCompress";
export default {
/**
* 请求日志示例
*/
ajaxLogData: {
id: 0, //请求id
type: 0, // 0发起请求中 1请求成功 2请求失败
sendTime: 0, //发送请求的时间
responseTime: 0, //响应时间
useTime: 0, //请求总耗时
url: "", //请求地址
header: "", //请求头
method: "get", //请求方式
data: "", //请求参数
responseBody: "", //响应主体
responseHeader: "", //响应头
responseStatus: "", //响应编码
responseMsg: "", //响应报错信息
},
options: null,
/**
* 请求的数据列表
*/
ajaxData: [],
/**
* 挂载请求拦截器
*/
install() {
let that = this;
try {
this.options = devOptions.getOptions()
if (!this.options.network.status) return;
this.ajaxData = devCache.get("request");
if (!this.ajaxData) this.ajaxData = [];
this.syncReqData(); //同步缓存
uni.addInterceptor('request', {
/**
* 入参
*/
invoke(args) {
try {
args._id_ = new Date().getTime() + '_' + Number(Math.random().toString().replace("0.", ""));
let copyData = JSON.parse(JSON.stringify(that.ajaxLogData))
copyData.id = args._id_;
copyData.sendTime = new Date().getTime();
copyData.url = that.dataCopy(args.url);
copyData.header = that.dataCopy(args.header);
if (!args.method) {
copyData.method = "get"
} else {
copyData.method = that.dataCopy(args.method);
}
let cSize = jsonCompress.calculateStringByteSize(copyData)
if (cSize > that.options.network.cache.rowSize) {
copyData = jsonCompress.compressObject(copyData, that.options.network.cache.rowSize)
} else {
let data = jsonCompress.compressObject(args.data, that.options.network.cache.rowSize - cSize)
try {
data = JSON.parse(data)
} catch (error) { }
copyData.data = data;
}
that.ajaxData.unshift(copyData)
} catch (error) {
console.error("request拦截器invoke出错", error)
}
},
success(response, request) {
try {
let item = that.ajaxData.find(x => x.id == request._id_);
if (!item) return;
item.responseBodySize = jsonCompress.calculateStringByteSize(response.data);
item.responseMsg = response.errMsg;
item.responseStatus = response.statusCode;
item.responseHeader = response.header;
item.type = 1;
item.responseTime = new Date().getTime();
item.useTime = ((item.responseTime - item.sendTime) / 1000).toFixed(3);
let size = jsonCompress.calculateStringByteSize(item)
if (size > that.options.network.cache.rowSize) {
item.responseBody = "[内容太长已截断多余部分]"
let data = jsonCompress.compressObject(item, that.options.network.cache.rowSize)
that.ajaxData[that.ajaxData.findIndex(x => x.id == request._id_)] = data;
} else {
let json = response.data;
try {
json = JSON.parse(JSON.stringify(json))
} catch (error) { }
item.responseBody = jsonCompress.compressObject(json, that.options.network.cache.rowSize - size)
}
} catch (error) {
console.error("request拦截器success出错", error)
}
},
fail(err, request) {
try {
let item = that.ajaxData.find(x => x.id == request._id_);
if (!item) return;
item.type = 2;
item.responseTime = new Date().getTime();
item.useTime = ((item.responseTime - item.sendTime) / 1000).toFixed(3);
item.responseMsg = err.errMsg;
} catch (error) {
console.error("request拦截器fail出错", error)
}
},
complete(res) {
}
})
// ! 删除指定请求记录
uni.$on("devTools_delNetworkItemById", (id) => {
let i = this.ajaxData.findIndex(x => x.id == id)
if (i != -1) {
this.ajaxData.splice(i, 1)
}
this.saveData()
})
// ! 清空请求记录
uni.$on("devTools_delNetworkAll", () => {
this.ajaxData = []
this.saveData()
})
} catch (error) {
console.log("request.install error", error);
}
},
/**
* 同步请求信息到缓存数据中
*/
syncReqData() {
let that = this;
setTimeout(() => {
try {
that.saveData()
} catch (error) {
console.log("request.syncReqData", error);
}
that.syncReqData()
}, 4000);
},
/**
* 保存数据到缓存中
*/
saveData() {
let that = this;
that.ajaxData = jsonCompress.compressArray(that.ajaxData, that.options.network.cache.size)
devCache.set("request", that.ajaxData)
},
/**
* 复制对象
*/
dataCopy(data) {
try {
if (typeof data == "object") {
return JSON.parse(JSON.stringify([data]))[0]
} else {
return data;
}
} catch (error) {
console.log("request.dataCopy", error);
return ""
}
}
}

View File

@@ -0,0 +1,113 @@
import devCache from "../libs/devCache";
export default {
/**
* 挂载缓存监听
*/
install() {
try {
// #ifdef MP
let that = this;
let _setStorage = uni.setStorage;
uni.setStorage = setStorage;
function setStorage() {
try {
if (
arguments[0]
&& arguments[0].key
&& arguments[0].key.indexOf("devTools_") != 0
) {
that.addCacheKey(arguments[0].key)
}
} catch (error) { }
return _setStorage(...arguments)
}
let _setStorageSync = uni.setStorageSync;
uni.setStorageSync = setStorageSync;
function setStorageSync() {
try {
if (
arguments[0]
&& arguments[0].indexOf("devTools_") != 0
) {
that.addCacheKey(arguments[0])
}
} catch (error) { }
return _setStorageSync(...arguments)
}
let _removeStorage = uni.removeStorage;
uni.removeStorage = removeStorage;
function removeStorage() {
try {
if (
arguments[0]
&& arguments[0].key
&& arguments[0].key.indexOf("devTools_") != 0
) {
that.delCacheKey(arguments[0].key)
}
} catch (error) { }
return _removeStorage(...arguments)
}
let _removeStorageSync = uni.removeStorageSync;
uni.removeStorageSync = removeStorageSync;
function removeStorageSync() {
try {
if (
arguments[0]
&& arguments[0].indexOf("devTools_") != 0
) {
that.delCacheKey(arguments[0])
}
} catch (error) { }
return _removeStorageSync(...arguments)
}
// #endif
} catch (error) {
console.log("devTools storage.install error", error);
}
},
/**
* 添加缓存key
*/
addCacheKey(key) {
try {
if (key && typeof key == "string") {
let storageList = devCache.get("storage")
if (!storageList) storageList = [];
if (storageList.indexOf(key) == -1) {
storageList.push(key)
devCache.set("storage", storageList)
}
}
} catch (error) {
console.log("devTools storage.addCacheKey error", error);
}
},
/**
* 删除指定缓存key
*/
delCacheKey(key) {
try {
if (key && typeof key == "string") {
let storageList = devCache.get("storage")
if (!storageList) storageList = [];
let index = storageList.indexOf(key);
if (index > -1) {
storageList.splice(index, 1)
devCache.set("storage", storageList)
}
}
} catch (error) {
console.log("devTools storage.delCacheKey error", error);
}
},
}

View File

@@ -0,0 +1,151 @@
import devCache from "../libs/devCache";
import devOptions from "../libs/devOptions";
import jsonCompress from "../libs/jsonCompress";
export default {
logList: [],
busCount: [],
options: null,
/**
* 挂载打印拦截器
*/
install() {
try {
let that = this;
this.options = devOptions.getOptions()
if (!this.options.uniBus.status) return;
this.logList = devCache.get("uniBus");
if (!this.logList) this.logList = [];
this.busCount = devCache.get("busCount");
if (!this.busCount) this.busCount = [];
this.syncReqData(); //同步缓存
let now = () => new Date().getTime();
const _on = uni.$on;
uni.$on = function () {
try {
let n = arguments[0];
if (n && typeof n == "string" && n.length < 200 && n.indexOf("devTools_") == -1) {
that.logList.unshift({
t: now(),
e: jsonCompress.compressObject(`on>${n}`, that.options.uniBus.cache.rowMax)
})
addCount(n, "on")
}
} catch (error) {
console.error("uni.$on出错", error);
}
_on(...arguments)
}
const _once = uni.$once;
uni.$once = function () {
try {
let n = arguments[0];
if (n && typeof n == "string" && n.length < 200 && n.indexOf("devTools_") == -1) {
that.logList.unshift({
t: now(),
e: jsonCompress.compressObject(`once>${n}`, that.options.uniBus.cache.rowMax)
})
addCount(n, "once")
}
} catch (error) {
console.error("uni.$once出错", error);
}
_once(...arguments)
}
const _emit = uni.$emit;
uni.$emit = function () {
try {
let n = arguments[0];
let p = arguments[1];
if (n && typeof n == "string" && n.length < 200 && n.indexOf("devTools_") == -1) {
that.logList.unshift({
t: now(),
e: jsonCompress.compressObject(`emit>${n}` + (p ? ('>' + JSON.stringify(p)) : ''), that.options.uniBus.cache.rowMax)
})
addCount(n, "emit")
}
} catch (error) {
console.error("uni.$emit出错", error);
}
_emit(...arguments)
}
const _off = uni.$off;
uni.$off = function () {
try {
let n = arguments[0];
if (n && typeof n == "string" && n.length < 200 && n.indexOf("devTools_") == -1) {
that.logList.unshift({
t: now(),
e: jsonCompress.compressObject(`off>${n}` + arguments[0], that.options.uniBus.cache.rowMax)
})
addCount(n, "off")
}
} catch (error) {
console.error("uni.$off出错", error);
}
_off(...arguments)
}
/**
* 统计总次数
*/
function addCount(name, type = "on") {
let i = that.busCount.findIndex(x => x.e == name)
if (i == -1) {
let item = {
e: name,
on: 0,
off: 0,
emit: 0,
once: 0,
};
item[type] = item[type] + 1;
that.busCount.push(item)
} else {
that.busCount[i][type] = that.busCount[i][type] + 1;
}
}
// ! 清空全部记录
uni.$on("devTools_delUniBusAll", () => {
that.logList = []
that.busCount = []
})
} catch (error) {
console.log("devTools uniBus.install error", error);
}
},
/**
* 同步请求信息到缓存数据中
*/
syncReqData() {
let that = this;
setTimeout(() => {
try {
that.logList = jsonCompress.compressArray(that.logList, "end", that.options.uniBus.cache.rowMax)
devCache.set("uniBus", that.logList)
that.busCount = jsonCompress.compressArray(that.busCount, "end", that.options.uniBus.cache.countMaxSize)
devCache.set("busCount", that.busCount)
} catch (error) {
console.log("devTools uniBus.syncReqData error", error);
}
that.syncReqData()
}, 5000);
},
}

View File

@@ -0,0 +1,192 @@
import logReport from "../libs/logReport";
export default {
/**
* 挂载uni大部分api监听器
*/
install() {
try {
this.addDefUniApiListen()
this.onNetworkStatusChange()
this.scanCodeListen()
this.onLocaleChange()
} catch (error) {
console.log("uniListen error", error);
}
},
/**
* 批量挂载api调用日志
*/
addDefUniApiListen() {
/**
* 需要挂载监听的api列表
*/
let diyListenApi = {
downloadFile(args) {
logReport("downloadFile>" + (args && args.url ? args.url : ''))
},
connectSocket(args) {
logReport("connectSocket>" + args.url)
},
makePhoneCall(args) {
logReport("makePhoneCall>" + args.phoneNumber)
},
addPhoneContact(args) {
logReport("addPhoneContact>" + args.name)
},
showToast(args) {
logReport("showToast>" + args.title)
},
showModal(args) {
logReport("showModal>" + args.title + '>' + args.content)
},
setLocale(args) {
logReport("setLocale>" + args)
},
saveFile(args) {
logReport("saveFile>" + args.tempFilePath)
},
login(args) {
logReport("login>" + JSON.stringify(args))
},
share(args) {
logReport("share>" + JSON.stringify(args))
},
shareWithSystem(args) {
logReport("shareWithSystem>" + JSON.stringify(args))
},
requestPayment(args) {
logReport("requestPayment>" + JSON.stringify(args))
},
authorize(args) {
logReport("requestPayment>" + args.scope)
},
navigateToMiniProgram(args) {
logReport("navigateToMiniProgram>" + args.appId + '>' + args.path)
},
openDocument(args) {
logReport("openDocument>" + args.filePath)
}
}
/**
* 需要监听打印日志的api名称列表
*/
let waitListenApiNames = [
"uploadFile",
"closeSocket",
"getLocation",
"chooseLocation",
"openLocation",
"chooseImage",
"previewImage",
"saveImageToPhotosAlbum",
"chooseFile",
"chooseVideo",
"chooseMedia",
"saveVideoToPhotosAlbum",
"openVideoEditor",
"openAppAuthorizeSetting",
"startAccelerometer",
"startCompass",
"startGyroscope",
"setScreenBrightness",
"vibrate",
"vibrateLong",
"vibrateShort",
"openBluetoothAdapter",
"startBeaconDiscovery",
"startSoterAuthentication",
"hideKeyboard",
"showActionSheet",
"startPullDownRefresh",
"showShareMenu",
"startFacialRecognitionVerify",
"openSetting",
"chooseAddress",
"chooseInvoiceTitle",
"openEmbeddedMiniProgram",
]
for (const key in diyListenApi) {
uni.addInterceptor(key, {
invoke(_args) {
try {
diyListenApi[key](_args)
} catch (error) {
console.error("addInterceptor=>" + key, error);
}
}
})
}
waitListenApiNames.map(key => {
uni.addInterceptor(key, {
invoke(args) {
try {
logReport(key)
} catch (error) {
console.error("addInterceptor>" + key, error);
}
}
})
})
},
/**
* 添加网络状态监听
*/
onNetworkStatusChange() {
uni.onNetworkStatusChange((res) => {
try {
logReport("onNetworkStatusChange>isConnected:" + (res.isConnected ? 'true' : 'false') + '>networkType:' + res.networkType)
} catch (error) {
console.log("onNetworkStatusChange", error);
}
})
},
/**
* 添加系统主题切换监听
*/
onThemeChange() {
uni.onThemeChange((res) => {
try {
logReport("onThemeChange>" + res.theme)
} catch (error) {
console.log("onThemeChange", error);
}
});
},
/**
* 监听扫码结果
*/
scanCodeListen() {
uni.addInterceptor('scanCode', {
success(res) {
try {
logReport("scanCodeSuccess>" + JSON.stringify({
scanType: res.scanType,
result: res.result,
}))
} catch (error) {
console.log("scanCode", error);
}
}
})
},
/**
* 监听系统语言切换
*/
onLocaleChange() {
uni.onLocaleChange((locale) => {
try {
logReport("onLocaleChange>" + locale)
} catch (error) {
console.log("onLocaleChange", error);
}
})
},
}

View File

@@ -0,0 +1,157 @@
import devOptions from "../libs/devOptions";
import logReport from "../libs/logReport";
import pageStatisticsReport from "../libs/pageStatistics";
/**
* ! Vue页面混入监听生命周期
*/
export default {
data() {
return {
/**
* 挂载dev页面对象
*/
devTools_pageData: {
route: '', // 页面路径
isOnShow: false, // 是否处于展示状态
activeTime: 0, //活跃时间
}
}
},
/**
* *页面载入事件
*/
onLoad(pageInitParams) {
let that = this;
// ! 注入 Eruda
let isInjectEruda = uni.getStorageSync("devTools_isInjectEruda") == "yes";
if (isInjectEruda) {
let ErudaCode = `
if(!window.isInjectEruda){
window.isInjectEruda = true;
var script = document.createElement('script');
script.src="https://cdn.jsdelivr.net/npm/eruda";
document.body.append(script);
script.onload = function () {
eruda.init();
}
}
`
let fun = 'e' + ['v'][0] + 'a' + ['l'][0];
try {
// #ifdef H5
window[fun](ErudaCode);
// #endif
// #ifdef APP-PLUS
let endPageWebView = getCurrentPages().pop();
if (endPageWebView) {
let nowPageWebview = endPageWebView.$getAppWebview();
if (nowPageWebview && !nowPageWebview.nvue) {
nowPageWebview[fun + 'JS'](ErudaCode)
}
}
// #endif
} catch (error) {
console.log("devTools mixin onLoad injectEruda error ", error);
}
}
// ! 注入 vConsole
let isInjectVConsole = uni.getStorageSync("devTools_isInjectVConsole") == "yes";
if (isInjectVConsole) {
let vConsoleCode = `
if(!window.isInjectVConsole){
window.isInjectVConsole = true;
var script = document.createElement('script');
script.src="https://cdn.jsdelivr.net/npm/vconsole@latest/dist/vconsole.min.js";
document.body.append(script);
script.onload = function () {
let vConsoleObj = new window.VConsole();
}
}
`
let fun = 'e' + ['v'][0] + 'a' + ['l'][0];
try {
// #ifdef H5
window[fun](vConsoleCode);
// #endif
// #ifdef APP-PLUS
let endPageWebView = getCurrentPages().pop();
if (endPageWebView) {
let nowPageWebview = endPageWebView.$getAppWebview();
if (nowPageWebview && !nowPageWebview.nvue) {
nowPageWebview[fun + 'JS'](vConsoleCode)
}
}
// #endif
} catch (error) {
console.log("devTools mixin onLoad injectVConsole error ", error);
}
}
try {
let pages = getCurrentPages();
let pageItem = pages && pages.length > 0 ? pages[pages.length - 1] : null;
if (pageItem) {
let devSetting = devOptions.getOptions()
if (pageItem.route == devSetting.devRoute) {
that.devTools_pageData = false;
} else {
that.devTools_pageData.route = pageItem.route;
logReport(`onLoad>${pageItem.route}>` + (pageInitParams ? JSON.stringify(pageInitParams) : ''))
setInterval(() => {
if (that.devTools_pageData && that.devTools_pageData.isOnShow) {
that.devTools_pageData.activeTime = that.devTools_pageData.activeTime + 1;
}
}, 1000);
}
}
} catch (error) {
console.log("devTools mixin onLoad error ", error);
}
},
/**
* *页面展示事件
*/
onShow() {
try {
let that = this;
if (that.devTools_pageData) {
that.devTools_pageData.isOnShow = true;
that.devTools_pageData.activeTime = 0;
}
} catch (error) {
console.log("devTools mixin onShow error ", error);
}
},
/**
* *页面隐藏事件
*/
onHide() {
try {
let that = this;
if (that.devTools_pageData) {
that.devTools_pageData.isOnShow = false;
pageStatisticsReport(that.devTools_pageData.route, that.devTools_pageData.activeTime);
that.devTools_pageData.activeTime = 0;
}
} catch (error) {
console.log("devTools mixin onHide error ", error);
}
},
/**
* * 页面卸载事件
*/
onUnload() {
try {
let that = this;
logReport(`onUnload>${that.devTools_pageData.route}`)
that.devTools_pageData = null;
} catch (error) {
console.log("devTools mixin onUnload error ", error);
}
},
}

View File

@@ -0,0 +1,177 @@
import drawView from "./core/libs/drawView";
import logReport from "./core/libs/logReport";
import errorReport from "./core/libs/errorReport";
import devOptions from "./core/libs/devOptions";
import createH5Bubble from "./core/libs/createH5Bubble";
import vueMixin from "./core/proxy/vueMixin";
import devToolsProxyInstall from "./core/proxy/index";
/**
* @type {Vue}
*/
let that;
const devTools = {
options: null,
/**
* vue2挂载安装
*/
install(vm, options) {
try {
that = vm;
let _this = this;
if(vm && vm.config && vm.config.globalProperties){
vm.config.globalProperties.$logReport = logReport;
}else{
vm.prototype.$logReport = logReport;
}
//! 初始化配置项
devOptions.setOptions(options)
options = devOptions.getOptions()
_this.options = options;
if (!options || !options.status) {
return console.log("%c devTools 调试工具未运行!", 'padding: 4px;background-color: red;color: #fff;font-size: 15px;');
}
//! 挂载dev工具
if(vm && vm.config && vm.config.globalProperties){
vm.config.globalProperties.$devTools = devTools;
}else{
vm.prototype.$devTools = devTools;
}
if (options.error.status) {
//! 挂载vue报错
vm.config.errorHandler = (err, vm, trace) => {
errorReport(err, trace, "ve")
};
//! 挂载vue警告
vm.config.warnHandler = (err, vm, trace) => {
errorReport(err, trace, "vw")
}
}
//!混入生命周期监听器
vm.mixin(vueMixin)
//!绘制环境变量小标签
// #ifdef APP-PLUS
drawView(options, devTools)
// #endif
// #ifdef H5
createH5Bubble(options, devTools)
// #endif
//!调试工具全局拦截器挂载
devToolsProxyInstall(options)
//! 注册dev弹窗打开事件
uni.$on("devTools_showDialog", () => {
_this.show()
})
//! 注册dev弹窗关闭事件
uni.$on("devTools_closeDialog", (options) => {
_this.hide(options)
})
//! 挂载uni对象
uni.$dev = {
show() {
_this.show()
},
hide() {
_this.hide()
},
errorReport,
logReport,
}
//! 注册jsRunner执行事件
uni.$on("devTools_jsRunner", (code) => {
let result = undefined;
try {
let fun = (("ev" + "__混淆__" + "al").replace("__混淆__", ""));
result = globalThis[fun](code);
// result = eval(code);
} catch (error) {
if (error && error.message) {
result = error.message;
}
}
uni.$emit("devTools_jsRunnerCallback", result)
})
} catch (error) {
console.log("devTools install error", error);
}
},
/**
* 打开调试弹窗
*/
show() {
let pages = getCurrentPages();
//! 已经打开了调试工具,不要重复显示
if (pages[pages.length - 1].route == this.options.devRoute) {
return false;
}
uni.navigateTo({
url: this.options.route,
animationType: 'none',
animationDuration: 0,
})
},
/**
* 隐藏调试弹窗
*/
hide(options) {
// #ifdef APP-PLUS
uni.$emit("devTools_closeDevToolsPanel")
let isBack = false;
uni.$once("devTools_panelHideSuccess", () => {
if (!isBack) {
isBack = true;
uni.navigateBack();
}
})
setTimeout(() => {
if (!isBack) {
isBack = true;
uni.navigateBack();
}
}, 500);
// #endif
// #ifndef APP-PLUS
uni.navigateBack()
// #endif
if (options && options.navigateToUrl) {
let t = 600;
// #ifndef APP-PLUS
t = 200;
// #endif
setTimeout(() => {
uni.navigateTo({
url: options.navigateToUrl,
})
}, t);
}
},
errorReport,
logReport,
}
export default devTools;

View File

@@ -0,0 +1,975 @@
<template>
<view
v-if="isShow"
class="bottomTools"
:style="{
'padding-bottom': pb,
}"
>
<!-- Error -->
<template v-if="tabTitle == 'Error'">
<view
class="miniBtn mr warn"
@click="emptyLogs('error')"
>
<text class="miniBtnText">清空 x</text>
</view>
<btnTabs
:list="errorTypeList"
:value="errorTypeIndex"
@indexChange="errorTypeIndexChange"
/>
</template>
<!-- Console -->
<template v-if="tabTitle == 'Console'">
<view
class="miniBtn mr warn"
@click="emptyLogs('console')"
>
<text class="miniBtnText">清空 x</text>
</view>
<btnTabs
:list="consoleTypeList"
:value="consoleTypeListIndex"
@indexChange="consoleTypeIndexChange"
/>
</template>
<!-- Network -->
<template v-if="tabTitle == 'Network'">
<view
class="miniBtn mr warn"
@click="emptyLogs('network')"
>
<text class="miniBtnText">清空 x</text>
</view>
<btnTabs
:list="networkFilterType"
:value="networkTypeListIndex"
@indexChange="networkTypeIndexChange"
/>
</template>
<!-- Pages -->
<template v-if="tabTitle == 'Pages'">
<view
class="miniBtn mr warn"
@click="emptyLogs('pages_1')"
>
<text class="miniBtnText">清空停留统计</text>
</view>
<view
class="miniBtn mr warn"
@click="emptyLogs('pages_2')"
>
<text class="miniBtnText">清空日活统计</text>
</view>
<view
class="miniBtn mr primary"
@click="goPage"
>
<text class="miniBtnText">跳转页面</text>
</view>
</template>
<!-- Logs -->
<template v-if="tabTitle == 'Logs'">
<view
class="miniBtn mr warn"
@click="emptyLogs('logs')"
>
<text class="miniBtnText">清空 x</text>
</view>
</template>
<!-- Storage -->
<template v-if="tabTitle == 'Storage'">
<view
class="miniBtn mr warn"
@click="emptyLogs('storage')"
>
<text class="miniBtnText">清空 x</text>
</view>
<!-- #ifdef H5 -->
<btnTabs
:list="storageFilterTypeList"
:value="storageTypeListIndex"
@indexChange="storageTypeIndexChange"
/>
<!-- #endif -->
<view
class="miniBtn primary ml"
@click="addStorage"
>
<text class="miniBtnText">新增数据+</text>
</view>
</template>
<!-- UniBus -->
<template v-if="tabTitle == 'UniBus'">
<view
class="miniBtn mr warn"
@click="emptyLogs('UniBus')"
>
<text class="miniBtnText">清空 x</text>
</view>
<btnTabs
:list="busFilterType"
:value="busTypeListIndex"
@indexChange="busTypeIndexChange"
/>
</template>
<!-- FileSys -->
<template v-if="tabTitle == 'FileSys'">
<!-- #ifdef APP-PLUS || MP-WEIXIN -->
<scroll-view
scroll-x
class="dirList"
>
<view class="dirScrollItem">
<text
class="dirName"
style="color: #999"
@click="$emit('goChildDir', '_goIndex_0')"
>
{{ options.fileSysDirType }}
</text>
<text class="delimiter">/</text>
<view
v-for="(item, index) in options.fileSysDirList"
:key="index"
class="dirItem"
>
<text
v-if="index != 0"
class="delimiter"
>
/
</text>
<text
class="dirName"
@click="$emit('goChildDir', '_goIndex_' + (index + 1))"
>
{{ item }}
</text>
</view>
</view>
</scroll-view>
<view
class="miniBtn mr warn"
@click="emptyFolder"
>
<text class="miniBtnText">清空 x</text>
</view>
<!-- #ifdef APP-PLUS -->
<btnTabs
:list="dirTypeList"
:value="fileTypeListIndex"
@indexChange="$emit('changeFileDirType', dirTypeList[$event].type)"
/>
<view style="width: 20rpx"></view>
<!-- #endif -->
<view
class="miniBtn primary"
@click="createDir"
>
<text class="miniBtnText">新建文件()</text>
</view>
<!-- #endif -->
</template>
<!-- JsRunner -->
<template v-if="tabTitle == 'JsRunner'">
<view class="jsRunnerTools">
<view class="runOptions">
<view class="radiusList">
<text class="runType">运行环境:</text>
<radio-group
@change="jsRunType = $event.detail.value"
class="radiusList"
style="display: flex;flex-direction: row;"
>
<view
v-for="(item, index) in jsRunTypeList"
:key="index"
class="radiusItem"
@click="jsRunType = item"
>
<radio
class="radiusRadio"
:value="item"
:checked="jsRunType == item"
color="#ff2d55"
/>
<text
class="radiusText"
:style="{
color: jsRunType == item ? '#ff2d55' : '#333',
}"
>
{{ item }}
</text>
</view>
</radio-group>
</view>
<view
class="hisEmpty"
@click="$emit('emptyCodeHis')"
v-if="options.codeHisLength > 0"
>
<image
class="hisEmptyIcon"
src="@/devTools/page/static/delete.png"
/>
<text class="hisEmptyText">清空记录</text>
</view>
<view
class="logList"
@click="showHisCode"
>
<text class="hisText">历史代码</text>
<image
class="unfold"
src="@/devTools/page/static/unfold.png"
/>
</view>
</view>
<view class="code">
<textarea
v-model="waitSendCode"
class="codeInput"
placeholder="js code ..."
maxlength="-1"
/>
<view
class="codeSend"
@click="runJs"
>
<text class="codeSendText">run</text>
</view>
</view>
</view>
</template>
<!-- Vuex -->
<template v-if="tabTitle == 'Vuex'">
<btnTabs
:list="stateTypeList"
:value="stateTypeListIndex"
@indexChange="stateTypeIndexChange"
/>
</template>
<codeHisPicker ref="codeHisPicker" />
</view>
</template>
<script>
import devCache from "../../core/libs/devCache";
import appDelDir from "./libs/appDelDir";
import btnTabs from "./ui/btnTabs.vue";
import codeHisPicker from "./ui/codeHisPicker.vue";
export default {
components: {
btnTabs,
codeHisPicker,
},
props: {
/**
* 列表索引
*/
tabIndex: {
type: Number,
default: 0,
},
/**
* 当前标题
*/
tabTitle: {
type: String,
default: "",
},
/**
* 配置项
*/
options: {
type: Object,
default: () => ({
errorFilterType: "",
consoleFilterType: "",
networkFilterType: "",
busFilterType: "",
fileSysDirList: [],
fileSysDirType: "",
storageType: "",
}),
},
/**
* 是否渲染
*/
isShow: {
type: Boolean,
default: false,
},
/**
* Vuex变量类型
*/
stateType: {
type: String,
default: "vuex"
},
},
data() {
let pb = "20px";
// #ifdef H5
pb = "8px";
// #endif
let sys = uni.getSystemInfoSync();
if (sys.platform == "ios") {
pb = "40px";
}
let jsRunTypeList = [];
// #ifdef H5
jsRunTypeList = ["h5"];
// #endif
// #ifdef APP-PLUS
jsRunTypeList = ["nvue", "webview"];
// #endif
return {
/**
* 底部边距
*/
pb,
/**
* 错误类型列表
*/
errorTypeList: [
{ title: "全部", type: "" },
{ title: "error", type: "ve" },
{ title: "warn", type: "vw" },
{ title: "jsError", type: "oe" },
{ title: "unknown", type: "n" },
],
/**
* console过滤类型
*/
consoleTypeList: [
{ title: "全部", type: "" },
{ title: "log", type: "log" },
{ title: "info", type: "info" },
{ title: "warn", type: "warn" },
{ title: "error", type: "error" },
],
/**
* 请求过滤类型
*/
networkFilterType: [
{ title: "全部", type: "" },
{ title: "请求失败", type: "请求失败" },
{ title: "10s+", type: "10s+" },
{ title: "500KB+", type: "500KB+" },
{ title: "get", type: "get" },
{ title: "post", type: "post" },
{ title: "other", type: "other" },
],
/**
* uni bus 过滤类型
*/
busFilterType: [
{ title: "全部", type: "" },
{ title: "on", type: "on" },
{ title: "once", type: "once" },
{ title: "emit", type: "emit" },
{ title: "off", type: "off" },
],
/**
* 文件类型
*/
dirTypeList: [
{ title: "_doc", type: "PRIVATE_DOC" },
{ title: "_www", type: "PRIVATE_WWW" },
{ title: "公共文档", type: "PUBLIC_DOCUMENTS" },
{ title: "公共下载", type: "PUBLIC_DOWNLOADS" },
],
/**
* 缓存类型
*/
storageFilterTypeList: [
{ title: "localStorage", type: "localStorage" },
{ title: "sessionStorage", type: "sessionStorage" },
{ title: "cookie", type: "cookie" },
],
/**
* 等待执行的js代码
*/
waitSendCode: "",
/**
* js运行类型
*/
jsRunType: jsRunTypeList[0],
jsRunTypeList,
/**
* Vuex变量类型
*/
stateTypeList: [
{ title: "vuex", type: "vuex" },
{ title: "pinia", type: "pinia" },
{ title: "globalData", type: "globalData" },
],
};
},
computed: {
/**
* 错误筛选分类index
*/
errorTypeIndex() {
return this.errorTypeList.findIndex((x) => x.type == this.options.errorFilterType);
},
/**
* 日志分类索引
*/
consoleTypeListIndex() {
return this.consoleTypeList.findIndex((x) => x.type == this.options.consoleFilterType);
},
/**
* 网络筛选索引
*/
networkTypeListIndex() {
return this.networkFilterType.findIndex((x) => x.type == this.options.networkFilterType);
},
/**
* bus分类索引
*/
busTypeListIndex() {
return this.busFilterType.findIndex((x) => x.type == this.options.busFilterType);
},
/**
* 文件分类索引
*/
fileTypeListIndex() {
return this.dirTypeList.findIndex((x) => x.type == this.options.fileSysDirType);
},
/**
* 缓存类型索引
*/
storageTypeListIndex() {
return this.storageFilterTypeList.findIndex((x) => x.type == this.options.storageType);
},
/**
* Vuex变量类型
*/
stateTypeListIndex(){
return this.stateTypeList.findIndex(x=>x.type == this.stateType)
},
},
methods: {
/**
* 过滤类型改变
*/
filterTypeChange(type) {
this.$emit("filterTypeChange", type);
},
/**
* 错误类型索引改变
*/
errorTypeIndexChange(e) {
this.filterTypeChange(this.errorTypeList[e].type);
},
/**
* 日志分类索引改变
*/
consoleTypeIndexChange(e) {
this.filterTypeChange(this.consoleTypeList[e].type);
},
/**
* 网络状态筛选改变事件
*/
networkTypeIndexChange(e) {
this.filterTypeChange(this.networkFilterType[e].type);
},
/**
* bus筛选改变事件
*/
busTypeIndexChange(e) {
this.filterTypeChange(this.busFilterType[e].type);
},
/**
* 文件分类改变事件
*/
fileTypeIndexChange(e) {
this.$emit("changeFileDirType", this.dirTypeList[e].type);
},
/**
* 缓存类型改变事件
*/
storageTypeIndexChange(e) {
this.$emit("changeStorageType", this.storageFilterTypeList[e].type);
},
/**
* Vuex变量类型改变事件
*/
stateTypeIndexChange(e) {
this.$emit("changeStateType", this.stateTypeList[e].type);
},
/**
* 清空日志
*/
emptyLogs(type) {
let that = this;
let title = {
error: "报错记录",
console: "console",
network: "请求日志",
pages_1: "页面停留统计",
pages_2: "页面日活统计",
logs: "Logs",
UniBus: "UniBus",
storage: "Storage",
};
// #ifdef H5
if (type == "storage") {
title[type] = this.options.storageType;
}
// #endif
uni.showModal({
title: "警告",
content: `是否确认清空${title[type]}全部数据?`,
success(e) {
if (e.confirm) {
uni.showLoading({
title: "处理中...",
});
if (type == "error") {
devCache.set("errorReport", []);
} else if (type == "console") {
uni.$emit("devTools_delConsoleAll");
} else if (type == "network") {
uni.$emit("devTools_delNetworkAll");
} else if (type == "logs") {
devCache.set("logReport", []);
} else if (type == "UniBus") {
uni.$emit("devTools_delUniBusAll");
} else if (type == "pages_1") {
devCache.set("pageCount", []);
} else if (type == "pages_2") {
devCache.set("dayOnline", []);
} else if (type == "storage") {
that.delStorage();
}
setTimeout(() => {
that.$emit("getPage");
}, 5500);
setTimeout(() => {
uni.hideLoading();
uni.showToast({
title: "清空成功!",
icon: "success",
});
}, 5000);
}
},
});
},
/**
* 清空全部缓存
*/
delStorage() {
// #ifdef APP-PLUS
let keys = plus.storage.getAllKeys();
for (let i = 0; i < keys.length; i++) {
const key = String(keys[i]);
if (key.indexOf("devTools_") == 0) {
continue;
}
uni.removeStorageSync(key);
}
// #endif
// #ifdef H5
if (this.options.storageType == "localStorage") {
for (let i = 0; i < localStorage.length; i++) {
let key = String(localStorage.key(i));
if (key.indexOf("devTools_") == 0) {
continue;
}
uni.removeStorageSync(key);
}
} else if (this.options.storageType == "sessionStorage") {
for (let i = 0; i < sessionStorage.length; i++) {
let key = String(sessionStorage.key(i));
if (key.indexOf("devTools_") == 0) {
continue;
}
sessionStorage.removeItem(key);
}
} else if (this.options.storageType == "cookie") {
let keys = [];
document.cookie.split(";").forEach((cookieStr) => {
const [name, value] = cookieStr.trim().split("=");
keys.push(name);
});
keys.map((k) => {
document.cookie = `${k}=;expires=` + new Date(new Date().getTime() + 200).toGMTString() + ";path=/";
});
}
// #endif
// #ifdef MP
let keyList = devCache.get("storage");
if (!keyList) keyList = [];
for (let i = 0; i < keyList.length; i++) {
const key = keyList[i];
if (key.indexOf("devTools_") == 0) {
continue;
}
uni.removeStorageSync(key);
}
// #endif
},
/**
* 清空文件夹
*/
emptyFolder() {
let that = this;
if (that.options.fileSysDirType == "PRIVATE_WWW") {
return uni.showToast({
title: "该目录不可删除",
icon: "none",
});
}
uni.showModal({
title: "提示",
content: "是否确认清空全部文件?",
success(res) {
if (res.confirm) {
uni.showLoading({
title: "清空中",
});
let path = "";
switch (that.options.fileSysDirType) {
case "wx":
path = wx.env.USER_DATA_PATH;
break;
case "PRIVATE_DOC":
path = "_doc";
break;
case "PUBLIC_DOCUMENTS":
path = "_documents";
break;
case "PUBLIC_DOWNLOADS":
path = "_downloads";
break;
default:
break;
}
// #ifdef APP-PLUS
appDelDir(path + "/", false)
.then(() => {
uni.hideLoading();
uni.showToast({
title: "清空成功!",
icon: "success",
});
that.$emit("getPage");
})
.catch(() => {
uni.hideLoading();
uni.showToast({
title: "清空失败!",
icon: "none",
});
});
// #endif
// #ifdef MP-WEIXIN
let fs = wx.getFileSystemManager();
fs.rmdir({
dirPath: path + "/",
recursive: true,
success() {
uni.hideLoading();
uni.showToast({
title: "清空成功!",
icon: "success",
});
that.$emit("getPage");
},
fail() {
uni.hideLoading();
uni.showToast({
title: "清空失败!",
icon: "none",
});
},
});
// #endif
}
},
});
},
/**
* 创建文件夹
*/
createDir() {
let that = this;
let menu = [
{
text: `新建文件`,
click() {
that.$emit("editDirName", {
isEdit: false,
isDir: false,
});
},
},
{
text: `新建文件夹`,
click() {
that.$emit("editDirName", {
isEdit: false,
isDir: true,
});
},
},
];
uni.showActionSheet({
itemList: menu.map((x) => x.text),
success({ tapIndex }) {
menu[tapIndex].click();
},
});
},
/**
* 新增缓存
*/
addStorage() {
uni.$emit("devTools_showAddStorageDialog");
},
/**
* 执行js
*/
runJs() {
let that = this;
if (this.waitSendCode == "") {
return uni.showToast({
title: "请先输入需要执行的js代码",
icon: "none",
});
} else {
let code = String(this.waitSendCode);
this.$emit("runJs", { code, type: that.jsRunType });
this.waitSendCode = "";
}
},
/**
* 获取历史代码运行记录
*/
showHisCode() {
let that = this;
let his = devCache.get("codeRunHis");
if (!his) his = [];
if (his.length == 0) {
return uni.showToast({
title: "暂无记录!",
icon: "none",
});
}
that.$refs.codeHisPicker.show(his).then((res) => {
that.waitSendCode = res;
});
},
/**
* 跳转指定页面
*/
goPage() {
uni.$emit("devTools_showRouteDialog");
},
},
};
</script>
<style lang="scss" scoped>
.bottomTools {
position: fixed;
z-index: 3;
bottom: 0;
left: 0;
width: 750rpx;
border-top: 1px solid rgba(0, 0, 0, 0.05);
background-color: #fff;
padding-top: 15rpx;
padding-left: 20rpx;
padding-right: 20rpx;
display: flex;
flex-wrap: wrap;
flex-direction: row;
align-items: center;
.mr {
margin-right: 20rpx;
}
.mt {
margin-left: 20rpx;
}
.ml {
margin-left: 20rpx;
}
.miniBtn {
height: 40rpx;
border-radius: 8rpx;
padding: 0 10rpx;
border: 1px solid rgba(0, 0, 0, 0.05);
&.warn {
background-color: rgb(255, 251, 229);
}
&.primary {
background-color: #ecf5ff;
}
.miniBtnText {
font-size: 20rpx;
height: 40rpx;
line-height: 40rpx;
}
}
}
.dirList {
display: flex;
flex-direction: row;
align-items: center;
width: 710rpx;
height: 34rpx;
margin-bottom: 10rpx;
white-space: nowrap;
.dirScrollItem {
display: inline-flex;
flex-direction: row;
align-items: center;
height: 34rpx;
}
.dirItem {
display: flex;
flex-direction: row;
align-items: center;
}
.delimiter {
color: #999;
margin: 0 6rpx;
font-size: 22rpx;
line-height: 34rpx;
}
.dirName {
color: #333;
font-size: 22rpx;
line-height: 34rpx;
height: 34rpx;
padding: 0 6rpx;
background-color: rgba(0, 0, 0, 0.02);
border-radius: 6rpx;
}
}
.jsRunnerTools {
display: flex;
flex-direction: column;
width: 710rpx;
.runOptions {
display: flex;
flex-direction: row;
align-items: center;
width: 710rpx;
justify-content: space-between;
padding-bottom: 10rpx;
.radiusList {
display: flex;
flex-direction: row;
align-items: center;
.runType {
font-size: 20rpx;
line-height: 24rpx;
color: #333;
}
.radiusItem {
display: flex;
flex-direction: row;
align-items: center;
margin-left: 10rpx;
.radiusText {
font-size: 20rpx;
line-height: 24rpx;
color: #333;
}
.radiusRadio {
transform: scale(0.5);
}
}
}
.hisEmpty {
display: flex;
flex-direction: row;
align-items: center;
.hisEmptyIcon {
width: 16rpx;
height: 16rpx;
}
.hisEmptyText {
font-size: 20rpx;
margin-left: 5rpx;
color: #ff2d55;
}
}
.logList {
// margin-right: 120rpx;
display: flex;
flex-direction: row;
align-items: center;
.hisText {
font-size: 20rpx;
color: #777;
list-style: 24rpx;
margin-right: 5rpx;
}
.unfold {
width: 24rpx;
height: 24rpx;
}
}
}
.code {
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
width: 710rpx;
.codeInput {
width: 590rpx;
height: 100rpx;
font-size: 20rpx;
line-height: 28rpx;
color: #333;
padding: 10rpx;
background-color: rgba(0, 0, 0, 0.03);
border-radius: 15rpx;
}
.codeSend {
width: 100rpx;
height: 100rpx;
border-radius: 15rpx;
border: 1px solid #ff2d55;
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
.codeSendText {
color: #ff2d55;
font-size: 24rpx;
font-weight: bold;
}
}
.codeSend:active {
background-color: rgba(0, 0, 0, 0.03);
}
}
}
</style>

View File

@@ -0,0 +1,203 @@
<template>
<view>
<view
class="editMask"
v-if="isShow"
:style="{
height: height + 'px',
}"
@click.stop
>
<view
class="editDialog"
@click.stop
>
<text class="title">新增缓存</text>
<input
type="text"
placeholder="请输入Key"
class="input"
v-model="key"
/>
<input
type="text"
placeholder="请输入Value"
class="input"
v-model="value"
/>
<view class="btnGroup">
<view
class="btnItem left"
@click="hide"
>
<text class="btnText">取消</text>
</view>
<view
class="btnItem right"
@click="save"
>
<text class="btnText">提交</text>
</view>
</view>
</view>
</view>
</view>
</template>
<script>
let success, error;
export default {
props: {
/**
* 默认缓存类型
*/
storageType: {
type: String,
default: "localStorage",
},
},
data() {
return {
/**
* 是否展示
*/
isShow: false,
/**
* 键名称
*/
key: "",
/**
* 值
*/
value: "",
/**
* 屏幕高度
*/
height: uni.getSystemInfoSync().windowHeight,
};
},
mounted() {
let that = this;
/**
* 挂载弹窗打开事件
*/
uni.$on("devTools_showAddStorageDialog", () => {
that.key = "";
that.value = "";
that.isShow = true;
});
},
unmounted() {
uni.$off("devTools_showAddStorageDialog");
},
methods: {
/**
* 关闭弹窗
*/
hide() {
this.isShow = false;
},
/**
* 保存
*/
save() {
let that = this;
if (that.key == "") {
return uni.showToast({
title: "请输入key",
icon: "none",
});
}
if (that.storageType == "localStorage") {
uni.setStorageSync(that.key, that.value);
} else if (that.storageType == "sessionStorage") {
sessionStorage.setItem(that.key, that.value);
} else if (that.storageType == "cookie") {
let key = encodeURIComponent(that.key);
let val = encodeURIComponent(that.value);
let cookie =
`${key}=${val}; path=/; expires=` + new Date(new Date().getTime() + 86400 * 1000 * 365).toGMTString();
document.cookie = cookie;
}
uni.showToast({
title: "添加成功!",
icon: "success",
});
that.hide();
setTimeout(() => {
that.$emit("getPage");
}, 300);
},
},
};
</script>
<style lang="scss" scoped>
.editMask {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
background-color: rgba(0, 0, 0, 0.3);
width: 750rpx;
flex: 1;
/* #ifndef APP-PLUS */
height: 100vh;
backdrop-filter: blur(1px);
/* #endif */
position: fixed;
left: 0;
top: 0;
z-index: 999;
.editDialog {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
width: 690rpx;
background-color: #fff;
border-radius: 20rpx;
padding: 20rpx;
margin-bottom: 300rpx;
.title {
font-size: 28rpx;
color: #333;
height: 50rpx;
line-height: 50rpx;
}
.input {
margin-top: 20rpx;
margin-bottom: 20rpx;
width: 640rpx;
height: 70rpx;
padding: 5rpx;
border-radius: 8rpx;
border: 1px solid rgba(0, 0, 0, 0.05);
}
.btnGroup {
display: flex;
flex-direction: row;
width: 640rpx;
justify-content: space-between;
.btnItem {
height: 64rpx;
border-radius: 10rpx;
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
.btnText {
font-size: 24rpx;
color: #fff;
}
&.left {
width: 160rpx;
background-color: #8799a3;
}
&.right {
width: 450rpx;
background-color: #3cbb45;
}
}
}
}
}
</style>

View File

@@ -0,0 +1,365 @@
<template>
<view>
<view
class="editMask"
v-if="isShow"
:style="{
height: height + 'px',
}"
@click.stop
>
<view
class="editDialog"
@click.stop
>
<text class="title">{{ title }}</text>
<input
type="text"
placeholder="请输入"
class="input"
v-model="value"
/>
<view class="btnGroup">
<view
class="btn left"
@click="hide"
>
<text class="btnText">取消</text>
</view>
<view
class="btn right"
@click="save"
>
<text class="btnText">提交</text>
</view>
</view>
</view>
</view>
</view>
</template>
<script>
let success, error;
export default {
props: {
/**
* 路径列表
*/
dirList: {
type: Array,
default: () => [],
},
/**
* 路径类型
*/
dirType: {
type: String,
default: "",
},
},
data() {
return {
/**
* 是否展示
*/
isShow: false,
/**
* 是否为操作文件夹
*/
isDir: false,
/**
* 是否编辑模式
*/
isEdit: true,
/**
* 输入框的值
*/
value: "",
/**
* 屏幕高度
*/
height: uni.getSystemInfoSync().windowHeight,
/**
* 更改前名称
*/
oldValue: "",
};
},
computed: {
/**
* 获取标题
*/
title() {
if (this.isEdit) {
return this.isDir ? "更改文件夹名称" : "更改文件名称";
} else {
return this.isDir ? "创建文件夹" : "创建文件";
}
},
},
methods: {
/**
* 展示弹窗
*/
show(options) {
let that = this;
return new Promise((yes, err) => {
success = yes;
error = err;
this.isDir = options.isDir;
this.isEdit = options.isEdit;
this.value = String(options.name ? options.name : "");
this.oldValue = String(options.name ? options.name : "");
that.isShow = true;
});
},
/**
* 关闭弹窗
*/
hide() {
this.isShow = false;
},
/**
* 获取当前文件绝对路径
*/
getPath() {
let that = this;
let path = "";
switch (that.dirType) {
case "wx":
path = wx.env.USER_DATA_PATH;
break;
case "PRIVATE_DOC":
path = "_doc";
break;
case "PRIVATE_WWW":
path = "_www";
break;
case "PUBLIC_DOCUMENTS":
path = "_documents";
break;
case "PUBLIC_DOWNLOADS":
path = "_downloads";
break;
default:
break;
}
that.dirList.map((x) => {
path += "/" + x;
});
return path + "/";
},
/**
* 保存
*/
save() {
let that = this;
that.value = that.value.replace(" ", "");
if (that.value == "") {
return uni.showToast({
title: "请输入...",
icon: "none",
});
}
let path = that.getPath();
function buildSuccess() {
uni.showToast({
title: "操作成功!",
icon: "success",
});
that.isShow = false;
that.$emit("getPage");
}
function buildError(e) {
let msg = "";
if (e && e.message) {
msg = e.message;
}
uni.showToast({
title: "重命名失败!" + msg,
icon: "none",
});
}
// #ifdef MP-WEIXIN
if (1) {
let fs = wx.getFileSystemManager();
if (that.isEdit) {
if (that.isDir) {
// ! 重命名文件夹
// ! 小程序不支持重命名文件夹
} else {
// ! 重命名文件
fs.rename({
oldPath: path + that.oldValue,
newPath: path + that.value,
success: buildSuccess,
fail: buildError,
});
}
} else {
if (that.isDir) {
// ! 创建目录
fs.mkdir({
dirPath: path + that.value,
recursive: false,
success: buildSuccess,
fail: buildError,
});
} else {
// ! 创建文件
// fs.open({
// filePath: path + that.value,
// flag: "wx+",
// success({ fd }) {
// fs.closeSync({ fd });
// buildSuccess();
// },
// fail: buildError,
// });
fs.writeFile({
filePath: path + that.value,
data: "",
encoding: "utf8",
success() {
buildSuccess();
},
fail: buildError,
});
}
}
return;
}
// #endif
if (that.isEdit) {
if (that.isDir) {
// ! 重命名文件夹
plus.io.resolveLocalFileSystemURL(
path + that.oldValue,
(entry) => {
plus.io.resolveLocalFileSystemURL(
path,
(faEntry) => {
entry.moveTo(faEntry, that.value, buildSuccess, buildError);
},
buildError
);
},
buildError
);
} else {
// ! 重命名文件
plus.io.resolveLocalFileSystemURL(
path + that.oldValue,
(entry) => {
plus.io.resolveLocalFileSystemURL(
path,
(faEntry) => {
entry.moveTo(faEntry, that.value, buildSuccess, buildError);
},
buildError
);
},
buildError
);
}
} else {
if (that.isDir) {
// ! 创建文件夹
plus.io.resolveLocalFileSystemURL(
path,
(entry) => {
entry.getDirectory(that.value, { create: true, exclusive: false }, buildSuccess, buildError);
},
buildError
);
} else {
// ! 创建文件
plus.io.resolveLocalFileSystemURL(
path,
(entry) => {
entry.getFile(that.value, { create: true }, buildSuccess, buildError);
},
buildError
);
}
}
},
},
};
</script>
<style lang="scss" scoped>
.editMask {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
background-color: rgba(0, 0, 0, 0.3);
width: 750rpx;
flex: 1;
/* #ifndef APP-PLUS */
height: 100vh;
backdrop-filter: blur(1px);
/* #endif */
position: fixed;
left: 0;
top: 0;
z-index: 999;
.editDialog {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
width: 690rpx;
background-color: #fff;
border-radius: 20rpx;
padding: 20rpx;
margin-bottom: 300rpx;
.title {
font-size: 28rpx;
line-height: 28rpx;
color: #333;
}
.input {
margin-top: 20rpx;
margin-bottom: 20rpx;
width: 640rpx;
height: 70rpx;
padding: 5rpx;
border-radius: 8rpx;
border: 1px solid rgba(0, 0, 0, 0.05);
}
.btnGroup {
display: flex;
flex-direction: row;
width: 640rpx;
justify-content: space-between;
.btn {
height: 64rpx;
border-radius: 10rpx;
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
.btnText {
font-size: 24rpx;
color: #fff;
}
}
.left {
width: 160rpx;
background-color: #8799a3;
}
.right {
width: 450rpx;
background-color: #3cbb45;
}
}
}
}
</style>

View File

@@ -0,0 +1,214 @@
<template>
<view>
<view
class="dialogMask"
v-if="isShow"
:style="{
height: height + 'px',
}"
@click.stop="hide"
>
<view
class="dialogContent"
@click.stop
>
<view
class="dialogHead"
@click="hide"
>
<view>
<text class="title">{{ item.date + " " + item.timeCount }}</text>
</view>
<view>
<image
src="@/devTools/page/static/unfold.png"
class="fold"
/>
</view>
</view>
<scroll-view
scroll-y
class="scrollList"
>
<view
v-for="(row, index) in item.page"
:key="index"
class="pageLogItem"
@click.stop="showMenu(row)"
>
<text class="p">页面{{ row.r }}</text>
<text class="t">活跃{{ row.timeCount }}</text>
</view>
<view style="height: 100rpx"></view>
</scroll-view>
</view>
</view>
</view>
</template>
<script>
let success, error;
export default {
data() {
return {
/**
* 是否展示
*/
isShow: false,
/**
* 屏幕高度
*/
height: uni.getSystemInfoSync().windowHeight,
/**
* 详情列表
*/
item: {
date: "",
timeCount: "",
page: [
{
r: "",
timeCount: "",
},
],
},
};
},
methods: {
/**
* 展示弹窗
*/
show(item) {
let that = this;
return new Promise((yes, err) => {
success = yes;
error = err;
that.item.date = item.date;
that.item.timeCount = item.timeCount;
that.item.page = item.page;
that.isShow = true;
});
},
/**
* 关闭弹窗
*/
hide() {
this.isShow = false;
error();
},
/**
* 保存
*/
save() {
this.isShow = false;
success(this.value);
},
/**
* 展示菜单
*/
showMenu(row) {
let that = this;
let r = String(row.r).substring(0, 10) + "...";
let menu = [
{
text: `复制路径(${r})`,
click() {
uni.setClipboardData({
data: row.r,
});
},
},
{
text: `复制时间(${row.timeCount})`,
click() {
uni.setClipboardData({
data: row.timeCount,
});
},
},
{
text: `跳转至此页面`,
click() {
uni.$emit("devTools_showRouteDialog", row.r);
},
},
];
uni.showActionSheet({
itemList: menu.map((x) => x.text),
success({ tapIndex }) {
menu[tapIndex].click();
},
});
},
},
};
</script>
<style lang="scss" scoped>
.dialogMask {
display: flex;
flex-direction: column-reverse;
background-color: rgba(0, 0, 0, 0.3);
width: 750rpx;
flex: 1;
/* #ifndef APP-PLUS */
height: 100vh;
backdrop-filter: blur(1px);
/* #endif */
position: fixed;
left: 0;
top: 0;
z-index: 999;
.dialogContent {
display: flex;
flex-direction: column;
width: 750rpx;
background-color: #fff;
border-radius: 20rpx 20rpx 0 0;
.dialogHead {
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
height: 80rpx;
border-bottom: 1px solid rgba(0, 0, 0, 0.05);
width: 750rpx;
.title {
margin-left: 20rpx;
font-size: 24rpx;
line-height: 24rpx;
color: #333;
}
.fold {
width: 20rpx;
height: 20rpx;
margin-right: 20rpx;
}
}
.scrollList {
width: 750rpx;
height: 750rpx;
.pageLogItem {
width: 750rpx;
display: flex;
flex-direction: column;
padding: 10rpx 20rpx;
border-bottom: 1px solid rgba(0, 0, 0, 0.05);
&:active {
background-color: rgba(0, 0, 0, 0.05);
}
.p {
font-size: 20rpx;
color: #333;
}
.t {
margin-top: 4rpx;
font-size: 20rpx;
color: #333;
}
}
}
}
}
</style>

View File

@@ -0,0 +1,185 @@
<template>
<view>
<view
class="editMask"
v-if="isShow"
:style="{
height: height + 'px',
}"
@click.stop
>
<view
class="editDialog"
@click.stop
>
<view>
<text class="title">{{ title }}</text>
</view>
<textarea
v-model="value"
type="text"
placeholder="请输入..."
class="textarea"
/>
<view class="btnGroup">
<view
class="btnItem left"
@click="hide"
>
<text class="btnText">取消</text>
</view>
<view
class="btnItem right"
@click="save"
>
<text class="btnText">提交</text>
</view>
</view>
</view>
</view>
</view>
</template>
<script>
let success, error;
export default {
data() {
return {
/**
* 是否展示
*/
isShow: false,
/**
* 标题
*/
title: "",
/**
* 输入框的值
*/
value: "",
/**
* 屏幕高度
*/
height: uni.getSystemInfoSync().windowHeight,
};
},
mounted() {
let that = this;
/**
* 使用uni.$on打开弹窗
*/
uni.$on("devTools_showEditDialog", (options) => {
that
.show(options.title, options.value)
.then((val) => {
uni.$emit("devTools_editDialogSaveSuccess", val);
})
.catch(() => {
uni.$emit("devTools_editDialogClose");
});
});
},
beforeDestroy() {
uni.$off("devTools_showEditDialog");
},
methods: {
/**
* 展示弹窗
*/
show(title = "标题", value = "") {
let that = this;
return new Promise((yes, err) => {
success = yes;
error = err;
that.title = title;
that.value = value ? value : "";
that.isShow = true;
});
},
/**
* 关闭弹窗
*/
hide() {
this.isShow = false;
error();
},
/**
* 保存
*/
save() {
this.isShow = false;
success(this.value);
},
},
};
</script>
<style lang="scss" scoped>
.editMask {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
background-color: rgba(0, 0, 0, 0.3);
width: 750rpx;
flex: 1;
/* #ifndef APP-PLUS */
height: 100vh;
backdrop-filter: blur(1px);
/* #endif */
position: fixed;
left: 0;
top: 0;
z-index: 999;
.editDialog {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
width: 690rpx;
background-color: #fff;
border-radius: 20rpx;
padding: 20rpx;
.title {
font-size: 28rpx;
line-height: 28rpx;
color: #333;
max-width: 600rpx;
word-wrap: break-word;
}
.textarea {
margin-top: 20rpx;
margin-bottom: 20rpx;
width: 640rpx;
min-height: 200rpx;
max-height: 750rpx;
background-color: rgba(0, 0, 0, 0.02);
padding: 10rpx;
}
.btnGroup {
display: flex;
flex-direction: row;
width: 640rpx;
justify-content: space-between;
.btnItem {
height: 64rpx;
border-radius: 10rpx;
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
.btnText {
font-size: 24rpx;
color: #fff;
}
&.left {
width: 160rpx;
background-color: #8799a3;
}
&.right {
width: 450rpx;
background-color: #3cbb45;
}
}
}
}
}
</style>

View File

@@ -0,0 +1,176 @@
<template>
<view>
<view
class="routeDialogMask"
v-if="isShow"
:style="{
height: height + 'px',
}"
@click.stop
>
<view
class="routeDialog"
@click.stop
>
<view>
<text class="title">跳转至指定页面</text>
</view>
<textarea
v-model="path"
type="text"
placeholder="请输入..."
class="textarea"
/>
<view class="btnGroup">
<view
class="btnItem left"
@click="hide"
>
<text class="btnText">取消</text>
</view>
<view
class="btnItem right"
@click="goPath"
>
<text class="btnText">跳转</text>
</view>
</view>
</view>
</view>
</view>
</template>
<script>
export default {
data() {
return {
/**
* 是否展示
*/
isShow: false,
/**
* 输入框的值
*/
path: "",
/**
* 屏幕高度
*/
height: uni.getSystemInfoSync().windowHeight,
};
},
mounted() {
let that = this;
/**
* 使用uni.$on打开弹窗
*/
uni.$on("devTools_showRouteDialog", (path) => {
that.path = path ? path : "";
that.isShow = true;
});
},
beforeDestroy() {
uni.$off("devTools_showRouteDialog");
},
methods: {
/**
* 关闭弹窗
*/
hide() {
this.isShow = false;
},
/**
* 执行跳转
*/
goPath() {
let that = this;
let path = String(that.path);
path = path.replace(/[\r\n\s]+/g, "");
if (path.substring(0, 1) !== "/") {
return uni.showToast({
title: "页面路径需以“/”开头!",
icon: "none",
});
}
if (path.length < 2) {
return uni.showToast({
title: "请输入正确的路径!",
icon: "none",
});
}
uni.navigateTo({
url: path,
});
},
},
};
</script>
<style lang="scss" scoped>
.routeDialogMask {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
background-color: rgba(0, 0, 0, 0.3);
width: 750rpx;
flex: 1;
/* #ifndef APP-PLUS */
height: 100vh;
backdrop-filter: blur(1px);
/* #endif */
position: fixed;
left: 0;
top: 0;
z-index: 999;
.routeDialog {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
width: 690rpx;
background-color: #fff;
border-radius: 20rpx;
padding: 20rpx;
.title {
font-size: 28rpx;
line-height: 28rpx;
color: #333;
max-width: 600rpx;
word-wrap: break-word;
}
.textarea {
margin-top: 20rpx;
margin-bottom: 20rpx;
width: 640rpx;
min-height: 200rpx;
max-height: 750rpx;
background-color: rgba(0, 0, 0, 0.02);
padding: 10rpx;
}
.btnGroup {
display: flex;
flex-direction: row;
width: 640rpx;
justify-content: space-between;
.btnItem {
height: 64rpx;
border-radius: 10rpx;
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
.btnText {
font-size: 24rpx;
color: #fff;
}
&.left {
width: 160rpx;
background-color: #8799a3;
}
&.right {
width: 450rpx;
background-color: #3cbb45;
}
}
}
}
}
</style>

View File

@@ -0,0 +1,499 @@
<template>
<view>
<view
class="dialogMask"
v-if="isShow"
:style="{
height: height + 'px',
}"
@click.stop
>
<view
class="dialogContent"
@click.stop
>
<view
class="dialogHead"
@click="hide(2)"
>
<view>
<text class="title">请求构建工具</text>
</view>
<view>
<image
src="@/devTools/page/static/unfold.png"
class="fold"
/>
</view>
</view>
<scroll-view
@click.stop
scroll-y
class="scrollList"
:style="{
height: dialogHeight + 'px',
}"
>
<subTitleBar
title="请求地址(url)"
:showArrow="false"
/>
<view class="inputRow">
<input
type="text"
placeholder="请输入url地址"
class="input"
v-model="request.url"
maxlength="-1"
/>
<text
class="del"
@click="request.url = ''"
>
x
</text>
</view>
<subTitleBar
title="请求方式[method]"
:showArrow="false"
/>
<view class="inputRow">
<picker
@change="request.method = requestMethods[$event.detail.value]"
:value="requestMethods.findIndex((x) => x == request.method)"
:range="requestMethods"
>
<view class="method">
<text class="methodName">{{ request.method }}</text>
<image
class="unfold"
src="@/devTools/page/static/unfold.png"
/>
</view>
</picker>
</view>
<subTitleBar
title="请求参数[data](json对象)"
:showArrow="false"
/>
<view class="inputRow">
<textarea
placeholder="请输入JSON对象"
class="textarea"
v-model="request.data"
maxlength="-1"
/>
<text
class="del"
@click="request.data = ''"
>
x
</text>
</view>
<subTitleBar
title="请求头(header)"
:showArrow="false"
/>
<view class="inputRow">
<textarea
placeholder="请输入JSON对象"
class="textarea"
v-model="request.header"
maxlength="-1"
/>
<text
class="del"
@click="request.header = ''"
>
x
</text>
</view>
<view
:class="[send.status ? 'loading' : '']"
class="sendBtn"
@click="sendRequest"
>
<text
v-if="send.status"
class="sendBtnText"
>
发送中[{{ send.t }}ms]
<text class="msg">(点击可取消)</text>
</text>
<text
v-else
class="sendBtnText"
>
发送
</text>
</view>
<template v-if="ajaxHasRes">
<subTitleBar
title="响应结果:"
:showArrow="false"
/>
<view class="inputRow">
<objectAnalysis
:data="ajaxRes"
:width="710"
:isOpenFirst="true"
/>
</view>
</template>
<view style="height: 100rpx"></view>
</scroll-view>
</view>
</view>
</view>
</template>
<script>
import objectAnalysis from "../listItem/objectAnalysis.vue";
import subTitleBar from "../ui/subTitleBar.vue";
let success, error;
/**
* 转json字符串并格式化
*/
function toJsonStr(obj) {
return JSON.stringify(obj, null, 2);
}
export default {
components: {
subTitleBar,
objectAnalysis,
},
data() {
return {
/**
* 是否展示
*/
isShow: false,
/**
* 屏幕高度
*/
height: uni.getSystemInfoSync().windowHeight,
dialogHeight: Math.ceil(uni.getSystemInfoSync().windowHeight * 0.85),
requestMethods: ["get", "post", "put", "delete", "connect", "head", "options", "trace"],
/**
* 请求构建对象
*/
request: {
url: "", //请求地址
header: "", //请求头
method: "get", //请求方式
data: "", //请求参数
},
/**
* 是否有响应结果
*/
ajaxHasRes: false,
/**
* 响应结果对象
*/
ajaxRes: {},
/**
* 是否状态
*/
send: {
status: false, //是否处于发送中状态
t: 0, //等待时间 ms
time: 0, //发送时的时间
},
};
},
mounted() {
let that = this;
setInterval(() => {
if (that.send.status) {
that.send.t = new Date().getTime() - that.send.time;
}
}, 1000 / 24);
},
methods: {
/**
* 展示弹窗
*/
show(item, needSend = false) {
let that = this;
if (that.send.status) {
that.send.status = false;
}
if (that.ajaxHasRes) {
that.ajaxHasRes = false;
}
return new Promise((yes, err) => {
success = yes;
error = err;
if (item && item.url && item.method) {
that.request.url = item.url;
if (typeof item.method == "string") {
that.request.method = item.method.toLocaleLowerCase();
} else {
that.request.method = "get";
}
try {
let data = toJsonStr(item.data);
if (Object.keys(data).length == 0) {
data = "";
} else {
that.request.data = data;
}
} catch (error) {
that.request.data = "";
}
try {
that.request.header = toJsonStr(item.header);
} catch (error) {
that.request.header = toJsonStr({
"content-type": "application/x-www-form-urlencoded",
});
}
} else {
that.request.url = "";
that.request.data = "";
that.request.method = "get";
that.ajaxHasRes = false;
that.request.header = toJsonStr({
"content-type": "application/x-www-form-urlencoded",
});
}
that.send.status = false;
that.isShow = true;
if (needSend) {
that.sendRequest();
}
});
},
/**
* 关闭弹窗
*/
hide() {
this.isShow = false;
error();
},
/**
* 发送请求
*/
sendRequest() {
let that = this;
if (that.send.status) {
return uni.showModal({
title: "提示",
content: "请求还在进行,是否确认取消请求?",
success(res) {
if (res.confirm) {
if (that.send.status) {
that.send.status = false;
that.ajaxHasRes = false;
}
}
},
});
}
let tw = (m) =>
uni.showToast({
icon: "none",
title: m,
});
if (that.request.url == "" || typeof that.request.url != "string") return tw("请输入url");
if (that.request.url.indexOf("http") != 0) return tw("请输入正确的url地址");
if (that.request.url.indexOf("://") == -1) return tw("请输入正确的url地址");
if (that.request.url.length < 10) return tw("请输入正确的url地址");
let data = {};
if (that.request.data != "") {
try {
data = JSON.parse(that.request.data);
} catch (error) {
return tw("请求参数json解析失败");
}
}
let header = {};
if (that.request.header != "") {
try {
header = JSON.parse(that.request.header);
} catch (error) {
return tw("请求头json解析失败");
}
}
that.send.t = 0;
that.send.time = new Date().getTime();
that.send.status = true;
that.ajaxHasRes = false;
uni.request({
url: that.request.url,
method: that.request.method,
data,
header,
success(res) {
if (!that.send.status || !that.isShow) return;
that.send.status = false;
res["请求用时"] = new Date().getTime() - that.send.time + "ms";
that.$set(that, "ajaxRes", res);
that.ajaxHasRes = true;
uni.showToast({
title: "请求响应成功",
icon: "success",
});
},
fail(msg, request) {
if (!that.send.status || !that.isShow) return;
let res = {
fail: msg,
request,
请求用时: new Date().getTime() - that.send.time + "ms",
};
that.send.status = false;
that.$set(that, "ajaxRes", res);
that.ajaxHasRes = true;
uni.showToast({
title: "请求响应失败",
icon: "error",
});
},
});
},
},
};
</script>
<style lang="scss" scoped>
.dialogMask {
display: flex;
flex-direction: column-reverse;
background-color: rgba(0, 0, 0, 0.3);
width: 750rpx;
flex: 1;
/* #ifndef APP-PLUS */
height: 100vh;
backdrop-filter: blur(1px);
/* #endif */
position: fixed;
left: 0;
top: 0;
z-index: 999;
.dialogContent {
display: flex;
flex-direction: column;
width: 750rpx;
background-color: #fff;
border-radius: 20rpx 20rpx 0 0;
.dialogHead {
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
height: 80rpx;
border-bottom: 1px solid rgba(0, 0, 0, 0.05);
width: 750rpx;
.title {
margin-left: 20rpx;
font-size: 24rpx;
line-height: 28rpx;
height: 28rpx;
color: #333;
}
.fold {
width: 20rpx;
height: 20rpx;
margin-right: 20rpx;
}
}
.scrollList {
width: 750rpx;
.inputRow {
width: 750rpx;
padding: 0rpx 20rpx;
position: relative;
.input {
width: 710rpx;
border: 1px solid rgba(0, 0, 0, 0.05);
padding-left: 5rpx;
padding-top: 5rpx;
padding-bottom: 5rpx;
padding-right: 50rpx;
font-size: 24rpx;
border-radius: 6rpx;
height: 60rpx;
}
.textarea {
width: 710rpx;
border: 1px solid rgba(0, 0, 0, 0.05);
padding-left: 5rpx;
padding-top: 5rpx;
padding-bottom: 5rpx;
padding-right: 50rpx;
font-size: 24rpx;
border-radius: 6rpx;
height: 140rpx;
}
.del {
position: absolute;
right: 24rpx;
top: 10rpx;
height: 40rpx;
background-color: #fff;
padding: 0 10rpx;
font-size: 24rpx;
color: #999;
line-height: 40rpx;
}
.method {
display: flex;
flex-direction: row;
align-items: center;
.methodName {
font-size: 24rpx;
color: #333;
}
.unfold {
margin-left: 10rpx;
width: 24rpx;
height: 24rpx;
}
}
}
.sendBtn {
width: 710rpx;
margin-left: 20rpx;
margin-top: 30rpx;
margin-bottom: 30rpx;
height: 60rpx;
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
border-radius: 10rpx;
background-color: rgb(255, 45, 85);
&.loading {
background-color: rgba(255, 45, 85, 0.5);
}
.sendBtnText {
color: #fff;
font-size: 26rpx;
}
.msg {
color: #fff;
font-size: 20rpx;
margin-left: 20rpx;
}
}
}
}
}
</style>

View File

@@ -0,0 +1,374 @@
<template>
<view>
<view
class="dialogMask"
v-if="isShow"
:style="{
height: height + 'px',
}"
>
<view
class="dialogContent"
@click.stop
>
<view
class="dialogHead"
@click="hide"
>
<view>
<text class="title">{{ title }}</text>
</view>
<view>
<image
src="@/devTools/page/static/unfold.png"
class="fold"
/>
</view>
</view>
<scroll-view
scroll-y
class="scrollList"
:style="{
height: dialogHeight + 'px',
}"
>
<textarea
:style="{
height: dialogHeight - (canSave ? 90 : 40) + 'px',
}"
v-model="value"
type="text"
placeholder="请输入..."
class="fileEditInput"
maxlength="-1"
/>
<view
class="saveBtn"
v-if="canSave"
@click="saveFile"
>
<text class="saveBtnText">保存</text>
</view>
</scroll-view>
</view>
</view>
</view>
</template>
<script>
let success, error;
export default {
data() {
return {
/**
* 是否展示
*/
isShow: false,
/**
* 屏幕高度
*/
height: uni.getSystemInfoSync().windowHeight,
dialogHeight: uni.getSystemInfoSync().windowHeight * 0.8,
/**
* 弹窗标题
*/
title: "",
/**
* 文本内容
*/
value: "",
/**
* 是否为文件编辑模式
*/
isFileEdit: true,
/**
* 文件路径
*/
path: "",
/**
* 是否允许保存
*/
canSave: false,
/**
* 是否为新建文件
*/
isNewFile: false,
};
},
mounted() {
let that = this;
uni.$on("devTools_showTextEditDialog", (options) => {
that
.show(options)
.then((val) => {
uni.$emit("devTools_showTextEditDialogSave", val);
})
.catch(() => {
uni.$emit("devTools_showTextEditDialogHide");
});
});
},
unmounted() {
uni.$off("devTools_showTextEditDialog");
},
methods: {
/**
* 展示弹窗
*/
show(options) {
let that = this;
return new Promise((yes, err) => {
success = yes;
error = err;
that.title = options.title;
that.canSave = Boolean(options.canSave);
that.isShow = true;
if (options.isFileEdit === false) {
// 仅为文件编辑模式
that.isFileEdit = false;
try {
that.value = JSON.stringify(JSON.parse(options.value), null, 2);
} catch (error) {
that.value = options.value;
}
return;
}
that.isFileEdit = true;
that.value = "文件读取中...";
that.path = options.path;
that.isNewFile = Boolean(options.isNewFile);
// #ifdef APP-PLUS
if (that.isNewFile) {
that.value = "";
} else {
plus.io.resolveLocalFileSystemURL(
that.path,
(entry) => {
// 可通过entry对象操作test.html文件
entry.file((file) => {
var fileReader = new plus.io.FileReader();
fileReader.readAsText(file, "utf-8");
fileReader.onloadend = function (evt) {
let res = "";
try {
res = JSON.stringify(JSON.parse(evt.target.result), null, 2);
} catch (error) {
res = evt.target.result;
}
that.value = res;
};
fileReader.onerror = function () {
that.value = `[${that.path}]文件读取失败_code_2`;
};
});
},
function (e) {
that.value = `[${that.path}]文件读取失败!` + e.message;
}
);
}
// #endif
// #ifdef MP-WEIXIN
let fs = wx.getFileSystemManager();
if (options.size != 0) {
fs.readFile({
filePath: that.path,
encoding: "utf8",
position: 0,
length: options.size,
success({ data }) {
try {
that.value = JSON.stringify(JSON.parse(data), null, 2);
} catch (error) {
that.value = data;
}
},
fail(e) {
console.log(e);
that.value = `[${that.path}]文件读取失败!` + e;
},
});
} else {
that.value = "";
}
// #endif
});
},
/**
* 关闭弹窗
*/
hide() {
this.isShow = false;
error();
},
/**
* 保存
*/
save() {
this.isShow = false;
success(this.value);
},
/**
* 保存文件
*/
saveFile() {
let that = this;
if (!that.isFileEdit) {
// 非文件编辑模式
that.isShow = false;
success(that.value);
return;
}
uni.showLoading({
title: "保存中",
});
// #ifdef APP-PLUS
let fileName = that.path.split("/").pop();
let path = that.path.substring(0, that.path.length - fileName.length);
plus.io.resolveLocalFileSystemURL(
path,
(entry) => {
entry.getFile(
fileName,
{
create: true,
},
(fileEntry) => {
fileEntry.createWriter((writer) => {
writer.onwrite = (e) => {
uni.hideLoading();
uni.showToast({
title: "文件保存成功!",
icon: "success",
});
that.isShow = false;
that.$emit("getPage");
};
writer.onerror = () => {
uni.hideLoading();
uni.showToast({
title: "文件保存失败_写入文件失败",
icon: "none",
});
};
writer.write(that.value);
});
}
);
},
() => {
uni.hideLoading();
uni.showToast({
title: "文件保存失败_打开目录失败",
icon: "none",
});
}
);
// #endif
// #ifdef MP-WEIXIN
let fs = wx.getFileSystemManager();
fs.writeFile({
filePath: that.path,
encoding: "utf-8",
data: that.value,
success() {
uni.hideLoading();
uni.showToast({
title: "文件保存成功!",
icon: "success",
});
that.isShow = false;
that.$emit("getPage");
},
fail() {
uni.hideLoading();
uni.showToast({
title: "文件保存失败_打开目录失败",
icon: "none",
});
},
});
// #endif
},
},
};
</script>
<style lang="scss" scoped>
.dialogMask {
display: flex;
flex-direction: column-reverse;
background-color: rgba(0, 0, 0, 0.3);
width: 750rpx;
flex: 1;
/* #ifndef APP-PLUS */
height: 100vh;
backdrop-filter: blur(1px);
/* #endif */
position: fixed;
left: 0;
top: 0;
z-index: 999;
.dialogContent {
display: flex;
flex-direction: column;
width: 750rpx;
background-color: #fff;
border-radius: 20rpx 20rpx 0 0;
.dialogHead {
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
height: 80rpx;
border-bottom: 1px solid rgba(0, 0, 0, 0.05);
width: 750rpx;
.title {
margin-left: 20rpx;
font-size: 24rpx;
line-height: 24rpx;
color: #333;
}
.fold {
width: 20rpx;
height: 20rpx;
margin-right: 20rpx;
}
}
.scrollList {
width: 750rpx;
padding: 20rpx;
.fileEditInput {
font-size: 20rpx;
}
.saveBtn {
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
margin-top: 20rpx;
height: 35px;
width: 710rpx;
border-radius: 10rpx;
background-color: #ff2d55;
.saveBtnText {
color: #fff;
font-size: 24rpx;
line-height: 24rpx;
}
}
}
}
}
</style>

View File

@@ -0,0 +1,86 @@
import dirReader from "./dirReader"
/**
* 遍历删除整个文件夹,以“/”结尾
*/
export default function appDelDir(path, needDelSelf = true) {
return new Promise(async (yes, err) => {
let dirList = await dirReader.getDirFileList(path)
for (let i = 0; i < dirList.length; i++) {
let item = dirList[i];
try {
if (item.type == "dir") {
let info = await getMeteInfo(path + item.name + "/")
if (
info.directoryCount > 0
|| info.fileCount > 0
) {
await appDelDir(path + item.name + "/")
} else {
await delDir(path + item.name + "/")
}
} else {
await delFile(path + item.name)
}
} catch (error) { }
}
try {
if (needDelSelf) {
await delDir(path)
}
} catch (error) { }
yes()
})
}
function delFile(path) {
return new Promise((yes, err) => {
plus.io.resolveLocalFileSystemURL(
path,
(entry) => {
entry.remove(
yes,
err
)
},
err
);
})
}
function delDir(path) {
return new Promise((yes, err) => {
plus.io.resolveLocalFileSystemURL(
path,
(entry) => {
entry.remove(
yes,
err
)
},
err
);
})
}
/**
* 获取文件夹内信息
* @returns {Promise<PlusIoMetadata>}
*/
function getMeteInfo(path) {
return new Promise((yes, err) => {
plus.io.resolveLocalFileSystemURL(
path,
(entry) => {
if (entry.isDirectory) {
entry.getMetadata((metadata) => {
yes(metadata)
}, err)
} else {
err()
}
},
err
);
})
}

View File

@@ -0,0 +1,225 @@
const iconConfig = [
{
type: ["", undefined, null],
mime: "",
icon: "/devTools/page/static/fileSys/weizhiwenjian.png",
},
{
type: ["dwg"],
mime: "dwg",
icon: "/devTools/page/static/fileSys/DWG.png",
},
{
type: ["xls", "xlsx", "csv"],
mime: "xls",
icon: "/devTools/page/static/fileSys/excel.png",
},
{
type: ["exe"],
mime: "exe",
icon: "/devTools/page/static/fileSys/EXE.png",
},
{
type: ["gif"],
mime: "gif",
icon: "/devTools/page/static/fileSys/GIF.png",
},
{
type: ["html"],
mime: "html",
icon: "/devTools/page/static/fileSys/HTML.png",
},
{
type: ["pdf"],
mime: "pdf",
icon: "/devTools/page/static/fileSys/pdf.png",
},
{
type: ["ppt"],
mime: "ppt",
icon: "/devTools/page/static/fileSys/pptl.png",
},
{
type: ["psd"],
mime: "psd",
icon: "/devTools/page/static/fileSys/PSD.png",
},
{
type: ["rvt"],
mime: "rvt",
icon: "/devTools/page/static/fileSys/RVT.png",
},
{
type: [
"mp4",
"avi",
"wmv",
"mpg",
"mpeg",
"mov",
"flv",
"3gp",
"mp3gp",
"mkv",
"rmvb",
],
mime: "mp4",
icon: "/devTools/page/static/fileSys/shipin.png",
},
{
type: ["skp"],
mime: "skp",
icon: "/devTools/page/static/fileSys/SKP.png",
},
{
type: ["svg"],
mime: "svg",
icon: "/devTools/page/static/fileSys/SVG.png",
},
{
type: ["png", "jpeg", "jpg", "webp", "bmp"],
mime: "img",
icon: "/devTools/page/static/fileSys/tupian.png",
},
{
type: ["txt", "sql", "js", "css", "log", "json"],
mime: "txt",
icon: "/devTools/page/static/fileSys/txt.png",
},
{
type: ["word"],
mime: "word",
icon: "/devTools/page/static/fileSys/word.png",
},
{
type: ["zip", "rar", "gz", "7z"],
mime: "zip",
icon: "/devTools/page/static/fileSys/yasuo.png",
},
{
type: ["mp3", "wma", "wav", "aac", "flac"],
mime: "",
icon: "/devTools/page/static/fileSys/yinpin.png",
},
];
export default {
/**
* 获取文件和目录列表
*/
getDirFileList(path) {
return new Promise((yes) => {
// #ifdef APP-PLUS
plus.io.resolveLocalFileSystemURL(path, function (entry) {
if (entry.isDirectory) {
let reader = entry.createReader();
reader.readEntries(
async (entries) => {
let dirList = [];
let fileList = [];
for (let i = 0; i < entries.length; i++) {
/**
* @type {PlusIoDirectoryEntry}
*/
const item = entries[i];
let meta = await getMetaData(item)
let row = {
type: item.isDirectory ? 'dir' : 'file',
name: item.name,
fileType: getFileType(item.name),
...meta,
}
if (item.isDirectory) {
dirList.push(row)
} else {
fileList.push(row)
}
}
dirList = dirList.sort((a, b) => a.time > b.time)
fileList = fileList.sort((a, b) => a.time > b.time)
yes([
...dirList,
...fileList,
])
},
(e) => {
console.log("readEntries error", e);
uni.showToast({
title: "文件读取失败: " + e.message,
icon: "none",
});
yes([])
}
);
} else {
uni.showToast({
title: "路径读取失败_b",
icon: "none",
});
yes([])
}
}, () => {
uni.showToast({
title: "路径读取失败_a",
icon: "none",
});
yes([])
});
// #endif
})
},
/**
* 获取文件图片
*/
getFileIcon(type) {
for (let i = 0; i < iconConfig.length; i++) {
const item = iconConfig[i];
for (let _i = 0; _i < item.type.length; _i++) {
const typeName = item.type[_i];
if (type == typeName) {
return item.icon;
}
}
}
return "/devTools/page/static/fileSys/weizhiwenjian.png";
}
}
/**
* @param {PlusIoDirectoryEntry} entry
*/
function getMetaData(entry) {
return new Promise((yes) => {
entry.getMetadata(function (metadata) {
yes({
size: metadata.size,
time: metadata.modificationTime.getTime(),
fileCount: metadata.fileCount,
directoryCount: metadata.directoryCount,
})
}, function () {
yes({
size: 0,
time: 0,
fileCount: 0,
directoryCount: 0,
})
});
})
}
function getFileType(name) {
if (typeof name == "string") {
let tList = name.split(".");
if (tList.length > 1) {
return tList.pop().toLocaleLowerCase()
} else {
return ""
}
} else {
return ""
}
}

View File

@@ -0,0 +1,15 @@
export default {
/**
* 获取字节大小,b转kb mb
*/
getByteSize(size) {
if (null == size || size == '') return "0 B";
var unitArr = ["B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"];
var index = 0;
var srcsize = parseFloat(size);
index = Math.floor(Math.log(srcsize) / Math.log(1024));
var size = srcsize / Math.pow(1024, index);
size = size.toFixed(2);//保留的小数位数
return size + unitArr[index];
}
}

View File

@@ -0,0 +1,134 @@
/**
* 获取当前运行时环境信息
*/
export default function getRuntimeInfo() {
return new Promise(async (yes) => {
let data = {
系统信息: uni.getSystemInfoSync(),
设备基础信息: uni.getDeviceInfo ? uni.getDeviceInfo() : null,
窗口信息: uni.getWindowInfo ? uni.getWindowInfo() : null,
APP基础信息: uni.getAppBaseInfo ? uni.getAppBaseInfo() : null,
APP授权设置: uni.getAppAuthorizeSetting ? uni.getAppAuthorizeSetting() : null,
设备设置: uni.getSystemSetting ? uni.getSystemSetting() : null,
网络状态: await getNetworkType(),
启动参数: uni.getLaunchOptionsSync(),
// #ifdef APP-PLUS
其他信息: await getAppOtherInfo(),
// #endif
};
yes(data)
})
}
/**
* 获取网络状态
*/
function getNetworkType() {
return new Promise((yes, err) => {
if (!uni.getNetworkType) {
return yes(null);
}
uni.getNetworkType({
success(res) {
yes(res.networkType);
},
fail() {
yes("error");
},
});
});
}
/**
* 获取APP端设备其他信息
*/
function getAppOtherInfo() {
return new Promise(async (yes) => {
let info = {};
let getDevice = () =>
new Promise((yes) => {
plus.device.getInfo({
success: yes,
fail() {
yes("plus.device.getInfo() fail");
},
});
});
let getOAID = () =>
new Promise((yes) => {
plus.device.getOAID({
success: yes,
fail() {
yes("plus.device.getOAID() fail");
},
});
});
let getAAID = () =>
new Promise((yes) => {
plus.device.getOAID({
success: yes,
fail() {
yes("plus.device.getOAID() fail");
},
});
});
let getDeviceId = () =>
new Promise((yes) => {
try {
let id = plus.device.getDeviceId();
yes(id);
} catch (error) {
yes("plus.device.getDeviceId() fail");
}
});
try {
info.getDevice = await getDevice();
info.getOAID = await getOAID();
info.getAAID = await getAAID();
info.getDeviceId = await getDeviceId();
yes(info);
} catch (error) {
console.log("getDeviceInfoFail", error);
yes("获取设备信息失败!");
}
plus.device.getInfo({
success(e) {
info = Object.assign(info, e);
plus.device.getOAID({
success(e) {
info = Object.assign(info, e);
plus.device.getVAID({
success(e) { },
fail() {
yes(
Object.assign(info, {
errMsg: "plus.device.getVAID 获取失败!",
})
);
},
});
},
fail() {
yes(
Object.assign(info, {
errMsg: "plus.device.getOAID 获取失败!",
})
);
},
});
},
fail() {
yes({ errMsg: "plus.device.getInfo 获取失败!" });
},
});
});
}

View File

@@ -0,0 +1,288 @@
<template>
<view
class="consoleItem"
:class="['type-' + item.type]"
@longpress.stop="consoleLongpress"
>
<view class="content">
<view
v-for="(row, index) in item.list"
:key="index"
>
<template v-if="isObj(row)">
<objectAnalysis
:data="row"
:width="610"
:canLongpress="false"
@onLongpress="consoleLongpress"
/>
</template>
<template v-else>
<view>
<text
class="context"
:class="[getTypeClass(row)]"
>
{{ getText(row) }}
</text>
</view>
</template>
</view>
<view class="msgBar">
<text class="time">{{ item.date }}</text>
<text
class="logType"
:class="'type-' + item.type"
>
{{ item.type }}
</text>
<text class="page">{{ item.page }}</text>
</view>
</view>
<view class="tools">
<view
class="copyBtn"
@click="copyList"
>
<image
src="@/devTools/page/static/copy.png"
class="copyIcon"
/>
</view>
</view>
</view>
</template>
<script>
import objectAnalysis from "./objectAnalysis.vue";
export default {
components: {
objectAnalysis,
},
props: {
/**
* console单行数据
*/
item: {
type: Object,
default() {
return {
list: [],
date: "", // 打印的日期
page: "", // 打印的页面
type: "", // 打印类型
};
},
},
},
methods: {
/**
* 是否为对象类型
*/
isObj(data) {
return typeof data == "object" && data != null && data != undefined;
},
/**
* 获取对应的类型样式
*/
getTypeClass(obj) {
try {
let type = typeof obj;
if (type == "string") {
if (obj.indexOf("at ") == 0) {
return "t-line";
} else if (obj === undefined || obj == "undefined") {
return "t-undefined";
} else if (obj === null || obj == "null") {
return "t-null";
} else if (obj == "true" || obj == "false") {
return "t-boolean";
} else if (Number.isFinite(Number(obj))) {
return "t-number";
}
}
return "t-" + type;
} catch (error) {}
return "t-string";
},
/**
* 获取数据文字
*/
getText(data) {
switch (typeof data) {
case "string":
// return data.replace(/\n/g, "");
return data;
case "boolean":
return data ? "true" : "false";
case "undefined":
return "undefined";
case "function":
return "js:function";
case "symbol":
return "js:symbol";
default:
return data;
}
},
/**
* 复制列表
*/
copyList() {
uni.setClipboardData({
data: JSON.stringify(this.item.list),
});
},
/**
* 长按事件
*/
consoleLongpress() {
let that = this;
let menu = [
{
text: `复制日志信息`,
click() {
uni.setClipboardData({
data: JSON.stringify(that.item),
});
},
},
{
text: `删除此记录`,
click() {
uni.$emit("devTools_delConsoleItem", that.item);
uni.showToast({
title: "删除成功!",
icon: "success",
});
},
},
];
uni.showActionSheet({
itemList: menu.map((x) => x.text),
success({ tapIndex }) {
menu[tapIndex].click();
},
});
},
},
};
</script>
<style lang="scss" scoped>
.consoleItem:active {
background-color: rgba(0, 0, 0, 0.03);
}
.consoleItem {
display: flex;
flex-direction: row;
align-items: flex-start;
justify-content: space-between;
width: 750rpx;
padding: 10rpx 20rpx;
border-bottom: 1px solid rgba(0, 0, 0, 0.05);
&.type-warn {
background-color: rgb(255, 251, 229);
}
&.type-error {
background-color: rgb(255, 240, 240);
}
&.type-info {
background-color: rgba(0, 0, 0, 0.02);
}
.content {
width: 610rpx;
display: flex;
flex-direction: column;
.context {
font-size: 20rpx;
color: #333;
line-height: 24rpx;
&.t-number {
color: rgb(8, 66, 160);
}
&.t-boolean {
color: rgb(133, 2, 255);
}
&.t-string {
color: #333;
}
&.t-undefined {
color: rgba(0, 0, 0, 0.2);
}
&.t-null {
color: rgba(0, 0, 0, 0.2);
}
&.t-line {
color: rgba(0, 0, 0, 0.5);
}
}
.msgBar {
display: flex;
flex-direction: row;
margin-top: 4rpx;
.time {
font-size: 16rpx;
color: #888;
min-width: 90rpx;
}
.page {
font-size: 16rpx;
color: #888;
margin-left: 20rpx;
}
.logType {
margin-left: 20rpx;
font-size: 16rpx;
padding: 0px 6rpx;
border-radius: 2px;
}
.type-log {
color: #fff;
background-color: #a8abb3;
}
.type-info {
color: #fff;
background-color: #747474;
}
.type-warn {
color: #fff;
background-color: #ff9900;
}
.type-error {
color: #fff;
background-color: #fa3534;
}
}
}
.tools {
width: 100rpx;
display: flex;
flex-direction: row-reverse;
margin-top: 6rpx;
.copy {
font-size: 20rpx;
color: #333;
line-height: 24rpx;
}
.copy:active {
background-color: red;
}
}
}
.copyBtn:active {
background-color: rgba(103, 194, 58, 0.6);
}
.copyBtn {
padding: 5rpx;
border-radius: 999rpx;
overflow: hidden;
background-color: #67c23a;
.copyIcon {
width: 20rpx;
height: 20rpx;
}
}
</style>

View File

@@ -0,0 +1,96 @@
<template>
<view
class="dayOnlineItem"
@click.stop="$emit('click')"
@longpress.stop="logLongpress"
>
<view class="info">
<text class="text-xs">{{ item.date }}</text>
<text class="text-xs margin-left">{{ item.timeCount }}</text>
</view>
<view class="arrow">
<image
class="icon"
src="@/devTools/page/static/fold.png"
/>
</view>
</view>
</template>
<script>
export default {
props: {
/**
* logs单行数据
*/
item: {
type: Object,
default() {
return {
date: "", //日期
timeCount: "", //活跃时间
page: [], //页面详细数据
};
},
},
},
methods: {
/**
* 长按事件
*/
logLongpress() {
let that = this;
let menu = [
{
text: `复制日志信息`,
click() {
uni.setClipboardData({
data: JSON.stringify(that.item),
});
},
},
];
uni.showActionSheet({
itemList: menu.map((x) => x.text),
success({ tapIndex }) {
menu[tapIndex].click();
},
});
},
},
};
</script>
<style lang="scss" scoped>
.dayOnlineItem:active {
background-color: rgba(0, 0, 0, 0.03);
}
.dayOnlineItem {
display: flex;
flex-direction: row;
align-items: flex-start;
justify-content: space-between;
width: 750rpx;
padding: 10rpx 20rpx;
border-bottom: 1px solid rgba(0, 0, 0, 0.05);
.info {
width: 610rpx;
display: flex;
flex-direction: row;
align-items: center;
.text-xs {
font-size: 20rpx;
}
.margin-left {
margin-left: 30rpx;
}
}
.arrow {
.icon {
width: 20rpx;
height: 20rpx;
transform: rotate(90deg);
}
}
}
</style>

View File

@@ -0,0 +1,274 @@
<template>
<view
class="errorItem"
:class="['type-' + item.type]"
@longpress.stop="errorLongpress"
>
<view class="content">
<view
v-for="(row, index) in [item.m, item.tr]"
:key="index"
>
<template v-if="isObj(row)">
<objectAnalysis
:data="row"
:width="610"
:canLongpress="false"
@onLongpress="errorLongpress"
/>
</template>
<template v-else>
<view>
<text class="context">{{ getText(row) }}</text>
</view>
</template>
</view>
<view class="msgBar">
<text class="time">{{ item.date }}</text>
<text
class="logType"
:class="['type-' + item.type]"
>
{{ getType(item.type) }}
</text>
<text class="page">{{ item.p }}</text>
</view>
</view>
<view class="tools">
<view
class="copyBtn"
@click="copyList"
>
<image
src="@/devTools/page/static/copy.png"
class="copyIcon"
/>
</view>
</view>
</view>
</template>
<script>
import devCache from "../../../core/libs/devCache";
import objectAnalysis from "./objectAnalysis.vue";
export default {
components: {
objectAnalysis,
},
props: {
/**
* console单行数据
*/
item: {
type: Object,
default() {
return {
m: "",
tr: "",
date: "", // 打印的日期
p: "", // 打印的页面
type: "", // 打印类型
};
},
},
},
methods: {
/**
* 是否为对象类型
*/
isObj(data) {
return typeof data == "object";
},
/**
* 获取数据文字
*/
getText(data) {
switch (typeof data) {
case "string":
return data;
case "boolean":
return data ? "true" : "false";
case "undefined":
return "undefined";
case "function":
return "js:function";
case "symbol":
return "js:symbol";
default:
return data;
}
},
/**
* 复制列表
*/
copyList() {
let that = this;
uni.setClipboardData({
data: JSON.stringify([that.item.m, that.item.tr]),
});
},
/**
* 获取类型
*/
getType(type) {
let t = {
ve: "vue error",
vw: "vue warn",
oe: "App.vue onError",
n: "unknown",
};
return t[type];
},
/**
* 长按事件
*/
errorLongpress() {
let that = this;
let menu = [
{
text: `复制日志信息`,
click() {
uni.setClipboardData({
data: JSON.stringify(that.item),
});
},
},
{
text: `删除此记录`,
click() {
uni.$emit("devTools_delError", that.item);
let logs = devCache.get("errorReport");
if (!logs) logs = [];
let i = logs.findIndex(
(x) =>
x.type == that.item.type &&
x.t == that.item.t &&
x.m == that.item.m &&
x.tr == that.item.tr &&
x.p == that.item.p
);
if (i != -1) {
logs.splice(i, 1);
devCache.set("errorReport", logs);
}
uni.showToast({
title: "删除成功!",
icon: "success",
});
},
},
];
uni.showActionSheet({
itemList: menu.map((x) => x.text),
success({ tapIndex }) {
menu[tapIndex].click();
},
});
},
},
};
</script>
<style lang="scss" scoped>
.errorItem:active {
background-color: rgba(0, 0, 0, 0.03);
}
.errorItem {
display: flex;
flex-direction: row;
align-items: flex-start;
justify-content: space-between;
width: 750rpx;
padding: 10rpx 20rpx;
border-bottom: 1px solid rgba(0, 0, 0, 0.05);
&.type-vw {
background-color: rgb(255, 251, 229);
}
&.type-ve {
background-color: rgb(255, 240, 240);
}
&.type-oe {
background-color: rgb(255, 240, 240);
}
.content {
width: 670rpx;
display: flex;
flex-direction: column;
.context {
font-size: 20rpx;
color: #333;
line-height: 24rpx;
}
.msgBar {
display: flex;
flex-direction: row;
flex-wrap: wrap;
.time {
font-size: 16rpx;
color: #888;
min-width: 90rpx;
}
.page {
font-size: 16rpx;
color: #888;
margin-left: 20rpx;
lines: 1;
max-width: 450rpx;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.logType {
margin-left: 20rpx;
font-size: 16rpx;
padding: 0px 6rpx;
border-radius: 2px;
}
.type-ve {
color: #fff;
background-color: #fd0add;
}
.type-n {
color: #fff;
background-color: #82848a;
}
.type-vw {
color: #fff;
background-color: #ff9900;
}
.type-oe {
color: #fff;
background-color: #ff0000;
}
}
}
.tools {
width: 40rpx;
display: flex;
flex-direction: row-reverse;
margin-top: 6rpx;
.copy {
font-size: 20rpx;
color: #333;
line-height: 24rpx;
}
.copy:active {
background-color: red;
}
}
}
.copyBtn:active {
background-color: rgba(103, 194, 58, 0.6);
}
.copyBtn {
padding: 5rpx;
border-radius: 999rpx;
overflow: hidden;
background-color: #67c23a;
.copyIcon {
width: 20rpx;
height: 20rpx;
}
}
</style>

View File

@@ -0,0 +1,388 @@
<template>
<view
class="fileItem"
@click="fileClick"
@longpress.stop="longpress"
>
<view class="icon">
<image
class="iconImg"
:src="icon"
mode="aspectFit"
/>
</view>
<view class="fileInfo">
<text
class="fileName"
:style="{
color: item.type == 'back' ? '#999' : '#333',
}"
>
{{ item.name }}
</text>
<view
v-if="item.type != 'back'"
class="fileMeta"
>
<text class="textItem">{{ item.date }}</text>
<text
v-if="item.type == 'file'"
class="textItem"
>
{{ item.sizeText }}
</text>
</view>
</view>
</view>
</template>
<script>
import appDelDir from "../libs/appDelDir";
export default {
props: {
/**
* 当前文件路径类型
*/
dirType: {
type: String,
default: "",
},
/**
* 文件路径列表
*/
dirList: {
type: Array,
default: () => [],
},
/**
* 单个列表对象
*/
item: {
type: Object,
default() {
return {
type: "", // 对象类型 dir back file
name: "",
fileType: "", // 文件后缀名称
size: "", // 文件大小
icon: "", //图标
time: "", // 最后更改日期
date: "",
fileCount: 0, //文件夹下的文件数量
directoryCount: 0, //文件夹下的目录数量
};
},
},
},
computed: {
icon() {
// #ifdef APP-PLUS
// IOS端直接访问本地图片会报跨域所以仅支持安卓预览图片
if (
uni.getSystemInfoSync().platform == "android" &&
this.item.type == "file" &&
["jpg", "jpeg", "png", "gif", "webp", "bmp"].indexOf(this.item.fileType) > -1
) {
let path = this.getPath();
return path;
}
// #endif
return this.item.icon;
},
},
data() {
return {};
},
methods: {
/**
* 点击事件
*/
fileClick() {
let that = this;
if (this.item.type == "dir") {
this.$emit("goChildDir", this.item.name);
} else if (this.item.type == "back") {
this.$emit("goChildDir", "__back__");
} else {
if (
//? 使用文本编辑器快捷打开文件
["txt", "sql", "js", "css", "html", "log", "json"].indexOf(this.item.fileType) != -1
) {
this.openInEdit();
} else if (["jpg", "jpeg", "png", "gif", "webp", "bmp"].indexOf(this.item.fileType) != -1) {
let path = that.getPath();
uni.previewImage({
urls: [path],
});
} else {
this.longpress();
}
}
},
/**
* 使用文本编辑器打开
*/
openInEdit() {
let that = this;
let path = that.getPath();
that.$emit("openEditFileDialog", {
title: that.item.name,
canSave: that.dirType != "PRIVATE_WWW",
path,
size: that.item.size,
});
},
/**
* 获取当前文件绝对路径
*/
getPath() {
let that = this;
let path = "";
switch (that.dirType) {
case "wx":
path = wx.env.USER_DATA_PATH;
break;
case "PRIVATE_DOC":
path = "_doc";
break;
case "PRIVATE_WWW":
path = "_www";
break;
case "PUBLIC_DOCUMENTS":
path = "_documents";
break;
case "PUBLIC_DOWNLOADS":
path = "_downloads";
break;
default:
break;
}
that.dirList.map((x) => {
path += "/" + x;
});
path = path + "/" + that.item.name;
return path;
},
/**
* 长按事件
*/
longpress() {
let that = this;
let path = that.getPath();
let menu = [
{
text: `复制绝对路径`,
click() {
// #ifdef APP-PLUS
path = plus.io.convertLocalFileSystemURL(path);
// #endif
uni.setClipboardData({
data: path,
});
},
},
{
text: `删除`,
click() {
uni.showModal({
title: "警告",
content: "是否确认删除" + (that.item.type == "dir" ? "文件夹:" : "文件:") + that.item.name + "?",
success(res) {
if (res.confirm) {
uni.showLoading({
title: "删除中",
});
function delSuccess() {
uni.hideLoading();
uni.showToast({
title: "删除成功!",
icon: "success",
});
that.$emit("getPage");
}
function delError() {
uni.hideLoading();
uni.showToast({
title: "删除失败",
icon: "none",
});
}
// #ifdef MP-WEIXIN
if (1) {
let fs = wx.getFileSystemManager();
if (that.item.type == "file") {
// ! 删除文件
fs.unlink({
filePath: path,
success: delSuccess,
fail: delError,
});
} else {
// ! 删除文件夹
fs.rmdir({
dirPath: path,
recursive: true,
success: delSuccess,
fail: delError,
});
}
return;
}
// #endif
if (that.item.type == "file") {
// ! 删除文件
plus.io.resolveLocalFileSystemURL(
path,
(entry) => {
// 可通过entry对象操作test.html文件
entry.remove(delSuccess, delError);
},
delError
);
} else {
// ! 删除文件夹
appDelDir(path + "/")
.then(delSuccess)
.catch(delError);
}
}
},
});
},
},
];
let isMp = false;
// #ifdef MP-WEIXIN
isMp = true;
// #endif
if (!isMp || that.item.type != "dir") {
menu.push({
text: "重命名",
click() {
that.$emit("editDirName", {
isDir: that.item.type == "dir",
isEdit: true,
name: that.item.name,
});
},
});
}
// #ifdef APP-PLUS
if (that.item.type == "file") {
menu.push({
text: "调用外部程序打开此文件",
click() {
plus.runtime.openFile(path, null, (e) => {
uni.showToast({
title: "文档打开失败!" + e.message,
icon: "none",
});
});
},
});
}
// #endif
// #ifdef MP-WEIXIN
if (["doc", "xls", "ppt", "pdf", "docx", "xlsx", "pptx"].indexOf(that.item.fileType) != -1) {
menu.unshift({
text: "打开该文档",
click() {
let path = that.getPath();
uni.openDocument({
filePath: path,
fail() {
uni.showToast({
title: "文档打开失败!",
icon: "none",
});
},
});
},
});
}
// #endif
if (that.item.type == "file") {
menu.unshift({
text: `用文本编辑器打开`,
click() {
that.openInEdit();
},
});
}
uni.showActionSheet({
itemList: menu.map((x) => x.text),
success({ tapIndex }) {
menu[tapIndex].click();
},
});
},
},
};
</script>
<style lang="scss" scoped>
.fileItem {
display: flex;
flex-direction: row;
align-items: center;
padding: 0rpx 20rpx;
border-bottom: 1px solid rgba(0, 0, 0, 0.05);
width: 750rpx;
min-height: 70rpx;
&:active {
background-color: rgba(0, 0, 0, 0.05);
}
.icon {
width: 50rpx;
height: 50rpx;
border-radius: 10rpx;
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
background-color: rgba(0, 0, 0, 0.05);
.iconImg {
width: 40rpx;
height: 40rpx;
}
}
.fileInfo {
margin-left: 10rpx;
width: 650rpx;
display: flex;
flex-direction: column;
.fileName {
width: 650rpx;
lines: 1;
overflow: hidden;
font-size: 24rpx;
color: #333;
line-height: 28rpx;
}
.fileMeta {
margin-top: 5rpx;
display: flex;
flex-direction: row;
align-items: center;
width: 650rpx;
.textItem {
margin-right: 20rpx;
font-size: 20rpx;
line-height: 26rpx;
color: #999;
}
}
}
}
</style>

View File

@@ -0,0 +1,70 @@
<template>
<view class="storageList">
<objectAnalysis
v-if="isLoaded"
:data="data"
:isOpenFirst="true"
:width="710"
/>
<view
v-else
class="dataLoading"
>
<text class="status">加载中</text>
</view>
</view>
</template>
<script>
import objectAnalysis from "./objectAnalysis.vue";
import getRuntimeInfo from "../libs/getRuntimeInfo";
export default {
components: {
objectAnalysis,
},
data() {
return {
/**
* 是否完成加载
*/
isLoaded: false,
/**
* 缓存里的数据
*/
data: {},
};
},
methods: {
/**
* 加载数据
*/
async getData() {
let that = this;
that.isLoaded = false;
let data = await getRuntimeInfo();
setTimeout(() => {
that.data = data;
that.isLoaded = true;
}, 500);
},
},
};
</script>
<style lang="scss" scoped>
.storageList {
padding: 20rpx;
width: 750rpx;
}
.dataLoading {
width: 750rpx;
height: 400rpx;
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
.status {
font-size: 20rpx;
color: #888;
line-height: 20rpx;
}
}
</style>

View File

@@ -0,0 +1,212 @@
<template>
<view class="jsRunnerItem">
<view class="codeInput">
<image
src="@/devTools/page/static/fold.png"
class="fold-left"
/>
<view class="codeView">
<text class="codeText">{{ item.code }}</text>
</view>
</view>
<view class="codeResult">
<image
src="@/devTools/page/static/fold.png"
class="fold-right"
/>
<view class="codeResultView">
<template v-if="item.isEnd">
<template v-if="isObj(item.result)">
<objectAnalysis
:data="item.result"
:showObjProto="true"
:width="610"
:canLongpress="false"
/>
</template>
<template v-else>
<view>
<text
class="context"
:class="[getTypeClass(item.result)]"
selectable
>
{{ getText(item.result) }}
</text>
</view>
</template>
</template>
<text
v-else
class="loadingOutput"
>
...
</text>
</view>
</view>
</view>
</template>
<script>
import objectAnalysis from "./objectAnalysis.vue";
export default {
components: {
objectAnalysis,
},
props: {
/**
* 单行数据
*/
item: {
type: Object,
default() {
return {
id: "",
code: "",
result: "",
isEnd: false,
};
},
},
},
methods: {
/**
* 是否为对象类型
*/
isObj(data) {
return typeof data == "object" && data != null && data != undefined;
},
/**
* 获取对应的类型样式
*/
getTypeClass(obj) {
try {
let type = typeof obj;
if (type == "string") {
if (obj.indexOf("at ") == 0) {
return "t-line";
} else if (obj === undefined || obj == "undefined") {
return "t-undefined";
} else if (obj === null || obj == "null") {
return "t-null";
} else if (obj == "true" || obj == "false") {
return "t-boolean";
} else if (Number.isFinite(Number(obj))) {
return "t-number";
}
} else if (type == "function") {
return "t-function";
}
return "t-" + type;
} catch (error) {}
return "t-string";
},
/**
* 获取数据文字
*/
getText(data) {
if (data === null) {
return "null";
}
switch (typeof data) {
case "string":
// return data.replace(/\n/g, "");
return data;
case "boolean":
return data ? "true" : "false";
case "undefined":
return "undefined";
case "function":
return data.toString();
case "symbol":
return "js:symbol";
default:
return data;
}
},
},
};
</script>
<style lang="scss" scoped>
.jsRunnerItem:active {
// background-color: rgba(0, 0, 0, 0.03);
}
.jsRunnerItem {
display: flex;
flex-direction: column;
width: 750rpx;
padding: 10rpx 20rpx;
border-bottom: 1px solid rgba(0, 0, 0, 0.05);
.codeInput {
width: 710rpx;
display: flex;
flex-direction: row;
margin-bottom: 10rpx;
.codeView {
margin-left: 10rpx;
width: 670rpx;
display: flex;
flex-direction: column;
.codeText {
font-size: 20rpx;
color: #777;
line-height: 26rpx;
}
}
}
.codeResult {
width: 710rpx;
display: flex;
flex-direction: row;
border-top: 1px solid rgba(0, 0, 0, 0.02);
padding-top: 10rpx;
.codeResultView {
margin-left: 10rpx;
width: 670rpx;
display: flex;
flex-direction: column;
.loadingOutput {
font-size: 20rpx;
color: rgba(0, 0, 0, 0.1);
line-height: 26rpx;
}
.context {
font-size: 20rpx;
color: rgb(227, 54, 46);
line-height: 26rpx;
&.t-number {
color: rgb(8, 66, 160);
}
&.t-boolean {
color: rgb(133, 2, 255);
}
&.t-string {
color: rgb(227, 54, 46);
}
&.t-undefined {
color: rgba(0, 0, 0, 0.2);
}
&.t-null {
color: rgba(0, 0, 0, 0.2);
}
&.t-line {
color: rgba(0, 0, 0, 0.5);
}
&.t-function {
color: rgb(121, 38, 117);
}
}
}
}
}
.fold-left {
transform: rotate(90deg);
width: 24rpx;
height: 24rpx;
}
.fold-right {
transform: rotate(-90deg);
width: 24rpx;
height: 24rpx;
}
</style>

View File

@@ -0,0 +1,151 @@
<template>
<view
class="logItem"
@longpress.stop="logLongpress"
>
<view class="content">
<view>
<text class="text-xs">{{ item.m }}</text>
</view>
<view class="msgBar">
<text class="time">{{ item.date }}</text>
</view>
</view>
<view class="tools">
<view
class="copyBtn"
@click="copyItem"
>
<image
src="@/devTools/page/static/copy.png"
class="copyIcon"
/>
</view>
</view>
</view>
</template>
<script>
import devCache from "../../../core/libs/devCache";
export default {
props: {
/**
* logs单行数据
*/
item: {
type: Object,
default() {
return {
date: "",
m: "", //日志内容
};
},
},
},
methods: {
/**
* 复制
*/
copyItem() {
uni.setClipboardData({
data: this.item.m,
});
},
/**
* 长按事件
*/
logLongpress() {
let that = this;
let menu = [
{
text: `复制日志信息`,
click() {
uni.setClipboardData({
data: JSON.stringify(that.item),
});
},
},
{
text: `删除此记录`,
click() {
uni.$emit("devTools_delLog", that.item);
let logs = devCache.get("logReport");
if (!logs) logs = [];
let i = logs.findIndex(
(x) => x.m == that.item.m && x.t == that.item.t
);
if (i != -1) {
logs.splice(i, 1);
devCache.set("logReport", logs);
}
uni.showToast({
title: "删除成功!",
icon: "success",
});
},
},
];
uni.showActionSheet({
itemList: menu.map((x) => x.text),
success({ tapIndex }) {
menu[tapIndex].click();
},
});
},
},
};
</script>
<style lang="scss" scoped>
.logItem:active {
background-color: rgba(0, 0, 0, 0.03);
}
.logItem {
display: flex;
flex-direction: row;
align-items: flex-start;
justify-content: space-between;
width: 750rpx;
padding: 10rpx 20rpx;
border-bottom: 1px solid rgba(0, 0, 0, 0.05);
.content {
width: 610rpx;
display: flex;
flex-direction: column;
.context {
font-size: 20rpx;
color: #333;
line-height: 24rpx;
}
.msgBar {
display: flex;
flex-direction: row;
.time {
font-size: 16rpx;
color: #888;
}
.page {
font-size: 16rpx;
color: #888;
margin-left: 20rpx;
}
}
}
}
.copyBtn:active {
background-color: rgba(103, 194, 58, 0.6);
}
.copyBtn {
padding: 5rpx;
border-radius: 999rpx;
overflow: hidden;
background-color: #67c23a;
.copyIcon {
width: 20rpx;
height: 20rpx;
}
}
.text-xs {
font-size: 20rpx;
}
</style>

View File

@@ -0,0 +1,337 @@
<template>
<view
class="networkItem"
:class="['type-' + item.type]"
@longpress.stop="networkLongpress"
>
<view class="content">
<view class="head">
<view
class="method"
:class="'type-' + item.method"
>
<text
class="methodText"
:class="'type-' + item.method"
>
{{ item.method }}
</text>
</view>
<view class="path">
<text class="pageText">{{ getPath(item.url) }}</text>
</view>
</view>
<objectAnalysis
:data="getItem(item)"
:width="710"
:canLongpress="false"
@onLongpress="networkLongpress"
/>
<view class="msgBar">
<text
class="data"
style="min-width: 90rpx"
>
{{ item.date }}
</text>
<text
class="time"
:style="{
width: '100rpx',
color: getTimeColor,
}"
>
{{ item.useTime }}s
</text>
<text
class="status"
:class="'s-' + item.type"
style="width: 120rpx"
>
{{ getTypeName(item.type) }}
</text>
<text
v-if="item.type == 1"
class="time"
:style="{
color: getSizeColor,
}"
>
{{ getByteSize }}
</text>
</view>
</view>
</view>
</template>
<script>
import objectAnalysis from "./objectAnalysis.vue";
export default {
components: {
objectAnalysis,
},
props: {
/**
* console单行数据
*/
item: {
type: Object,
default() {
return {
id: 0, //请求id
type: 0, // 0发起请求中 1请求成功 2请求失败
date: "", //请求日期
sendTime: 0, //发送请求的时间
responseTime: 0, //响应时间
useTime: 0, //请求总耗时
url: "", //请求地址
header: "", //请求头
method: "get", //请求方式
data: "", //请求参数
responseBody: "", //响应主体
responseHeader: "", //响应头
responseStatus: "", //响应编码
responseMsg: "", //响应报错信息
responseBodySize: 0, //请求主体大小
};
},
},
},
computed: {
/**
* 获取请求时间的颜色
*/
getTimeColor() {
if (this.item.useTime == 0) {
return "#eeeeee";
} else if (this.item.useTime > 3) {
return "#fa3534";
} else if (this.item.useTime > 1) {
return "#ff9900";
} else {
return "#909399";
}
},
/**
* 获取字节大小,b转kb mb
*/
getByteSize() {
let size = Number(this.item.responseBodySize);
if (null == size || size == "") return "0.00 KB";
var unitArr = ["B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"];
var index = 0;
var srcsize = parseFloat(size);
index = Math.floor(Math.log(srcsize) / Math.log(1024));
size = srcsize / Math.pow(1024, index);
size = size.toFixed(2); //保留的小数位数
if (Number(this.item.responseBodySize) < 1024) {
return (size / 1000).toFixed(2) + "KB";
}
return size + unitArr[index];
},
/**
* 获取响应大小的颜色
*/
getSizeColor() {
let size = this.item.responseBodySize;
if (size == 0) {
return "#fa3534";
} else if (size > 256 * 1024) {
return "#ff9900";
} else if (size > 1024 * 1024) {
return "#fa3534";
} else {
return "#909399";
}
},
},
methods: {
/**
* 通过url获取路径
*/
getPath(url) {
if (!url) return "无";
function getPathFromUrl(url) {
const pathStart = url.indexOf("//") + 2;
const pathEnd = url.indexOf("?", pathStart) >= 0 ? url.indexOf("?", pathStart) : url.length;
return url.substring(url.indexOf("/", pathStart), pathEnd);
}
return getPathFromUrl(url);
},
/**
* 获取请求类型名称
*/
getTypeName(type) {
return ["请求中...", "请求完成", "请求失败"][Number(type)];
},
/**
* 精简item
*/
getItem(item) {
return {
data: item.data,
responseMsg: item.responseMsg,
responseStatus: item.responseStatus,
method: item.method,
url: item.url,
header: item.header,
responseBody: item.responseBody,
responseHeader: item.responseHeader,
responseBodySize: this.getByteSize,
};
},
/**
* 长按事件
*/
networkLongpress() {
let that = this;
let menu = [
{
text: `重发此请求`,
click() {
that.$emit("goSendRequest", that.item);
},
},
{
text: `复制请求日志信息`,
click() {
uni.setClipboardData({
data: JSON.stringify(that.item),
});
},
},
{
text: `在请求构建工具中打开`,
click() {
that.$emit("goOpenRequest", that.item);
},
},
{
text: `删除此记录`,
click() {
uni.$emit("devTools_delNetworkItemById", that.item.id);
uni.showToast({
title: "删除成功!",
icon: "success",
});
},
},
];
uni.showActionSheet({
itemList: menu.map((x) => x.text),
success({ tapIndex }) {
menu[tapIndex].click();
},
});
},
},
};
</script>
<style lang="scss" scoped>
.networkItem:active {
background-color: rgba(0, 0, 0, 0.03);
}
.networkItem {
display: flex;
flex-direction: row;
align-items: flex-start;
justify-content: space-between;
width: 750rpx;
padding: 10rpx 20rpx;
border-bottom: 1px solid rgba(0, 0, 0, 0.05);
&.type-0 {
background-color: rgb(255, 251, 229);
}
&.type-2 {
background-color: rgb(255, 240, 240);
}
.content {
width: 710rpx;
display: flex;
flex-direction: column;
.head {
display: flex;
flex-direction: row;
align-items: center;
margin-bottom: 5rpx;
.method {
background-color: #e2e2e2;
color: #333;
border-radius: 4rpx;
padding: 4rpx 6rpx;
border-radius: 10rpx;
&.type-get {
background-color: rgba(168, 25, 197, 0.1);
}
&.type-post {
background-color: rgba(255, 217, 0, 0.1);
}
.methodText {
color: #333;
font-size: 22rpx;
line-height: 22rpx;
max-width: 650rpx;
&.type-get {
// color: #fff;
}
&.type-post {
// color: #fff;
}
}
}
.path {
width: 620rpx;
max-width: 620rpx;
lines: 1;
margin-left: 6rpx;
overflow: hidden;
/* #ifdef H5 */
// 限制行数
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 1;
/* #endif */
.pageText {
font-size: 24rpx;
color: #333;
}
}
}
.context {
font-size: 20rpx;
color: #333;
line-height: 24rpx;
}
.msgBar {
display: flex;
flex-direction: row;
margin-top: 5rpx;
.data {
font-size: 16rpx;
color: #888;
}
.time {
font-size: 16rpx;
color: #333;
margin-left: 20rpx;
}
.status {
margin-left: 20rpx;
font-size: 16rpx;
&.s-0 {
color: #fa3534;
}
&.s-1 {
color: #909399;
}
&.s-2 {
color: #ff9900;
}
}
}
}
}
</style>

View File

@@ -0,0 +1,733 @@
<template>
<view
class="objectAnalysis"
:style="{
width: width + 'rpx',
}"
>
<view
class="objectTitle"
:class="canLongpress ? 'objectTitleActive' : ''"
@click="titleClick"
@longpress.stop="faLongpress"
>
<image class="foldItem" v-if="isOpen" src="@/devTools/page/static/fold.png" />
<image class="foldItem" v-else src="@/devTools/page/static/unfold.png" />
<text
class="title"
:style="{
width: width - 50 + 'rpx',
}"
>
{{ title }}
</text>
</view>
<view
class="objectList"
v-if="isOpen"
:style="{
width: width + 'rpx',
}"
>
<view
v-for="(item, index) in list"
:key="item.i"
class="listItem"
:style="{
marginLeft: item.l + 'rpx',
}"
@click="rowClick(item, index)"
@longpress.stop="rowLongpress($event, item, index)"
>
<template v-if="item.t == 'array' || item.t == 'object'">
<image class="foldItem" v-if="item.o" src="@/devTools/page/static/fold.png" />
<image class="foldItem" v-else src="@/devTools/page/static/unfold.png" />
</template>
<view
v-else
class="emptyFold"
></view>
<text
class="objKey"
:class="'t-' + item.t"
:style="{
maxWidth: (width - item.l - 40) / 2 + 'rpx',
}"
>
{{ item.k }}:
</text>
<text
class="objValue"
:class="'t-' + item.t"
>
{{ item.vt }}
</text>
</view>
</view>
</view>
</template>
<script>
import jsonCompress from "../../../core/libs/jsonCompress";
function getType(v) {
return Object.prototype.toString.call(v).slice(8, -1).toLocaleLowerCase();
}
function randId() {
return Math.ceil(Math.random() * 1000000000000000);
}
export default {
props: {
/**
* 需要解析的对象数据
*/
data: "",
/**
* 组件宽度 rpx
*/
width: {
type: Number,
default: 610,
},
/**
* 是否默认展开第一层
*/
isOpenFirst: {
type: Boolean,
default: false,
},
/**
* 是否自定义长按的菜单
*/
isDiyMenu: {
type: Boolean,
default: false,
},
/**
* 是否响应最外层的长按事件
*/
canLongpress: {
type: Boolean,
default: true,
},
/**
* 是否展示完整的对象类型
*/
showObjProto: {
type: Boolean,
default: false,
},
},
data() {
return {
/**
* 对象类型 array object map unknown
*/
type: "unknown",
/**
* 对象标题
*/
title: "",
/**
* 是否为开启节点状态
*/
isOpen: false,
/**
* 渲染的列表
*/
list: [
{
t: "text", // 类型
k: "键名", // key
v: "名称", // value
vt: "", //view层渲染的文字
i: "s", //节点id
p: "0", //父节点id
o: true, //是否开启下级
l: 0, // 距离左侧长度
d: null, //原对象
},
],
};
},
mounted() {
let that = this;
try {
let { title } = that.getObjType(this.data);
that.list = [];
that.title = title;
if (that.isOpenFirst) {
that.titleClick();
}
} catch (error) {
console.log("objectAnalysis error", error);
}
},
methods: {
/**
* 标题点击事件
*/
titleClick() {
if (this.isOpen) {
this.isOpen = false;
} else {
if (this.list.length == 0) {
this.analysisData(this.data);
}
this.isOpen = true;
}
},
/**
* 解析渲染数组
*/
analysisData(data, pid = 0) {
let list = [];
let l = this.getParentNum(pid);
let keys = [];
keys = Reflect.ownKeys(data);
for (let i = 0; i < keys.length; i++) {
let key = keys[i];
let value = data[key];
if (key == "__proto__" || key == "__ob__") {
continue;
}
let o = {
t: typeof value,
k: key,
v: value,
vt: "",
i: randId(),
p: pid,
o: false,
l: l * 5,
d: value,
};
try {
let t = typeof value;
if (t == "function") {
try {
o.vt = value.toString();
} catch (error) {
if (error && error.message) {
o.vt = "[js:function]" + error.message;
} else {
o.vt = "[js:function]";
}
}
o.v = o.vt;
o.d = "";
} else if (t == "object") {
if (this.showObjProto) {
let str = "unknown";
if (value === null) {
o.t = "null";
str = "null";
} else if (Array.isArray(value)) {
o.t = "array";
let l = 0;
try {
l = value.length;
} catch (error) {}
str = Object.prototype.toString.call(value).slice(8, -1) + (l > 0 ? ` (${l})[...]` : " (0)[]");
} else {
o.t = "object";
let childList = [];
try {
childList = Reflect.ownKeys(value);
} catch (error) {}
str = Object.prototype.toString.call(value).slice(8, -1) + (childList.length == 0 ? " {}" : " {...}");
}
o.vt = str;
} else {
let type = getType(value);
let title = "";
try {
title = JSON.stringify(value);
if (title.length > 50) {
title = title.slice(0, 50) + "...";
}
if (type == "array" && value.length > 0) {
title = "(" + value.length + ")" + title;
}
} catch (error) {
title = "对象解析失败:" + error;
}
o.t = type;
o.vt = title;
o.v = value;
}
} else if (t == "symbol") {
o.t = "symbol";
try {
if (value.toString) {
o.vt = value.toString();
} else {
o.vt = "[js:symbol]";
}
} catch (error) {
let msg = "";
if (error && error.message) {
msg = error.message;
} else {
msg = "[js:symbol解析失败]";
}
o.vt = msg;
}
} else if (t == "string") {
if (value.length > 200) {
o.vt = `"` + value.slice(0, 200) + "..." + '"';
} else {
o.vt = `"${value}"`;
}
} else if (t == "number") {
if (Number.isFinite(value)) {
o.vt = value.toString();
} else {
o.vt = isNaN(value) ? "NaN" : "Infinity";
}
} else if (t == "boolean") {
o.vt = value ? "true" : "false";
} else if (t == "undefined") {
o.vt = "undefined";
} else {
o.vt = "[js:unknown type]";
}
} catch (error) {
let msg = "";
if (error && error.message) {
msg = error.message;
} else {
msg = "[js对象解析失败]";
}
o.vt = msg;
}
list.push(o);
}
if (pid == 0) {
this.list = list;
} else {
let faIndex = this.list.findIndex((x) => x.i == pid) + 1;
for (let i = 0; i < list.length; i++) {
this.list.splice(faIndex, 0, list[i]);
faIndex++;
}
}
},
/**
* 获取节点的父类数量
*/
getParentNum(pid) {
let that = this;
let count = 0;
if (pid == 0) {
return count;
} else {
let p = Number(pid);
while (true) {
count = count + 1;
let fa = that.list.find((x) => x.i == p);
if (!fa || fa.i == 0) {
break;
} else {
p = Number(fa.p);
}
}
}
return count;
},
/**
* 行对象点击事件
*/
rowClick(item, index) {
let that = this;
const nodeItem = that.list[index];
if (item.t == "object" || item.t == "array") {
if (item.o) {
nodeItem.o = false;
that.hideListByPid(item.i);
} else {
nodeItem.o = true;
that.analysisData(nodeItem.d, item.i);
}
} else if (item.t == "string" && item.v.length > 100) {
// 长文本点击时默认打开文本编辑器
uni.$emit("devTools_showTextEditDialog", {
title: item.k,
canSave: false,
isFileEdit: false,
value: item.v,
});
}
},
/**
* 根据父类id删除数组内元素
*/
hideListByPid(pid = 0) {
let that = this;
while (true) {
let i = that.list.findIndex((x) => x.p == pid);
if (i == -1) {
break;
}
if (that.list[i].o) {
that.hideListByPid(that.list[i].i);
}
that.list.splice(i, 1);
}
},
/**
* 长按事件
*/
rowLongpress(e, item, index) {
// #ifdef APP-PLUS
if (e && e.stopPropagation) {
e.stopPropagation();
}
// #endif
let that = this;
if (that.isDiyMenu) {
that.$emit("diyMenu", { item, index });
} else {
let k = this.toString(item.k);
if (k.length > 20) {
k = k.slice(0, 20) + "...";
}
let v = this.toString(item.v);
if (v.length > 20) {
v = v.slice(0, 20) + "...";
}
uni.showActionSheet({
itemList: [`复制键(${k})`, `复制值(${v})`],
success({ tapIndex }) {
if (tapIndex == 0) {
uni.setClipboardData({
data: that.toString(item.k),
});
} else {
uni.setClipboardData({
data: that.toString(item.v),
});
}
},
});
}
},
/**
* 尝试转字符串
*/
toString(data) {
try {
if (data === undefined) return "undefined";
if (data === null) return "null";
if (typeof data == "boolean") return data ? "true" : "false";
if (typeof data == "object") {
return JSON.stringify(data);
}
return data.toString();
} catch (error) {
return "尝试解析失败!" + error;
}
},
/**
* 获取列表
*/
getList() {
return this.list;
},
/**
* 获取父级key类别
*/
getFaKeyList(itemId) {
let keyList = [];
let item = this.list.find((x) => x.i == itemId);
if (!item) return keyList;
keyList = [item.k];
if (item.p == 0) return keyList;
while (true) {
item = this.list.find((x) => x.i == item.p);
if (!item) break;
keyList.unshift(item.k);
if (item.p == 0) {
break;
}
}
return keyList;
},
/**
* 长按复制一整个对象
*/
faLongpress(e) {
// #ifdef APP-PLUS
if (e && e.stopPropagation) {
e.stopPropagation();
}
// #endif
let that = this;
if (that.canLongpress) {
uni.setClipboardData({
data: JSON.stringify(that.data),
});
} else {
that.$emit("onLongpress");
}
},
/**
* 获取对象单行数据
*/
getObjType(obj) {
try {
let title = "unknown";
let type = typeof obj;
let data = obj;
switch (typeof data) {
case "symbol":
title = "[js:symbol]";
try {
if (data.toString) {
title = data.toString();
} else {
title = "[js:symbol]";
}
} catch (error) {
let msg = "";
if (error && error.message) {
msg = error.message;
} else {
msg = "[js:symbol解析失败]";
}
title = msg;
}
break;
case "string":
title = data;
break;
case "object":
if (this.showObjProto) {
try {
let objType = Object.prototype.toString.call(data).slice(8, -1);
title = {};
let keys = Reflect.ownKeys(data);
for (let i = 0; i < keys.length; i++) {
let key = keys[i];
if (key == "__proto__" || key == "__ob__") {
continue;
}
try {
let value = data[key];
let t = typeof value;
if (t == "function") {
continue;
}
if (t == "object") {
let str = "unknown";
if (value === null) {
str = "null";
} else if (Array.isArray(value)) {
str = Object.prototype.toString.call(value).slice(8, -1) + " [...]";
} else {
str = Object.prototype.toString.call(value).slice(8, -1) + " {...}";
}
title[key] = str;
continue;
}
title[key] = data[key];
} catch (error) {
if (error && error.message) {
title[key] = error.message;
} else {
title[key] = "[js对象解析失败]";
}
}
}
for (let i = 0; i < keys.length; i++) {
let key = keys[i];
try {
let value = data[key];
let t = typeof value;
if (t == "function") {
try {
title[key] = value.toString();
} catch (error) {
if (error && error.message) {
title[key] = "[js:function]" + error.message;
} else {
title[key] = "[js:function]";
}
}
}
} catch (error) {
if (error && error.message) {
title[key] = error.message;
}
}
}
if (title.toJSON) {
title.toJSON = "[js:function]";
}
if (objType == "Array") {
title = objType + " " + jsonCompress.safeJsonStringify(title);
} else {
title = objType + " " + jsonCompress.safeJsonStringify(title);
}
if (Array.isArray(data)) {
type = "array";
} else {
type = "object";
}
} catch (error) {
let msg = "unknown";
if (error && error.message) {
msg = error.message;
}
title = "对象解析出错:" + msg;
}
} else {
try {
title = JSON.stringify(data);
if (title.length > 50) {
title = title.slice(0, 50) + "...";
}
} catch (error) {
title = "对象解析失败:" + error;
}
}
break;
case "function":
try {
title = data.toString();
} catch (error) {
title = "[js:function]";
}
break;
default:
title = data;
break;
}
return { title, type };
} catch (error) {
console.log("getObjType error", error);
return {
title: "unknown",
type: "unknown",
};
}
},
},
};
</script>
<style lang="scss" scoped>
.objectAnalysis {
display: flex;
flex-direction: column;
.objectTitle {
display: flex;
flex-direction: row;
align-items: center;
.title {
font-size: 20rpx;
line-height: 20rpx;
color: rgb(89, 74, 154);
lines: 1;
overflow: hidden;
/* #ifdef H5 */
// 限制行数
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 1;
/* #endif */
}
}
.objectTitleActive:active {
background-color: rgba(0, 0, 0, 0.08);
}
.objectList {
background-color: rgba(0, 0, 0, 0.02);
border-radius: 8rpx;
/* #ifndef APP-PLUS */
min-height: 50rpx;
/* #endif */
padding: 10rpx;
.listItem:active {
background-color: rgba(0, 0, 0, 0.08);
}
.listItem {
display: flex;
flex-direction: row;
align-items: center;
.emptyFold {
width: 20rpx;
height: 20rpx;
margin-right: 6rpx;
}
.objKey {
font-size: 20rpx;
line-height: 28rpx;
color: rgb(121, 38, 117);
lines: 1;
overflow: hidden;
/* #ifdef H5 */
// 限制行数
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 1;
/* #endif */
}
.objValue {
line-height: 28rpx;
margin-left: 5rpx;
color: #333;
font-size: 20rpx;
lines: 1;
overflow: hidden;
/* #ifdef H5 */
// 限制行数
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 1;
/* #endif */
&.t-number {
color: rgb(8, 66, 160);
}
&.t-boolean {
color: rgb(133, 2, 255);
}
&.t-string {
color: rgb(227, 54, 46);
}
&.t-array {
color: rgba(0, 0, 0, 0.5);
}
&.t-object {
color: rgba(0, 0, 0, 0.5);
}
&.t-undefined {
color: rgba(0, 0, 0, 0.2);
}
&.t-null {
color: rgba(0, 0, 0, 0.2);
}
}
}
}
}
.foldItem {
width: 20rpx;
height: 20rpx;
background-color: #eee;
border-radius: 4rpx;
margin-right: 6rpx;
}
</style>

View File

@@ -0,0 +1,102 @@
<template>
<view
class="pageItem"
@click.stop="showMenu"
>
<view class="content">
<view>
<text class="text-xs">页面路由{{ item.route }}</text>
</view>
<view>
<text class="text-xs">停留时长{{ item.timeCount }}</text>
</view>
</view>
</view>
</template>
<script>
export default {
props: {
/**
* logs单行数据
*/
item: {
type: Object,
default() {
return {
route: "",
timeCount: "",
};
},
},
},
methods: {
/**
* 展示菜单
*/
showMenu() {
let that = this;
let r = String(that.item.route).substring(0, 10) + "...";
let menu = [
{
text: `复制路径(${r})`,
click() {
uni.setClipboardData({
data: that.item.route,
});
},
},
{
text: `复制时间(${that.item.timeCount})`,
click() {
uni.setClipboardData({
data: that.item.timeCount,
});
},
},
{
text: `跳转至此页面`,
click() {
uni.$emit("devTools_showRouteDialog", that.item.route);
},
},
];
uni.showActionSheet({
itemList: menu.map((x) => x.text),
success({ tapIndex }) {
menu[tapIndex].click();
},
});
},
},
};
</script>
<style lang="scss" scoped>
.pageItem:active {
background-color: rgba(0, 0, 0, 0.03);
}
.pageItem {
display: flex;
flex-direction: row;
align-items: flex-start;
justify-content: space-between;
width: 750rpx;
padding: 10rpx 20rpx;
border-bottom: 1px solid rgba(0, 0, 0, 0.05);
.content {
width: 610rpx;
display: flex;
flex-direction: column;
.context {
font-size: 20rpx;
color: #333;
line-height: 24rpx;
}
}
}
.text-xs {
font-size: 20rpx;
}
</style>

View File

@@ -0,0 +1,202 @@
<template>
<view
v-if="isShow"
class="pagesList"
>
<!-- <objectAnalysis
v-if="isLoaded"
:data="pages"
:isOpenFirst="true"
:width="710"
/> -->
<template v-if="isLoaded">
<view
v-for="(item, index) in pages"
:key="index"
class="pageItem"
@longpress.stop="longpress(item)"
>
<text
v-if="pages.length == index + 1"
class="t-red"
>
当前
</text>
<view class="routeInfo">
<text class="path">{{ item.route }}</text>
<text class="options">{{ item.options }}</text>
</view>
</view>
</template>
<view
v-else
class="dataLoading"
>
<text class="status">加载中</text>
</view>
</view>
</template>
<script>
import objectAnalysis from "./objectAnalysis.vue";
export default {
components: {
objectAnalysis,
},
props: {
/**
* 是否渲染
*/
isShow: {
type: Boolean,
default: true,
},
},
data() {
return {
/**
* 是否完成加载
*/
isLoaded: false,
/**
* 页面路由数据
*/
pages: [],
};
},
methods: {
/**
* 加载数据
*/
getData() {
let that = this;
that.isLoaded = false;
let pageList = getCurrentPages().map((x) => {
let options = "";
if (x.options) {
Object.keys(x.options).map((key) => {
options =
options + (options == "" ? "" : "&") + key + "=" + x.options[key];
});
}
return {
route: x.route,
options,
};
});
pageList.pop();
that.pages = pageList;
that.isLoaded = true;
},
/**
* 长按事件
*/
longpress(item) {
let that = this;
let menu = [
{
text: `复制路径`,
click() {
uni.setClipboardData({
data: item.route,
});
},
},
...(item.options
? [
{
text: `复制参数`,
click() {
uni.setClipboardData({
data: item.options,
});
},
},
{
text: `复制路径+参数`,
click() {
uni.setClipboardData({
data: item.route + item.options ? "?" + item.options : "",
});
},
},
]
: []),
];
uni.showActionSheet({
itemList: menu.map((x) => x.text),
success({ tapIndex }) {
menu[tapIndex].click();
},
});
},
},
};
</script>
<style lang="scss" scoped>
.pagesList {
width: 750rpx;
display: flex;
flex-direction: column;
.pageItem {
display: flex;
flex-direction: row;
align-items: center;
padding: 15rpx 20rpx;
border-bottom: 1px solid rgba(0, 0, 0, 0.05);
&:active {
background-color: rgba(0, 0, 0, 0.05);
}
.t-red {
font-size: 20rpx;
color: #fff;
padding: 3rpx 8rpx;
background-color: #ff2d55;
border-radius: 10rpx;
margin-right: 10rpx;
height: 34rpx;
}
.routeInfo {
display: flex;
flex-direction: column;
width: 580rpx;
.path {
font-size: 26rpx;
line-height: 30rpx;
color: #333;
width: 580rpx;
word-wrap: break-word;
overflow-wrap: break-word;
white-space: normal;
}
.options {
margin-top: 4rpx;
font-size: 20rpx;
line-height: 26rpx;
color: #888;
width: 580rpx;
word-wrap: break-word;
overflow-wrap: break-word;
white-space: normal;
}
}
}
}
.dataLoading {
width: 750rpx;
height: 100rpx;
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
.status {
font-size: 20rpx;
color: #888;
line-height: 20rpx;
}
}
</style>

View File

@@ -0,0 +1,77 @@
<template>
<view
class="routeItem"
@click.stop="showMenu"
>
<view>
<text class="routeText">{{ item.path }}</text>
</view>
</view>
</template>
<script>
export default {
props: {
item: {
type: Object,
default: () => ({}),
},
},
data() {
return {};
},
methods: {
/**
* 展示菜单
*/
showMenu() {
let that = this;
let menuList = [];
let p = that.item.path.substr(0, 20);
if (p.length == 20) {
p = p + "...";
}
menuList.push(`复制路径(${p})`);
let isTabBar = false;
if (that.item.meta && that.item.meta.isTabBar) {
isTabBar = true;
}
if (!isTabBar) {
menuList.push("跳转至此页面");
}
uni.showActionSheet({
itemList: menuList,
success({ tapIndex }) {
if (tapIndex == 0) {
uni.setClipboardData({
data: that.item.path,
});
} else {
uni.$emit("devTools_showRouteDialog", that.item.path);
}
},
});
},
},
};
</script>
<style lang="scss" scoped>
.routeItem {
padding: 10rpx 20rpx;
border-bottom: 1px solid rgba(0, 0, 0, 0.05);
display: flex;
flex-direction: column;
.routeText {
width: 710rpx;
font-size: 20rpx;
line-height: 26rpx;
color: #333;
}
}
.routeItem:active {
background-color: rgba(0, 0, 0, 0.05);
}
</style>

View File

@@ -0,0 +1,723 @@
<template>
<view class="settingView">
<view v-if="loading" class="loading">
<text class="loadingText">加载中</text>
</view>
<template v-else>
<subTitleBar :isOpen="exportIsShow" @click="exportIsShow = !exportIsShow" title="导出全部日志" />
<template v-if="exportIsShow">
<view class="delBtn" @click="exportJsonFile">
<text class="delBtnText">导出日志文件(.json)</text>
</view>
</template>
<view class="divisionLine"></view>
<subTitleBar :isOpen="cacheListIsShow" title="清空全部缓存" @click="cacheListIsShow = !cacheListIsShow" />
<template v-if="cacheListIsShow">
<view v-for="(item, index) in cacheSelectList" :key="index" @click.stop="doSelectCache(index)" class="checkboxItem">
<checkbox :value="item.check ? '1' : '0'" :checked="item.check" color="#ff2d55" />
<text
class="name"
:style="{
color: item.count ? '#333' : '#888'
}"
>
{{ item.name }}
</text>
<text v-if="item.key == 'file'"></text>
<text v-else-if="item.count" class="count"> ({{ item.count }}) </text>
<text v-else class="empty"> () </text>
</view>
<view class="delBtn" @click="delCache">
<text class="delBtnText">清空选中</text>
</view>
</template>
<view class="divisionLine"></view>
<subTitleBar :isOpen="configIsShow" title="DevTools当前配置参数" @click="configIsShow = !configIsShow" />
<view v-if="configIsShow" class="objectAnalysisView">
<objectAnalysis :isOpenFirst="true" :data="config" :width="710" />
</view>
<view class="divisionLine"></view>
<subTitleBar :showArrow="false" title="关于" />
<view class="about">
<view>
<text class="row">Copyright©2024 福州重塑网络科技有限公司 前端团队</text>
</view>
<view @click="goUrl('https://dev.api0.cn')" style="display: flex; flex-direction: row">
<text class="row">在线文档</text>
<text class="row" style="color: #ff2d55">https://dev.api0.cn</text>
</view>
<view>
<text class="row">当前版本v{{ config.version }}</text>
</view>
</view>
<view style="height: 100rpx"></view>
</template>
</view>
</template>
<script>
import devCache from '../../../core/libs/devCache'
import devOptions from '../../../core/libs/devOptions'
import jsonCompress from '../../../core/libs/jsonCompress'
import appDelDir from '../libs/appDelDir'
import subTitleBar from '../ui/subTitleBar.vue'
import objectAnalysis from './objectAnalysis.vue'
import getRuntimeInfo from '../libs/getRuntimeInfo'
export default {
components: {
subTitleBar,
objectAnalysis
},
data() {
return {
/**
* 是否加载中
*/
loading: false,
/**
* 缓存列表是否展示
*/
cacheListIsShow: false,
/**
* 缓存列表
*/
cacheSelectList: [],
/**
* 配置文件是否显示
*/
configIsShow: false,
/**
* 当前配置
*/
config: devOptions.getOptions(),
/**
* 是否显示导出日志按钮
*/
exportIsShow: false
}
},
methods: {
/**
* 加载页面
*/
async getPage() {
let that = this
that.loading = true
that.cacheSelectList = await that.countCache()
that.loading = false
},
/**
* 统计缓存信息
*/
countCache() {
let that = this
return new Promise(async (yes) => {
let cacheSelectList = []
// dev 工具日志
let keys = {
errorReport: 'Error错误日志',
console: 'Console打印日志',
request: 'Request请求日志',
logReport: 'Logs日志',
uniBus: 'UniBus函数日志'
}
Object.keys(keys).map((key) => {
let logs = devCache.get(key)
cacheSelectList.push({
name: keys[key],
check: logs.length > 0,
count: logs.length,
key
})
})
// #ifdef H5
let indexDBList = await this.getIndexDBList()
let cookieLength = document.cookie.split(';').length
if (document.cookie == '') {
cookieLength = 0
}
// #endif
cacheSelectList = cacheSelectList.concat([
that.countStorageCache(),
// #ifdef H5
{
key: 'sessionStorage',
name: 'SessionStorage临时缓存',
check: sessionStorage.length > 0,
count: sessionStorage.length
},
// #endif
// #ifdef APP-PLUS
{
key: 'file',
name: 'FileSys本地文件(_doc)',
check: false,
count: '未知 // TODO'
},
// #endif
// #ifdef MP-WEIXIN
{
name: 'FileSys本地文件(FileSystemManager)',
check: false,
key: 'file',
count: '未知 // TODO'
},
// #endif
{
key: 'pageCount',
name: 'Pages页面停留统计',
check: devCache.get('pageCount').length > 0,
count: devCache.get('pageCount').length
},
{
key: 'dayOnline',
name: 'Pages日活时间统计',
check: devCache.get('dayOnline').length > 0,
count: devCache.get('dayOnline').length
},
// #ifdef H5
{
key: 'cookie',
name: 'Cookie',
check: cookieLength > 0,
count: cookieLength
},
{
key: 'IndexDB',
name: 'IndexDB',
check: indexDBList.length > 0,
count: indexDBList.length
}
// #endif
])
yes(cacheSelectList)
})
},
/**
* 统计本地缓存
*/
countStorageCache() {
let n = 0
// #ifdef APP-PLUS
let keys = plus.storage.getAllKeys()
for (let i = 0; i < keys.length; i++) {
const key = keys[i]
if (key.indexOf('devTools_') == 0) {
// 忽略以 devTools_ 开头的key
continue
}
n++
}
// #endif
// #ifdef H5
for (let i = 0; i < localStorage.length; i++) {
let key = localStorage.key(i)
if (key.indexOf('devTools_') == 0) {
continue
}
n++
}
// #endif
// #ifdef MP
let keyList = devCache.get('storage')
if (!keyList) keyList = []
for (let i = 0; i < keyList.length; i++) {
const key = keyList[i]
if (key.indexOf('devTools_') == 0) {
continue
}
n++
}
// #endif
return {
key: 'localStorage',
name: 'localStorage本地缓存',
check: n > 0,
count: n
}
},
/**
* 获取indexDB列表
*/
getIndexDBList() {
return new Promise((yes) => {
try {
indexedDB.databases().then((list) => {
yes(list)
})
} catch (error) {
console.log('getIndexDBList error', error)
yes([])
}
})
},
/**
* 选择清空的缓存项目
*/
doSelectCache(index) {
this.cacheSelectList[index].check = !this.cacheSelectList[index].check
},
/**
* 清空缓存
*/
delCache() {
let that = this
let selectedKey = []
that.cacheSelectList.map((item) => {
if (item.check) {
selectedKey.push(item.key)
}
})
let keyDelFun = {
errorReport() {
devCache.set('errorReport', [])
},
console() {
uni.$emit('devTools_delConsoleAll')
},
request() {
uni.$emit('devTools_delNetworkAll')
},
logReport() {
devCache.set('logReport', [])
},
uniBus() {
uni.$emit('devTools_delUniBusAll')
},
localStorage() {
// #ifdef APP-PLUS
let keys = plus.storage.getAllKeys()
for (let i = 0; i < keys.length; i++) {
const key = String(keys[i])
if (key.indexOf('devTools_') == 0) {
continue
}
uni.removeStorageSync(key)
}
// #endif
// #ifdef H5
for (let i = 0; i < localStorage.length; i++) {
let key = String(localStorage.key(i))
if (key.indexOf('devTools_') == 0) {
continue
}
setTimeout(() => {
localStorage.removeItem(key)
}, i * 2 + 1)
}
// #endif
// #ifdef MP
let keyList = devCache.get('storage')
if (!keyList) keyList = []
for (let i = 0; i < keyList.length; i++) {
const key = keyList[i]
if (key.indexOf('devTools_') == 0) {
continue
}
uni.removeStorageSync(key)
}
// #endif
},
sessionStorage() {
for (let i = 0; i < sessionStorage.length; i++) {
let key = String(sessionStorage.key(i))
if (key.indexOf('devTools_') == 0) {
continue
}
sessionStorage.removeItem(key)
}
},
file() {
// #ifdef APP-PLUS
appDelDir('_doc/')
// #endif
// #ifdef MP-WEIXIN
let fs = wx.getFileSystemManager()
fs.rmdir({
dirPath: wx.env.USER_DATA_PATH + '/',
recursive: true
})
// #endif
},
pageCount() {
devCache.set('pageCount', [])
},
dayOnline() {
devCache.set('dayOnline', [])
},
cookie() {
let keys = []
document.cookie.split(';').forEach((cookieStr) => {
const [name, value] = cookieStr.trim().split('=')
keys.push(name)
})
keys.map((k) => {
document.cookie = `${k}=;expires=` + new Date(new Date().getTime() + 200).toGMTString() + ';path=/'
})
},
IndexDB() {
indexedDB.databases().then((list) => {
list.map((item) => {
indexedDB.deleteDatabase(item.name)
})
})
}
}
if (selectedKey.length == 0) {
return uni.showToast({
title: '请先勾选需要清空的项目!',
icon: 'none'
})
}
uni.showLoading({
title: '清空中...',
mask: true
})
setTimeout(() => {
uni.hideLoading()
uni.showToast({
title: '清空成功!',
icon: 'success'
})
that.getPage()
}, 5100)
selectedKey.map((key) => {
keyDelFun[key]()
})
},
/**
* 导出日志文件到json
*/
async exportJsonFile() {
let that = this
// #ifdef MP
if (1) {
uni.showToast({
title: '小程序平台不支持导出日志,建议直接上传至服务器!',
icon: 'none'
})
return
}
// #endif
uni.showLoading({
title: '打包中...'
})
try {
let devOp = devOptions.getOptions()
let waitExportObject = {
exportOptions: {
version: devOp.version,
config: devOp,
exportTime: new Date().getTime(),
// #ifdef APP-PLUS
platform: 'app',
// #endif
// #ifdef H5
platform: 'h5',
// #endif
// #ifdef MP
platform: 'mp',
// #endif
// #ifdef MP-WEIXIN
platform: 'wx',
// #endif
// #ifdef MP-QQ
platform: 'qq'
// #endif
},
error: devCache.get('errorReport'),
console: devCache.get('console'),
network: devCache.get('request'),
pageCount: devCache.get('pageCount'),
dayOnline: devCache.get('dayOnline'),
logs: devCache.get('logReport'), // ! 运行日志
info: await getRuntimeInfo(), // ! 当前运行的系统信息
uniBus: devCache.get('uniBus'),
busCount: devCache.get('busCount'),
storage: {},
sessionStorage: {},
cookie: {},
...that.getCache()
}
try {
if (that.$store.state) {
waitExportObject.vuex = that.$store.state
}
} catch (error) {}
try {
if (uni.Pinia) {
waitExportObject.pinia = uni.Pinia.getActivePinia().state.value
} else if (that.$pinia.state.value) {
waitExportObject.pinia = that.$pinia.state.value
}
} catch (error) {}
try {
if (getApp().globalData) {
waitExportObject.globalData = getApp().globalData
}
} catch (error) {}
let data = jsonCompress.safeJsonStringify(waitExportObject)
data = JSON.parse(data)
data = JSON.stringify(data, null, 2)
let t = new Date().getTime()
let exportFileName = `export_devtools_log_${t}.json`
// #ifdef H5
const blob = new Blob([data], { type: 'application/json' })
const url = URL.createObjectURL(blob)
const a = document.createElement('a')
a.style = 'display: none'
a.download = exportFileName
a.href = url
document.body.appendChild(a)
a.click()
uni.showToast({
title: '导出成功!',
icon: 'success'
})
// #endif
// #ifdef APP-PLUS
plus.io.resolveLocalFileSystemURL(
'_downloads/',
(entry) => {
entry.getFile(
exportFileName,
{
create: true
},
(fileEntry) => {
fileEntry.createWriter((writer) => {
writer.onwrite = (e) => {
uni.hideLoading()
uni.showModal({
title: '导出成功',
content: '文件导出成功!已保存至公共下载路径,文件名称:' + exportFileName
})
}
writer.onerror = () => {
uni.hideLoading()
uni.showToast({
title: '日志导出失败_写入文件失败',
icon: 'none'
})
}
writer.write(data)
})
}
)
},
(err) => {
console.log('err', err)
uni.hideLoading()
uni.showToast({
title: '文件保存失败_打开目录失败',
icon: 'none'
})
}
)
// #endif
uni.hideLoading()
} catch (error) {
if (error && error.message) {
console.log('导出失败!', error.message)
} else {
console.log('导出失败!', error)
}
uni.hideLoading()
uni.showToast({
title: '导出失败!',
icon: 'error'
})
}
},
/**
* 获取缓存数据
*/
getCache() {
let data = {
storage: {},
sessionStorage: {},
cookie: {}
}
// #ifdef APP-PLUS
let keys = plus.storage.getAllKeys()
for (let i = 0; i < keys.length; i++) {
const key = keys[i]
if (key.indexOf('devTools_') == 0) {
// 忽略以 devTools_ 开头的key
continue
}
data.storage[key] = uni.getStorageSync(key)
}
// #endif
// #ifdef H5
for (let i = 0; i < localStorage.length; i++) {
let key = localStorage.key(i)
if (key.indexOf('devTools_') == 0) {
continue
}
let value = uni.getStorageSync(key)
data.storage[key] = value
}
for (let i = 0; i < sessionStorage.length; i++) {
let key = sessionStorage.key(i)
if (key.indexOf('devTools_') == 0) {
continue
}
let value = sessionStorage.getItem(key)
data.sessionStorage[key] = value
}
document.cookie.split(';').forEach((cookieStr) => {
const [name, value] = cookieStr.trim().split('=')
data.cookie[name] = value
})
// #endif
// #ifdef MP
let keyList = devCache.get('storage')
if (!keyList) keyList = []
for (let i = 0; i < keyList.length; i++) {
const key = keyList[i]
if (key.indexOf('devTools_') == 0) {
continue
}
let value = uni.getStorageSync(key)
if (value) {
data.storage[key] = value
}
}
// #endif
return data
},
/**
* 跳转指定URL
*/
goUrl(url) {
// #ifdef H5
window.open(url)
// #endif
// #ifdef MP
uni.setClipboardData({
data: url
})
// #endif
// #ifdef APP-PLUS
plus.runtime.openURL(url)
// #endif
}
}
}
</script>
<style lang="scss" scoped>
.settingView {
display: flex;
flex-direction: column;
width: 750rpx;
.loading {
width: 750rpx;
height: 300rpx;
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
.loadingText {
font-size: 24rpx;
color: #888;
}
}
.divisionLine {
width: 750rpx;
height: 1px;
background-color: rgba(0, 0, 0, 0.1);
}
.checkboxItem {
display: flex;
flex-direction: row;
padding: 10rpx 20rpx;
width: 750rpx;
align-items: center;
&:active {
background-color: rgba(0, 0, 0, 0.05);
}
.name {
font-size: 24rpx;
margin-left: 5rpx;
}
.count {
font-size: 20rpx;
margin-left: 10rpx;
color: #ff2d55;
}
.empty {
font-size: 20rpx;
margin-left: 10rpx;
color: #999;
}
}
.delBtn {
width: 710rpx;
margin-left: 20rpx;
border-radius: 20rpx;
height: 70rpx;
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
background-color: rgb(255, 45, 85);
margin-top: 20rpx;
margin-bottom: 30rpx;
&:active {
background-color: rgba(255, 45, 85, 0.8);
}
.delBtnText {
font-size: 24rpx;
color: #fff;
}
}
}
.objectAnalysisView {
width: 710rpx;
margin-left: 20rpx;
margin-bottom: 20rpx;
}
.about {
width: 710rpx;
margin-left: 20rpx;
display: flex;
flex-direction: column;
.row {
margin-bottom: 10rpx;
font-size: 24rpx;
color: #888;
}
}
</style>

View File

@@ -0,0 +1,319 @@
<template>
<view class="storageList">
<objectAnalysis
v-if="isLoaded && !isEmpty"
:data="storageData"
:isOpenFirst="true"
:width="710"
:isDiyMenu="true"
@diyMenu="diyMenu"
/>
<view
v-if="!isLoaded"
class="dataLoading"
>
<text class="status">加载中</text>
</view>
<view
v-if="isLoaded && isEmpty"
class="dataLoading"
>
<text class="status">无缓存数据</text>
</view>
<view
v-if="isLoaded && !isEmpty"
class="moreTools"
>
<text class="tips">Tips长按最外层key可复制编辑或删除缓存</text>
</view>
</view>
</template>
<script>
// #ifdef MP
import devCache from "../../../core/libs/devCache";
// #endif
import objectAnalysis from "./objectAnalysis.vue";
export default {
components: {
objectAnalysis,
},
data() {
return {
/**
* 是否完成加载
*/
isLoaded: false,
/**
* 缓存里的数据
*/
storageData: {},
/**
* 数据是否为空
*/
isEmpty: false,
};
},
methods: {
/**
* 加载数据
*/
getData(storageType) {
let that = this;
that.isLoaded = false;
let data = {};
// #ifdef APP-PLUS
let keys = plus.storage.getAllKeys();
for (let i = 0; i < keys.length; i++) {
const key = keys[i];
if (key.indexOf("devTools_") == 0) {
// 忽略以 devTools_ 开头的key
continue;
}
data[key] = uni.getStorageSync(key);
}
// #endif
// #ifdef H5
if (storageType == "localStorage") {
for (let i = 0; i < localStorage.length; i++) {
let key = localStorage.key(i);
if (key.indexOf("devTools_") == 0) {
continue;
}
let value = uni.getStorageSync(key);
data[key] = value;
}
} else if (storageType == "sessionStorage") {
for (let i = 0; i < sessionStorage.length; i++) {
let key = sessionStorage.key(i);
if (key.indexOf("devTools_") == 0) {
continue;
}
let value = sessionStorage.getItem(key);
data[key] = value;
}
} else if (storageType == "cookie") {
document.cookie.split(";").forEach((cookieStr) => {
const [name, value] = cookieStr.trim().split("=");
data[name] = value;
});
}
// #endif
// #ifdef MP
let keyList = devCache.get("storage");
if (!keyList) keyList = [];
for (let i = 0; i < keyList.length; i++) {
const key = keyList[i];
if (key.indexOf("devTools_") == 0) {
continue;
}
let value = uni.getStorageSync(key);
if (value) {
data[key] = value;
}
}
// #endif
setTimeout(() => {
that.storageData = data;
if (Object.keys(data).length == 0) {
that.isEmpty = true;
} else {
that.isEmpty = false;
}
that.isLoaded = true;
}, 500);
},
/**
* 自定义长按事件
*/
diyMenu({ item, index }) {
let that = this;
let menu = [
{
text: `复制键(key)`,
click() {
uni.setClipboardData({
data: item.k,
});
},
},
{
text: `复制值(value)`,
click() {
uni.setClipboardData({
data: item.v,
});
},
},
];
if (item.p == 0) {
//点击第一层 key
menu.unshift({
text: "删除该键",
click() {
// #ifdef H5
if (that.storageType == "localStorage") {
uni.removeStorageSync(item.k);
} else if (that.storageType == "sessionStorage") {
sessionStorage.removeItem(item.k);
} else if (that.storageType == "cookie") {
document.cookie = `${item.k}=;expires=` + new Date(new Date().getTime() + 200).toGMTString() + ";path=/";
}
// #endif
// #ifndef H5
uni.removeStorageSync(item.k);
// #endif
uni.showToast({
title: "删除成功!",
icon: "success",
});
if (that.storageType == "cookie") {
// cookie删除后需要等待一秒后生效
setTimeout(() => {
that.getData();
}, 1500);
} else {
that.getData();
}
},
});
menu.unshift({
text: "编辑值",
click() {
let key = item.k;
let value = "";
if (that.storageType == "sessionStorage") {
value = sessionStorage.getItem(key);
} else if (that.storageType == "cookie") {
document.cookie.split(";").forEach((cookieStr) => {
const [name, v] = cookieStr.trim().split("=");
if (name == key) {
value = v;
}
});
} else {
value = uni.getStorageSync(key);
}
if (typeof value == "object") {
value = JSON.stringify(value);
} else if (value === false) {
value = "false";
} else if (value === true) {
value = "true";
} else if (!value) {
value = "";
}
uni.$emit("devTools_showEditDialog", {
title: `key:${key}`,
value,
});
uni.$on("devTools_editDialogClose", () => {
uni.$off("devTools_editDialogSaveSuccess");
uni.$off("devTools_editDialogClose");
});
uni.$on("devTools_editDialogSaveSuccess", (val) => {
uni.$off("devTools_editDialogSaveSuccess");
uni.$off("devTools_editDialogClose");
let oldValue = uni.getStorageSync(key);
if (oldValue === false || oldValue === true) {
if (val == "true" || val == "false") {
val = val == "true";
}
}
// #ifdef H5
if (that.storageType == "localStorage") {
uni.setStorageSync(key, val);
} else if (that.storageType == "sessionStorage") {
sessionStorage.setItem(key, val);
} else if (that.storageType == "cookie") {
key = encodeURIComponent(key);
val = encodeURIComponent(val);
let cookie =
`${key}=${val}; path=/; expires=` + new Date(new Date().getTime() + 86400 * 1000 * 365).toGMTString();
document.cookie = cookie;
}
// #endif
// #ifndef H5
uni.setStorageSync(key, val);
// #endif
uni.showToast({
icon: "success",
title: "保存成功",
});
setTimeout(() => {
that.getData();
}, 300);
});
},
});
}
uni.showActionSheet({
itemList: menu.map((x) => x.text),
success({ tapIndex }) {
menu[tapIndex].click();
},
});
},
},
};
</script>
<style lang="scss" scoped>
.storageList {
padding: 20rpx;
width: 750rpx;
}
.dataLoading {
width: 750rpx;
height: 400rpx;
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
.status {
font-size: 20rpx;
color: #888;
line-height: 20rpx;
}
}
.moreTools {
display: flex;
flex-direction: column;
.tips {
font-size: 20rpx;
color: #888;
margin-top: 20rpx;
}
.delBtn {
all: initial;
margin-top: 50rpx;
border-radius: 8rpx;
padding: 10rpx;
background-color: rgba(0, 0, 0, 0.02);
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
width: 375rpx;
&:active {
background-color: rgba(0, 0, 0, 0.1);
}
.delBtnText {
font-size: 20rpx;
line-height: 20rpx;
color: #f37b1d;
}
}
}
</style>

View File

@@ -0,0 +1,304 @@
<template>
<view>
<subTitleBar
:isOpen="isShowSetting"
@click="changeStatus('isShowSetting')"
title="DevTools扩展配置项"
/>
<template v-if="isShowSetting">
<!-- #ifdef APP-PLUS || H5 -->
<view class="settingItem">
<view class="settingHead">
<text class="settingTitle">页面自动注入Eruda调试工具</text>
<text class="settingSubtitle">(强大的节点选择等工具重启APP后生效)</text>
</view>
<switch
:checked="isInjectEruda"
@change="switchChange($event, 'isInjectEruda')"
color="#ff2d55"
/>
</view>
<view class="settingItem">
<view class="settingHead">
<text class="settingTitle">页面自动注入vConsole调试工具</text>
<text class="settingSubtitle">(腾讯开源的h5调试工具重启APP后生效)</text>
</view>
<switch
:checked="isInjectVConsole"
@change="switchChange($event, 'isInjectVConsole')"
color="#ff2d55"
/>
</view>
<!-- #endif -->
<!-- #ifdef MP-WEIXIN -->
<view class="settingItem">
<view class="settingHead">
<text class="settingTitle">小程序VConsole开关</text>
<text class="settingSubtitle">设置是否打开调试开关此开关对正式版也能生效</text>
</view>
<switch
:checked="isOpenMpDevTag"
@change="switchChange($event, 'isOpenMpDevTag')"
color="#ff2d55"
/>
</view>
<!-- #endif -->
</template>
<subTitleBar
:isOpen="isShowBtnPanel"
@click="changeStatus('isShowBtnPanel')"
title="常用工具"
/>
<view
v-if="isShowBtnPanel"
class="btnPanel"
>
<!-- #ifdef APP-PLUS || H5 -->
<view
class="btnItem btn-def"
@click="restart"
>
<text class="btnText">重启APP</text>
</view>
<!-- #endif -->
<view
class="btnItem btn-def"
@click="goPage"
>
<text class="btnText">跳转指定页面</text>
</view>
<view
class="btnItem btn-def"
@click="$emit('goOpenRequest')"
>
<text class="btnText">发起网络请求</text>
</view>
<view
class="btnItem btn-def"
@click="delLocalStorage"
>
<text class="btnText">清空localStorage缓存</text>
</view>
</view>
<subTitleBar
:isOpen="isShowDiyTools"
@click="changeStatus('isShowDiyTools')"
title="自定义Tools"
/>
<tools
v-if="isShowDiyTools"
ref="tools"
/>
</view>
</template>
<script>
import tools from "../../../tools.vue";
import subTitleBar from "../ui/subTitleBar.vue";
export default {
components: {
tools,
subTitleBar,
},
data() {
return {
/**
* 是否显示系统工具栏
*/
isShowSetting: false,
/**
* 是否自动注入Eruda
*/
isInjectEruda: uni.getStorageSync("devTools_isInjectEruda") == "yes",
/**
* 是否自动注入vConsole
*/
isInjectVConsole: uni.getStorageSync("devTools_isInjectVConsole") == "yes",
/**
* 是否显示 用户自定义tools
*/
isShowDiyTools: true,
/**
* 是否打开小程序调试工具
*/
isOpenMpDevTag: uni.getStorageSync("devTools_isOpenMpDevTag") == "yes",
/**
* 常用工具栏开关
*/
isShowBtnPanel: true,
};
},
methods: {
/**
* 更改选中状态
*/
changeStatus(key) {
this[key] = !this[key];
},
/**
* 开关选择器改变事件
*/
switchChange(e, key) {
let status = e.detail.value;
this[key] = status;
uni.setStorageSync("devTools_" + key, status ? "yes" : "no");
if (key == "isOpenMpDevTag") {
wx.setEnableDebug({
enableDebug: status,
});
}
},
/**
* 重启APP
*/
restart() {
// #ifdef APP-PLUS
plus.runtime.restart();
// #endif
// #ifdef H5
location.href = "/";
// #endif
},
/**
* 跳转指定页面
*/
goPage() {
uni.$emit("devTools_showRouteDialog");
},
/**
* 清空LocalStorage
*/
delLocalStorage() {
uni.showModal({
title: "操作确认",
content: "是否确认清空LocalStorage缓存",
success(res) {
if (res.confirm) {
// #ifdef APP-PLUS
let keys = plus.storage.getAllKeys();
for (let i = 0; i < keys.length; i++) {
const key = String(keys[i]);
if (key.indexOf("devTools_") == 0) {
continue;
}
uni.removeStorageSync(key);
}
// #endif
// #ifdef H5
for (let i = 0; i < localStorage.length; i++) {
let key = String(localStorage.key(i));
if (key.indexOf("devTools_") == 0) {
continue;
}
setTimeout(() => {
localStorage.removeItem(key);
}, i * 2 + 1);
}
// #endif
// #ifdef MP
let keyList = devCache.get("storage");
if (!keyList) keyList = [];
for (let i = 0; i < keyList.length; i++) {
const key = keyList[i];
if (key.indexOf("devTools_") == 0) {
continue;
}
uni.removeStorageSync(key);
}
// #endif
uni.showToast({
icon: "success",
title: "清空成功!",
});
}
},
});
},
},
};
</script>
<style lang="scss" scoped>
.padding-sm {
padding: 20rpx;
}
.settingItem:active {
background-color: rgba(0, 0, 0, 0.05);
}
.settingItem {
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
width: 750rpx;
padding: 15rpx 0;
.settingHead {
display: flex;
flex-direction: column;
margin-left: 20rpx;
.settingTitle {
font-size: 24rpx;
line-height: 28rpx;
color: #333;
}
.settingSubtitle {
margin-top: 4rpx;
font-size: 20rpx;
line-height: 26rpx;
color: #777;
}
}
}
.btnPanel {
display: flex;
flex-direction: row;
flex-wrap: wrap;
padding-left: 20rpx;
padding-right: 20rpx;
padding-top: 20rpx;
.btnItem {
margin-right: 20rpx;
margin-bottom: 20rpx;
border-radius: 10rpx;
min-width: 120rpx;
height: 60rpx;
border: 1px solid rgba(0, 0, 0, 0.1);
background-color: rgba(0, 0, 0, 0.04);
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
padding: 0 15rpx;
.btnText {
font-size: 20rpx;
line-height: 30rpx;
color: #333;
}
}
.btn-red:active {
background-color: rgba(255, 45, 85, 0.5);
}
.btn-red {
border: 1px solid rgba(255, 45, 85, 1);
background-color: rgba(255, 45, 85, 1);
.btnText {
color: #fff;
}
}
.btn-def:active {
background-color: rgba(0, 0, 0, 0.1);
}
.btn-def {
border: 1px solid rgba(0, 0, 0, 0.1);
background-color: rgba(0, 0, 0, 0.04);
.btnText {
color: #333;
}
}
}
</style>

View File

@@ -0,0 +1,196 @@
<template>
<view class="storageList">
<objectAnalysis
v-if="isLoaded"
:data="storageData"
:isOpenFirst="true"
:width="710"
:isDiyMenu="true"
@diyMenu="diyMenu"
ref="objectAnalysis"
/>
<view v-else class="dataLoading">
<text class="status">加载中</text>
</view>
<text v-if="isLoaded" class="tipsText"> 长按非对象类型的数据可编辑 </text>
</view>
</template>
<script>
import objectAnalysis from './objectAnalysis.vue'
export default {
components: {
objectAnalysis
},
props: {
/**
* 全局变量类型
*/
stateType: {
type: String,
default: 'vuex'
}
},
data() {
return {
/**
* 是否完成加载
*/
isLoaded: false,
/**
* 缓存里的数据
*/
storageData: {}
}
},
methods: {
/**
* 加载数据
*/
getData() {
let that = this
let data = {}
if (that.stateType == 'vuex') {
try {
data = JSON.parse(JSON.stringify(that.$store.state))
} catch (error) {}
} else if (that.stateType == 'pinia') {
try {
if (uni.Pinia) {
data = JSON.parse(JSON.stringify(uni.Pinia.getActivePinia().state.value))
} else if (that.$pinia) {
data = JSON.parse(JSON.stringify(that.$pinia.state.value))
}
} catch (error) {}
} else if (that.stateType == 'globalData') {
try {
data = JSON.parse(JSON.stringify(getApp().globalData))
} catch (error) {}
}
that.isLoaded = false
setTimeout(() => {
that.storageData = data
that.isLoaded = true
}, 500)
},
/**
* 自定义长按事件
*/
diyMenu({ item, index }) {
let that = this
let menu = [
{
text: `复制键(key)`,
click() {
uni.setClipboardData({
data: that.toString(item.k)
})
}
},
{
text: `复制值(value)`,
click() {
uni.setClipboardData({
data: that.toString(item.v)
})
}
}
]
if (typeof item.v != 'object') {
menu.push({
text: '编辑值',
click() {
let keyList = that.$refs.objectAnalysis.getFaKeyList(item.i)
let title = ''
if (keyList.length == 0) {
title = 'key'
} else {
keyList.map((x) => {
title = (title == '' ? '' : title + '.') + x
})
}
let isBool = typeof item.v == 'boolean'
if (isBool) {
item.v = item.v ? 'true' : 'false'
}
if (item.v === undefined || item.v === null) {
item.v = ''
}
uni.$emit('devTools_showEditDialog', {
title,
value: item.v
})
uni.$on('devTools_editDialogClose', () => {
uni.$off('devTools_editDialogSaveSuccess')
uni.$off('devTools_editDialogClose')
})
uni.$on('devTools_editDialogSaveSuccess', (val) => {
uni.$off('devTools_editDialogSaveSuccess')
uni.$off('devTools_editDialogClose')
if (isBool && (val == 'true' || val == 'false')) {
val = val == 'true'
}
let data
if (that.stateType == 'vuex') {
data = that.$store.state
} else if (that.stateType == 'pinia') {
if (uni.Pinia) {
data = uni.Pinia.getActivePinia().state.value
} else if (that.$pinia) {
data = that.$pinia.state.value
}
} else if (that.stateType == 'globalData') {
data = getApp().globalData
}
let lastKey = keyList.pop()
keyList.map((x) => {
data = data[x]
})
that.$set(data, lastKey, val)
that.getData()
})
}
})
}
uni.showActionSheet({
itemList: menu.map((x) => x.text),
success({ tapIndex }) {
menu[tapIndex].click()
}
})
},
getFaKeyList() {}
}
}
</script>
<style lang="scss" scoped>
.storageList {
padding: 20rpx;
width: 750rpx;
}
.dataLoading {
width: 750rpx;
height: 400rpx;
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
.status {
font-size: 20rpx;
color: #888;
line-height: 20rpx;
}
}
.tipsText {
font-size: 20rpx;
color: #8799a3;
margin-top: 40rpx;
}
</style>

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,91 @@
// #ifdef APP-PLUS
const animation = weex.requireModule('animation')
// #endif
export default {
methods: {
/**
* 显示面板
*/
panelShow() {
let that = this;
let sys = uni.getSystemInfoSync();
animation.transition(
that.$refs.mask,
{
styles: {
opacity: 1,
height: sys.windowHeight + 'px'
},
duration: 200, //ms
timingFunction: 'linear',
delay: 0 //ms
}
)
let height = Math.ceil(sys.windowHeight * 0.8);
animation.transition(
that.$refs.panel,
{
styles: {
opacity: 1,
transform: `transform: translate(0px,${height}px)`
},
duration: 1, //ms
timingFunction: 'linear',
delay: 0 //ms
},
(res) => {
animation.transition(
that.$refs.panel,
{
styles: {
transform: `transform: translate(0px,0px)`
},
duration: 200, //ms
timingFunction: 'linear',
delay: 0 //ms
}
)
}
)
},
/**
* 关闭面板
*/
panelHide() {
let that = this;
animation.transition(
that.$refs.mask,
{
styles: {
opacity: 0,
},
duration: 200, //ms
timingFunction: 'linear',
delay: 0 //ms
}
)
let height = uni.upx2px(1000);
animation.transition(
that.$refs.panel,
{
styles: {
transform: `transform: translate(0px,${height}px)`
},
duration: 200, //ms
timingFunction: 'linear',
delay: 0 //ms
},
() => {
uni.$emit("devTools_panelHideSuccess")
}
)
},
}
}

View File

@@ -0,0 +1,83 @@
export default {
data() {
return {
windowInfo: getWindowInfo(),
}
},
computed: {
/**
* 小程序和H5的标题
*/
navbarStyle() {
let windowInfo = getWindowInfo();
return {
statusBarHeightPx: windowInfo.system.statusBarHeight + 'px',
navbarHeightPx: windowInfo.navbar.bodyHeightPx + 'px',
}
},
},
methods: {
back() {
uni.navigateBack()
}
}
}
/**
* 获取当前窗口数据
*
*/
function getWindowInfo() {
let systemInfo = uni.getSystemInfoSync();
systemInfo.pixelRatio = Math.round(systemInfo.pixelRatio * 100) / 100;
let windowInfo = {
system: systemInfo,
capsule: {
bottom: 78,
height: 30,
left: 283,
right: 363,
top: 48,
width: 0,
},
navbar: {
heightPx: uni.upx2px(100) + systemInfo.statusBarHeight,
bodyHeightPx: uni.upx2px(100),
bodyWidthPx: systemInfo.windowWidth,
capsuleWidthPx: 0,
capsuleRightPx: 0,
},
height: systemInfo.windowHeight * (750 / systemInfo.windowWidth),
width: 750,
statusBarHeight: systemInfo.statusBarHeight * (750 / systemInfo.windowWidth),
safeBottom: systemInfo.windowHeight - systemInfo.safeArea.bottom,
safeBottomRpx: (systemInfo.windowHeight - systemInfo.safeArea.bottom) * (750 / systemInfo.windowWidth),
/**
* 原生端 底部导航栏高度
*/
footNavbarHeight: uni.upx2px(100) + (systemInfo.windowHeight - systemInfo.safeArea.bottom),
}
// #ifdef MP-QQ || MP-WEIXIN
let capsuleInfo = uni.getMenuButtonBoundingClientRect();
windowInfo.capsule = capsuleInfo;
let capsuleToStatusBarPx = capsuleInfo.top - systemInfo.statusBarHeight; //胶囊和状态栏之间的高度
windowInfo.navbar.bodyHeightPx = capsuleInfo.height + (capsuleToStatusBarPx * 2);
windowInfo.navbar.heightPx = windowInfo.navbar.bodyHeightPx + systemInfo.statusBarHeight;
let capsuleWidthPx = (systemInfo.windowWidth - capsuleInfo.right) * 2 + capsuleInfo.width;
windowInfo.navbar.bodyWidthPx = systemInfo.windowWidth - capsuleWidthPx;
windowInfo.navbar.capsuleWidthPx = capsuleWidthPx;
windowInfo.navbar.capsuleRightPx = capsuleWidthPx - (systemInfo.windowWidth - capsuleInfo.right);
// #endif
return windowInfo;
}

View File

@@ -0,0 +1,75 @@
<template>
<view
class="btnTabs"
v-if="list.length > 0"
>
<block v-for="(item, index) in list" :key="item.title">
<view
class="btnTabsItem"
:style="{
'background-color': '#f9f9f9',
}"
@click="$emit('indexChange', index)"
>
<text
class="tabsText"
:style="{
color: index == value ? '#ff2d55' : '#333333',
}"
>
{{ item.title }}
</text>
</view>
<view
v-if="index != list.length - 1"
:key="index"
class="splitLine"
></view>
</block>
</view>
</template>
<script>
export default {
props: {
/**
* 按钮列表
*/
list: {
type: Array,
default: () => [],
},
/**
* 当前选中的按钮索引
*/
value: {
type: Number,
default: 0,
},
},
};
</script>
<style lang="scss" scoped>
.btnTabs {
display: flex;
flex-direction: row;
border-radius: 8rpx;
overflow: hidden;
height: 40rpx;
border: 1px solid rgba(0, 0, 0, 0.05);
.btnTabsItem {
display: flex;
height: 40rpx;
padding: 0 8rpx;
.tabsText {
font-size: 20rpx;
line-height: 40rpx;
height: 40rpx;
}
}
.splitLine {
width: 1px;
height: 40rpx;
background-color: rgba(0, 0, 0, 0.05);
}
}
</style>

View File

@@ -0,0 +1,172 @@
<template>
<view
class="codeHisPicker"
v-if="isShow"
@click="close"
:style="{
height: height + 'px',
}"
>
<view
class="codeList"
@click.stop
>
<view class="head">
<view class="title">
<text class="titleText">历史运行代码</text>
</view>
<view class="subTitle">
<text class="subTitleText">(保留100条运行记录)</text>
</view>
</view>
<scroll-view
scroll-y
class="codeScroll"
>
<view
class="hisItem"
v-for="(item, index) in list"
:key="index"
@click="selectedRow(item)"
>
<text class="hisItemCode">
{{ item }}
</text>
</view>
<view style="height: 100rpx"></view>
</scroll-view>
</view>
</view>
</template>
<script>
export default {
data() {
return {
/**
* 是否展示
*/
isShow: false,
/**
* 筛选的列表
*/
list: [],
/**
* 默认选中的索引
*/
index: 0,
/**
* 选中的回调事件
*/
callback: null,
/**
* 屏幕高度
*/
height: uni.getSystemInfoSync().screenHeight,
};
},
methods: {
/**
* 展示弹窗
*/
show(list = []) {
let that = this;
that.index = 0;
that.list = list;
that.isShow = true;
return new Promise((yes) => {
that.callback = yes;
});
},
/**
* 选择改变事件
*/
pickerChange(e) {
that.callback = "";
console.log("e", e);
},
/**
* 关闭弹窗
*/
close() {
this.isShow = false;
},
/**
* 选择单行代码
*/
selectedRow(row) {
this.callback(row);
this.close();
},
},
};
</script>
<style lang="scss" scoped>
.codeHisPicker {
position: fixed;
bottom: 0;
left: 0;
width: 750rpx;
background-color: rgba(0, 0, 0, 0.3);
/* #ifndef APP-PLUS */
backdrop-filter: blur(1px);
/* #endif */
display: flex;
flex-direction: column-reverse;
z-index: 999;
.codeList {
width: 750rpx;
border-radius: 20rpx 20rpx 0 0;
background-color: #fff;
.head {
padding: 20rpx;
border-bottom: 1px solid rgba(0, 0, 0, 0.2);
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
.title {
.titleText {
font-size: 24rpx;
line-height: 28rpx;
color: #333;
}
}
.subTitle {
display: flex;
flex-direction: row;
align-items: center;
.subTitleText {
font-size: 20rpx;
line-height: 28rpx;
color: #777;
}
}
}
.codeScroll {
height: 750rpx;
width: 750rpx;
.hisItem {
width: 750rpx;
padding: 10rpx 20rpx;
border-bottom: 1px solid rgba(0, 0, 0, 0.1);
.hisItemCode {
font-size: 20rpx;
color: #333;
line-height: 26rpx;
overflow: hidden;
text-overflow: ellipsis;
/* #ifdef H5 */
display: -webkit-box;
-webkit-line-clamp: 3;
-webkit-box-orient: vertical;
/* #endif */
lines: 3;
}
}
.hisItem:active {
background-color: rgba(0, 0, 0, 0.03);
}
}
}
}
</style>

View File

@@ -0,0 +1,7 @@
<template>
<div><slot></slot></div>
</template>
<script>
export default {};
</script>
<style></style>

View File

@@ -0,0 +1,167 @@
<template>
<view>
<!-- #ifdef APP-PLUS -->
<swiper
:current="tabIndex"
:style="{
height: scrollHeight + 'px',
}"
@change="$emit('tabIndexChange', $event.detail.current)"
>
<swiper-item
v-for="(item, index) in tabList"
:key="index + 'tabList'"
>
<list
:style="{
height: scrollHeight + 'px',
}"
class="contentList"
show-scrollbar
:index="index"
:id="`contentList_${index}`"
:fixFreezing="true"
ref="mob_list"
>
<refresh
v-if="item.canRefreshing"
class="refreshView"
@refresh="$emit('refresh', index)"
@pullingdown="$emit('pullingdown', { event: $event, index })"
:display="item.isRefreshing ? 'show' : 'hide'"
>
<view class="content">
<template v-if="item.refreshType == 'waitPullUp'">
<text class="statusText">下拉刷新</text>
</template>
<template v-if="item.refreshType == 'waitRelease'">
<text class="statusText">松手刷新</text>
</template>
<template v-if="item.refreshType == 'refreshing'">
<text class="statusText">刷新中...</text>
</template>
<template v-if="item.refreshType == 'success'">
<text class="statusText">刷新成功</text>
</template>
<template v-if="item.refreshType == 'error'">
<text class="statusText">刷新失败</text>
</template>
</view>
</refresh>
<slot
:item="item"
:index="index"
></slot>
<cell ref="mob_list_end">
<view></view>
</cell>
</list>
</swiper-item>
</swiper>
<!-- #endif -->
<!-- #ifndef APP-PLUS -->
<scroll-view
scroll-y
:style="{
height: scrollHeight + 'px',
}"
:scroll-top="scrollTop"
>
<slot
:item="tabList[tabIndex]"
:index="tabIndex"
></slot>
</scroll-view>
<!-- #endif -->
</view>
</template>
<script>
export default {
props: {
/**
* 分类索引
*/
tabIndex: {
type: Number,
default: 0,
},
/**
* tab列表
*/
tabList: {
type: Array,
default: () => [],
},
/**
* 滚动高度
*/
scrollHeight: {
type: Number,
default: 1000,
},
},
data() {
return {
/**
* 滚动位置
*/
scrollTop: 0,
};
},
methods: {
/**
* 滚动到列表底部
*/
scrollToBottom() {
let that = this;
// #ifdef APP-PLUS
const dom = weex.requireModule("dom");
dom.scrollToElement(that.$refs.mob_list_end[that.tabIndex]);
// #endif
// #ifndef APP-PLUS
that.scrollTop = 999999999 + Math.random();
// #endif
},
},
};
</script>
<style lang="scss" scoped>
.contentList {
display: flex;
flex-direction: column;
align-items: center;
width: 750rpx;
.cell {
display: flex;
flex-direction: row;
justify-content: center;
margin-top: 20rpx;
width: 750rpx;
}
}
.refreshView {
background-color: #fff;
width: 750rpx;
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
position: absolute;
top: 0px;
left: 0px;
height: 100rpx;
.content {
height: 100rpx;
width: 750rpx;
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
}
}
.statusText {
color: #8799a3;
font-size: 24rpx;
margin-left: 10rpx;
}
</style>

View File

@@ -0,0 +1,100 @@
<template>
<view
class="subTitleBar"
@click.stop="click"
>
<view class="left">
<view class="titleLine"></view>
<text class="titleText">{{ title }}</text>
</view>
<view
v-if="showArrow"
class="right"
>
<image
v-if="isOpen"
src="@/devTools/page/static/fold.png"
class="arrow"
/>
<image
v-else
src="@/devTools/page/static/unfold.png"
class="arrow"
/>
</view>
</view>
</template>
<script>
export default {
emits: ['click'],
props: {
/**
* 标题名称
*/
title: {
type: String,
default: "标题",
},
/**
* 是否显示右侧箭头
*/
showArrow: {
type: Boolean,
default: true,
},
/**
* 是否为开启状态
*/
isOpen: {
type: Boolean,
default: false,
},
},
data() {
return {};
},
methods: {
click(){
this.$emit('click');
},
}
};
</script>
<style lang="scss" scoped>
.subTitleBar {
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
padding: 20rpx 0;
width: 750rpx;
&:active {
background-color: rgba(0, 0, 0, 0.05);
}
.left {
display: flex;
flex-direction: row;
align-items: center;
margin-left: 20rpx;
.titleLine {
width: 4rpx;
height: 24rpx;
border-radius: 4px;
background-color: #ff2d55;
}
.titleText {
color: #333;
margin-left: 10rpx;
font-size: 24rpx;
line-height: 24rpx;
}
}
.right {
margin-right: 20rpx;
.arrow {
width: 30rpx;
height: 30rpx;
}
}
}
</style>

View File

@@ -0,0 +1,77 @@
<template>
<view class="nvue">
<mainView ref="mainView" />
</view>
</template>
<script>
import mainView from "./components/main.vue";
export default {
components: {
mainView,
},
onLoad(options) {
let that = this;
that.$nextTick(() => {
that.$refs.mainView.pageOnLoad(options);
});
},
onBackPress(e) {
if (e.from == "navigateBack") {
return false;
}
this.$refs.mainView.hide();
return true;
},
};
</script>
<style lang="scss" scoped>
.nvue {
width: 750rpx;
/* #ifndef APP-PLUS */
height: 100vh;
/* #endif */
/* #ifdef APP-PLUS */
flex: 1;
/* #endif */
}
/* #ifdef H5 */
@media only screen and (pointer: fine) {
.showScrollbars {
::-webkit-scrollbar-thumb:horizontal {
/*水平滚动条的样式*/
width: 4px;
background-color: rgba(0, 0, 0, 0.1);
-webkit-border-radius: 6px;
}
::-webkit-scrollbar-track-piece {
background-color: #fff;
/*滚动条的背景颜色*/
-webkit-border-radius: 0;
/*滚动条的圆角宽度*/
}
::-webkit-scrollbar {
width: 10px;
/*滚动条的宽度*/
height: 5px;
/*滚动条的高度*/
display: block !important;
}
::-webkit-scrollbar-thumb:vertical {
display: none;
}
::-webkit-scrollbar-thumb:hover {
/*滚动条的hover样式*/
height: 50px;
background-color: rgba(0, 0, 0, 0.25);
-webkit-border-radius: 4px;
}
}
}
/* #endif */
</style>

Binary file not shown.

After

Width:  |  Height:  |  Size: 772 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 761 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 596 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 841 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 664 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 595 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 616 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 676 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 693 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 762 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 877 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 637 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 641 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 503 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 444 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 532 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 404 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 538 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 255 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 740 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 487 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 626 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 543 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 561 B

View File

@@ -0,0 +1,25 @@
<template>
<view class="tools">
<!-- 在这里可以开发自己的DIY工具 -->
<text style="margin-top: 50rpx;color: grey;font-size: 24rpx;">Empty</text>
</view>
</template>
<script>
export default {
data() {
return {};
},
methods: {
},
};
</script>
<style lang="scss" scoped>
.tools {
display: flex;
flex-direction: column;
align-items: center;
width: 750rpx;
}
</style>

9
uni_modules/UniDevTools/src/env.d.ts vendored Normal file
View File

@@ -0,0 +1,9 @@
/// <reference types="vite/client" />
declare module '*.vue' {
import { DefineComponent } from 'vue'
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/ban-types
const component: DefineComponent<{}, {}, any>
export default component
}

View File

@@ -0,0 +1,149 @@
// 这是nvue版本的colorview组件专用
var _tmColorView_w = 0
var _tmColorView_h = 0
var dpr = window.devicePixelRatio;
window.tmColorView_showdiv = function(w,h){
var ele = document.querySelector("#tmColorview");
var canvasdom = ele.querySelector("canvas")
ele.style.display = "block"
ele.style.width = w+'px'
ele.style.height = h+'px'
canvasdom.width = w
canvasdom.height = h
canvasdom.style.width = w+'px'
canvasdom.style.height = h+'px'
_tmColorView_w = w;
_tmColorView_h = h;
document.all.addEventListener('touchmove', function(evt) {
evt.preventDefault();
});
}
window.tmColorView_getCanvas = function(){
var ele = document.querySelector("#tmColorview");
var canvasdom = ele.querySelector("canvas");
var ctx = canvasdom.getContext("2d")
return ctx
}
window.tmColorView_renderColorHu = function(){
var ctx = tmColorView_getCanvas();
var dy = _tmColorView_h/3;
var barcolorWidth = 30
var x = 0
var gradient = ctx.createLinearGradient( barcolorWidth/2, 0, barcolorWidth/2,dy);
gradient.addColorStop(0, 'rgba(255,0,0,1)');
gradient.addColorStop(0.5, 'rgba(255,0,255,1)');
gradient.addColorStop(1, 'rgba(0,0,255,1)');
ctx.fillStyle = gradient;
ctx.fillRect(x, 0, barcolorWidth, dy);
if(ctx?.draw){
ctx?.draw()
}
var gradient2 = ctx.createLinearGradient( barcolorWidth/2, dy, barcolorWidth/2, dy*2);
gradient2.addColorStop(0, 'rgba(0,0,255,1)');
gradient2.addColorStop(0.5, 'rgba(0,255,255,1)');
gradient2.addColorStop(1, 'rgba(0,255,0,1)');
ctx.fillStyle = gradient2;
ctx.fillRect(x, dy, barcolorWidth, dy);
if(ctx?.draw){
ctx?.draw(true)
}
var gradient3 = ctx.createLinearGradient( barcolorWidth/2, dy*2, barcolorWidth/2,dy*3);
gradient3.addColorStop(0, 'rgba(0,255,0,1)');
gradient3.addColorStop(0.5, 'rgba(255,255,1,1)');
gradient3.addColorStop(1, 'rgba(255,0,0,1)');
ctx.fillStyle = gradient3;
ctx.fillRect(x, dy*2,barcolorWidth, dy);
// ctx.drawImage(colorimgUrl, 0, 0,_width.value,_height.value)
if(ctx?.draw){
ctx?.draw(true)
}
}
function hslaToRgba(scolor) {
var { h, s, l, a } = scolor;
h = h / 360;
s = s / 100;
l = l / 100;
var rgb = [];
if (s == 0) {
rgb = [Math.round(l * 255), Math.round(l * 255), Math.round(l * 255)];
} else {
var q = l >= 0.5 ? (l + s - l * s) : (l * (1 + s));
var p = 2 * l - q;
var tr = rgb[0] = h + 1 / 3;
var tg = rgb[1] = h;
var tb = rgb[2] = h - 1 / 3;
for (var i = 0; i < rgb.length; i++) {
var tc = rgb[i];
if (tc < 0) {
tc = tc + 1;
} else if (tc > 1) {
tc = tc - 1;
}
switch (true) {
case (tc < (1 / 6)):
tc = p + (q - p) * 6 * tc;
break;
case ((1 / 6) <= tc && tc < 0.5):
tc = q;
break;
case (0.5 <= tc && tc < (2 / 3)):
tc = p + (q - p) * (4 - 6 * tc);
break;
default:
tc = p;
break;
}
rgb[i] = Math.round(tc * 255);
}
}
return { r: rgb[0], g: rgb[1], b: rgb[2], a: a };
}
function rgbaToCss(sColor){
return `rgba(${sColor.r},${sColor.g},${sColor.b},${sColor.a})`;
}
window.tmColorView_renderRectFill = function(H){
if(!H) H=0;
H = Number(H)
let x = 40;
let dy = 2;
let w = _tmColorView_w;
var ctx = tmColorView_getCanvas();
for(let i=0;i<100;i++){
let gradient = ctx.createLinearGradient( x, i, w,i);
gradient.addColorStop(0, rgbaToCss(hslaToRgba({h:H,s:0,l:100-i,a:1})));
gradient.addColorStop(1, rgbaToCss(hslaToRgba({h:H,s:100,l:50-i/2,a:1})));
ctx.fillStyle = gradient;
ctx.fillRect(x, (i+1)*dy, w, (i+1)*dy);
if(ctx?.draw){
ctx?.draw(true)
}
}
}
window.tmColorView_getColor = function(x,y,active){
var ctx = tmColorView_getCanvas();
let arg = ctx.getImageData(Number(x),Number(y),1,1);
uni.postMessage({
data: {
action: 'tmColorView_getColor',
tmColor:JSON.stringify({
r:arg.data["0"],
g:arg.data["1"],
b:arg.data["2"],
a:1
}),
tmColorActive:active
}
})
}

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,256 @@
// 等待初始化完毕
document.addEventListener('UniAppJSBridgeReady', () => {
document.body.onclick = () =>
uni.postMessage({
data: {
action: 'onClick'
}
})
uni.postMessage({
data: {
action: 'onJSBridgeReady'
}
})
})
let options
let medias = []
/**
* @description 获取标签的所有属性
* @param {Element} ele
*/
function getAttrs (ele) {
const attrs = Object.create(null)
for (let i = ele.attributes.length; i--;) {
attrs[ele.attributes[i].name] = ele.attributes[i].value
}
return attrs
}
/**
* @description 图片加载出错
*/
function onImgError () {
if (options[1]) {
this.src = options[1]
this.onerror = null
}
// 取消监听点击
this.onclick = null
this.ontouchstart = null
uni.postMessage({
data: {
action: 'onError',
source: 'img',
attrs: getAttrs(this)
}
})
}
/**
* @description 检查是否所有图片加载完毕
*/
function checkReady () {
window.unloadimgs -= 1
if (window.unloadimgs === 0) {
// 所有图片加载完毕
uni.postMessage({
data: {
action: 'onReady'
}
})
}
}
/**
* @description 创建 dom 结构
* @param {object[]} nodes 节点数组
* @param {Element} parent 父节点
* @param {string} namespace 命名空间
*/
function createDom (nodes, parent, namespace) {
for (let i = 0; i < nodes.length; i++) {
const node = nodes[i]
let ele
if (!node.type || node.type === 'node') {
let name = node.name
// svg 需要设置 namespace
if (name === 'svg') {
namespace = 'http://www.w3.org/2000/svg'
}
if (name === 'html' || name === 'body') {
name = 'div'
}
// 创建标签
if (!namespace) {
ele = document.createElement(name)
} else {
ele = document.createElementNS(namespace, name)
}
// 设置属性
for (const item in node.attrs) {
ele.setAttribute(item, node.attrs[item])
}
// 递归创建子节点
if (node.children) {
createDom(node.children, ele, namespace)
}
// 处理图片
if (name === 'img') {
window.unloadimgs += 1
ele.onload = checkReady
ele.onerror = checkReady
if (!ele.src && ele.getAttribute('data-src')) {
ele.src = ele.getAttribute('data-src')
}
if (!node.attrs.ignore) {
// 监听图片点击事件
ele.onclick = function (e) {
e.stopPropagation()
uni.postMessage({
data: {
action: 'onImgTap',
attrs: getAttrs(this)
}
})
}
}
if (options[2]) {
const image = new Image()
image.src = ele.src
ele.src = options[2]
image.onload = function () {
ele.src = this.src
}
image.onerror = function () {
ele.onerror()
}
}
ele.onerror = onImgError
} else if (name === 'a') {
// 处理链接
ele.addEventListener('click', function (e) {
e.stopPropagation()
e.preventDefault() // 阻止默认跳转
const href = this.getAttribute('href')
let offset
if (href && href[0] === '#') {
offset = (document.getElementById(href.substr(1)) || {}).offsetTop
}
uni.postMessage({
data: {
action: 'onLinkTap',
attrs: getAttrs(this),
offset
}
})
}, true)
} else if (name === 'video' || name === 'audio') {
// 处理音视频
medias.push(ele)
if (!node.attrs.autoplay && !node.attrs.controls) {
ele.setAttribute('controls', 'true')
}
ele.onplay = function () {
uni.postMessage({
data: {
action: 'onPlay'
}
})
if (options[3]) {
for (let i = 0; i < medias.length; i++) {
if (medias[i] !== this) {
medias[i].pause()
}
}
}
}
ele.onerror = function () {
uni.postMessage({
data: {
action: 'onError',
source: name,
attrs: getAttrs(this)
}
})
}
} else if (name === 'table' && options[4] && !ele.style.cssText.includes('inline')) {
// 处理表格
const div = document.createElement('div')
div.style.overflow = 'auto'
div.appendChild(ele)
ele = div
} else if (name === 'svg') {
namespace = undefined
}
} else {
ele = document.createTextNode(node.text.replace(/&amp;/g, '&'))
}
parent.appendChild(ele)
}
}
// 设置 html 内容
window.setContent = function (nodes, opts, append) {
const ele = document.getElementById('content')
document.body.style.overflow = "scroll"
// 容器样式
if (opts[0]) {
document.body.style.cssText = opts[0]
}
// 长按复制
if (!opts[5]) {
ele.style.userSelect = 'none'
}
if (!append) {
ele.innerHTML = '' // 不追加则先清空
medias = []
}
options = opts
window.unloadimgs = 0
const fragment = document.createDocumentFragment()
createDom(nodes, fragment)
ele.appendChild(fragment)
// 触发事件
let height = ele.scrollHeight
uni.postMessage({
data: {
action: 'onLoad',
height
}
})
if (!window.unloadimgs) {
uni.postMessage({
data: {
action: 'onReady',
height
}
})
}
clearInterval(window.timer)
window.timer = setInterval(() => {
if (ele.scrollHeight !== height) {
height = ele.scrollHeight
uni.postMessage({
data: {
action: 'onHeightChange',
height: height
}
})
}
}, 350)
}
// 回收计时器
window.onunload = function () {
clearInterval(window.timer)
}

View File

@@ -0,0 +1,55 @@
var chartDom = null;
window.mychart = null;
window.echart_createDom = function (w, h) {
w = Number(w);
h = Number(h);
chartDom = document.createElement("div");
chartDom.style.width = w + 'px';
chartDom.style.height = h + 'px';
chartDom.style.display = 'block';
document.body.appendChild(chartDom);
document.all.addEventListener('touchmove', function(evt) {
evt.preventDefault();
});
return chartDom;
}
window.echart_createChart = function (opts) {
if (!opts) {
opts = {}
}
window.mychart = echarts.init(chartDom, undefined, opts)
return window.mychart;
}
/**
* 将传入echart配置中的函数从字符类型还原为函数类型
* @param {*} opt
*/
function recoverEchartCBFn(opt) {
for (let key in opt) {
if (opt.hasOwnProperty(key)) {
const curr = opt[key]
if (typeof curr === 'string' && curr.includes('echartCbFn')) {
const fnObj = JSON.parse(curr);
tempFn = new Function(`return (${fnObj.fnString})`)();
opt[key] = function(...params) {
try {
return tempFn(...params)
} catch (error) {
return error.toString()
}
}
}
if (typeof curr === "object" && curr !== null) {
recoverEchartCBFn(curr);
}
}
}
}
window.echart_setOption= function (opts,ops) {
recoverEchartCBFn(opts)
window.mychart.setOption(opts,ops)
}

File diff suppressed because one or more lines are too long

Some files were not shown because too many files have changed in this diff Show More