refactor: migrate json-viewer from vue-json-viewer to vue-json-pretty
- Replace vue-json-viewer with vue-json-pretty (actively maintained, Vue 3 native) - Map original props to vue-json-pretty API in bindProps for backward compatibility - Implement copy functionality via renderNodeActions slot with i18n support - Update style.scss from .jv-* to .vjs-* class namesmaster^2
parent
9badda3d11
commit
f71094e878
|
|
@ -45,7 +45,7 @@
|
|||
"qrcode": "catalog:",
|
||||
"tippy.js": "catalog:",
|
||||
"vue": "catalog:",
|
||||
"vue-json-viewer": "catalog:",
|
||||
"vue-json-pretty": "catalog:",
|
||||
"vue-router": "catalog:",
|
||||
"vue-tippy": "catalog:"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -10,14 +10,12 @@ import type {
|
|||
JsonViewerValue,
|
||||
} from './types';
|
||||
|
||||
import { computed, useAttrs } from 'vue';
|
||||
// @ts-expect-error - vue-json-viewer does not expose compatible typings for this import path
|
||||
import VueJsonViewerImport from 'vue-json-viewer';
|
||||
import { computed, ref, useAttrs } from 'vue';
|
||||
import VueJsonPretty from 'vue-json-pretty';
|
||||
import 'vue-json-pretty/lib/styles.css';
|
||||
|
||||
import { $t } from '@vben/locales';
|
||||
|
||||
import { isBoolean } from '@vben-core/shared/utils';
|
||||
|
||||
import JsonBigint from 'json-bigint';
|
||||
|
||||
defineOptions({ name: 'JsonViewer' });
|
||||
|
|
@ -42,33 +40,31 @@ const emit = defineEmits<{
|
|||
valueClick: [value: JsonViewerValue];
|
||||
}>();
|
||||
|
||||
/** CJS/UMD 在 Vite 下解析为 { default: Component },需解包否则会出现 missing template or render */
|
||||
const VueJsonViewer =
|
||||
(VueJsonViewerImport as { default?: typeof VueJsonViewerImport }).default ??
|
||||
VueJsonViewerImport;
|
||||
|
||||
const attrs: SetupContext['attrs'] = useAttrs();
|
||||
|
||||
function handleClick(event: MouseEvent) {
|
||||
if (
|
||||
event.target instanceof HTMLElement &&
|
||||
event.target.classList.contains('jv-item')
|
||||
) {
|
||||
const pathNode = event.target.closest('.jv-push');
|
||||
if (!pathNode || !pathNode.hasAttribute('path')) {
|
||||
return;
|
||||
}
|
||||
const param: JsonViewerValue = {
|
||||
el: event.target,
|
||||
path: pathNode.getAttribute('path') || '',
|
||||
depth: Number(pathNode.getAttribute('depth')) || 0,
|
||||
value: event.target.textContent || undefined,
|
||||
};
|
||||
const copiedPath = ref<null | string>(null);
|
||||
|
||||
param.value = JSON.parse(param.value);
|
||||
emit('valueClick', param);
|
||||
}
|
||||
emit('click', event);
|
||||
const copyConfig = computed(() => {
|
||||
return {
|
||||
copiedText: $t('ui.jsonViewer.copied'),
|
||||
copyText: $t('ui.jsonViewer.copy'),
|
||||
timeout: 2000,
|
||||
};
|
||||
});
|
||||
|
||||
function handleCopy(node: any, defaultCopy: () => void) {
|
||||
defaultCopy();
|
||||
copiedPath.value = node.path;
|
||||
emit('copied', {
|
||||
action: 'copy',
|
||||
text: JSON.stringify(node.content),
|
||||
trigger: node.el ?? document.body,
|
||||
});
|
||||
setTimeout(() => {
|
||||
if (copiedPath.value === node.path) {
|
||||
copiedPath.value = null;
|
||||
}
|
||||
}, copyConfig.value.timeout ?? 2000);
|
||||
}
|
||||
|
||||
// 支持显示 bigint 数据,如较长的订单号
|
||||
|
|
@ -86,30 +82,46 @@ const jsonData = computed<Record<string, any>>(() => {
|
|||
});
|
||||
|
||||
const bindProps = computed<Recordable<any>>(() => {
|
||||
const copyable = {
|
||||
copyText: $t('ui.jsonViewer.copy'),
|
||||
copiedText: $t('ui.jsonViewer.copied'),
|
||||
timeout: 2000,
|
||||
...(isBoolean(props.copyable) ? {} : props.copyable),
|
||||
};
|
||||
const prettyTheme =
|
||||
props.theme === 'dark' || props.theme === 'dark-json-theme'
|
||||
? 'dark'
|
||||
: 'light';
|
||||
|
||||
return {
|
||||
...props,
|
||||
...attrs,
|
||||
value: jsonData.value,
|
||||
onCopied: (event: JsonViewerAction) => emit('copied', event),
|
||||
onKeyclick: (key: string) => emit('keyClick', key),
|
||||
onClick: (event: MouseEvent) => handleClick(event),
|
||||
copyable: props.copyable ? copyable : false,
|
||||
data: jsonData.value,
|
||||
deep: props.expanded ? Infinity : props.expandDepth,
|
||||
showDoubleQuotes: props.showDoubleQuotes,
|
||||
showLine: props.boxed,
|
||||
showLength: true,
|
||||
showIcon: true,
|
||||
theme: prettyTheme,
|
||||
collapsedNodeLength: props.previewMode ? 0 : Infinity,
|
||||
renderNodeActions: !!props.copyable,
|
||||
};
|
||||
});
|
||||
</script>
|
||||
<template>
|
||||
<VueJsonViewer v-bind="bindProps">
|
||||
<template #copy="slotProps">
|
||||
<slot name="copy" v-bind="slotProps"></slot>
|
||||
</template>
|
||||
</VueJsonViewer>
|
||||
<div :class="[props.theme, { boxed: props.boxed }]" class="vben-json-viewer">
|
||||
<VueJsonPretty v-bind="bindProps">
|
||||
<template #renderNodeActions="{ node, defaultActions }">
|
||||
<slot name="copy" :node="node" :default-actions="defaultActions">
|
||||
<span
|
||||
v-if="props.copyable"
|
||||
class="vben-json-copy-btn"
|
||||
:class="[{ 'is-copied': copiedPath === node.path }]"
|
||||
@click.stop="handleCopy(node, defaultActions.copy)"
|
||||
>
|
||||
{{
|
||||
copiedPath === node.path
|
||||
? copyConfig.copiedText
|
||||
: copyConfig.copyText
|
||||
}}
|
||||
</span>
|
||||
</slot>
|
||||
</template>
|
||||
</VueJsonPretty>
|
||||
</div>
|
||||
</template>
|
||||
<style lang="scss">
|
||||
@use './style.scss';
|
||||
|
|
|
|||
|
|
@ -1,98 +1,91 @@
|
|||
.default-json-theme {
|
||||
.vben-json-viewer {
|
||||
font-family: Consolas, Menlo, Courier, monospace;
|
||||
font-size: 14px;
|
||||
color: hsl(var(--foreground));
|
||||
white-space: nowrap;
|
||||
background: hsl(var(--background));
|
||||
|
||||
&.jv-container.boxed {
|
||||
&.boxed {
|
||||
padding: 10px;
|
||||
border: 1px solid hsl(var(--border));
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.jv-ellipsis {
|
||||
display: inline-block;
|
||||
padding: 0 4px 2px;
|
||||
font-size: 0.9em;
|
||||
line-height: 0.9;
|
||||
vertical-align: 2px;
|
||||
.vjs-key {
|
||||
color: hsl(var(--heavy-foreground));
|
||||
}
|
||||
|
||||
.vjs-value-null,
|
||||
.vjs-value-undefined {
|
||||
color: hsl(var(--secondary-foreground));
|
||||
}
|
||||
|
||||
.vjs-value-boolean {
|
||||
color: hsl(var(--red-400));
|
||||
}
|
||||
|
||||
.vjs-value-number {
|
||||
color: hsl(var(--info-foreground));
|
||||
}
|
||||
|
||||
.vjs-value-string {
|
||||
color: hsl(var(--primary));
|
||||
overflow-wrap: break-word;
|
||||
white-space: normal;
|
||||
}
|
||||
|
||||
.vjs-tree-brackets {
|
||||
color: hsl(var(--heavy-foreground));
|
||||
}
|
||||
|
||||
.vjs-comment {
|
||||
color: hsl(var(--secondary-foreground));
|
||||
}
|
||||
|
||||
.vjs-tree-node {
|
||||
&.is-highlight {
|
||||
background-color: hsl(var(--accent));
|
||||
}
|
||||
}
|
||||
|
||||
.vjs-carets {
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
color: hsl(var(--primary));
|
||||
}
|
||||
}
|
||||
|
||||
.vjs-indent-unit {
|
||||
&.has-line {
|
||||
border-left: 1px solid hsl(var(--border));
|
||||
}
|
||||
}
|
||||
|
||||
.vben-json-copy-btn {
|
||||
display: inline-block;
|
||||
padding: 0 6px;
|
||||
margin-left: 8px;
|
||||
font-size: 12px;
|
||||
line-height: 20px;
|
||||
color: hsl(var(--primary));
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
background-color: hsl(var(--secondary));
|
||||
border-radius: 3px;
|
||||
}
|
||||
opacity: 0;
|
||||
transition: opacity 0.2s;
|
||||
|
||||
.jv-button {
|
||||
color: hsl(var(--primary));
|
||||
}
|
||||
|
||||
.jv-key {
|
||||
color: hsl(var(--heavy-foreground));
|
||||
}
|
||||
|
||||
.jv-item {
|
||||
&.jv-array {
|
||||
color: hsl(var(--heavy-foreground));
|
||||
&:hover {
|
||||
background-color: hsl(var(--accent));
|
||||
}
|
||||
|
||||
&.jv-boolean {
|
||||
color: hsl(var(--red-400));
|
||||
}
|
||||
|
||||
&.jv-function {
|
||||
color: hsl(var(--destructive-foreground));
|
||||
}
|
||||
|
||||
&.jv-number {
|
||||
color: hsl(var(--info-foreground));
|
||||
}
|
||||
|
||||
&.jv-number-float {
|
||||
color: hsl(var(--info-foreground));
|
||||
}
|
||||
|
||||
&.jv-number-integer {
|
||||
color: hsl(var(--info-foreground));
|
||||
}
|
||||
|
||||
&.jv-object {
|
||||
color: hsl(var(--accent-darker));
|
||||
}
|
||||
|
||||
&.jv-undefined {
|
||||
color: hsl(var(--secondary-foreground));
|
||||
}
|
||||
|
||||
&.jv-string {
|
||||
color: hsl(var(--primary));
|
||||
overflow-wrap: break-word;
|
||||
white-space: normal;
|
||||
&.is-copied {
|
||||
color: hsl(var(--success-foreground, var(--primary)));
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
&.jv-container .jv-code {
|
||||
padding: 10px;
|
||||
|
||||
&.boxed:not(.open) {
|
||||
padding-bottom: 20px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
&.open {
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
|
||||
.jv-toggle {
|
||||
&::before {
|
||||
padding: 0 2px;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
&::before {
|
||||
background: hsl(var(--accent-foreground));
|
||||
}
|
||||
}
|
||||
}
|
||||
.vjs-tree-node:hover .vben-json-copy-btn {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -492,9 +492,9 @@ catalogs:
|
|||
vue-i18n:
|
||||
specifier: ^11.4.4
|
||||
version: 11.4.4
|
||||
vue-json-viewer:
|
||||
specifier: ^3.0.4
|
||||
version: 3.0.4
|
||||
vue-json-pretty:
|
||||
specifier: ^2.6.0
|
||||
version: 2.6.0
|
||||
vue-router:
|
||||
specifier: ^5.0.7
|
||||
version: 5.0.7
|
||||
|
|
@ -1689,9 +1689,9 @@ importers:
|
|||
vue:
|
||||
specifier: ^3.5.34
|
||||
version: 3.5.34(typescript@6.0.3)
|
||||
vue-json-viewer:
|
||||
vue-json-pretty:
|
||||
specifier: 'catalog:'
|
||||
version: 3.0.4(vue@3.5.34(typescript@6.0.3))
|
||||
version: 2.6.0(vue@3.5.34(typescript@6.0.3))
|
||||
vue-router:
|
||||
specifier: 'catalog:'
|
||||
version: 5.0.7(@vue/compiler-sfc@3.5.34)(pinia@3.0.4(typescript@6.0.3)(vue@3.5.34(typescript@6.0.3)))(vue@3.5.34(typescript@6.0.3))
|
||||
|
|
@ -6404,9 +6404,6 @@ packages:
|
|||
resolution: {integrity: sha512-xRwvIOMGrfOAnM1JYtqQImuaNtDEv9v6oIYAs4LIHwTiKee8uwvIi363igssOC0O5U04i4AlENs79LQLu9tEMw==}
|
||||
engines: {node: '>=20'}
|
||||
|
||||
clipboard@2.0.11:
|
||||
resolution: {integrity: sha512-C+0bbOqkezLIsmWSvlsXS0Q0bmkugu7jcfMIACB+RDEntIzQIkdr148we28AfSloQLRdZlYL/QYyrq05j/3Faw==}
|
||||
|
||||
cliui@6.0.0:
|
||||
resolution: {integrity: sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==}
|
||||
|
||||
|
|
@ -6805,9 +6802,6 @@ packages:
|
|||
resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==}
|
||||
engines: {node: '>=0.4.0'}
|
||||
|
||||
delegate@3.2.0:
|
||||
resolution: {integrity: sha512-IofjkYBZaZivn0V8nnsMJGBr4jVLxHDheKSW88PyxS5QC4Vo9ZbZVvhzlSxY87fVq3STR6r+4cGepyHkcWOQSw==}
|
||||
|
||||
denque@2.1.0:
|
||||
resolution: {integrity: sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==}
|
||||
engines: {node: '>=0.10'}
|
||||
|
|
@ -7611,9 +7605,6 @@ packages:
|
|||
globrex@0.1.2:
|
||||
resolution: {integrity: sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==}
|
||||
|
||||
good-listener@1.2.2:
|
||||
resolution: {integrity: sha512-goW1b+d9q/HIwbVYZzZ6SsTr4IgE+WA44A0GmPIQstuOrgsFcT7VEJ48nmr9GaRtNu0XTKacFLGnBPAM6Afouw==}
|
||||
|
||||
gopd@1.2.0:
|
||||
resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
|
@ -9618,9 +9609,6 @@ packages:
|
|||
seemly@0.3.10:
|
||||
resolution: {integrity: sha512-2+SMxtG1PcsL0uyhkumlOU6Qo9TAQ/WyH7tthnPIOQB05/12jz9naq6GZ6iZ6ApVsO3rr2gsnTf3++OV63kE1Q==}
|
||||
|
||||
select@1.1.2:
|
||||
resolution: {integrity: sha512-OwpTSOfy6xSs1+pwcNrv0RBMOzI39Lp3qQKUTPVVPRjCdNa5JH/oPRiqsesIskK8TVgmRiHwO4KXlV2Li9dANA==}
|
||||
|
||||
semver-compare@1.0.0:
|
||||
resolution: {integrity: sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow==}
|
||||
|
||||
|
|
@ -10085,9 +10073,6 @@ packages:
|
|||
resolution: {integrity: sha512-B71/4oyj61iNH0KeCamLuE2rmKuTO5byTOSVwECM5FA7TiAiAW+UqTKZ9ERueC4qvgSttUhdmq1mXC3kJqGX7A==}
|
||||
engines: {node: '>=12.22'}
|
||||
|
||||
tiny-emitter@2.1.0:
|
||||
resolution: {integrity: sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q==}
|
||||
|
||||
tinybench@2.9.0:
|
||||
resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==}
|
||||
|
||||
|
|
@ -10757,8 +10742,9 @@ packages:
|
|||
peerDependencies:
|
||||
vue: ^3.5.34
|
||||
|
||||
vue-json-viewer@3.0.4:
|
||||
resolution: {integrity: sha512-pnC080rTub6YjccthVSNQod2z9Sl5IUUq46srXtn6rxwhW8QM4rlYn+CTSLFKXWfw+N3xv77Cioxw7B4XUKIbQ==}
|
||||
vue-json-pretty@2.6.0:
|
||||
resolution: {integrity: sha512-glz1aBVS35EO8+S9agIl3WOQaW2cJZW192UVKTuGmryx01ZvOVWc4pR3t+5UcyY4jdOfBUgVHjcpRpcnjRhCAg==}
|
||||
engines: {node: '>= 10.0.0', npm: '>= 5.0.0'}
|
||||
peerDependencies:
|
||||
vue: ^3.5.34
|
||||
|
||||
|
|
@ -15441,12 +15427,6 @@ snapshots:
|
|||
slice-ansi: 8.0.0
|
||||
string-width: 8.2.1
|
||||
|
||||
clipboard@2.0.11:
|
||||
dependencies:
|
||||
good-listener: 1.2.2
|
||||
select: 1.1.2
|
||||
tiny-emitter: 2.1.0
|
||||
|
||||
cliui@6.0.0:
|
||||
dependencies:
|
||||
string-width: 4.2.3
|
||||
|
|
@ -15838,8 +15818,6 @@ snapshots:
|
|||
|
||||
delayed-stream@1.0.0: {}
|
||||
|
||||
delegate@3.2.0: {}
|
||||
|
||||
denque@2.1.0: {}
|
||||
|
||||
depcheck@1.4.7:
|
||||
|
|
@ -16852,10 +16830,6 @@ snapshots:
|
|||
|
||||
globrex@0.1.2: {}
|
||||
|
||||
good-listener@1.2.2:
|
||||
dependencies:
|
||||
delegate: 3.2.0
|
||||
|
||||
gopd@1.2.0: {}
|
||||
|
||||
graceful-fs@4.2.10: {}
|
||||
|
|
@ -18944,8 +18918,6 @@ snapshots:
|
|||
|
||||
seemly@0.3.10: {}
|
||||
|
||||
select@1.1.2: {}
|
||||
|
||||
semver-compare@1.0.0: {}
|
||||
|
||||
semver@5.7.2:
|
||||
|
|
@ -19508,8 +19480,6 @@ snapshots:
|
|||
|
||||
throttle-debounce@5.0.2: {}
|
||||
|
||||
tiny-emitter@2.1.0: {}
|
||||
|
||||
tinybench@2.9.0: {}
|
||||
|
||||
tinyclip@0.1.12: {}
|
||||
|
|
@ -20161,9 +20131,8 @@ snapshots:
|
|||
'@vue/devtools-api': 6.6.4
|
||||
vue: 3.5.34(typescript@6.0.3)
|
||||
|
||||
vue-json-viewer@3.0.4(vue@3.5.34(typescript@6.0.3)):
|
||||
vue-json-pretty@2.6.0(vue@3.5.34(typescript@6.0.3)):
|
||||
dependencies:
|
||||
clipboard: 2.0.11
|
||||
vue: 3.5.34(typescript@6.0.3)
|
||||
|
||||
vue-router@5.0.7(@vue/compiler-sfc@3.5.34)(pinia@3.0.4(typescript@6.0.3)(vue@3.5.34(typescript@6.0.3)))(vue@3.5.34(typescript@6.0.3)):
|
||||
|
|
|
|||
|
|
@ -203,7 +203,7 @@ catalog:
|
|||
vue: ^3.5.34
|
||||
vue-eslint-parser: ^10.4.0
|
||||
vue-i18n: ^11.4.4
|
||||
vue-json-viewer: ^3.0.4
|
||||
vue-json-pretty: ^2.6.0
|
||||
vue-router: ^5.0.7
|
||||
vue-tippy: ^6.7.1
|
||||
vue-tsc: ^3.3.1
|
||||
|
|
|
|||
Loading…
Reference in New Issue