Skip to content

Commit 28b4865

Browse files
committed
feat: 基本功能实现
1 parent 9808af1 commit 28b4865

File tree

5 files changed

+463
-0
lines changed

5 files changed

+463
-0
lines changed

src/index.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import JsonTree from './json-tree/JsonTree.vue'
2+
3+
export default JsonTree

src/json-tree/JsonTree.vue

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
<template>
2+
<div class="json-tree">
3+
<JsonTreeNode ref="jsonTreeNode"
4+
:json-data="jsonData"
5+
:show-line="showLine"
6+
:indent="indent"
7+
/>
8+
</div>
9+
</template>
10+
11+
<script>
12+
import JsonTreeNode from './JsonTreeNode'
13+
14+
export default {
15+
name: 'JsonTree',
16+
components: {
17+
JsonTreeNode
18+
},
19+
props: {
20+
jsonData: {
21+
required: true
22+
},
23+
// 是否显示树图的辅助连接线
24+
showLine: {
25+
type: Boolean,
26+
default: true
27+
},
28+
// 每一层级的缩进
29+
// 如果类型是 string(如 '20px'), 则将左边距设置为 20px
30+
// 如果类型是 number(如 10), 则将左边距设置为 10px
31+
indent: {
32+
type: [String, Number],
33+
default: '20px'
34+
}
35+
},
36+
methods: {
37+
expandAll () {
38+
console.time()
39+
this.$refs.jsonTreeNode.expandNode()
40+
console.timeEnd()
41+
},
42+
collapseAll () {
43+
console.time()
44+
this.$refs.jsonTreeNode.collapseNode()
45+
console.timeEnd()
46+
},
47+
// TODO
48+
filter (text) {},
49+
// TODO: 查找指定项, 从 key 或 value 中查找
50+
// config: {
51+
// origin: 'key' // key(仅在 key 中查找), value(仅在 value 中查找), both(在所有中查找)
52+
// }
53+
find (keyword, config) {},
54+
// TODO: 根据访问路径展开指定项
55+
expand (paths) {}
56+
}
57+
}
58+
</script>

