yangpei
4 years ago
12 changed files with 499 additions and 12 deletions
@ -0,0 +1,67 @@ |
|||
import React, { useEffect } from 'react' |
|||
import Log from './log' |
|||
import logStore, { ConsoleType } from './store' |
|||
|
|||
const MyConsole: React.FC = () => { |
|||
const console: any = {} |
|||
|
|||
useEffect(() => { |
|||
executeConsole() |
|||
}, []) |
|||
|
|||
const processLog = (item: ConsoleType) => { |
|||
const { logType } = item |
|||
let { infos = [] } = item |
|||
if (!infos.length) return |
|||
infos = [].slice.call(infos || []) |
|||
logStore.addLog({ logType, infos }) |
|||
printOriginLog(item) |
|||
} |
|||
|
|||
const clearLog = () => { |
|||
logStore.clearLog() |
|||
} |
|||
|
|||
const printOriginLog = (item: ConsoleType) => { |
|||
const { logType, infos } = item |
|||
if (typeof console[logType] === 'function') { |
|||
console[logType].apply(window.console, infos) |
|||
} |
|||
} |
|||
|
|||
const executeConsole = () => { |
|||
if (!window.console) { |
|||
return |
|||
} |
|||
|
|||
// 模拟浏览器中的console方法
|
|||
const methodList = ['log', 'info', 'warn', 'debug', 'error'] |
|||
methodList.map(method => { |
|||
// @ts-ignore
|
|||
console[method] = window.console[method] |
|||
}) |
|||
|
|||
methodList.map(method => { |
|||
// @ts-ignore
|
|||
window.console[method] = (...args: any[]) => { |
|||
processLog({ |
|||
logType: method, |
|||
infos: args |
|||
}) |
|||
} |
|||
}) |
|||
|
|||
window.console.clear = (...args: any[]) => { |
|||
clearLog() |
|||
console.clear.apply(window.console, args) |
|||
} |
|||
} |
|||
|
|||
return ( |
|||
<div style={{ height: '100%' }}> |
|||
<Log logList={logStore.computeLogList} /> |
|||
</div> |
|||
) |
|||
} |
|||
|
|||
export default MyConsole |
@ -0,0 +1,59 @@ |
|||
import React from 'react' |
|||
import DataView from '../logView' |
|||
import { ConsoleType } from '../store' |
|||
|
|||
interface Props { |
|||
logList: ConsoleType[] |
|||
} |
|||
|
|||
const Log: React.FC<Props> = (props) => { |
|||
const renderLogList = (logList: ConsoleType[]) => { |
|||
return logList.map((log: ConsoleType, index: number) => { |
|||
const dataView = log.infos.map((info: any, i: number) => { |
|||
return typeof info !== 'object' || info === null ? |
|||
<div key={i}>{`${info}`}</div> : |
|||
<DataView data={info} key={i} /> |
|||
}) |
|||
let ele = null |
|||
|
|||
// 根据不同console类型展示不同的样式
|
|||
switch (log.logType) { |
|||
case 'warn': |
|||
ele = <div |
|||
key={index} |
|||
style={{ |
|||
background: '#fffacd', |
|||
color: 'orange', |
|||
border: '1px solid #ffb930' |
|||
}} |
|||
> |
|||
{dataView} |
|||
</div> |
|||
break |
|||
case 'log': |
|||
ele = <div key={index}> |
|||
{dataView} |
|||
</div> |
|||
break |
|||
case 'error': |
|||
ele = <div |
|||
key={index} |
|||
style={{ |
|||
background: '#ffe4e1', |
|||
border: '1px solid #f4a0ab' |
|||
}} |
|||
> |
|||
<div style={{ color: '#dc143c' }}>{dataView}</div> |
|||
</div> |
|||
break |
|||
} |
|||
return ele |
|||
}) |
|||
} |
|||
|
|||
return ( |
|||
<div style={{ height: '100%', overflow: 'auto' }}>{renderLogList(props.logList)}</div> |
|||
) |
|||
} |
|||
|
|||
export default Log |
@ -0,0 +1,114 @@ |
|||
import React, { useState, useEffect } from 'react' |
|||
// import DataList from './dataList'
|
|||
import Simple from './simple' |
|||
import styles from './index.module.less' |
|||
|
|||
const previewComplex = (data: any) => { |
|||
if (Array.isArray(data)) { |
|||
return <span>Array[{data.length}]</span> |
|||
} |
|||
switch (typeof data) { |
|||
case 'function': |
|||
return <span>{data.name || 'fn'}()</span> |
|||
case 'object': |
|||
case 'undefined': |
|||
case null: |
|||
return '{…}' |
|||
} |
|||
return null |
|||
} |
|||
|
|||
interface Props { |
|||
startOpen?: boolean |
|||
noSort?: boolean |
|||
name: string |
|||
value: any |
|||
} |
|||
|
|||
interface State { |
|||
open: boolean |
|||
loading?: boolean |
|||
} |
|||
|
|||
const DataItem: React.FC<Props> = (props) => { |
|||
const [state, setState] = useState<State>({ open: !!props.startOpen, loading: false }) |
|||
let opener = null |
|||
let children = null |
|||
let { name } = props |
|||
let preview |
|||
|
|||
useEffect(() => { |
|||
if (state.open && props.value) { |
|||
setState({ loading: true, open: true }) |
|||
} |
|||
}, []) |
|||
|
|||
useEffect(() => { |
|||
const data = props.value |
|||
const otype = typeof data |
|||
let complex = true |
|||
|
|||
if ( |
|||
otype === 'number' || |
|||
otype === 'string' || |
|||
data == null || |
|||
otype === 'boolean' |
|||
) { |
|||
preview = <Simple data={data} /> |
|||
complex = false |
|||
} else { |
|||
preview = previewComplex(data) |
|||
} |
|||
|
|||
const open = state.open |
|||
|
|||
if (complex && Object.keys(props.value).length) { |
|||
opener = ( |
|||
<div onClick={toggleOpen()} className={styles.opener}> |
|||
{open ? |
|||
<span className={styles.openerOpen} /> : |
|||
<span className={styles.openerClose} /> |
|||
} |
|||
</div> |
|||
) |
|||
} |
|||
|
|||
// if (complex && open) {
|
|||
// children = <DataList data={data} />
|
|||
// }
|
|||
|
|||
if (name.length > 50) { |
|||
name = name.slice(0, 50) + '…' |
|||
} |
|||
}, [props]) |
|||
|
|||
const toggleOpen = () => { |
|||
return () => { |
|||
const data = props.value |
|||
const isArray = Array.isArray(data) |
|||
|
|||
if (state.loading) return |
|||
if (!isArray && !Object.keys(props.value).length) return |
|||
if (isArray && data.length === 0) return |
|||
|
|||
setState({ |
|||
open: !state.open |
|||
}) |
|||
} |
|||
} |
|||
|
|||
return ( |
|||
<li className={styles.codeWrap}> |
|||
<div className={styles.codeBox}> |
|||
{opener} |
|||
<div onClick={toggleOpen()} className={styles.codeKey}> |
|||
{name ? `${name}:` : ''} |
|||
</div> |
|||
<div className={styles.codeVal}>{preview}</div> |
|||
</div> |
|||
{children} |
|||
</li> |
|||
) |
|||
} |
|||
|
|||
export default DataItem |
@ -0,0 +1,64 @@ |
|||
import React, { useState, useEffect } from 'react' |
|||
import DataItem from './dataItem' |
|||
import styles from './index.module.less' |
|||
|
|||
interface DataListProps { |
|||
data: any |
|||
startOpen?: boolean |
|||
noSort?: boolean |
|||
} |
|||
|
|||
const alphanumericSort = (a: string, b: string): number => { |
|||
if ('' + +a === a) { |
|||
if ('' + +b !== b) { |
|||
return -1 |
|||
} |
|||
return +a < +b ? -1 : 1 |
|||
} |
|||
return a < b ? -1 : 1 |
|||
} |
|||
|
|||
const DataList: React.FC<DataListProps> = (props) => { |
|||
const [element, setElement] = useState<any[]>([]) |
|||
const renderItem = (name: string, key: string) => { |
|||
const data = props.data |
|||
return ( |
|||
<DataItem |
|||
key={key} |
|||
name={name} |
|||
startOpen={props.startOpen} |
|||
value={data[name]} |
|||
/> |
|||
) |
|||
} |
|||
|
|||
useEffect(() => { |
|||
const { data } = props |
|||
if (!data) return |
|||
|
|||
const isArray = Array.isArray(data) |
|||
const ele: any[] = [] |
|||
if (isArray) { |
|||
data.forEach((item: any, i: number) => { |
|||
ele.push(renderItem(String(i), String(i))) |
|||
}) |
|||
} else { |
|||
const names = Object.keys(data) |
|||
if (!props.noSort) { |
|||
names.sort(alphanumericSort) |
|||
} |
|||
names.forEach((name, i) => { |
|||
ele.push(renderItem(name, name)) |
|||
}) |
|||
} |
|||
setElement(ele) |
|||
}) |
|||
|
|||
return ( |
|||
!element.length ? |
|||
<div>{Array.isArray(props.data) ? 'Empty array' : 'Empty object'}</div> : |
|||
<ul className={styles.codeContainer}>{element}</ul> |
|||
) |
|||
} |
|||
|
|||
export default DataList |
@ -0,0 +1,48 @@ |
|||
.codeContainer { |
|||
padding: 10px; |
|||
font-size: 12px; |
|||
list-style: none; |
|||
margin: 0; |
|||
padding: 0; |
|||
} |
|||
|
|||
.codeBox { |
|||
display: flex; |
|||
position: relative; |
|||
} |
|||
|
|||
.codeVal { |
|||
display: flex; |
|||
white-space: pre; |
|||
word-break: break-word; |
|||
margin-left: 10px; |
|||
flex: 1; |
|||
color: #183691; |
|||
} |
|||
|
|||
.codeKey { |
|||
margin-left: 5px; |
|||
color: #905; |
|||
} |
|||
|
|||
.opener { |
|||
cursor: pointer; |
|||
margin-left: -10px; |
|||
position: absolute; |
|||
} |
|||
|
|||
.openerOpen { |
|||
border-color: #bbb transparent transparent transparent; |
|||
border-style: solid; |
|||
border-width: 6px 4px 0 4px; |
|||
display: inline-block; |
|||
vertical-align: middle; |
|||
} |
|||
|
|||
.openerClose { |
|||
border-color: transparent transparent transparent #bbb; |
|||
border-style: solid; |
|||
border-width: 4px 0 4px 6px; |
|||
display: inline-block; |
|||
vertical-align: middle; |
|||
} |
@ -0,0 +1,61 @@ |
|||
import React, { useState, useEffect } from 'react' |
|||
import DataList from './dataList' |
|||
import styles from './index.module.less' |
|||
|
|||
interface DataViewProps { |
|||
data: any |
|||
startOpen?: boolean |
|||
} |
|||
|
|||
interface State { |
|||
open: boolean |
|||
} |
|||
|
|||
const DataView: React.FC<DataViewProps> = (props) => { |
|||
const [state, setState] = useState<State>({ open: !!props.startOpen }) |
|||
let children = null |
|||
let preview |
|||
|
|||
const toggleOpen = () => { |
|||
return () => { |
|||
setState({ |
|||
open: !state.open |
|||
}) |
|||
} |
|||
} |
|||
|
|||
useEffect(() => { |
|||
const { data } = props |
|||
const { open } = state |
|||
if (!data) { |
|||
return |
|||
} |
|||
const isArray = Array.isArray(data) |
|||
children = open ? <DataList data={data} /> : null |
|||
if (isArray) { |
|||
preview = '[...]' |
|||
} else { |
|||
preview = '{...}' |
|||
} |
|||
}, [props]) |
|||
|
|||
return ( |
|||
<ul className={styles.codeContainer}> |
|||
<li className={styles.codeWrap}> |
|||
<div className={styles.codeBox}> |
|||
<div onClick={toggleOpen()} className={styles.opener}> |
|||
{state.open ? ( |
|||
<span className={styles.openerOpen} /> |
|||
) : ( |
|||
<span className={styles.openerClose} /> |
|||
)} |
|||
</div> |
|||
<div className={styles.codeVal}>{preview}</div> |
|||
</div> |
|||
{children} |
|||
</li> |
|||
</ul> |
|||
) |
|||
} |
|||
|
|||
export default DataView |
@ -0,0 +1,28 @@ |
|||
import React, { useState, useEffect } from 'react' |
|||
|
|||
interface Props { |
|||
data: any |
|||
} |
|||
|
|||
const Simple: React.FC<Props> = (props) => { |
|||
const [data, setData] = useState(props.data) |
|||
|
|||
const valueToText = (value: any) => { |
|||
if (value === undefined) { |
|||
return 'undefined' |
|||
} else if (typeof value === 'number') { |
|||
return value.toString() |
|||
} |
|||
return JSON.stringify(value) |
|||
} |
|||
|
|||
useEffect(() => { |
|||
if (typeof data === 'string' && data.length > 200) { |
|||
setData(data.slice(0, 200) + '…') |
|||
} |
|||
}, [props]) |
|||
|
|||
return <div style={{ color: '#333' }}>{valueToText(data)}</div> |
|||
} |
|||
|
|||
export default Simple |
@ -0,0 +1,35 @@ |
|||
export interface ConsoleType { |
|||
logType: string |
|||
infos: any[] |
|||
} |
|||
|
|||
export class ConsoleStore { |
|||
logList: ConsoleType[] = [] |
|||
logType: string = 'All' |
|||
|
|||
addLog(log: ConsoleType) { |
|||
this.logList.push(log) |
|||
} |
|||
|
|||
clearLog() { |
|||
this.logList = [] |
|||
} |
|||
|
|||
get computeLogList() { |
|||
let ret: ConsoleType[] = [] |
|||
if (this.logType === 'All') { |
|||
ret = this.logList |
|||
} else { |
|||
ret = this.logList.filter((item) => { |
|||
return item.logType === this.logType.toLowerCase() |
|||
}) |
|||
} |
|||
return ret |
|||
} |
|||
|
|||
changeLogType(type: string) { |
|||
this.logType = type |
|||
} |
|||
} |
|||
|
|||
export default new ConsoleStore() |
Loading…
Reference in new issue