Skip to content

Commit dced71a

Browse files
authored
Merge pull request #15 from hsdt/feat/xml-viewer
Sửa lỗi convert sai xml cho component xmlViewer
2 parents 23f4a7a + bbe7439 commit dced71a

5 files changed

Lines changed: 185 additions & 119 deletions

File tree

projects/shared/utils/virtual-html-parser.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ export class VirtualHTMLParser {
1919
'wbr',
2020
'Textarea',
2121
'InputOTP',
22+
'XmlViewer',
2223
];
2324
static parseToTree(
2425
htmlString: string,

projects/shell/src/app/pages/home/home.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,9 @@ export class Home{
6161
<div>Ảnh đã lưu:</div>
6262
<img :src="data.signature" alt="signature" style="max-width:200px;border:1px solid #ccc;" />
6363
</div>
64+
</PageA4>
65+
<PageA4 style="padding:5mm">
66+
<XmlViewer url="https://sample-files.com/downloads/data/xml/international.xml" />
6467
</PageA4>`;
6568
editMode = true;
6669
context = {

projects/template-editor/src/components/preview/PreviewWrapper.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ export default {
9494
size="md" v-model="hsBenhAn.NguyenNhanTuVong" value="DO_BENH" />
9595
</PageA4>
9696
<PageA4 style="padding:5mm">
97-
<XmlViewer url="http://localhost:5000/Content/Upload/2026/04/13/BA26000003_du_lieu.xml" />
97+
<XmlViewer url="https://sample-files.com/downloads/data/xml/international.xml" />
9898
</PageA4>
9999
`,
100100
context: {
Lines changed: 137 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -1,75 +1,88 @@
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-
&lt;{{ key }}&gt;
19-
</span>
20-
<template v-if="isOpen(key + '-' + idx)">
21-
<TreeView :data="item" />
22-
<span class="tree-key tree-close">&lt;/{{ key }}&gt;</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">&lt;!-- {{ value }} --&gt;</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+
&lt;{{ key }}{{ formatAttrs(item) }}&gt;
26+
</span>
27+
28+
<template v-if="isOpen(String(key) + idx)">
29+
<TreeView :data="item" />
30+
<span class="tree-key">&lt;/{{ key }}&gt;</span>
31+
</template>
32+
33+
<template v-else>
34+
<div class="tree-key">&lt;/{{ key }}&gt;</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+
&lt;{{ key }}{{ formatAttrs(value) }}/&gt;
44+
</span>
45+
</template>
46+
47+
<template v-else-if="value['#text'] && Object.keys(value).length === 1">
48+
<span class="tree-key">
49+
&lt;{{ key }}{{ formatAttrs(value) }}&gt;
50+
</span>
51+
<span class="tree-value">{{ value['#text'] }}</span>
52+
<span class="tree-key">&lt;/{{ key }}&gt;</span>
2353
</template>
54+
2455
<template v-else>
25-
<div class="tree-collapsed">
26-
...<br />
27-
<span class="tree-key tree-close">&lt;/{{ key }}&gt;</span>
28-
</div>
56+
<span class="tree-key">
57+
<span @click="toggle(key)" class="arrow" :class="{ open: isOpen(key) }">▶</span>
58+
&lt;{{ key }}{{ formatAttrs(value) }}&gt;
59+
</span>
60+
61+
<template v-if="isOpen(key)">
62+
<TreeView :data="value" />
63+
<span class="tree-key">&lt;/{{ key }}&gt;</span>
64+
</template>
65+
66+
<template v-else>
67+
<div class="tree-key">&lt;/{{ key }}&gt;</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">&lt;{{ key }}/&gt;</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">&lt;{{ key }}&gt;</span>
38-
<span class="tree-value">{{ value['#text'] }}</span>
39-
<span class="tree-key tree-close">&lt;/{{ key }}&gt;</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-
&lt;{{ key }}&gt;
45-
</span>
46-
<template v-if="isOpen(key)">
47-
<TreeView :data="value" />
48-
<span class="tree-key tree-close">&lt;/{{ key }}&gt;</span>
49-
</template>
50-
<template v-else>
51-
<div class="tree-collapsed">
52-
...<br />
53-
<span class="tree-key tree-close">&lt;/{{ key }}&gt;</span>
54-
</div>
55-
</template>
56-
</template>
57-
</li>
58-
<li v-else :key="key + '-val'">
59-
<span class="tree-key">&lt;{{ key }}&gt;:</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">&lt;/{{ key }}&gt;</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
7487
export 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

Comments
 (0)