src/json-tree/JsonTreeNode.vue

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
<template>
2+
<div class="json-tree__node"
3+
:data-json-tree-node-deep="deep"
4+
:style="{ paddingLeft: nodePaddingLeft }">
5+
<!-- 展开和收起两种状态 -->
6+
<i v-text="localCollapse ? '+' : '-'"
7+
:class="{
8+
'cursor-pointer': true,
9+
'json-tree__toggle': true,
10+
'json-tree__toggle--hidden': shouldHideToggle
11+
}"
12+
@click.stop="onToggle">
13+
</i>
14+
<div :class="{
15+
'json-tree__content': true,
16+
'json-tree__content--collapse': localCollapse
17+
}" @click.stop="onNodeClick">
18+
<div class="json-tree__content__header">
19+
<span v-if="showJsonKey">
20+
<span v-text="jsonKey" :class="[
21+
'json-tree__key',
22+
`json-tree__key--${typeof jsonKey}`
23+
]"></span>
24+
<span class="json-tree__colon">:</span>
25+
</span>
26+
<template v-if="localCollapse">
27+
<template v-if="jsonDataType === 'array'">
28+
<span v-text="shouldHideToggle ? '[]' : '[...]'"
29+
class="json-tree__bracket json-tree__bracket--collapse"
30+
:class="shouldHideToggle ? '' : 'cursor-pointer'"
31+
@click.stop="onToggle">
32+
</span>
33+
<span v-text="cntTip" class="json-tree__cnt-tips"></span>
34+
</template>
35+
36+
<template v-else-if="jsonDataType === 'object'">
37+
<span v-text="shouldHideToggle ? '{}' : '{...}'"
38+
class="json-tree__brace json-tree__brace--collapse"
39+
:class="shouldHideToggle ? '' : 'cursor-pointer'"
40+
@click.stop="onToggle">
41+
</span>
42+
<span v-text="cntTip" class="json-tree__cnt-tips"></span>
43+
</template>
44+
<span v-else v-text="displayedJsonVal"
45+
:class="[
46+
'json-tree__val',
47+
`json-tree__val--${jsonDataType}`
48+
]">
49+
</span>
50+
</template>
51+
<template v-else>
52+
<span v-if="jsonDataType === 'array'">
53+
<span class="json-tree__bracket--left">[</span>
54+
<span v-text="cntTip" class="json-tree__cnt-tips"></span>
55+
</span>
56+
<span v-else-if="jsonDataType === 'object'">
57+
<span v-text="'{'" class="json-tree__brace--left"></span>
58+
<span v-text="cntTip" class="json-tree__cnt-tips"></span>
59+
</span>
60+
<span v-else v-text="displayedJsonVal"
61+
:class="[
62+
'json-tree__val',
63+
`json-tree__val--${jsonDataType}`
64+
]">
65+
</span>
66+
</template>
67+
</div>
68+
<template v-if="['array', 'object'].includes(jsonDataType)">
69+
<JsonTreeNode
70+
v-for="(childData, i) in jsonData"
71+
:key="`${deep}-${i}`"
72+
:ref="`jsonTreeNode${i}`"
73+
:json-key="i"
74+
:json-keys="[...jsonKeys, i]"
75+
:json-data="childData"
76+
:deep="deep + 1"
77+
:show-line="showLine"
78+
/>
79+
</template>
80+
<div v-if="!localCollapse && ['array', 'object'].includes(jsonDataType)"
81+
class="json-tree__content__footer">
82+
<span v-if="jsonDataType === 'array'"
83+
v-text="']'"
84+
class="json-tree__bracket--right">
85+
</span>
86+
<span v-else-if="jsonDataType === 'object'"
87+
v-text="'}'"
88+
class="json-tree__brace--right">
89+
</span>
90+
</div>
91+
<div v-if="showLine" class="json-tree__connector-line"></div>
92+
</div>
93+
</div>
94+
</template>
95+
96+
<script src="./json-tree-node.js"></script>
97+
98+
<style lang="scss" src="./json-tree.scss"></style>

