Compare commits
1 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
faeeef8655 |
@ -48,4 +48,26 @@ export default defineUniPages({
|
||||
},
|
||||
],
|
||||
},
|
||||
subPackages: [
|
||||
{
|
||||
root: 'devTools/page',
|
||||
pages: [
|
||||
{
|
||||
path: 'index',
|
||||
style: {
|
||||
navigationStyle: 'custom',
|
||||
// #ifdef APP-PLUS
|
||||
softinputMode: 'adjustResize',
|
||||
// backgroundColor: 'transparent',
|
||||
animationDuration: 1,
|
||||
animationType: 'none',
|
||||
popGesture: 'none',
|
||||
bounce: 'none',
|
||||
titleNView: false,
|
||||
// #endif
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
})
|
||||
|
9
src/devTools/READEME.MD
Normal file
@ -0,0 +1,9 @@
|
||||
# UniDevTools - 调试工具
|
||||
|
||||
支持 Vue2+Vue3 的跨平台调试工具
|
||||
|
||||
> 文档&安装教程 https://dev.api0.cn/
|
||||
|
||||
当前版本:v3.8
|
||||
|
||||
更新日期:2025.5.5
|
69
src/devTools/config.js
Normal file
@ -0,0 +1,69 @@
|
||||
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
|
156
src/devTools/core/components/mpDevBubble.vue
Normal 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>
|
142
src/devTools/core/libs/createH5Bubble.js
Normal 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
|
131
src/devTools/core/libs/devCache.js
Normal file
@ -0,0 +1,131 @@
|
||||
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,
|
||||
)
|
||||
},
|
||||
}
|
184
src/devTools/core/libs/devOptions.js
Normal file
@ -0,0 +1,184 @@
|
||||
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.81,
|
||||
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 {}
|
||||
}
|
||||
}
|
118
src/devTools/core/libs/drawView.js
Normal file
@ -0,0 +1,118 @@
|
||||
/**
|
||||
* 绘制调试工具
|
||||
*/
|
||||
|
||||
/**
|
||||
* 入口文件
|
||||
*/
|
||||
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
|
60
src/devTools/core/libs/errorReport.js
Normal file
@ -0,0 +1,60 @@
|
||||
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
|
333
src/devTools/core/libs/jsonCompress.js
Normal file
@ -0,0 +1,333 @@
|
||||
/**
|
||||
* json压缩工具
|
||||
*/
|
||||
export default {
|
||||
/**
|
||||
* 压缩js对象成json字符串,并控制json字节大小,多余部分裁剪
|
||||
*/
|
||||
compressObject(obj = {}, maxSize = 1024 * 10.24) {
|
||||
try {
|
||||
if (obj === undefined || obj === null) return obj
|
||||
if (typeof obj == 'string') {
|
||||
return this.truncateStrBySize(obj, maxSize)
|
||||
}
|
||||
if (typeof obj == 'number') {
|
||||
return obj
|
||||
}
|
||||
|
||||
let t = new Date().getTime()
|
||||
|
||||
const type = typeof obj
|
||||
|
||||
if (type === 'symbol') {
|
||||
obj = 'Symbol->' + obj.toString()
|
||||
} else if (type === 'bigint') {
|
||||
obj = 'bigint->' + obj.toString()
|
||||
} else if (typeof Error != 'undefined' && obj instanceof Error) {
|
||||
obj = `Error->${obj.name}\n${obj.message}\n${obj.stack}`
|
||||
} else if (typeof Date != 'undefined' && obj instanceof Date) {
|
||||
obj = 'Date->' + obj.toISOString()
|
||||
} else if (typeof obj == 'function') {
|
||||
obj = 'Function->' + obj.toString()
|
||||
} else if (typeof RegExp != 'undefined' && obj instanceof RegExp) {
|
||||
obj = 'RegExp->' + obj.toString()
|
||||
} else if (typeof Map != 'undefined' && obj instanceof Map) {
|
||||
obj = `Map->(${obj.size}) { ${Array.from(obj.entries())
|
||||
.map(([key, value]) => `${convertToString(key)} => ${convertToString(value)}`)
|
||||
.join(', ')} }`
|
||||
} else if (typeof Set != 'undefined' && obj instanceof Set) {
|
||||
obj = `Set->(${obj.size}) { ${Array.from(obj.values())
|
||||
.map((value) => convertToString(value))
|
||||
.join(', ')} }`
|
||||
} else if (typeof Blob != 'undefined' && obj instanceof Blob) {
|
||||
obj = `Blob->{ size: ${obj.size}, type: ${obj.type} }`
|
||||
} else if (typeof File != 'undefined' && obj instanceof File) {
|
||||
obj = `File->{ name: "${obj.name}", size: ${obj.size}, type: ${obj.type}, lastModified: ${new Date(obj.lastModified).toISOString()} }`
|
||||
} else if (typeof URL != 'undefined' && obj instanceof URL) {
|
||||
obj = `URL->{ href: "${obj.href}", protocol: "${obj.protocol}", host: "${obj.host}", pathname: "${obj.pathname}", search: "${obj.search}", hash: "${obj.hash}" }`
|
||||
} else if (typeof FormData != 'undefined' && obj instanceof FormData) {
|
||||
const entries = []
|
||||
obj.forEach((key, item) => {
|
||||
entries.push(key)
|
||||
})
|
||||
obj = `FormData->{ ${entries.join(', ')} }`
|
||||
} else if (typeof Location != 'undefined' && obj instanceof Location) {
|
||||
obj = `Location->{ href: "${obj.href}", protocol: "${obj.protocol}", host: "${obj.host}", pathname: "${obj.pathname}", search: "${obj.search}", hash: "${obj.hash}" }`
|
||||
} else if (typeof Document != 'undefined' && obj instanceof Document) {
|
||||
obj = `Document->{ title: "${obj.title}", URL: "${obj.URL}" }`
|
||||
} else if (typeof Window !== 'undefined' && obj instanceof Window) {
|
||||
obj = `Window->{ location: ${this.compressObject(obj.location)}, document: ${this.compressObject(obj.document)} }`
|
||||
} else if (typeof Element != 'undefined' && obj instanceof Element) {
|
||||
obj = `Element->{ tagName: "${obj.tagName}", id: "${obj.id}", class: "${obj.className}" }`
|
||||
} else if (typeof HTMLCanvasElement != 'undefined' && obj instanceof HTMLCanvasElement) {
|
||||
obj = `Canvas->{ width: ${obj.width}, height: ${obj.height} }`
|
||||
} else if (typeof HTMLAudioElement != 'undefined' && obj instanceof HTMLAudioElement) {
|
||||
obj = `Audio->{ src: "${obj.src}", duration: ${obj.duration} }`
|
||||
} else if (typeof HTMLVideoElement != 'undefined' && obj instanceof HTMLVideoElement) {
|
||||
obj = `Video->{ src: "${obj.src}", width: ${obj.videoWidth}, height: ${obj.videoHeight}, duration: ${obj.duration} }`
|
||||
} else if (typeof Storage != 'undefined' && obj instanceof Storage) {
|
||||
obj = `Storage->{ length: ${obj.length} }`
|
||||
} else if (
|
||||
typeof Worker != 'undefined' &&
|
||||
typeof ServiceWorker != 'undefined' &&
|
||||
typeof SharedWorker != 'undefined' &&
|
||||
(obj instanceof Worker || obj instanceof ServiceWorker || obj instanceof SharedWorker)
|
||||
) {
|
||||
obj = `Worker->${obj.constructor.name} { scriptURL: "${obj.scriptURL}" }`
|
||||
} else if (typeof WebSocket != 'undefined' && obj instanceof WebSocket) {
|
||||
obj = `WebSocket->{ url: "${obj.url}", readyState: ${obj.readyState} }`
|
||||
} else if (typeof XMLHttpRequest != 'undefined' && obj instanceof XMLHttpRequest) {
|
||||
obj = `XMLHttpRequest->{ readyState: ${obj.readyState}, status: ${obj.status} }`
|
||||
} else if (typeof EventSource != 'undefined' && obj instanceof EventSource) {
|
||||
obj = `EventSource->{ url: "${obj.url}", readyState: ${obj.readyState} }`
|
||||
} else if (typeof MediaStream != 'undefined' && obj instanceof MediaStream) {
|
||||
obj = `MediaStream->{ id: "${obj.id}", active: ${obj.active} }`
|
||||
} else if (typeof RTCPeerConnection != 'undefined' && obj instanceof RTCPeerConnection) {
|
||||
obj = `RTCPeerConnection->{ connectionState: "${obj.connectionState}" }`
|
||||
} else if (typeof AudioContext != 'undefined' && obj instanceof AudioContext) {
|
||||
obj = `AudioContext->{ state: "${obj.state}" }`
|
||||
} else if (typeof Element != 'undefined' && obj instanceof Element) {
|
||||
obj = `Element->{ tagName: "${obj.tagName}", id: "${obj.id}", class: "${obj.className}" }`
|
||||
} else if (typeof HTMLCanvasElement != 'undefined' && obj instanceof HTMLCanvasElement) {
|
||||
obj = `Canvas->{ width: ${obj.width}, height: ${obj.height} }`
|
||||
} else if (typeof HTMLAudioElement != 'undefined' && obj instanceof HTMLAudioElement) {
|
||||
obj = `Audio->{ src: "${obj.src}", duration: ${obj.duration} }`
|
||||
} else if (typeof HTMLVideoElement != 'undefined' && obj instanceof HTMLVideoElement) {
|
||||
obj = `Video->{ src: "${obj.src}", width: ${obj.videoWidth}, height: ${obj.videoHeight}, duration: ${obj.duration} }`
|
||||
} else if (typeof Geolocation != 'undefined' && obj instanceof Geolocation) {
|
||||
obj = `Geolocation->{ }`
|
||||
} else if (typeof Performance != 'undefined' && obj instanceof Performance) {
|
||||
obj = `Performance->{ now: ${obj.now()} }`
|
||||
} else if (typeof Event != 'undefined' && obj instanceof Event) {
|
||||
obj = `Event->{ type: "${obj.type}", target: "${obj.target}" }`
|
||||
}
|
||||
|
||||
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 ''
|
||||
}
|
||||
},
|
||||
}
|
63
src/devTools/core/libs/logReport.js
Normal file
@ -0,0 +1,63 @@
|
||||
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
|
124
src/devTools/core/libs/pageLinkList.js
Normal file
@ -0,0 +1,124 @@
|
||||
import devCache from './devCache'
|
||||
|
||||
export default {
|
||||
pageRouteMap: [],
|
||||
pageRouteKeyMap: {},
|
||||
/**
|
||||
* 安装路径分析插件
|
||||
*/
|
||||
install() {
|
||||
let allRoutes = this.getAllRoutes()
|
||||
|
||||
let pageRouteKeyMap = devCache.get('pageRouteKeyMap')
|
||||
if (!pageRouteKeyMap || typeof pageRouteKeyMap != 'object') {
|
||||
pageRouteKeyMap = {}
|
||||
}
|
||||
|
||||
let lastNo = 0
|
||||
Object.keys(pageRouteKeyMap).forEach((key) => {
|
||||
let item = Number(pageRouteKeyMap[key])
|
||||
if (item > lastNo) {
|
||||
lastNo = item
|
||||
}
|
||||
})
|
||||
|
||||
allRoutes.forEach((item) => {
|
||||
if (!pageRouteKeyMap[item.path]) {
|
||||
pageRouteKeyMap[item.path] = lastNo + 1
|
||||
lastNo = lastNo + 1
|
||||
}
|
||||
})
|
||||
|
||||
devCache.set('pageRouteKeyMap', pageRouteKeyMap)
|
||||
this.pageRouteKeyMap = pageRouteKeyMap
|
||||
|
||||
let pageRouteMap = devCache.get('pageRouteMap')
|
||||
if (!pageRouteMap || typeof pageRouteMap != 'object') {
|
||||
pageRouteMap = {}
|
||||
}
|
||||
|
||||
Object.keys(pageRouteMap).forEach((key) => {
|
||||
try {
|
||||
let n = Number(pageRouteMap[key])
|
||||
if (!Number.isInteger(n) || n < 0) {
|
||||
pageRouteMap[key] = 1
|
||||
}
|
||||
} catch (error) {}
|
||||
})
|
||||
this.pageRouteMap = pageRouteMap
|
||||
|
||||
this.saveData()
|
||||
},
|
||||
/**
|
||||
* 获取APP注册的所有路由
|
||||
* @returns {{path: string}[]} 返回路由列表
|
||||
*/
|
||||
getAllRoutes() {
|
||||
let pages = []
|
||||
// #ifdef H5 || APP-PLUS
|
||||
try {
|
||||
__uniRoutes.map((item) => {
|
||||
let path = item.alias ? item.alias : item.path
|
||||
pages.push({ path })
|
||||
})
|
||||
} catch (error) {
|
||||
pages = []
|
||||
}
|
||||
// #endif
|
||||
// #ifdef MP-WEIXIN
|
||||
try {
|
||||
let wxPages = __wxConfig.pages
|
||||
wxPages.map((item) => {
|
||||
pages.push({
|
||||
path: '/' + item,
|
||||
})
|
||||
})
|
||||
} catch (error) {
|
||||
pages = []
|
||||
}
|
||||
// #endif
|
||||
return pages
|
||||
},
|
||||
/**
|
||||
* 写入路由列表
|
||||
*/
|
||||
pushPageRouteMap(list = []) {
|
||||
if (!list || list.length == 0) {
|
||||
list = getCurrentPages()
|
||||
}
|
||||
let key = ''
|
||||
list.forEach((item) => {
|
||||
let path = item.route.indexOf('/') == 0 ? item.route : '/' + item.route
|
||||
let keyItem = this.pageRouteKeyMap[path]
|
||||
if (!keyItem) {
|
||||
keyItem = path
|
||||
}
|
||||
if (key == '') {
|
||||
key = keyItem + ''
|
||||
} else {
|
||||
key = key + ',' + keyItem
|
||||
}
|
||||
})
|
||||
|
||||
if (this.pageRouteMap[key]) {
|
||||
this.pageRouteMap[key] = this.pageRouteMap[key] + 1
|
||||
} else {
|
||||
this.pageRouteMap[key] = 1
|
||||
}
|
||||
},
|
||||
/**
|
||||
* 保存路由缓存
|
||||
*/
|
||||
saveData() {
|
||||
let that = this
|
||||
setTimeout(
|
||||
() => {
|
||||
devCache.set('pageRouteMap', that.pageRouteMap)
|
||||
setTimeout(() => {
|
||||
that.saveData()
|
||||
}, 500)
|
||||
},
|
||||
Math.round(Math.random() * 3 * 1000) + 2000,
|
||||
)
|
||||
},
|
||||
}
|
71
src/devTools/core/libs/pageStatistics.js
Normal file
@ -0,0 +1,71 @@
|
||||
/**
|
||||
* !页面统计:访问次数、停留时长
|
||||
*/
|
||||
|
||||
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
|
94
src/devTools/core/libs/timeFormat.js
Normal 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)
|
||||
}
|
||||
}
|
413
src/devTools/core/proxy/console.js
Normal file
@ -0,0 +1,413 @@
|
||||
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)
|
||||
}
|
||||
},
|
||||
}
|
33
src/devTools/core/proxy/index.js
Normal file
@ -0,0 +1,33 @@
|
||||
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)
|
||||
}
|
||||
}
|
246
src/devTools/core/proxy/request.js
Normal file
@ -0,0 +1,246 @@
|
||||
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) {
|
||||
return new Promise(async (yes, err) => {
|
||||
//! 延迟请求返回,模拟弱网环境
|
||||
let speedLimit = uni.getStorageSync('devtools_uniResLimitType')
|
||||
if (speedLimit) {
|
||||
let delayDuration = {
|
||||
'2g': [30, 60],
|
||||
'3g-': [10, 30],
|
||||
'3g': [3, 10],
|
||||
'4g': [0.5, 3],
|
||||
}
|
||||
let sleepParam = delayDuration[speedLimit]
|
||||
if (sleepParam) {
|
||||
let sleepTime = rNum(sleepParam[0], sleepParam[1])
|
||||
await sleep(sleepTime * 1000)
|
||||
response.errMsg = response.errMsg + ` | [devtools模拟弱网延迟:${sleepTime}s]`
|
||||
}
|
||||
}
|
||||
|
||||
// ! 随机响应失败概率
|
||||
let resTimeout = uni.getStorageSync('devtools_uniResTimeout')
|
||||
let isFail = false
|
||||
if (resTimeout && resTimeout > 1) {
|
||||
let targetPro = Number(resTimeout)
|
||||
let randPro = rNum(0, 100)
|
||||
if (randPro < targetPro) {
|
||||
// 命中失败
|
||||
response.statusCode = rNum(400, 600) //生成随机400 ~ 600之间的状态码
|
||||
response.errMsg =
|
||||
response.errMsg +
|
||||
` | [devtools随机超时报错,当前命中的概率阶层为:${targetPro}%,生成的随机数为:${randPro}]`
|
||||
response.data = '[devTools]模拟请求失败结果!'
|
||||
isFail = true
|
||||
}
|
||||
}
|
||||
|
||||
// ! 记录响应内容
|
||||
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)
|
||||
}
|
||||
|
||||
if (isFail) {
|
||||
err(response.data)
|
||||
} else {
|
||||
yes(response)
|
||||
}
|
||||
})
|
||||
},
|
||||
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 ''
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* 随机生成n~m的数,支持两位小数
|
||||
*/
|
||||
function rNum(n, m) {
|
||||
return Number((Math.random() * (m - n) + n).toFixed(2))
|
||||
}
|
||||
|
||||
/**
|
||||
* 休眠指定时长
|
||||
*/
|
||||
function sleep(t) {
|
||||
return new Promise((y) => {
|
||||
setTimeout(y, t)
|
||||
})
|
||||
}
|
96
src/devTools/core/proxy/storage.js
Normal file
@ -0,0 +1,96 @@
|
||||
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)
|
||||
}
|
||||
},
|
||||
}
|
160
src/devTools/core/proxy/uniBus.js
Normal file
@ -0,0 +1,160 @@
|
||||
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)
|
||||
},
|
||||
}
|
195
src/devTools/core/proxy/uniListen.js
Normal file
@ -0,0 +1,195 @@
|
||||
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)
|
||||
}
|
||||
})
|
||||
},
|
||||
}
|
160
src/devTools/core/proxy/vueMixin.js
Normal file
@ -0,0 +1,160 @@
|
||||
import devOptions from '../libs/devOptions'
|
||||
import logReport from '../libs/logReport'
|
||||
import pageLinkList from '../libs/pageLinkList'
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
pageLinkList.pushPageRouteMap(pages)
|
||||
} 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)
|
||||
}
|
||||
},
|
||||
}
|
177
src/devTools/index.js
Normal 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'
|
||||
import pageLinkList from './core/libs/pageLinkList'
|
||||
|
||||
/**
|
||||
* @type {Vue}
|
||||
*/
|
||||
let that
|
||||
|
||||
const devTools = {
|
||||
options: null,
|
||||
/**
|
||||
* 挂载安装APP页面
|
||||
*/
|
||||
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)
|
||||
})
|
||||
|
||||
// ! 页面路由列表
|
||||
pageLinkList.install()
|
||||
} 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
|
914
src/devTools/page/components/bottomTools.vue
Normal file
@ -0,0 +1,914 @@
|
||||
<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>
|
||||
<MenuBtn
|
||||
:list="networkFilterType"
|
||||
:value="networkTypeListIndex"
|
||||
@indexChange="networkTypeIndexChange"
|
||||
title="筛选:"
|
||||
/>
|
||||
<view class="mr"></view>
|
||||
<RequestSpeedLimit />
|
||||
<view class="mr"></view>
|
||||
<RequestTimeoutMock />
|
||||
</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'
|
||||
import MenuBtn from './ui/menuBtn.vue'
|
||||
import RequestSpeedLimit from './ui/requestSpeedLimit.vue'
|
||||
import RequestTimeoutMock from './ui/requestTimeoutMock.vue'
|
||||
export default {
|
||||
components: {
|
||||
btnTabs,
|
||||
codeHisPicker,
|
||||
MenuBtn,
|
||||
RequestSpeedLimit,
|
||||
RequestTimeoutMock,
|
||||
},
|
||||
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: '', msg: '不使用筛选' },
|
||||
{ title: '请求失败', type: '请求失败', msg: '仅展示请求失败的记录' },
|
||||
{ title: '1s+', type: '1s+', msg: '仅展示响应超过1秒的记录' },
|
||||
{ title: '5s+', type: '5s+', msg: '仅展示响应超过5秒的记录' },
|
||||
{ title: '10s+', type: '10s+', msg: '仅展示响应超过10秒的记录' },
|
||||
{ title: '500KB+', type: '500KB+', msg: '仅展示响应内容超过500KB的记录' },
|
||||
{ title: '1MB+', type: '1MB+', msg: '仅展示响应内容超过1MB的记录' },
|
||||
{ title: 'get', type: 'get', msg: '仅展示get请求' },
|
||||
{ title: 'post', type: 'post', msg: '仅展示post请求' },
|
||||
{ title: 'other', type: 'other', msg: '除了get和post的其他请求' },
|
||||
],
|
||||
/**
|
||||
* 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;
|
||||
/* #ifndef APP-PLUS */
|
||||
white-space: nowrap;
|
||||
/* #endif */
|
||||
.dirScrollItem {
|
||||
display: 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;
|
||||
line-height: 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
.menuBtn {
|
||||
}
|
||||
</style>
|
185
src/devTools/page/components/dialog/addStorage.vue
Normal file
@ -0,0 +1,185 @@
|
||||
<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>
|
356
src/devTools/page/components/dialog/createDir.vue
Normal file
@ -0,0 +1,356 @@
|
||||
<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>
|
202
src/devTools/page/components/dialog/dayOnlinePageList.vue
Normal file
@ -0,0 +1,202 @@
|
||||
<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>
|
178
src/devTools/page/components/dialog/editDialog.vue
Normal file
@ -0,0 +1,178 @@
|
||||
<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;
|
||||
/* #ifndef APP-PLUS */
|
||||
max-width: 600rpx;
|
||||
word-wrap: break-word;
|
||||
/* #endif */
|
||||
/* #ifdef APP-PLUS */
|
||||
width: 600rpx;
|
||||
/* #endif */
|
||||
}
|
||||
.textarea {
|
||||
margin-top: 20rpx;
|
||||
margin-bottom: 20rpx;
|
||||
width: 640rpx;
|
||||
/* #ifndef APP-PLUS */
|
||||
min-height: 200rpx;
|
||||
max-height: 750rpx;
|
||||
/* #endif */
|
||||
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>
|
169
src/devTools/page/components/dialog/routeDialog.vue
Normal file
@ -0,0 +1,169 @@
|
||||
<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;
|
||||
/* #ifndef APP-PLUS */
|
||||
word-wrap: break-word;
|
||||
max-width: 600rpx;
|
||||
/* #endif */
|
||||
/* #ifdef APP-PLUS */
|
||||
width: 600rpx;
|
||||
/* #endif */
|
||||
}
|
||||
.textarea {
|
||||
margin-top: 20rpx;
|
||||
margin-bottom: 20rpx;
|
||||
width: 640rpx;
|
||||
/* #ifndef APP-PLUS */
|
||||
min-height: 200rpx;
|
||||
max-height: 750rpx;
|
||||
/* #endif */
|
||||
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>
|
443
src/devTools/page/components/dialog/sendRequest.vue
Normal file
@ -0,0 +1,443 @@
|
||||
<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解析失败!')
|
||||
}
|
||||
}
|
||||
|
||||
header['Devtoolssend'] = 1
|
||||
|
||||
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>
|
361
src/devTools/page/components/dialog/textFileEditDialog.vue
Normal file
@ -0,0 +1,361 @@
|
||||
<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>
|
77
src/devTools/page/components/libs/appDelDir.js
Normal file
@ -0,0 +1,77 @@
|
||||
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,
|
||||
)
|
||||
})
|
||||
}
|
216
src/devTools/page/components/libs/dirReader.js
Normal file
@ -0,0 +1,216 @@
|
||||
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 ''
|
||||
}
|
||||
}
|
15
src/devTools/page/components/libs/fileSize.js
Normal 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]
|
||||
},
|
||||
}
|
132
src/devTools/page/components/libs/getRuntimeInfo.js
Normal file
@ -0,0 +1,132 @@
|
||||
/**
|
||||
* 获取当前运行时环境信息
|
||||
*/
|
||||
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 获取失败!' })
|
||||
},
|
||||
})
|
||||
})
|
||||
}
|
271
src/devTools/page/components/listItem/consoleItem.vue
Normal file
@ -0,0 +1,271 @@
|
||||
<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;
|
||||
/* #ifndef APP-PLUS */
|
||||
min-width: 90rpx;
|
||||
/* #endif */
|
||||
}
|
||||
.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>
|
89
src/devTools/page/components/listItem/dayOnlineItem.vue
Normal file
@ -0,0 +1,89 @@
|
||||
<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>
|
265
src/devTools/page/components/listItem/errorItem.vue
Normal file
@ -0,0 +1,265 @@
|
||||
<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;
|
||||
/* #ifndef APP-PLUS */
|
||||
min-width: 90rpx;
|
||||
/* #endif */
|
||||
}
|
||||
.page {
|
||||
font-size: 16rpx;
|
||||
color: #888;
|
||||
margin-left: 20rpx;
|
||||
lines: 1;
|
||||
overflow: hidden;
|
||||
/* #ifndef APP-PLUS */
|
||||
max-width: 450rpx;
|
||||
white-space: nowrap;
|
||||
/* #endif */
|
||||
/* #ifdef APP-PLUS */
|
||||
width: 450rpx;
|
||||
/* #endif */
|
||||
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>
|
380
src/devTools/page/components/listItem/fileSysItem.vue
Normal file
@ -0,0 +1,380 @@
|
||||
<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;
|
||||
/* #ifndef APP-PLUS */
|
||||
min-height: 70rpx;
|
||||
/* #endif */
|
||||
&: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>
|
62
src/devTools/page/components/listItem/infoList.vue
Normal file
@ -0,0 +1,62 @@
|
||||
<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>
|
197
src/devTools/page/components/listItem/jsRunnerItem.vue
Normal file
@ -0,0 +1,197 @@
|
||||
<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>
|
140
src/devTools/page/components/listItem/logItem.vue
Normal file
@ -0,0 +1,140 @@
|
||||
<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>
|
330
src/devTools/page/components/listItem/networkItem.vue
Normal file
@ -0,0 +1,330 @@
|
||||
<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">
|
||||
{{ 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;
|
||||
/* #ifndef APP-PLUS */
|
||||
max-width: 650rpx;
|
||||
/* #endif */
|
||||
&.type-get {
|
||||
// color: #fff;
|
||||
}
|
||||
&.type-post {
|
||||
// color: #fff;
|
||||
}
|
||||
}
|
||||
}
|
||||
.path {
|
||||
width: 620rpx;
|
||||
/* #ifdef APP-PLUS */
|
||||
width: 620rpx;
|
||||
/* #endif */
|
||||
/* #ifndef APP-PLUS */
|
||||
max-width: 620rpx;
|
||||
/* #endif */
|
||||
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;
|
||||
/* #ifndef APP-PLUS */
|
||||
min-width: 90rpx;
|
||||
/* #endif */
|
||||
}
|
||||
.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>
|
731
src/devTools/page/components/listItem/objectAnalysis.vue
Normal file
@ -0,0 +1,731 @@
|
||||
<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>
|
99
src/devTools/page/components/listItem/pageItem.vue
Normal file
@ -0,0 +1,99 @@
|
||||
<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>
|
194
src/devTools/page/components/listItem/pages.vue
Normal file
@ -0,0 +1,194 @@
|
||||
<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;
|
||||
/* #ifndef APP-PLUS */
|
||||
word-wrap: break-word;
|
||||
overflow-wrap: break-word;
|
||||
white-space: normal;
|
||||
/* #endif */
|
||||
}
|
||||
.options {
|
||||
margin-top: 4rpx;
|
||||
font-size: 20rpx;
|
||||
line-height: 26rpx;
|
||||
color: #888;
|
||||
width: 580rpx;
|
||||
/* #ifndef APP-PLUS */
|
||||
word-wrap: break-word;
|
||||
overflow-wrap: break-word;
|
||||
white-space: normal;
|
||||
/* #endif */
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.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>
|
74
src/devTools/page/components/listItem/routeItem.vue
Normal file
@ -0,0 +1,74 @@
|
||||
<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>
|
746
src/devTools/page/components/listItem/setting.vue
Normal file
@ -0,0 +1,746 @@
|
||||
<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'),
|
||||
pageRouteMap: devCache.get('pageRouteMap'),
|
||||
pageRouteKeyMap: devCache.get('pageRouteKeyMap'),
|
||||
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>
|
313
src/devTools/page/components/listItem/storageList.vue
Normal file
@ -0,0 +1,313 @@
|
||||
<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 {
|
||||
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>
|
292
src/devTools/page/components/listItem/tools.vue
Normal file
@ -0,0 +1,292 @@
|
||||
<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;
|
||||
padding-right: 15rpx;
|
||||
.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;
|
||||
/* #ifndef APP-PLUS */
|
||||
min-width: 120rpx;
|
||||
/* #endif */
|
||||
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>
|
196
src/devTools/page/components/listItem/vuexList.vue
Normal 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>
|
1777
src/devTools/page/components/main.vue
Normal file
80
src/devTools/page/components/mixins/animationControl.js
Normal file
@ -0,0 +1,80 @@
|
||||
// #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')
|
||||
},
|
||||
)
|
||||
},
|
||||
},
|
||||
}
|
77
src/devTools/page/components/mixins/mp.js
Normal file
@ -0,0 +1,77 @@
|
||||
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
|
||||
}
|
68
src/devTools/page/components/ui/btnTabs.vue
Normal file
@ -0,0 +1,68 @@
|
||||
<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>
|
161
src/devTools/page/components/ui/codeHisPicker.vue
Normal file
@ -0,0 +1,161 @@
|
||||
<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>
|
7
src/devTools/page/components/ui/h5Cell.vue
Normal file
@ -0,0 +1,7 @@
|
||||
<template>
|
||||
<div><slot></slot></div>
|
||||
</template>
|
||||
<script>
|
||||
export default {}
|
||||
</script>
|
||||
<style></style>
|
218
src/devTools/page/components/ui/menuBtn.vue
Normal file
@ -0,0 +1,218 @@
|
||||
<template>
|
||||
<view>
|
||||
<view class="menuBtn" v-if="list.length > 0" @click="showMenu">
|
||||
<text class="menuText">{{ title }}</text>
|
||||
<text class="menuText">{{ list[value].title }}</text>
|
||||
<image src="@/devTools/page/static/menu.png" class="menuIcon" />
|
||||
</view>
|
||||
<view
|
||||
v-if="showMenuList"
|
||||
class="menuMock"
|
||||
:style="{
|
||||
height: height + 'px',
|
||||
}"
|
||||
@click.stop
|
||||
>
|
||||
<view class="closeBtn" @click="close">
|
||||
<text class="closeText">关闭</text>
|
||||
</view>
|
||||
<scroll-view
|
||||
:style="{
|
||||
maxHeight: height * 0.7 + 'px',
|
||||
}"
|
||||
scroll-y
|
||||
class="scrollList"
|
||||
>
|
||||
<view
|
||||
v-for="(item, index) in list"
|
||||
:key="item.title"
|
||||
class="menuSelectItem"
|
||||
:class="[
|
||||
index > 0 ? 'tl' : '',
|
||||
index == 0 ? 'i-s' : '',
|
||||
index == list.length ? 'i-e' : '',
|
||||
]"
|
||||
@click="menuSelect(index)"
|
||||
>
|
||||
<text
|
||||
class="menuSelectText"
|
||||
:style="{
|
||||
color: index == value ? '#ff2d55' : '#333333',
|
||||
}"
|
||||
>
|
||||
{{ item.title }}
|
||||
</text>
|
||||
<view v-if="item.msg">
|
||||
<text class="menuSelectMsg">{{ item.msg }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
/**
|
||||
* 按钮列表
|
||||
*/
|
||||
list: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
/**
|
||||
* 当前选中的按钮索引
|
||||
*/
|
||||
value: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
/**
|
||||
* 标题
|
||||
*/
|
||||
title: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
/**
|
||||
* 屏幕高度
|
||||
*/
|
||||
height: uni.getSystemInfoSync().windowHeight,
|
||||
/**
|
||||
* 是否展示菜单列表
|
||||
*/
|
||||
showMenuList: false,
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
/**
|
||||
* 展示菜单
|
||||
*/
|
||||
showMenu() {
|
||||
this.showMenuList = true
|
||||
},
|
||||
/**
|
||||
* 点击菜单选择事件
|
||||
*/
|
||||
menuSelect(index) {
|
||||
this.$emit('indexChange', index)
|
||||
this.showMenuList = false
|
||||
},
|
||||
/**
|
||||
* 关闭菜单弹窗
|
||||
*/
|
||||
close() {
|
||||
this.showMenuList = false
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.menuBtn {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 8rpx;
|
||||
height: 40rpx;
|
||||
border: 1px solid rgba(0, 0, 0, 0.05);
|
||||
padding: 0 8rpx;
|
||||
&:active {
|
||||
background-color: rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
.menuText {
|
||||
font-size: 20rpx;
|
||||
line-height: 40rpx;
|
||||
color: #333;
|
||||
}
|
||||
.menuIcon {
|
||||
margin-left: 8rpx;
|
||||
width: 28rpx;
|
||||
height: 28rpx;
|
||||
}
|
||||
}
|
||||
.menuMock {
|
||||
position: fixed;
|
||||
z-index: 90;
|
||||
width: 750rpx;
|
||||
left: 0;
|
||||
top: 0;
|
||||
flex: 1;
|
||||
/* #ifndef APP-PLUS */
|
||||
height: 100vh;
|
||||
backdrop-filter: blur(2px);
|
||||
/* #endif */
|
||||
display: flex;
|
||||
flex-direction: column-reverse;
|
||||
align-items: center;
|
||||
background-color: rgba(0, 0, 0, 0.3);
|
||||
.closeBtn {
|
||||
width: 710rpx;
|
||||
height: 80rpx;
|
||||
margin-bottom: 80rpx;
|
||||
border-radius: 20rpx;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background-color: #fff;
|
||||
transition: background-color 200ms ease-in-out;
|
||||
&:active {
|
||||
background-color: #dcdcdc;
|
||||
}
|
||||
.closeText {
|
||||
font-size: 24rpx;
|
||||
color: #333;
|
||||
line-height: 24rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
.scrollList {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
width: 710rpx;
|
||||
margin-bottom: 30rpx;
|
||||
border-radius: 20rpx;
|
||||
overflow: hidden;
|
||||
.menuSelectItem {
|
||||
width: 710rpx;
|
||||
/* #ifndef APP-PLUS */
|
||||
min-height: 80rpx;
|
||||
/* #endif */
|
||||
padding: 15rpx 0;
|
||||
background-color: #fff;
|
||||
transition: background-color 200ms ease-in-out;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
&.tl {
|
||||
border-top: 1px solid rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
&.i-s {
|
||||
border-radius: 20rpx 20rpx 0 0;
|
||||
}
|
||||
&.i-e {
|
||||
border-radius: 0 0 20rpx 20rpx;
|
||||
}
|
||||
&:active {
|
||||
background-color: #dcdcdc;
|
||||
}
|
||||
.menuSelectText {
|
||||
font-size: 24rpx;
|
||||
line-height: 24rpx;
|
||||
color: #333;
|
||||
}
|
||||
.menuSelectMsg {
|
||||
font-size: 20rpx;
|
||||
line-height: 20rpx;
|
||||
margin-top: 10rpx;
|
||||
color: #999999;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
158
src/devTools/page/components/ui/mobileSwiperScroll.vue
Normal file
@ -0,0 +1,158 @@
|
||||
<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>
|
47
src/devTools/page/components/ui/requestSpeedLimit.vue
Normal file
@ -0,0 +1,47 @@
|
||||
<template>
|
||||
<view>
|
||||
<MenuBtn :list="typeList" :value="index" @indexChange="change" title="网速:" />
|
||||
</view>
|
||||
</template>
|
||||
<script>
|
||||
import MenuBtn from './menuBtn.vue'
|
||||
export default {
|
||||
components: {
|
||||
MenuBtn,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
/**
|
||||
* 弱网模拟状态
|
||||
*/
|
||||
typeList: [
|
||||
{ title: '不限', type: '', msg: '正常响应请求' },
|
||||
{ title: '2G龟速', type: '2g', msg: '随机延迟30~60秒后响应' },
|
||||
{ title: '3G慢速', type: '3g-', msg: '随机延迟10~30秒后响应' },
|
||||
{ title: '3G略快', type: '3g', msg: '随机延迟3~10秒后响应' },
|
||||
{ title: '4G弱网', type: '4g', msg: '随机延迟0.5~3秒后响应' },
|
||||
],
|
||||
/**
|
||||
* 当前选中的索引
|
||||
*/
|
||||
index: 0,
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
let cache = uni.getStorageSync('devtools_uniResLimitType')
|
||||
let index = this.typeList.findIndex((x) => x.type == cache)
|
||||
if (index == -1) index = 0
|
||||
this.index = index
|
||||
},
|
||||
methods: {
|
||||
/**
|
||||
* 选项发生改变事件
|
||||
*/
|
||||
change(index) {
|
||||
uni.setStorageSync('devtools_uniResLimitType', this.typeList[index].type)
|
||||
this.index = index
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
<style lang="scss"></style>
|
50
src/devTools/page/components/ui/requestTimeoutMock.vue
Normal file
@ -0,0 +1,50 @@
|
||||
<template>
|
||||
<view>
|
||||
<MenuBtn :list="typeList" :value="index" @indexChange="change" title="超时:" />
|
||||
</view>
|
||||
</template>
|
||||
<script>
|
||||
import MenuBtn from './menuBtn.vue'
|
||||
export default {
|
||||
components: {
|
||||
MenuBtn,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
/**
|
||||
* 随机响应超时
|
||||
*/
|
||||
typeList: [
|
||||
{ title: '无', type: '', msg: '正常响应请求' },
|
||||
{ title: '5%', type: '5', msg: '随机5%的概率请求响应超时或报错' },
|
||||
{ title: '10%', type: '10', msg: '随机10%的概率请求响应超时或报错' },
|
||||
{ title: '30%', type: '30', msg: '随机30%的概率请求响应超时或报错' },
|
||||
{ title: '50%', type: '50', msg: '随机50%的概率请求响应超时或报错' },
|
||||
{ title: '70%', type: '70', msg: '随机70%的概率请求响应超时或报错' },
|
||||
{ title: '90%', type: '90', msg: '随机90%的概率请求响应超时或报错' },
|
||||
{ title: '100%', type: '100', msg: '所有请求均响应超时或报错' },
|
||||
],
|
||||
/**
|
||||
* 当前选中的索引
|
||||
*/
|
||||
index: 0,
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
let cache = uni.getStorageSync('devtools_uniResTimeout')
|
||||
let index = this.typeList.findIndex((x) => x.type == cache)
|
||||
if (index == -1) index = 0
|
||||
this.index = index
|
||||
},
|
||||
methods: {
|
||||
/**
|
||||
* 选项发生改变事件
|
||||
*/
|
||||
change(index) {
|
||||
uni.setStorageSync('devtools_uniResTimeout', this.typeList[index].type)
|
||||
this.index = index
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
<style lang="scss"></style>
|
86
src/devTools/page/components/ui/subTitleBar.vue
Normal file
@ -0,0 +1,86 @@
|
||||
<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>
|
77
src/devTools/page/index.nvue
Normal 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>
|
BIN
src/devTools/page/static/copy.png
Normal file
After Width: | Height: | Size: 772 B |
BIN
src/devTools/page/static/delete.png
Normal file
After Width: | Height: | Size: 761 B |
BIN
src/devTools/page/static/fileSys/AI.png
Normal file
After Width: | Height: | Size: 596 B |
BIN
src/devTools/page/static/fileSys/DWG.png
Normal file
After Width: | Height: | Size: 841 B |
BIN
src/devTools/page/static/fileSys/EXE.png
Normal file
After Width: | Height: | Size: 664 B |
BIN
src/devTools/page/static/fileSys/GIF.png
Normal file
After Width: | Height: | Size: 595 B |
BIN
src/devTools/page/static/fileSys/HTML.png
Normal file
After Width: | Height: | Size: 616 B |
BIN
src/devTools/page/static/fileSys/PSD.png
Normal file
After Width: | Height: | Size: 676 B |
BIN
src/devTools/page/static/fileSys/RVT.png
Normal file
After Width: | Height: | Size: 693 B |
BIN
src/devTools/page/static/fileSys/SKP.png
Normal file
After Width: | Height: | Size: 762 B |
BIN
src/devTools/page/static/fileSys/SVG.png
Normal file
After Width: | Height: | Size: 877 B |
BIN
src/devTools/page/static/fileSys/excel.png
Normal file
After Width: | Height: | Size: 637 B |
BIN
src/devTools/page/static/fileSys/pdf.png
Normal file
After Width: | Height: | Size: 641 B |
BIN
src/devTools/page/static/fileSys/pptl.png
Normal file
After Width: | Height: | Size: 503 B |
BIN
src/devTools/page/static/fileSys/shipin.png
Normal file
After Width: | Height: | Size: 444 B |
BIN
src/devTools/page/static/fileSys/tupian.png
Normal file
After Width: | Height: | Size: 532 B |
BIN
src/devTools/page/static/fileSys/txt.png
Normal file
After Width: | Height: | Size: 404 B |
BIN
src/devTools/page/static/fileSys/weizhiwenjian.png
Normal file
After Width: | Height: | Size: 538 B |
BIN
src/devTools/page/static/fileSys/wenjianjia.png
Normal file
After Width: | Height: | Size: 255 B |
BIN
src/devTools/page/static/fileSys/word.png
Normal file
After Width: | Height: | Size: 740 B |
BIN
src/devTools/page/static/fileSys/yasuo.png
Normal file
After Width: | Height: | Size: 487 B |
BIN
src/devTools/page/static/fileSys/yinpin.png
Normal file
After Width: | Height: | Size: 626 B |
BIN
src/devTools/page/static/fold.png
Normal file
After Width: | Height: | Size: 543 B |
BIN
src/devTools/page/static/menu.png
Normal file
After Width: | Height: | Size: 592 B |
BIN
src/devTools/page/static/refresh.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
src/devTools/page/static/unfold.png
Normal file
After Width: | Height: | Size: 561 B |
22
src/devTools/tools.vue
Normal file
@ -0,0 +1,22 @@
|
||||
<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>
|
13
src/main.ts
@ -7,6 +7,11 @@ import App from './App.vue'
|
||||
import { prototypeInterceptor, requestInterceptor, routeInterceptor } from './interceptors'
|
||||
import store from './store'
|
||||
|
||||
import devTools from './devTools/index.js'
|
||||
import devToolsConfig from './devTools/config.js'
|
||||
import mpDevBubble from './devTools/core/components/mpDevBubble.vue'
|
||||
import devToolsVueMixin from './devTools/core/proxy/vueMixin.js'
|
||||
|
||||
export function createApp() {
|
||||
const app = createSSRApp(App)
|
||||
app.use(store)
|
||||
@ -14,6 +19,14 @@ export function createApp() {
|
||||
app.use(requestInterceptor)
|
||||
app.use(prototypeInterceptor)
|
||||
app.use(VueQueryPlugin)
|
||||
//混入DevTools生命周期监听
|
||||
app.mixin(devToolsVueMixin)
|
||||
|
||||
//挂载Devtools
|
||||
app.use(devTools, devToolsConfig)
|
||||
|
||||
//注册小程序端专用的拖动浮标组件
|
||||
app.component('mpDevBubble', mpDevBubble)
|
||||
|
||||
return {
|
||||
app,
|
||||
|
@ -44,6 +44,26 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"subPackages": [
|
||||
{
|
||||
"root": "devTools/page",
|
||||
"pages": [
|
||||
{
|
||||
"path": "index",
|
||||
"style": {
|
||||
"navigationStyle": "custom",
|
||||
"softinputMode": "adjustResize",
|
||||
"animationDuration": 1,
|
||||
"animationType": "none",
|
||||
"popGesture": "none",
|
||||
"bounce": "none",
|
||||
"titleNView": false
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"__esModule": true,
|
||||
"pages": [
|
||||
{
|
||||
"path": "pages/index/index",
|
||||
@ -97,6 +117,5 @@
|
||||
"navigationBarTitleText": "修改密码"
|
||||
}
|
||||
}
|
||||
],
|
||||
"subPackages": []
|
||||
]
|
||||
}
|
@ -9,6 +9,7 @@
|
||||
|
||||
<template>
|
||||
<view>
|
||||
<mpDevBubble />
|
||||
<fg-navbar>关于</fg-navbar>
|
||||
<view
|
||||
class="bg-white overflow-hidden pt-2 px-4"
|
||||
|