11<template >
22 <ul class =" tree-view" >
3- <template v-for =" (value , key ) in data " >
4- <li v-if =" key === '#text'" :key =" key + '-text'" >
5- <span
6- v-if =" typeof value === 'string' && value.length > 80"
7- class =" tree-value-block"
8- >{{ value }}</span
9- >
10- <span v-else class =" tree-value" >{{ value }}</span >
11- </li >
12- <template v-else-if =" Array .isArray (value )" >
13- <li v-for =" (item, idx) in value" :key =" key + '-' + idx" >
14- <span @click =" toggle(key + '-' + idx)" class =" tree-key" >
15- <span class =" arrow" :class =" { open: isOpen(key + '-' + idx) }"
16- >▶</span
17- >
18- < ; {{ key }}> ;
19- </span >
20- <template v-if =" isOpen (key + ' -' + idx )" >
21- <TreeView :data =" item" />
22- <span class =" tree-key tree-close" >< ; /{{ key }}> ; </span >
3+ <template v-if =" Array .isArray (data )" >
4+ <TreeView v-for =" (item, idx) in data" :key =" idx" :data =" item" />
5+ </template >
6+
7+ <template v-else >
8+ <template v-for =" (value , key ) in filteredEntries (data ) " :key =" key " >
9+
10+ <!-- COMMENT -->
11+ <li v-if =" key === '#comment'" >
12+ <span class =" tree-comment" >< ; !-- {{ value }} --> ; </span >
13+ </li >
14+
15+ <!-- TEXT -->
16+ <li v-else-if =" key === '#text'" >
17+ <span class =" tree-value" >{{ value }}</span >
18+ </li >
19+
20+ <!-- ARRAY -->
21+ <li v-else-if =" Array.isArray(value)" >
22+ <div v-for =" (item, idx) in value" :key =" idx" >
23+ <span class =" tree-key" >
24+ <span @click =" toggle(String(key) + idx)" class =" arrow" :class =" { open: isOpen(String(key) + idx) }" >▶</span >
25+ < ; {{ key }}{{ formatAttrs(item) }}> ;
26+ </span >
27+
28+ <template v-if =" isOpen (String (key ) + idx )" >
29+ <TreeView :data =" item" />
30+ <span class =" tree-key" >< ; /{{ key }}> ; </span >
31+ </template >
32+
33+ <template v-else >
34+ <div class =" tree-key" >< ; /{{ key }}> ; </div >
35+ </template >
36+ </div >
37+ </li >
38+
39+ <!-- OBJECT -->
40+ <li v-else-if =" isObject(value)" >
41+ <template v-if =" Object .keys (value ).length === 0 " >
42+ <span class =" tree-key" >
43+ < ; {{ key }}{{ formatAttrs(value) }}/> ;
44+ </span >
45+ </template >
46+
47+ <template v-else-if =" value [' #text' ] && Object .keys (value ).length === 1 " >
48+ <span class =" tree-key" >
49+ < ; {{ key }}{{ formatAttrs(value) }}> ;
50+ </span >
51+ <span class =" tree-value" >{{ value['#text'] }}</span >
52+ <span class =" tree-key" >< ; /{{ key }}> ; </span >
2353 </template >
54+
2455 <template v-else >
25- <div class =" tree-collapsed" >
26- ...<br />
27- <span class =" tree-key tree-close" >< ; /{{ key }}> ; </span >
28- </div >
56+ <span class =" tree-key" >
57+ <span @click =" toggle(key)" class =" arrow" :class =" { open: isOpen(key) }" >▶</span >
58+ < ; {{ key }}{{ formatAttrs(value) }}> ;
59+ </span >
60+
61+ <template v-if =" isOpen (key )" >
62+ <TreeView :data =" value" />
63+ <span class =" tree-key" >< ; /{{ key }}> ; </span >
64+ </template >
65+
66+ <template v-else >
67+ <div class =" tree-key" >< ; /{{ key }}> ; </div >
68+ </template >
2969 </template >
3070 </li >
31- </template >
32- <li v-else-if =" isObject(value)" :key =" key + '-obj'" >
33- <template v-if =" Object .keys (value ).length === 0 " >
34- <span class =" tree-key" >< ; {{ key }}/> ; </span >
35- </template >
36- <template v-else-if =" Object .keys (value ).length === 1 && value [' #text' ]" >
71+
72+ <!-- VALUE -->
73+ <li v-else >
3774 <span class =" tree-key" >< ; {{ key }}> ; </span >
38- <span class =" tree-value" >{{ value['#text'] }}</span >
39- <span class =" tree-key tree-close" >< ; /{{ key }}> ; </span >
40- </template >
41- <template v-else >
42- <span @click =" toggle(key)" class =" tree-key" >
43- <span class =" arrow" :class =" { open: isOpen(key) }" >▶</span >
44- < ; {{ key }}> ;
45- </span >
46- <template v-if =" isOpen (key )" >
47- <TreeView :data =" value" />
48- <span class =" tree-key tree-close" >< ; /{{ key }}> ; </span >
49- </template >
50- <template v-else >
51- <div class =" tree-collapsed" >
52- ...<br />
53- <span class =" tree-key tree-close" >< ; /{{ key }}> ; </span >
54- </div >
55- </template >
56- </template >
57- </li >
58- <li v-else :key =" key + '-val'" >
59- <span class =" tree-key" >< ; {{ key }}> ; :</span >
60- <span
61- v-if =" typeof value === 'string' && value.length > 80"
62- class =" tree-value-block"
63- >{{ value }}</span
64- >
65- <span v-else class =" tree-value" >{{ value }}</span >
66- </li >
75+ <span class =" tree-value" >{{ value }}</span >
76+ <span class =" tree-key" >< ; /{{ key }}> ; </span >
77+ </li >
78+
79+ </template >
6780 </template >
6881 </ul >
6982</template >
7083
7184<script lang="ts">
72- import { defineComponent , ref } from " vue" ;
85+ import { defineComponent , ref , watch , onMounted } from " vue" ;
7386
7487export default defineComponent ({
7588 name: " TreeView" ,
@@ -78,14 +91,65 @@ export default defineComponent({
7891 },
7992 setup(props ) {
8093 const openKeys = ref <Record <string , boolean >>({});
94+
8195 const isObject = (val : any ) =>
8296 val && typeof val === " object" && ! Array .isArray (val );
97+
98+ // Recursively collect all keys for open state
99+ function collectKeys(obj : any , prefix = " " ): string [] {
100+ if (! obj || typeof obj !== " object" ) return [];
101+ let keys: string [] = [];
102+ for (const [key, value] of Object .entries (obj )) {
103+ if (key === " @attributes" ) continue ;
104+ if (Array .isArray (value )) {
105+ value .forEach ((item , idx ) => {
106+ keys .push (prefix + key + idx );
107+ keys = keys .concat (collectKeys (item , prefix + key + idx ));
108+ });
109+ } else if (isObject (value )) {
110+ keys .push (prefix + key );
111+ keys = keys .concat (collectKeys (value , prefix + key ));
112+ }
113+ }
114+ return keys ;
115+ }
116+
117+ // Set all keys to open
118+ function openAllKeys(data : any ) {
119+ const allKeys = collectKeys (data );
120+ const openObj: Record <string , boolean > = {};
121+ allKeys .forEach (k => { openObj [k ] = true ; });
122+ openKeys .value = openObj ;
123+ }
124+
125+ // Watch for data changes to open all
126+ watch (() => props .data , (val ) => {
127+ openAllKeys (val );
128+ }, { immediate: true , deep: true });
129+
83130 const isOpen = (key : string | number ) => openKeys .value [String (key )];
131+
84132 const toggle = (key : string | number ) => {
85133 const k = String (key );
86134 openKeys .value [k ] = ! openKeys .value [k ];
87135 };
88- return { isObject , isOpen , toggle };
136+
137+ const formatAttrs = (obj : any ) => {
138+ if (! obj || ! obj [' @attributes' ]) return ' ' ;
139+ return ' ' + Object .entries (obj [' @attributes' ])
140+ .map (([k , v ]) => ` ${k }="${v }" ` )
141+ .join (' ' );
142+ };
143+
144+
145+ const filteredEntries = (obj : any ): Record <string , any > | any [] => {
146+ if (! obj || typeof obj !== ' object' ) return {};
147+ return Object .fromEntries (
148+ Object .entries (obj ).filter (([key ]) => key !== ' @attributes' )
149+ );
150+ };
151+
152+ return { isObject , isOpen , toggle , formatAttrs , filteredEntries };
89153 },
90154});
91155 </script >
@@ -94,49 +158,30 @@ export default defineComponent({
94158.tree-view {
95159 list-style : none ;
96160 padding-left : 18px ;
97- font-family : " Fira Mono" , " Consolas" , " Menlo" , " Monaco" , monospace ;
98- font-size : 16px ;
161+ font-family : monospace ;
99162}
163+
100164.tree-key {
101- cursor : pointer ;
102165 color : #8e24aa ;
103- user-select : none ;
104- font-weight : 500 ;
105- transition : color 0.2s ;
106166}
167+
107168.tree-value {
108169 color : #333 ;
109170}
171+
110172.arrow {
173+ cursor : pointer ;
174+ user-select : none ;
111175 display : inline-block ;
112176 width : 1em ;
113- color : #888 ;
114- transition :
115- transform 0.2s ,
116- color 0.2s ;
117- vertical-align : middle ;
118- margin-right : 2px ;
119- font-size : 1em ;
177+ transition : transform 0.2s ;
120178}
179+
121180.arrow.open {
122181 transform : rotate (90deg );
123- color : #8e24aa ;
124182}
125- .tree-key {
126- display : inline-flex ;
127- align-items : center ;
128- }
129- .tree-value-block {
130- display : block ;
131- background : #f8f8f8 ;
132- color : #444 ;
133- border-radius : 4px ;
134- padding : 8px ;
135- margin : 4px 0 4px 16px ;
136- font-family : inherit ;
137- font-size : 15px ;
138- white-space : pre-wrap ;
139- overflow-x : auto ;
140- max-width : 100% ;
183+
184+ .tree-comment {
185+ color : green ;
141186}
142- </style >
187+ </style >
0 commit comments