src/json-tree/json-tree-node.js

Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
export default {
2+
name: 'JsonTreeNode',
3+
props: {
4+
jsonData: {
5+
required: true
6+
},
7+
showLine: {
8+
type: Boolean,
9+
default: true
10+
},
11+
indent: {
12+
type: [String, Number],
13+
default: '20px'
14+
},
15+
jsonKey: {
16+
type: [String, Number]
17+
},
18+
// 当前节点的 key path
19+
jsonKeys: {
20+
type: Array,
21+
default: () => []
22+
},
23+
deep: {
24+
type: Number,
25+
default: 0
26+
},
27+
collapse: {
28+
type: Boolean,
29+
default: true
30+
}
31+
},
32+
data () {
33+
return {
34+
localCollapse: !!this.collapse
35+
}
36+
},
37+
computed: {
38+
nodePaddingLeft () {
39+
if (this.deep === 0) return '0'
40+
41+
const { indent } = this
42+
if (typeof indent === 'string') return indent
43+
if (typeof indent === 'number') return `${indent}px`
44+
// 如果显示连接线, 则增大边距
45+
return this.showLine ? '20px' : '10px'
46+
},
47+
jsonDataType () {
48+
const { jsonData } = this
49+
if (Array.isArray(jsonData)) return 'array'
50+
// TODO: Set 和 Map 支持
51+
if (jsonData instanceof Map) return 'map'
52+
if (jsonData instanceof Set) return 'set'
53+
if (jsonData == null) return 'null'
54+
if (Object.prototype.toString.call(jsonData) === '[object Object]') {
55+
return 'object'
56+
}
57+
58+
// string, number, undefined, function, date, symbol
59+
return (typeof jsonData)
60+
},
61+
// 是否应该隐藏 toggle
62+
shouldHideToggle () {
63+
const { jsonData, jsonDataType } = this
64+
// 如果是空数组, 则隐藏 toggle
65+
if (jsonDataType === 'array') return jsonData.length === 0
66+
// 如果是空对象, 则隐藏 toggle
67+
if (jsonDataType === 'object') return Object.keys(jsonData).length === 0
68+
69+
// 以下几种特殊类型, 则隐藏 toggle
70+
return [
71+
'number',
72+
'string',
73+
'null',
74+
'undefined',
75+
'symbol',
76+
'map', // map 类型暂时不可展开
77+
'set' // set 类型暂时不可展开
78+
].includes(jsonDataType)
79+
},
80+
// 是否显示 key
81+
showJsonKey () {
82+
const { jsonKey } = this
83+
return (typeof jsonKey === 'number') || jsonKey
84+
},
85+
// 仅用于 jsonData 不是 array 且不是 object 时
86+
// TODO: 暂未考虑 Set 和 Map 类型
87+
displayedJsonVal () {
88+
const { jsonData, jsonDataType } = this
89+
if (['array', 'object'].includes(jsonDataType)) return ''
90+
if (['undefined', 'null'].includes(jsonDataType)) return String(jsonData)
91+
if (jsonDataType === 'string') return `"${jsonData}"`
92+
return jsonData // data 为 number 类型
93+
},
94+
cntTip () {
95+
const { jsonData, jsonDataType } = this
96+
97+
if (jsonDataType === 'array') {
98+
return `// ${jsonData.length} items`
99+
}
100+
101+
if (jsonDataType === 'object') {
102+
return `// ${Object.keys(jsonData).length} keys`
103+
}
104+
105+
return ''
106+
}
107+
},
108+
methods: {
109+
// 递归调用
110+
expandNode () {
111+
console.log('expandNode')
112+
const { jsonData, jsonDataType, asyncCall, $refs } = this
113+
if (this.shouldHideToggle) {
114+
this.localCollapse = true
115+
} else {
116+
this.localCollapse = false
117+
}
118+
119+
if (jsonDataType === 'array') {
120+
jsonData.forEach((v, i) => {
121+
asyncCall(() => {
122+
try {
123+
$refs[`jsonTreeNode${i}`][0].expandNode()
124+
} catch (e) {}
125+
})
126+
})
127+
} else if (jsonDataType === 'object') {
128+
for (const k in jsonData) {
129+
asyncCall(() => {
130+
try {
131+
$refs[`jsonTreeNode${k}`][0].expandNode()
132+
} catch (e) {}
133+
})
134+
}
135+
} else {
136+
// 其他类型的值不会有展开的操作
137+
}
138+
},
139+
collapseNode () {
140+
console.log('collapseNode')
141+
const { jsonData, jsonDataType, asyncCall, $refs } = this
142+
this.localCollapse = true
143+
if (jsonDataType === 'array') {
144+
jsonData.forEach((v, i) => {
145+
asyncCall(() => {
146+
try {
147+
$refs[`jsonTreeNode${i}`][0].collapseNode()
148+
} catch (e) {}
149+
})
150+
})
151+
} else if (jsonDataType === 'object') {
152+
for (const k in jsonData) {
153+
asyncCall(() => {
154+
try {
155+
$refs[`jsonTreeNode${k}`][0].collapseNode()
156+
} catch (e) {}
157+
})
158+
}
159+
} else {
160+
// 其他类型的值不会有展开的操作
161+
}
162+
},
163+
onToggle () {
164+
if (this.shouldHideToggle) return
165+
this.localCollapse = !this.localCollapse
166+
167+
if (this.localCollapse) {
168+
this.$emit('node-collapse', {})
169+
} else {
170+
this.$emit('node-expand', {})
171+
}
172+
},
173+
onNodeClick () {
174+
const { jsonKey, jsonKeys, jsonData } = this
175+
this.$emit('node-click', { jsonKey, jsonKeys, jsonData })
176+
},
177+
asyncCall (callback) {
178+
// if (typeof requestAnimationFrame === 'function') {
179+
// requestAnimationFrame(callback)
180+
// } else {
181+
// setTimeout(callback, 0)
182+
// }
183+
callback()
184+
}
185+
}
186+
}

0 commit comments

Comments
 (0)