diff --git a/packages/effects/common-ui/package.json b/packages/effects/common-ui/package.json
index aca45c7a..37b1b76a 100644
--- a/packages/effects/common-ui/package.json
+++ b/packages/effects/common-ui/package.json
@@ -35,6 +35,7 @@
"qrcode": "catalog:",
"tippy.js": "catalog:",
"vue": "catalog:",
+ "vue-json-viewer": "catalog:",
"vue-router": "catalog:",
"vue-tippy": "catalog:"
},
diff --git a/packages/effects/common-ui/src/components/index.ts b/packages/effects/common-ui/src/components/index.ts
index e1874de7..a1830850 100644
--- a/packages/effects/common-ui/src/components/index.ts
+++ b/packages/effects/common-ui/src/components/index.ts
@@ -3,6 +3,7 @@ export * from './captcha';
export * from './col-page';
export * from './ellipsis-text';
export * from './icon-picker';
+export * from './json-viewer';
export * from './page';
export * from './resize';
export * from './tippy';
diff --git a/packages/effects/common-ui/src/components/json-viewer/index.ts b/packages/effects/common-ui/src/components/json-viewer/index.ts
new file mode 100644
index 00000000..1aec78bb
--- /dev/null
+++ b/packages/effects/common-ui/src/components/json-viewer/index.ts
@@ -0,0 +1,3 @@
+export { default as JsonViewer } from './index.vue';
+
+export * from './types';
diff --git a/packages/effects/common-ui/src/components/json-viewer/index.vue b/packages/effects/common-ui/src/components/json-viewer/index.vue
new file mode 100644
index 00000000..2b609dcb
--- /dev/null
+++ b/packages/effects/common-ui/src/components/json-viewer/index.vue
@@ -0,0 +1,77 @@
+
+
+
+
+
+
+
+
+
diff --git a/packages/effects/common-ui/src/components/json-viewer/style.scss b/packages/effects/common-ui/src/components/json-viewer/style.scss
new file mode 100644
index 00000000..73ce22b9
--- /dev/null
+++ b/packages/effects/common-ui/src/components/json-viewer/style.scss
@@ -0,0 +1,98 @@
+.default-json-theme {
+ font-family: Consolas, Menlo, Courier, monospace;
+ font-size: 14px;
+ color: hsl(var(--foreground));
+ white-space: nowrap;
+ background: hsl(var(--background));
+
+ &.jv-container.boxed {
+ border: 1px solid hsl(var(--border));
+ }
+
+ .jv-ellipsis {
+ display: inline-block;
+ padding: 0 4px 2px;
+ font-size: 0.9em;
+ line-height: 0.9;
+ color: hsl(var(--secondary-foreground));
+ vertical-align: 2px;
+ cursor: pointer;
+ user-select: none;
+ background-color: hsl(var(--secondary));
+ border-radius: 3px;
+ }
+
+ .jv-button {
+ color: hsl(var(--primary));
+ }
+
+ .jv-key {
+ color: hsl(var(--heavy-foreground));
+ }
+
+ .jv-item {
+ &.jv-array {
+ color: hsl(var(--heavy-foreground));
+ }
+
+ &.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));
+ word-break: break-word;
+ white-space: normal;
+ }
+ }
+
+ &.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));
+ }
+ }
+ }
+ }
+}
diff --git a/packages/effects/common-ui/src/components/json-viewer/types.ts b/packages/effects/common-ui/src/components/json-viewer/types.ts
new file mode 100644
index 00000000..2ae69173
--- /dev/null
+++ b/packages/effects/common-ui/src/components/json-viewer/types.ts
@@ -0,0 +1,24 @@
+export interface JsonViewerProps {
+ /** 展开深度 */
+ expandDepth?: number;
+ /** 是否可复制 */
+ copyable?: boolean;
+ /** 是否排序 */
+ sort?: boolean;
+ /** 显示边框 */
+ boxed?: boolean;
+ /** 主题 */
+ theme?: string;
+ /** 是否展开 */
+ expanded?: boolean;
+ /** 时间格式化函数 */
+ timeformat?: (time: Date | number | string) => string;
+ /** 预览模式 */
+ previewMode?: boolean;
+ /** 显示数组索引 */
+ showArrayIndex?: boolean;
+ /** 显示双引号 */
+ showDoubleQuotes?: boolean;
+ /** 解析字符串 */
+ parseString?: boolean;
+}
diff --git a/packages/locales/src/langs/en-US/ui.json b/packages/locales/src/langs/en-US/ui.json
index 651a65ac..163fc0c0 100644
--- a/packages/locales/src/langs/en-US/ui.json
+++ b/packages/locales/src/langs/en-US/ui.json
@@ -25,6 +25,10 @@
"placeholder": "Select an icon",
"search": "Search icon..."
},
+ "jsonViewer": {
+ "copy": "Copy",
+ "copied": "Copied"
+ },
"fallback": {
"pageNotFound": "Oops! Page Not Found",
"pageNotFoundDesc": "Sorry, we couldn't find the page you were looking for.",
diff --git a/packages/locales/src/langs/zh-CN/ui.json b/packages/locales/src/langs/zh-CN/ui.json
index 2b6fe98d..2dda2f93 100644
--- a/packages/locales/src/langs/zh-CN/ui.json
+++ b/packages/locales/src/langs/zh-CN/ui.json
@@ -25,6 +25,10 @@
"placeholder": "选择一个图标",
"search": "搜索图标..."
},
+ "jsonViewer": {
+ "copy": "复制",
+ "copied": "已复制"
+ },
"fallback": {
"pageNotFound": "哎呀!未找到页面",
"pageNotFoundDesc": "抱歉,我们无法找到您要找的页面。",
diff --git a/playground/src/router/routes/modules/examples.ts b/playground/src/router/routes/modules/examples.ts
index 7287f0dd..f74c03fc 100644
--- a/playground/src/router/routes/modules/examples.ts
+++ b/playground/src/router/routes/modules/examples.ts
@@ -255,6 +255,15 @@ const routes: RouteRecordRaw[] = [
title: 'Tippy',
},
},
+ {
+ name: 'JsonViewer',
+ path: '/examples/json-viewer',
+ component: () => import('#/views/examples/json-viewer/index.vue'),
+ meta: {
+ icon: 'tabler:json',
+ title: 'JsonViewer',
+ },
+ },
],
},
];
diff --git a/playground/src/views/examples/json-viewer/data.ts b/playground/src/views/examples/json-viewer/data.ts
new file mode 100644
index 00000000..bd72e6fb
--- /dev/null
+++ b/playground/src/views/examples/json-viewer/data.ts
@@ -0,0 +1,51 @@
+export const json1 = {
+ additionalInfo: {
+ author: 'Your Name',
+ debug: true,
+ version: '1.3.10',
+ versionCode: 132,
+ },
+ additionalNotes: 'This JSON is used for demonstration purposes',
+ tools: [
+ {
+ description: 'Description of Tool 1',
+ name: 'Tool 1',
+ },
+ {
+ description: 'Description of Tool 2',
+ name: 'Tool 2',
+ },
+ {
+ description: 'Description of Tool 3',
+ name: 'Tool 3',
+ },
+ {
+ description: 'Description of Tool 4',
+ name: 'Tool 4',
+ },
+ ],
+};
+
+export const json2 = `
+{
+ "id": "chatcmpl-123",
+ "object": "chat.completion",
+ "created": 1677652288,
+ "model": "gpt-3.5-turbo-0613",
+ "system_fingerprint": "fp_44709d6fcb",
+ "choices": [{
+ "index": 0,
+ "message": {
+ "role": "assistant",
+ "content": "Hello there, how may I assist you today?"
+ },
+ "finish_reason": "stop"
+ }],
+ "usage": {
+ "prompt_tokens": 9,
+ "completion_tokens": 12,
+ "total_tokens": 21,
+ "debug_mode": true
+ }
+}
+`;
diff --git a/playground/src/views/examples/json-viewer/index.vue b/playground/src/views/examples/json-viewer/index.vue
new file mode 100644
index 00000000..0cf2e97e
--- /dev/null
+++ b/playground/src/views/examples/json-viewer/index.vue
@@ -0,0 +1,26 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 527ae0c5..2a303dea 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -474,6 +474,9 @@ catalogs:
vue-i18n:
specifier: ^11.1.0
version: 11.1.0
+ vue-json-viewer:
+ specifier: ^3.0.4
+ version: 3.0.4
vue-router:
specifier: ^4.5.0
version: 4.5.0
@@ -1533,6 +1536,9 @@ importers:
vue:
specifier: ^3.5.13
version: 3.5.13(typescript@5.7.3)
+ vue-json-viewer:
+ specifier: 'catalog:'
+ version: 3.0.4(vue@3.5.13(typescript@5.7.3))
vue-router:
specifier: 'catalog:'
version: 4.5.0(vue@3.5.13(typescript@5.7.3))
@@ -3535,6 +3541,10 @@ packages:
resolution: {integrity: sha512-DvpNSxiMrFqYMaGSRDDnQgO/L0MqNH4KWw9CUx8LRHHIdWp08En9DpmSRNpauUOxKpHAhyJJxx92BHZk9J84EQ==}
engines: {node: '>= 16'}
+ '@intlify/shared@11.1.1':
+ resolution: {integrity: sha512-2kGiWoXaeV8HZlhU/Nml12oTbhv7j2ufsJ5vQaa0VTjzUmZVdd/nmKFRAOJ/FtjO90Qba5AnZDwsrY7ZND5udA==}
+ engines: {node: '>= 16'}
+
'@intlify/unplugin-vue-i18n@6.0.3':
resolution: {integrity: sha512-9ZDjBlhUHtgjRl23TVcgfJttgu8cNepwVhWvOv3mUMRDAhjW0pur1mWKEUKr1I8PNwE4Gvv2IQ1xcl4RL0nG0g==}
engines: {node: '>= 18'}
@@ -5097,6 +5107,9 @@ packages:
resolution: {integrity: sha512-nPdaFdQ0h/GEigbPClz11D0v/ZJEwxmeVZGeMo3Z5StPtUTkA9o1lD6QwoirYiSDzbcwn2XcjwmCp68W1IS4TA==}
engines: {node: '>=18'}
+ clipboard@2.0.11:
+ resolution: {integrity: sha512-C+0bbOqkezLIsmWSvlsXS0Q0bmkugu7jcfMIACB+RDEntIzQIkdr148we28AfSloQLRdZlYL/QYyrq05j/3Faw==}
+
clipboardy@4.0.0:
resolution: {integrity: sha512-5mOlNS0mhX0707P2I0aZ2V/cmHUEO/fL7VFLqszkhUsxt7RwnmrInf/eEQKlf5GzvYeHIjT+Ov1HRfNmymlG0w==}
engines: {node: '>=18'}
@@ -5589,6 +5602,9 @@ 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'}
@@ -6451,6 +6467,9 @@ packages:
globjoin@0.1.4:
resolution: {integrity: sha512-xYfnw62CKG8nLkZBfWbhWwDw02CHty86jfPcc2cr3ZfeuK9ysoVPPEUxf21bAD/rWAgk52SuBrLJlefNy8mvFg==}
+ 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'}
@@ -8852,6 +8871,9 @@ packages:
seemly@0.3.9:
resolution: {integrity: sha512-bMLcaEqhIViiPbaumjLN8t1y+JpD/N8SiyYOyp0i0W6RgdyLWboIsUWAbZojF//JyerxPZR5Tgda+x3Pdne75A==}
+ select@1.1.2:
+ resolution: {integrity: sha512-OwpTSOfy6xSs1+pwcNrv0RBMOzI39Lp3qQKUTPVVPRjCdNa5JH/oPRiqsesIskK8TVgmRiHwO4KXlV2Li9dANA==}
+
semver-compare@1.0.0:
resolution: {integrity: sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow==}
@@ -9381,6 +9403,9 @@ packages:
through@2.3.8:
resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==}
+ tiny-emitter@2.1.0:
+ resolution: {integrity: sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q==}
+
tinybench@2.9.0:
resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==}
@@ -9978,6 +10003,11 @@ packages:
peerDependencies:
vue: ^3.5.13
+ vue-json-viewer@3.0.4:
+ resolution: {integrity: sha512-pnC080rTub6YjccthVSNQod2z9Sl5IUUq46srXtn6rxwhW8QM4rlYn+CTSLFKXWfw+N3xv77Cioxw7B4XUKIbQ==}
+ peerDependencies:
+ vue: ^3.5.13
+
vue-router@4.5.0:
resolution: {integrity: sha512-HDuk+PuH5monfNuY+ct49mNmkCRK4xJAV9Ts4z9UFc4rzdDnxQLyCMGGc8pKhZhHTVzfanpNwB/lwqevcBwI4w==}
peerDependencies:
@@ -12186,12 +12216,14 @@ snapshots:
'@intlify/shared@11.1.0': {}
+ '@intlify/shared@11.1.1': {}
+
'@intlify/unplugin-vue-i18n@6.0.3(@vue/compiler-dom@3.5.13)(eslint@9.19.0(jiti@2.4.2))(rollup@4.34.2)(typescript@5.7.3)(vue-i18n@11.1.0(vue@3.5.13(typescript@5.7.3)))(vue@3.5.13(typescript@5.7.3))':
dependencies:
'@eslint-community/eslint-utils': 4.4.1(eslint@9.19.0(jiti@2.4.2))
'@intlify/bundle-utils': 10.0.0(vue-i18n@11.1.0(vue@3.5.13(typescript@5.7.3)))
- '@intlify/shared': 11.1.0
- '@intlify/vue-i18n-extensions': 8.0.0(@intlify/shared@11.1.0)(@vue/compiler-dom@3.5.13)(vue-i18n@11.1.0(vue@3.5.13(typescript@5.7.3)))(vue@3.5.13(typescript@5.7.3))
+ '@intlify/shared': 11.1.1
+ '@intlify/vue-i18n-extensions': 8.0.0(@intlify/shared@11.1.1)(@vue/compiler-dom@3.5.13)(vue-i18n@11.1.0(vue@3.5.13(typescript@5.7.3)))(vue@3.5.13(typescript@5.7.3))
'@rollup/pluginutils': 5.1.4(rollup@4.34.2)
'@typescript-eslint/scope-manager': 8.23.0
'@typescript-eslint/typescript-estree': 8.23.0(typescript@5.7.3)
@@ -12213,11 +12245,11 @@ snapshots:
- supports-color
- typescript
- '@intlify/vue-i18n-extensions@8.0.0(@intlify/shared@11.1.0)(@vue/compiler-dom@3.5.13)(vue-i18n@11.1.0(vue@3.5.13(typescript@5.7.3)))(vue@3.5.13(typescript@5.7.3))':
+ '@intlify/vue-i18n-extensions@8.0.0(@intlify/shared@11.1.1)(@vue/compiler-dom@3.5.13)(vue-i18n@11.1.0(vue@3.5.13(typescript@5.7.3)))(vue@3.5.13(typescript@5.7.3))':
dependencies:
'@babel/parser': 7.26.7
optionalDependencies:
- '@intlify/shared': 11.1.0
+ '@intlify/shared': 11.1.1
'@vue/compiler-dom': 3.5.13
vue: 3.5.13(typescript@5.7.3)
vue-i18n: 11.1.0(vue@3.5.13(typescript@5.7.3))
@@ -14109,6 +14141,12 @@ snapshots:
slice-ansi: 5.0.0
string-width: 7.2.0
+ clipboard@2.0.11:
+ dependencies:
+ good-listener: 1.2.2
+ select: 1.1.2
+ tiny-emitter: 2.1.0
+
clipboardy@4.0.0:
dependencies:
execa: 8.0.1
@@ -14610,6 +14648,8 @@ snapshots:
delayed-stream@1.0.0: {}
+ delegate@3.2.0: {}
+
denque@2.1.0: {}
depcheck@1.4.7:
@@ -15675,6 +15715,10 @@ snapshots:
globjoin@0.1.4: {}
+ good-listener@1.2.2:
+ dependencies:
+ delegate: 3.2.0
+
gopd@1.2.0: {}
graceful-fs@4.2.10: {}
@@ -18142,6 +18186,8 @@ snapshots:
seemly@0.3.9: {}
+ select@1.1.2: {}
+
semver-compare@1.0.0: {}
semver@5.7.2: {}
@@ -18789,6 +18835,8 @@ snapshots:
through@2.3.8: {}
+ tiny-emitter@2.1.0: {}
+
tinybench@2.9.0: {}
tinyexec@0.3.2: {}
@@ -19493,6 +19541,11 @@ snapshots:
'@vue/devtools-api': 6.6.4
vue: 3.5.13(typescript@5.7.3)
+ vue-json-viewer@3.0.4(vue@3.5.13(typescript@5.7.3)):
+ dependencies:
+ clipboard: 2.0.11
+ vue: 3.5.13(typescript@5.7.3)
+
vue-router@4.5.0(vue@3.5.13(typescript@5.7.3)):
dependencies:
'@vue/devtools-api': 6.6.4
diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml
index 6a8542da..85c4bf0d 100644
--- a/pnpm-workspace.yaml
+++ b/pnpm-workspace.yaml
@@ -175,6 +175,7 @@ catalog:
vue: ^3.5.13
vue-eslint-parser: ^9.4.3
vue-i18n: ^11.1.0
+ vue-json-viewer: ^3.0.4
vue-router: ^4.5.0
vue-tippy: ^6.6.0
vue-tsc: 2.1.10