gaoluyang
2026-06-24 c0cb161bb52ce0fbdce5c66ec391d107c75e2452
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
<script lang="ts" setup>
import type { SetupContext } from 'vue';
 
import type { Recordable } from '..\..\..\..\..\types\src';
 
import type {
  JsonViewerAction,
  JsonViewerProps,
  JsonViewerToggle,
  JsonViewerValue,
} from './types';
 
import { computed, ref, useAttrs } from 'vue';
import VueJsonPretty from 'vue-json-pretty';
import 'vue-json-pretty/lib/styles.css';
 
import { $t } from '..\..\..\..\..\locales\src';
 
import JsonBigint from 'json-bigint';
 
defineOptions({ name: 'JsonViewer' });
 
const props = withDefaults(defineProps<JsonViewerProps>(), {
  expandDepth: 1,
  copyable: false,
  sort: false,
  boxed: false,
  theme: 'default-json-theme',
  expanded: false,
  previewMode: false,
  showArrayIndex: true,
  showDoubleQuotes: false,
});
 
const emit = defineEmits<{
  click: [event: MouseEvent];
  copied: [event: JsonViewerAction];
  keyClick: [key: string];
  toggle: [param: JsonViewerToggle];
  valueClick: [value: JsonViewerValue];
}>();
 
const attrs: SetupContext['attrs'] = useAttrs();
 
const copiedPath = ref<null | string>(null);
 
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 数据,如较长的订单号
const jsonData = computed<Record<string, any>>(() => {
  if (typeof props.value !== 'string') {
    return props.value || {};
  }
 
  try {
    return JsonBigint({ storeAsString: true }).parse(props.value);
  } catch (error) {
    console.error('JSON parse error:', error);
    return {};
  }
});
 
const bindProps = computed<Recordable<any>>(() => {
  const prettyTheme =
    props.theme === 'dark' || props.theme === 'dark-json-theme'
      ? 'dark'
      : 'light';
 
  return {
    ...attrs,
    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>
  <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';
</style>