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