├── README.md ├── index.d.ts ├── index.js ├── lib ├── DebugManager.js ├── utils │ ├── DebugConst.js │ ├── DebugUtils.js │ └── DebugWidgets.js └── views │ ├── FloatPanelController.js │ ├── SubViewDeployKey.js │ ├── SubViewDeviceInfo.js │ ├── SubViewLogHttp.js │ ├── SubViewLogWebView.js │ └── SubViewServerUrl.js └── package.json /README.md: -------------------------------------------------------------------------------- 1 | # react-native-debug-tool (开发调试工具) 2 | 3 | 4 | ### 安装 5 | 6 | npm install react-native-debug-tool --save 7 | 8 | or yarn add react-native-debug-tool 9 | 10 | 11 | ### 功能点 12 | 13 | * 支持显示设备信息:依赖于 react-native-device-info 基础库 14 | * 支持显示当前App的Http请求记录 15 | * 支持显示示当前App的WebView加载记录 16 | * 支持App连接服务器环境切换 17 | 18 | ### 用法 19 | 20 | 初始化方法: 21 | 22 | ``` 23 | DebugManager.initDeviceInfo(DeviceInfo) 24 | .initServerUrlMap(serverUrlMap, currentUrl, (baseUrl) => { 25 | 26 | }) 27 | .initStagingKeyMap(deployKeyMap, currentKey, (currentKey) => { 28 | 29 | }); 30 | 31 | 注:初始化方法为非必需方法,如果项目不需要支持【环境切换】与【设备信息查看】功能,可以不调用此方法 32 | 33 | // DeviceInfo => react-native-device-info 库的DeviceInfo对象 34 | 35 | // serverUrlMap => 连接服务器环境 key value Map集合 36 | 如:new Map([['test001','https://domain-001.net'],['test002','https://domain-002.net']]) 37 | 38 | // serverUrl => 默认连接的服务器环境 如:https://domain-001.net 39 | 40 | // baseUrl => 环境切换回调的当前的连接服务器环境 41 | 42 | ``` 43 | 功能1:展示设备信息(只要在初始化的时候传入DeviceInfo对象即可) 44 | 45 | 功能2:展示当前App的Http请求记录 46 | 47 | ```js 48 | 49 | fetch(url, params).then((response) => { 50 | DebugManager.appendHttpLogs({url, ...params}, response) 51 | }) 52 | 53 | ``` 54 | 功能3:展示当前App的WebView加载记录 55 | 56 | ```jsx 57 | 58 | { 60 | DebugManagerDebugManager.appendWebViewLogs(params.url); 61 | }} 62 | /> 63 | 64 | ``` 65 | 66 | 功能4:App连接服务器环境切换(在初始化的时候传入severUrlMap及serverUrl,在回调的时候存在本地供Http使用) 67 | 68 | 69 | 调出调试工具入口: 70 | ```js 71 | 72 | import RootSibling from 'react-native-root-siblings'; 73 | 74 | DebugManager.showFloat(RootSibling) //在App内需要的地方调用些方法展示工具入口浮点 75 | 76 | // only support react-native-root-siblings 3.x 77 | 78 | ``` 79 | 80 | 详细使用方法请参考 [示例](https://github.com/chende008/react-native-easy-app-sample) 81 | -------------------------------------------------------------------------------- /index.d.ts: -------------------------------------------------------------------------------- 1 | interface DebugManager { 2 | 3 | initServerUrlMap(serverUrlMap: object, currentUrl: string, changeCallback: (baseUrl: string) => void): DebugManager 4 | 5 | initDeviceInfo(deviceInfo: object): DebugManager; 6 | 7 | appendHttpLogs(params: object, response: object, parseResult?: object): void; 8 | 9 | appendWebViewLogs(loadUrl: string): void; 10 | 11 | appendLogs(text: string): void; 12 | 13 | showFloat(RootSiblings: object): void; 14 | } 15 | 16 | export var DebugManager: DebugManager; 17 | 18 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | import DebugManager from './lib/DebugManager' 2 | 3 | export {DebugManager} 4 | -------------------------------------------------------------------------------- /lib/DebugManager.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | import {costTimeWork, dateFormat, isEmpty, getApiName} from './utils/DebugUtils' 4 | import FloatPanelController from './views/FloatPanelController' 5 | 6 | let httpRequestLogs = [], webViewLoadLogs = [], normalLogs = [], debugLogOff = true; 7 | 8 | export default class DebugManager { 9 | 10 | static initServerUrlMap(serverUrlMap, currentUrl, changeCallback) { 11 | DebugManager.currentUrl = currentUrl; 12 | DebugManager.serverUrlMap = serverUrlMap; 13 | DebugManager.changeCallback = changeCallback; 14 | return DebugManager 15 | } 16 | 17 | static initStagingKeyMap(deployKeyMap, currentKey, changeKeyCallback) { 18 | DebugManager.currentKey = currentKey; 19 | DebugManager.deployKeyMap = deployKeyMap; 20 | DebugManager.changeKeyCallback = changeKeyCallback; 21 | return DebugManager 22 | } 23 | 24 | static initDeviceInfo(deviceInfo) { 25 | DebugManager.DeviceInfo = deviceInfo; 26 | return DebugManager 27 | } 28 | 29 | static appendHttpLogs(params, response, parseResult) {//Http请求日志(请求结果过大的数据不保存) parseResult->用于临时解析 30 | if (debugLogOff || isEmpty(response)) return; 31 | if (isEmpty(response._bodyText) && isEmpty(parseResult)) { 32 | let reader = new FileReader(); 33 | reader.addEventListener('loadend', () => this.appendHttpLogs(params, response, reader.result)) 34 | reader.readAsText(response._bodyBlob) 35 | } else { 36 | let resultStr = isEmpty(parseResult) ? response._bodyText : parseResult; 37 | costTimeWork(() => { 38 | let obj = { 39 | url: response.url, 40 | method: params.method, 41 | apiName: getApiName(response.url), 42 | headers: JSON.stringify(params.headers), 43 | body: JSON.stringify(params.body), 44 | result: resultStr.substr(0, 2000), 45 | completed: resultStr.length < 2000, 46 | timeStr: dateFormat(new Date(), 'MM月dd日 hh时mm分ss秒S毫秒') 47 | }; 48 | httpRequestLogs.unshift(obj); 49 | if (httpRequestLogs.length > 30) { 50 | httpRequestLogs.splice(10) 51 | }//若日志个数大于30,则删除前10个 52 | }) 53 | } 54 | } 55 | 56 | static appendWebViewLogs(loadUrl) {//webView加载Url 57 | if (debugLogOff) return; 58 | costTimeWork(() => { 59 | webViewLoadLogs.unshift({url: loadUrl, timeStr: dateFormat(new Date(), 'MM月dd日 hh时mm分ss秒S毫秒')}) 60 | if (webViewLoadLogs.length > 100) { 61 | webViewLoadLogs.splice(20) 62 | }//若url个数大于100,则删除前20个 63 | }) 64 | } 65 | 66 | static appendLogs(text) { 67 | if (debugLogOff) return; 68 | costTimeWork(() => { 69 | normalLogs.unshift({log: text, timeStr: dateFormat(new Date(), 'MM月dd日 hh时mm分ss秒S毫秒')}) 70 | if (normalLogs.length > 100) normalLogs.splice(20)//若url个数大于100,则删除前20个 71 | }) 72 | } 73 | 74 | static getHttpLogs() {//获取Http请求日志列表 75 | return httpRequestLogs 76 | } 77 | 78 | static clearHttpLogs() {//清除Http请求日志列表 79 | httpRequestLogs = [] 80 | } 81 | 82 | static getWebLoadLogs() {//获取webView加载日志列表 83 | return webViewLoadLogs 84 | } 85 | 86 | static getLogText() {//获取其它日志列表 87 | return normalLogs 88 | } 89 | 90 | static destroy() { 91 | httpRequestLogs = []; 92 | webViewLoadLogs = []; 93 | normalLogs = []; 94 | debugLogOff = true 95 | } 96 | 97 | static showFloat(RootSiblings, style) { // type of RootSiblings (3.x) 98 | debugLogOff = false; 99 | if (this.sibling) { 100 | this.sibling.update( this.sibling.destroy()}/>) 101 | } else { 102 | this.sibling = new RootSiblings( this.sibling.destroy()}/>) 103 | } 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /lib/utils/DebugConst.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import {Dimensions, PixelRatio} from 'react-native' 3 | 4 | export const DebugImgs = { 5 | iconLink: 'data:image/png;base64,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', 6 | iconBack: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAkCAYAAACJ8xqgAAAABGdBTUEAALGPC/xhBQAAAvNJREFUSA2tlj1oU1EUx01a8lGxIgWhySRx0ylphJJCKVbaovhdxUFEULB1qYJu4q6Dk1h1EBRcQkOlKrVFocVSqEkHkU52bXTQQgVDgkn8/UNfeIl9fXkvXjjc827O+eWc+3Hu9ez4D627u3t/oVCYKpfL31ub5cXj8QPAZoF1ejyejZZmgNFoNIb/e2B76T/5/f4hr1tgV1dXgogE64AxHwwGDy8uLv50BSTNfkAzyG5g00Q2uLCw8EvBOQYS2fFisfgaWBv+qUgkcoLIcoKpOQICuwBoAj8/8gLYuWQyWRDIaB5DsetjsdgVYI+x8zJ34+l0epS+XO/XUISs5hiwp4LR7mcymZGtYILbAonsDnYPZAzkLpHdlm7Vtk2ZObtXKpVuyZnIbgKrgK1gGt8SSHoeYA/pR7ApIdeWl5eVsm37Bzg8PNyyurr6DM+LpPiH/hJz9tKWtGlQc5aB+YDJ+QySB3ieNF81CpNdNUIqRjCfz6cYGwT0GzkJbNYJrApMJBK7crncFAO9gDboj5LmR6cw2bf29PTsATaNfgjYDxZigAXIuIHJxysYEMGybI3eZmAVoNtIrPy8Pp9viOiWiLKTTTy3WTSt7G3HvSqKgUCgH8s5oB3AP7CpE7aeFgaVs6ziqPKNjeazHZkBesTCZ9vhanFQkVSxxDoFsI30p4Dq21GruaRWVlaKbKOJ9fX1fVCiyNlwOPx1bW3tS6PU6kkxOxCh6+JQTdkMZGHKnJRRFVPGZfOE9G+Ybaz0mpTrjUh1NhQKqXz1EfWA9Gw2O19vZ/7eFihDAZjHDQH57EPfqT8yQ8z6lnNoNjB0NvxV9HFEl9QjKtF1TY3xu9E3DJQD86hr9DmiOqpr9DLXaNGAqbdN2Wys7UPKnwGeYjzK9jrINpvUdjPsHEVoOOkUAZ1E9HrQU+S08XpwBRQYaALgW6SdTz2WjukIuwYKqsrEwrwDqqKyVKlc+qGZpgcn5954cC41FaERCK+LCPobIv32F0UmVynF/1eIAAAAAElFTkSuQmCC', 7 | rightArrow: 'data:image/png;base64,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', 8 | }; 9 | 10 | export const DebugColors = { 11 | black: '#000000', 12 | white: '#FFFFFF', 13 | page_bg: '#F5F5F5', //默认页面背景色 14 | yellow: '#E18605', //按钮等颜色 15 | blue: '#007AFF', //按钮等颜色 16 | red: '#FF1D1D', 17 | text: '#000000', //默认字体颜色 18 | text_light: '#15161B', 19 | text_lighter: '#545454', 20 | text_hint: '#C8C8C8', 21 | text_gray: '#86878A', 22 | line: '#DEDEDF',//分割线色值 23 | disable: '#F4F4F5',//按钮不可用 24 | transparent: 'transparent', 25 | orange: '#FAB945' 26 | }; 27 | 28 | 29 | -------------------------------------------------------------------------------- /lib/utils/DebugUtils.js: -------------------------------------------------------------------------------- 1 | import {Alert, Clipboard, Platform} from 'react-native' 2 | import InteractionManager from 'react-native/Libraries/Interaction/InteractionMixin' 3 | import {DebugConst} from './DebugConst' 4 | 5 | export function showMsg(data) { 6 | Clipboard.setString(data); 7 | Alert.alert('【以下内容】已复制剪切板', '\n' + data + '\n') 8 | } 9 | 10 | export function isEmpty(obj) { 11 | if (obj === undefined || obj == null) return true; 12 | if (Array.isArray(obj) && obj.length === 0) {//数组 13 | return true; 14 | } else { 15 | if (typeof obj === 'string' && obj.trim() === '') return true;//字符串 16 | } 17 | return false; 18 | } 19 | 20 | export function toStr(target) {//返回字符串 21 | return typeof target === 'object' ? JSON.stringify(target) : target; 22 | } 23 | 24 | export function selfOr(self, another = null) {//返回自己或者另一个对象 25 | if (Array.isArray(self)) { 26 | return !isEmpty(self) ? self : [] 27 | } else { 28 | return !isEmpty(self) ? self : another 29 | } 30 | } 31 | 32 | export function dateFormat(dateTime = (new Date()).valueOf(), format = "yyyy-MM-dd") { 33 | if (/^\d{4}-\d{1,2}-\d{1,2}$/.test(dateTime)) return dateTime; 34 | let date = new Date(dateTime); 35 | let o = { 36 | "M+": date.getMonth() + 1, //月份 37 | "d+": date.getDate(), //日 38 | "h+": date.getHours(), //小时 39 | "m+": date.getMinutes(), //分 40 | "s+": date.getSeconds(), //秒 41 | "q+": Math.floor((date.getMonth() + 3) / 3), //季度 42 | "S": date.getMilliseconds() //毫秒 43 | }; 44 | if (/(y+)/.test(format)) format = format.replace(RegExp.$1, (date.getFullYear() + "").substr(4 - RegExp.$1.length)); 45 | for (let k in o) { 46 | if (new RegExp("(" + k + ")").test(format)) format = format.replace(RegExp.$1, (RegExp.$1.length === 1) ? (o[k]) : (("00" + o[k]).substr(("" + o[k]).length))); 47 | } 48 | return format; 49 | } 50 | 51 | export function isFunc(func) {//判断输入的是否是function 52 | return func && typeof func === 'function'; 53 | } 54 | 55 | export function costTimeWork(func) {//耗时操作 56 | InteractionManager.runAfterInteractions(() => { 57 | isFunc(func) && func();//若传的是有效方法,则回调 58 | }) 59 | } 60 | 61 | export function getApiName(url) { 62 | if (url.indexOf('?') > 0) { 63 | return url.replace(/^https?.*(com|net|org|cn|dev|moe)\/(.+)\?.*$/, '$2'); 64 | } else { 65 | return url.replace(/^https?.*(com|net|org|cn|dev|moe)\/(.+)\\?.*$/, '$2'); 66 | } 67 | } 68 | 69 | 70 | -------------------------------------------------------------------------------- /lib/utils/DebugWidgets.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | import {Image, StyleSheet, Text, TouchableOpacity, View} from 'react-native' 4 | 5 | import {imgUrl, isEmpty} from './DebugUtils' 6 | import {DebugColors, DebugImgs} from './DebugConst' 7 | 8 | export function Line({style, ...props}) {//水平分割线 9 | return 10 | } 11 | 12 | export function DebugItem({onPress, style, ...props}) { 13 | let {showLine, title, text} = props; 14 | let lineStyle = showLine ? {borderBottomWidth: 0.3, borderBottomColor: DebugColors.line} : {} 15 | return 16 | {!isEmpty(title) && {title}} 17 | 18 | {text} 19 | 20 | 21 | 22 | } 23 | 24 | 25 | const styles = StyleSheet.create({ 26 | itemStyle: { 27 | flex: 1, 28 | fontSize: 14, 29 | color: DebugColors.text, 30 | paddingVertical: 16, 31 | paddingLeft: 15 32 | } 33 | }) 34 | -------------------------------------------------------------------------------- /lib/views/FloatPanelController.js: -------------------------------------------------------------------------------- 1 | import React, {PureComponent} from 'react' 2 | import { 3 | Animated, 4 | Dimensions, 5 | Image, 6 | View, 7 | Text, 8 | PanResponder, 9 | ScrollView, 10 | StyleSheet, 11 | TouchableOpacity, 12 | SafeAreaView 13 | } from 'react-native' 14 | 15 | import SubViewLogHttp from './SubViewLogHttp' 16 | import SubViewDeviceInfo from './SubViewDeviceInfo' 17 | import SubViewLogWebView from './SubViewLogWebView' 18 | import SubViewServerUrl from './SubViewServerUrl' 19 | import SubViewDeployKey from './SubViewDeployKey' 20 | import {DebugColors, DebugImgs} from '../utils/DebugConst' 21 | import {DebugItem, Line} from '../utils/DebugWidgets' 22 | import {isEmpty, selfOr, showMsg} from '../utils/DebugUtils' 23 | import DebugManager from '../DebugManager' 24 | 25 | const {width: screenWidth, height: screenHeight} = Dimensions.get('window'); 26 | const IconRadius = 25 * 2//浮点直径 27 | const RightPadding = screenWidth - IconRadius - 10; 28 | const topMargin = screenHeight * 0.6//浮点初始位置距顶部高度 29 | 30 | export default class FloatPanelController extends PureComponent { 31 | 32 | constructor(props) { 33 | super(props); 34 | this.state = { 35 | title: '', 36 | currentUrl: '', 37 | currentKey: '', 38 | contentView: null, 39 | isOpen: false, 40 | toFloat: true, 41 | animating: false, 42 | animateValue: new Animated.Value(0), 43 | translateValue: new Animated.ValueXY({x: RightPadding, y: topMargin}), 44 | dataChangedCount: 0 45 | }; 46 | this.lastValueY = topMargin; 47 | this.lastValueX = RightPadding; 48 | this.listenerValue = {x: RightPadding, y: topMargin}; 49 | this.pageTransformAnim = new Animated.Value(screenWidth / 2)//页面切换动画 50 | } 51 | 52 | render() { 53 | let {translateValue, animateValue, contentView, title, currentUrl, currentKey} = this.state 54 | let animalStyle = { 55 | width: animateValue.interpolate({ 56 | inputRange: [0, 1], 57 | outputRange: [IconRadius, screenWidth] 58 | }), 59 | height: animateValue.interpolate({ 60 | inputRange: [0, 1], 61 | outputRange: [IconRadius, screenHeight] 62 | }), 63 | borderRadius: animateValue.interpolate({ 64 | inputRange: [0, 0.5, 1], 65 | outputRange: [IconRadius * 0.5, 20, 0] 66 | }) 67 | }; 68 | let transformStyle = {transform: translateValue.getTranslateTransform()}; 69 | return 70 | 71 | this.scrollView = scrollView}> 76 | {this.renderPageFirst(currentUrl, currentKey)} 77 | {this.renderPageSecond(contentView, title)} 78 | 79 | 80 | {this.renderFloatBtn()} 81 | 82 | } 83 | 84 | renderPageFirst = (currentUrl, currentKey) => { 85 | return 86 | 87 | 调试设置 88 | this.close()}>最小化 89 | 90 | 91 | {DebugManager.DeviceInfo && this.changeToDetail(1, 'DeviceInfo', '设备信息')} showLine/>} 92 | this.changeToDetail(1, 'LogHttp', 'Http请求日志')} showLine/> 93 | this.changeToDetail(1, 'LogWebView', 'WebView请求日志')} showLine/> 94 | {!isEmpty(DebugManager.serverUrlMap) && this.changeToDetail(1, 'EnvironmentChange', '服务器环境切换')} showLine/>} 95 | {!isEmpty(DebugManager.deployKeyMap) && this.changeToDetail(1, 'DeployKeyChange', '热更新渠道切换')} showLine/>} 96 | { 97 | DebugManager.getLogText().map((item, index) => { 98 | return 99 | showMsg(item.log)}>日志: 100 | {item.log} 101 | 102 | 103 | 时间:{item.timeStr} 104 | 105 | 106 | })} 107 | 108 | this.close(true)}>退出调试工具 109 | 110 | }; 111 | 112 | renderPageSecond = (contentView, title) => { 113 | return 114 | 115 | this.changeToDetail(0)}> 116 | 117 | 返回 118 | 119 | {selfOr(title, '数据加载中...')} 120 | {title.includes('Http请求日志') ? 121 | { 122 | if (DebugManager.getHttpLogs().length > 0) { 123 | DebugManager.clearHttpLogs(); 124 | this.changeToDetail(0) 125 | } 126 | }}>清空 127 | : } 128 | 129 | 130 | {contentView} 131 | 132 | }; 133 | 134 | changeToDetail = (pageIndex, type, title) => {//跳转页面码 135 | Animated.timing(this.pageTransformAnim, {toValue: screenWidth / 2 * (pageIndex + 1), useNativeDriver: false}).start() 136 | this.scrollView.scrollTo({x: screenWidth * pageIndex, y: 0, animated: true}) 137 | this.pageTransformAnim.addListener((anim) => { 138 | if (pageIndex === 1 && anim.value >= screenWidth) {//当切换到第二个页面时,展示详情 139 | this.renderContentView(type, title) 140 | } 141 | if (pageIndex === 0) {//切换到第一页面时,清空数据 142 | this.setState({contentView: null, title: ''}); 143 | setTimeout(() => this.setState({dataChangedCount: this.state.dataChangedCount + 1}), 800) 144 | } 145 | }) 146 | }; 147 | 148 | renderContentView = (type, title) => {//渲染第二页面内容布局 149 | let contentView = null; 150 | switch (type) { 151 | case 'DeviceInfo'://设备信息 152 | contentView = ; 153 | break; 154 | case 'LogHttp'://Http请求日志 155 | contentView = ; 156 | break; 157 | case 'LogWebView'://webView加载日志 158 | contentView = ; 159 | break; 160 | case 'EnvironmentChange'://服务器环境切换 161 | contentView = { 162 | this.setState({currentUrl: url}); 163 | this.changeToDetail(0) 164 | }}/>; 165 | break; 166 | case 'DeployKeyChange'://热更新部署Key切换 167 | contentView = { 168 | this.setState({currentKey: value}); 169 | this.changeToDetail(0) 170 | }}/>; 171 | break 172 | } 173 | this.setState({contentView: contentView, title: title}) 174 | }; 175 | 176 | renderFloatBtn() { 177 | let {isOpen, toFloat, animating} = this.state; 178 | if (!isOpen && toFloat && !animating) { 179 | return 180 | 181 | 182 | } 183 | return null 184 | } 185 | 186 | close = (exit = false) => {//关闭页面 187 | let {animating, toFloat, isOpen} = this.state; 188 | if (exit) {//退出调试功能 189 | DebugManager.destroy(); 190 | this.pageTransform(false); 191 | setTimeout(() => { 192 | this.props.close && this.props.close() 193 | }, 600) 194 | } 195 | if (animating) return; 196 | if (toFloat) isOpen && this.pageTransform(false) 197 | }; 198 | 199 | 200 | open = () => {//打开页面 201 | if (isEmpty(this.state.currentUrl)) { 202 | this.setState({currentUrl: DebugManager.currentUrl}) 203 | } 204 | if (isEmpty(this.state.currentKey)) { 205 | this.setState({currentKey: DebugManager.currentKey}) 206 | } 207 | !this.state.isOpen && this.pageTransform(true) 208 | }; 209 | 210 | componentWillMount() { 211 | this.state.translateValue.addListener( 212 | value => (this.listenerValue = value) 213 | ); 214 | this.gestureResponder = PanResponder.create({ 215 | onStartShouldSetPanResponder: () => true, 216 | onMoveShouldSetPanResponder: () => true, 217 | onPanResponderGrant: this.onFloatInit, 218 | onPanResponderMove: this.onFloatMove, 219 | onPanResponderRelease: this.onFloatRelease, 220 | onPanResponderTerminate: this.onFloatRelease 221 | }); 222 | this.setState({currentUrl: DebugManager.currentUrl}) 223 | } 224 | 225 | pageTransform = (isOpen) => {//页面切换动画 226 | this.setState({animating: true}); 227 | 228 | Animated.parallel([ 229 | Animated.timing(this.state.animateValue, { 230 | toValue: isOpen ? 1 : 0, 231 | duration: 500, 232 | useNativeDriver: false 233 | }), 234 | Animated.timing(this.state.translateValue.y, { 235 | toValue: isOpen ? 0 : this.lastValueY, 236 | duration: 500, 237 | useNativeDriver: false 238 | }), 239 | Animated.timing(this.state.translateValue.x, { 240 | toValue: isOpen ? 0 : this.lastValueX, 241 | duration: 500, 242 | useNativeDriver: false 243 | }) 244 | ]).start(() => { 245 | this.setState({isOpen: isOpen, animating: false}) 246 | }) 247 | }; 248 | 249 | onFloatInit = (event, gestureState) => { 250 | this.time = Date.parse(new Date()); 251 | this.state.translateValue.setOffset(this.listenerValue); 252 | this.state.translateValue.setValue({x: 0, y: 0}) 253 | }; 254 | 255 | onFloatMove = (evt, gestureState) => {//浮点 256 | Animated.event([null, {dx: this.state.translateValue.x, dy: this.state.translateValue.y}])(evt, gestureState) 257 | const {dx, dy} = gestureState 258 | }; 259 | 260 | onFloatRelease = (evt, gestureState) => { 261 | let {translateValue} = this.state; 262 | translateValue.flattenOffset(); 263 | const y = translateValue.y.__getValue(); 264 | if (y < 10 || y > screenHeight - IconRadius - 10) {//处理浮点Y轴 265 | Animated.spring(translateValue.y, { 266 | toValue: y < 10 ? 10 : screenHeight - IconRadius - 10, 267 | duration: 200, 268 | useNativeDriver: false 269 | }).start() 270 | } 271 | Animated.spring(translateValue.x, {//处理浮点X轴 272 | toValue: gestureState.moveX > screenWidth * 0.5 ? RightPadding : 10, 273 | duration: 200, 274 | useNativeDriver: false 275 | }).start(); 276 | 277 | // 记录最后一次位移值 278 | this.lastValueX = translateValue.x.__getValue(); 279 | this.lastValueY = translateValue.y.__getValue(); 280 | 281 | this.releaseTime = Date.parse(new Date()); 282 | // single tap 283 | if (this.releaseTime - this.time < 50 && 284 | Math.abs(gestureState.dx) < 10 && 285 | Math.abs(gestureState.dy) < 10) {//点击打开页面 286 | !this.state.isOpen && this.open() 287 | } 288 | } 289 | 290 | }; 291 | 292 | const styles = StyleSheet.create({ 293 | floatBtn: { 294 | left: 0, 295 | top: 0, 296 | borderRadius: IconRadius / 2, 297 | borderColor: DebugColors.blue, 298 | borderWidth: 2, 299 | height: IconRadius, 300 | width: IconRadius, 301 | position: 'absolute', 302 | alignItems: 'center', 303 | justifyContent: 'center', 304 | backgroundColor: 'gray' 305 | }, 306 | titleStyle: { 307 | fontSize: 17, 308 | fontWeight: 'bold', 309 | color: DebugColors.text, 310 | textAlign: 'center', 311 | paddingVertical: 15, 312 | backgroundColor: DebugColors.white 313 | }, 314 | rightText: { 315 | right: 0, 316 | padding: 10, 317 | fontSize: 13, 318 | color: DebugColors.text, 319 | position: 'absolute' 320 | 321 | }, 322 | backBtn: { 323 | flex: 1, 324 | flexDirection: 'row', 325 | alignItems: 'center', 326 | paddingHorizontal: 10 327 | }, 328 | exitBtn: { 329 | padding: 10, 330 | marginTop: 10, 331 | borderWidth: 0.5, 332 | borderRadius: 5, 333 | margin: 10, 334 | textAlign: 'center', 335 | color: DebugColors.text_light, 336 | borderColor: DebugColors.line, 337 | backgroundColor: DebugColors.page_bg 338 | }, 339 | title: { 340 | fontSize: 14, 341 | marginTop: 1, 342 | color: DebugColors.text_light, 343 | paddingVertical: 5, 344 | paddingHorizontal: 15, 345 | backgroundColor: DebugColors.disable 346 | }, 347 | borderStyle: { 348 | borderWidth: 0.5, 349 | borderRadius: 2, 350 | marginVertical: 3, 351 | marginHorizontal: 10, 352 | borderColor: DebugColors.line 353 | } 354 | }); 355 | -------------------------------------------------------------------------------- /lib/views/SubViewDeployKey.js: -------------------------------------------------------------------------------- 1 | import React, { PureComponent } from 'react' 2 | 3 | import { ScrollView, Text, TouchableOpacity } from 'react-native' 4 | 5 | import { DebugColors } from '../utils/DebugConst' 6 | import { Line } from '../utils/DebugWidgets' 7 | import DebugManager from '../DebugManager' 8 | 9 | export default class SubViewDeployKey extends PureComponent { 10 | 11 | constructor(props) { 12 | super(props) 13 | this.state = { ...props } 14 | } 15 | 16 | render() { 17 | let { changeKeyCallback, deployKeyMap } = DebugManager 18 | let { currentUrl, keyCallback } = this.state 19 | return { 20 | [...deployKeyMap.keys()].map((key, index) => { 21 | let value = deployKeyMap.get(key) 22 | let isCurrent = currentUrl === value 23 | return { 24 | if (currentUrl === value) return 25 | changeKeyCallback && changeKeyCallback(key, value) 26 | this.setState({ currentUrl: value }) 27 | keyCallback && keyCallback(value) 28 | }}> 29 | {key + (isCurrent ? '(当前环境)' : '')} 30 | {value} 31 | 32 | 33 | }) 34 | } 35 | } 36 | } 37 | 38 | -------------------------------------------------------------------------------- /lib/views/SubViewDeviceInfo.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | 3 | import {ScrollView, StyleSheet, Text} from 'react-native'; 4 | 5 | import {DebugColors, DebugConst} from '../utils/DebugConst'; 6 | import {dateFormat, isAndroid, isEmpty, showMsg} from '../utils/DebugUtils'; 7 | import DebugManager from '../DebugManager'; 8 | 9 | export default class SubViewDeviceInfo extends Component { 10 | 11 | constructor(props) { 12 | super(props); 13 | this.state = { 14 | infos: [], 15 | }; 16 | } 17 | 18 | componentWillMount(): void { 19 | let {infos} = this.state; 20 | let {DeviceInfo} = DebugManager; 21 | if (isEmpty(DeviceInfo)) { 22 | return null; 23 | } 24 | for (let method in DeviceInfo) { 25 | if (method.endsWith('Sync')) { 26 | continue; 27 | } 28 | let info = {}; 29 | try { 30 | if (method.startsWith('get')) { 31 | info.name = method.substr(3); 32 | } else { 33 | info.name = method; 34 | } 35 | let maxlength = 30, offsetLength; 36 | let result = DeviceInfo[method](); 37 | offsetLength = maxlength - (String(method)).length; 38 | if (result instanceof Promise) { 39 | result.then((data) => { 40 | if (typeof data === 'object') { 41 | info.value = JSON.stringify(data); 42 | } else { 43 | if (method.endsWith('Time')) { 44 | info.value = dateFormat(data, 'yyyy-MM-dd hh:mm:ss'); 45 | } else { 46 | info.value = String(data); 47 | } 48 | } 49 | info.value && infos.push(info); 50 | this.setState({infos}); 51 | }); 52 | } else { 53 | info.value = result; 54 | info.value && infos.push(info); 55 | this.setState({infos}); 56 | } 57 | } catch (e) { 58 | } 59 | } 60 | } 61 | 62 | render() { 63 | let {infos} = this.state; 64 | return { 65 | !isEmpty(infos) && infos.map(({name, value}, index) => { 66 | return showMsg(value)}>{name}: 67 | {value} 68 | ; 69 | })} 70 | 71 | ; 72 | } 73 | } 74 | 75 | const styles = StyleSheet.create({ 76 | title: { 77 | fontSize: 16, 78 | marginTop: 1, 79 | color: DebugColors.text, 80 | paddingVertical: 5, 81 | paddingHorizontal: 15, 82 | backgroundColor: DebugColors.disable, 83 | }, 84 | }); 85 | -------------------------------------------------------------------------------- /lib/views/SubViewLogHttp.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react' 2 | 3 | import {Alert, ScrollView, StyleSheet, Text, View} from 'react-native' 4 | 5 | import DebugManager from '../DebugManager' 6 | import {DebugColors} from '../utils/DebugConst' 7 | import {dateFormat, isEmpty, showMsg} from '../utils/DebugUtils' 8 | 9 | export default class SubViewLogHttp extends Component { 10 | 11 | constructor(props) { 12 | super(props); 13 | this.state = { 14 | dataList: DebugManager.getHttpLogs() 15 | }; 16 | } 17 | 18 | render() { 19 | let {dataList} = this.state; 20 | return {dataList.map((item, index) => { 21 | return 22 | showMsg(item.apiName)}> 23 | 接口名:{item.apiName} 24 | 25 | showMsg(item.url)}> 26 | 请求Url:{item.url} 27 | 28 | showMsg(item.method)}> 29 | 请求Method:{item.method} 30 | 31 | {!isEmpty(item.headers) && showMsg(item.headers)}> 32 | 请求Headers:{item.headers} 33 | } 34 | {!isEmpty(item.body) && showMsg(item.body)}> 35 | 请求Body:{item.body} 36 | } 37 | this.requestRetry(item)}>{item.completed ? '重新请求' : '获取完整结果'} 38 | showMsg(item.result)}> 39 | 请求结果:{item.result} 40 | 41 | 42 | 请求时间:{item.timeStr} 43 | 44 | 45 | })} 46 | 47 | } 48 | 49 | requestRetry = (item) => {//重新请求 50 | let {dataList} = this.state; 51 | let {method, headers, body} = item; 52 | let params = {method}; 53 | if (!isEmpty(headers)) { 54 | params.headers = JSON.parse(headers); 55 | } 56 | if (!isEmpty(body)) {//若有body,则拼接body 57 | params.body = JSON.parse(body); 58 | } 59 | fetch(item.url, params).then((response) => { 60 | if (response.status >= 200 && response.status < 400) { 61 | if (response && !isEmpty(response._bodyText)) { 62 | item.result = response._bodyText; 63 | item.timeStr = dateFormat(new Date(), 'MM月dd日 hh时mm分ss秒'); 64 | item.completed = true; 65 | } else { 66 | let reader = new FileReader(); 67 | reader.addEventListener("loadend", () => { 68 | item.result = reader.result; 69 | item.timeStr = dateFormat(new Date(), 'MM月dd日 hh时mm分ss秒'); 70 | item.completed = true; 71 | this.setState({dataList}); 72 | }); 73 | reader.readAsText(response._bodyBlob); 74 | } 75 | } else { 76 | Alert.alert('请求失败,错误码' + response.status); 77 | item.completed = false; 78 | } 79 | this.setState({dataList}); 80 | }).catch(error => { 81 | Alert.alert('请求异常') 82 | }) 83 | }; 84 | } 85 | 86 | const styles = StyleSheet.create({ 87 | title: { 88 | fontSize: 12, 89 | color: 'black', 90 | marginVertical: 2, 91 | paddingVertical: 3, 92 | paddingHorizontal: 3 93 | }, 94 | borderStyle: { 95 | borderWidth: 1, 96 | borderRadius: 2, 97 | marginVertical: 3, 98 | marginHorizontal: 3, 99 | borderColor: DebugColors.line 100 | }, 101 | showAll: { 102 | padding: 6, 103 | fontSize: 14, 104 | marginRight: 8, 105 | borderRadius: 5, 106 | color: DebugColors.white, 107 | alignSelf: 'flex-end', 108 | backgroundColor: DebugColors.orange 109 | } 110 | }); 111 | -------------------------------------------------------------------------------- /lib/views/SubViewLogWebView.js: -------------------------------------------------------------------------------- 1 | import React, {PureComponent} from 'react' 2 | 3 | import {ScrollView, StyleSheet, Text, View} from 'react-native' 4 | 5 | import DebugManager from '../DebugManager' 6 | import {DebugColors} from '../utils/DebugConst' 7 | import {showMsg} from '../utils/DebugUtils' 8 | 9 | export default class SubViewLogWebView extends PureComponent { 10 | 11 | render() { 12 | return { 13 | DebugManager.getWebLoadLogs().map((item, index) => { 14 | return 15 | showMsg(item.url)}> 16 | 请求Url: 17 | {item.url} 18 | 19 | 20 | 请求时间:{item.timeStr} 21 | 22 | 23 | })} 24 | 25 | } 26 | } 27 | const styles = StyleSheet.create({ 28 | title: { 29 | fontSize: 12, 30 | color: 'black', 31 | marginVertical: 2, 32 | paddingVertical: 3, 33 | paddingHorizontal: 3 34 | }, 35 | borderStyle: { 36 | borderWidth: 1, 37 | borderRadius: 2, 38 | marginVertical: 3, 39 | marginHorizontal: 10, 40 | borderColor: DebugColors.line 41 | } 42 | }); 43 | -------------------------------------------------------------------------------- /lib/views/SubViewServerUrl.js: -------------------------------------------------------------------------------- 1 | import React, {PureComponent} from 'react' 2 | 3 | import {ScrollView, Text, TouchableOpacity} from 'react-native' 4 | 5 | import {DebugColors} from '../utils/DebugConst' 6 | import {Line} from "../utils/DebugWidgets"; 7 | import DebugManager from '../DebugManager' 8 | 9 | export default class SubViewServerUrl extends PureComponent { 10 | 11 | constructor(props) { 12 | super(props); 13 | this.state = {...props}; 14 | } 15 | 16 | render() { 17 | let {changeCallback, serverUrlMap} = DebugManager; 18 | let {currentUrl, callback} = this.state; 19 | return { 20 | [...serverUrlMap.keys()].map((key, index) => { 21 | let value = serverUrlMap.get(key); 22 | let isCurrent = currentUrl === value; 23 | return { 24 | if (currentUrl === value) return; 25 | changeCallback && changeCallback(value); 26 | this.setState({currentUrl: value}); 27 | callback && callback(value); 28 | }}> 29 | {key + (isCurrent ? '(当前渠道)' : '')} 30 | {value} 31 | 32 | 33 | }) 34 | } 35 | } 36 | } 37 | 38 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-native-debug-tool", 3 | "version": "1.1.6", 4 | "description": "debug-tool for react-native", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "https://github.com/chende008/react-native-debug-tool.git" 12 | }, 13 | "keywords": [ 14 | "debug-tool" 15 | ], 16 | "author": "chende", 17 | "license": "MIT", 18 | "jest": { 19 | "preset": "react-native" 20 | } 21 | } 22 | --------------------------------------------------------------------------------