Przeglądaj źródła

【功能新增】基于 https://github.com/josdejong/jsoneditor 二次封装 JsonEditor 组件,提供 JSON 编辑器功能

puhui999 10 miesięcy temu
rodzic
commit
6265d6d923

+ 2 - 1
package.json

@@ -51,6 +51,7 @@
     "fast-xml-parser": "^4.3.2",
     "highlight.js": "^11.9.0",
     "jsencrypt": "^3.3.2",
+    "jsoneditor": "^10.1.3",
     "lodash-es": "^4.17.21",
     "markdown-it": "^14.1.0",
     "markmap-common": "^0.16.0",
@@ -67,7 +68,6 @@
     "sortablejs": "^1.15.3",
     "steady-xml": "^0.1.0",
     "url": "^0.11.3",
-    "v3-jsoneditor": "^0.0.6",
     "video.js": "^7.21.5",
     "vue": "3.5.12",
     "vue-dompurify-html": "^4.1.4",
@@ -85,6 +85,7 @@
     "@iconify/json": "^2.2.187",
     "@intlify/unplugin-vue-i18n": "^2.0.0",
     "@purge-icons/generated": "^0.9.0",
+    "@types/jsoneditor": "^9.9.5",
     "@types/lodash-es": "^4.17.12",
     "@types/node": "^20.11.21",
     "@types/nprogress": "^0.2.3",

+ 31 - 28
pnpm-lock.yaml

@@ -86,6 +86,9 @@ importers:
       jsencrypt:
         specifier: ^3.3.2
         version: 3.3.2
+      jsoneditor:
+        specifier: ^10.1.3
+        version: 10.1.3
       lodash-es:
         specifier: ^4.17.21
         version: 4.17.21
@@ -134,9 +137,6 @@ importers:
       url:
         specifier: ^0.11.3
         version: 0.11.4
-      v3-jsoneditor:
-        specifier: ^0.0.6
-        version: 0.0.6
       video.js:
         specifier: ^7.21.5
         version: 7.21.6
@@ -183,6 +183,9 @@ importers:
       '@purge-icons/generated':
         specifier: ^0.9.0
         version: 0.9.0
+      '@types/jsoneditor':
+        specifier: ^9.9.5
+        version: 9.9.5
       '@types/lodash-es':
         specifier: ^4.17.12
         version: 4.17.12
@@ -1693,6 +1696,9 @@ packages:
     resolution: {integrity: sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==}
     engines: {node: '>=10.13.0'}
 
+  '@types/ace@0.0.52':
+    resolution: {integrity: sha512-YPF9S7fzpuyrxru+sG/rrTpZkC6gpHBPF14W3x70kqVOD+ks6jkYLapk4yceh36xej7K4HYxcyz9ZDQ2lTvwgQ==}
+
   '@types/conventional-commits-parser@5.0.1':
     resolution: {integrity: sha512-7uz5EHdzz2TqoMfV7ee61Egf5y6NkcO4FB/1iCCQnbeiI1F3xzv3vK5dBCXUCLQgGYS+mUeigK1iKQzvED+QnQ==}
 
@@ -1804,6 +1810,9 @@ packages:
   '@types/json-schema@7.0.15':
     resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==}
 
+  '@types/jsoneditor@9.9.5':
+    resolution: {integrity: sha512-+Wex7QCirPcG90WA8/CmvDO21KUjz63/G7Yk52Yx/NhWHw5DyeET/L+wjZHAeNeNCCnMOTEtVX5gc3F4UXwXMQ==}
+
   '@types/lodash-es@4.17.12':
     resolution: {integrity: sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==}
 
@@ -2336,8 +2345,8 @@ packages:
     resolution: {integrity: sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==}
     hasBin: true
 
-  ace-builds@1.39.0:
-    resolution: {integrity: sha512-MqoZojv4gpc5QyTMor/dS6kmruDV9db9LVZbCiT4qYz6WsDiv4qyG5f7ZPc+wjUl6oLMqgCAsBjo1whdSVyMlQ==}
+  ace-builds@1.39.1:
+    resolution: {integrity: sha512-HcJbBzx8qY66t9gZo/sQu7pi0wO/CFLdYn1LxQO1WQTfIkMfyc7LRnBpsp/oNCSSU/LL83jXHN1fqyOTuIhUjg==}
 
   acorn-jsx@5.3.2:
     resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==}
@@ -4069,8 +4078,8 @@ packages:
     resolution: {integrity: sha512-WYDyuc/uFcGp6YtM2H0uKmUwieOuzeE/5YocFJLnLfclZ4inf3mRn8ZVy1s7Hxji7Jxm6Ss8gqpexD/GlKoGgg==}
     engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
 
-  jsoneditor@9.10.5:
-    resolution: {integrity: sha512-fVZ0NMt+zm4rqTKBv2x7zPdLeaRyKo1EjJkaR1QjK4gEM1rMwICILYSW1OPxSc1qqyAoDaA/eeNrluKoxOocCA==}
+  jsoneditor@10.1.3:
+    resolution: {integrity: sha512-zvbkiduFR19vLMJN1sSvBs9baGDdQRJGmKy6+/vQzDFhx//oEd6WAkrmmTeU4NNk9MAo+ZirENuwbtJXvS9M5g==}
 
   jsonfile@6.1.0:
     resolution: {integrity: sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==}
@@ -4079,8 +4088,8 @@ packages:
     resolution: {integrity: sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==}
     engines: {'0': node >= 0.2.0}
 
-  jsonrepair@3.1.0:
-    resolution: {integrity: sha512-idqReg23J0PVRAADmZMc5xQM3xeOX5bTB6OTyMnzq33IXJXmn9iJuWIEvGmrN80rQf4d7uLTMEDwpzujNcI0Rg==}
+  jsonrepair@3.12.0:
+    resolution: {integrity: sha512-SWfjz8SuQ0wZjwsxtSJ3Zy8vvLg6aO/kxcp9TWNPGwJKgTZVfhNEQBMk/vPOpYCDFWRxD6QWuI6IHR1t615f0w==}
     hasBin: true
 
   katex@0.16.11:
@@ -4402,9 +4411,6 @@ packages:
   mlly@1.7.3:
     resolution: {integrity: sha512-xUsx5n/mN0uQf4V548PKQ+YShA4/IW0KI1dZhrNrPCLG+xizETbHTkOa1f8/xut9JRPp8kQuMnz0oqwkTiLo/A==}
 
-  mobius1-selectr@2.4.13:
-    resolution: {integrity: sha512-Mk9qDrvU44UUL0EBhbAA1phfQZ7aMZPjwtL7wkpiBzGh8dETGqfsh50mWoX9EkjDlkONlErWXArHCKfoxVg0Bw==}
-
   moddle-xml@10.1.0:
     resolution: {integrity: sha512-erWckwLt+dYskewKXJso9u+aAZ5172lOiYxSOqKCPTy7L/xmqH1PoeoA7eVC7oJTt3PqF5TkZzUmbjGH6soQBg==}
 
@@ -5561,9 +5567,6 @@ packages:
     resolution: {integrity: sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==}
     hasBin: true
 
-  v3-jsoneditor@0.0.6:
-    resolution: {integrity: sha512-9G0sXWXUn67SBkn46ycWfwPwjuJu/lcsQaNzMtXAR2/95hMV21WfcRNsqJ+vVVrSHQehohB/9fVLwYEXz0u/KA==}
-
   vanilla-picker@2.12.3:
     resolution: {integrity: sha512-qVkT1E7yMbUsB2mmJNFmaXMWE2hF8ffqzMMwe9zdAikd8u2VfnsVY2HQcOUi2F38bgbxzlJBEdS1UUhOXdF9GQ==}
 
@@ -7334,6 +7337,8 @@ snapshots:
 
   '@trysound/sax@0.2.0': {}
 
+  '@types/ace@0.0.52': {}
+
   '@types/conventional-commits-parser@5.0.1':
     dependencies:
       '@types/node': 20.17.9
@@ -7468,6 +7473,11 @@ snapshots:
 
   '@types/json-schema@7.0.15': {}
 
+  '@types/jsoneditor@9.9.5':
+    dependencies:
+      '@types/ace': 0.0.52
+      ajv: 6.12.6
+
   '@types/lodash-es@4.17.12':
     dependencies:
       '@types/lodash': 4.17.13
@@ -7830,7 +7840,7 @@ snapshots:
   '@unocss/rule-utils@0.58.9':
     dependencies:
       '@unocss/core': 0.58.9
-      magic-string: 0.30.14
+      magic-string: 0.30.17
 
   '@unocss/rule-utils@66.1.0-beta.5':
     dependencies:
@@ -8272,7 +8282,7 @@ snapshots:
       jsonparse: 1.3.1
       through: 2.3.8
 
-  ace-builds@1.39.0: {}
+  ace-builds@1.39.1: {}
 
   acorn-jsx@5.3.2(acorn@8.14.0):
     dependencies:
@@ -10179,15 +10189,14 @@ snapshots:
       espree: 9.6.1
       semver: 7.6.3
 
-  jsoneditor@9.10.5:
+  jsoneditor@10.1.3:
     dependencies:
-      ace-builds: 1.39.0
+      ace-builds: 1.39.1
       ajv: 6.12.6
       javascript-natural-sort: 0.7.1
       jmespath: 0.16.0
       json-source-map: 0.6.1
-      jsonrepair: 3.1.0
-      mobius1-selectr: 2.4.13
+      jsonrepair: 3.12.0
       picomodal: 3.0.0
       vanilla-picker: 2.12.3
 
@@ -10199,7 +10208,7 @@ snapshots:
 
   jsonparse@1.3.1: {}
 
-  jsonrepair@3.1.0: {}
+  jsonrepair@3.12.0: {}
 
   katex@0.16.11:
     dependencies:
@@ -10550,8 +10559,6 @@ snapshots:
       pkg-types: 1.2.1
       ufo: 1.5.4
 
-  mobius1-selectr@2.4.13: {}
-
   moddle-xml@10.1.0:
     dependencies:
       min-dash: 4.2.2
@@ -11811,10 +11818,6 @@ snapshots:
 
   uuid@10.0.0: {}
 
-  v3-jsoneditor@0.0.6:
-    dependencies:
-      jsoneditor: 9.10.5
-
   vanilla-picker@2.12.3:
     dependencies:
       '@sphinxxxx/color-conversion': 2.2.2

+ 3 - 0
src/components/JsonEditor/index.ts

@@ -0,0 +1,3 @@
+import JsonEditor from './src/JsonEditor.vue'
+
+export { JsonEditor }

+ 109 - 0
src/components/JsonEditor/src/JsonEditor.vue

@@ -0,0 +1,109 @@
+<template>
+  <div ref="jsonEditorContainer" class="json-editor" :style="{ height }"></div>
+</template>
+<script setup lang="ts">
+import { useVModel } from '@vueuse/core'
+import { onBeforeUnmount, onMounted, ref, watch } from 'vue'
+import JSONEditor, { JSONEditorMode, JSONEditorOptions } from 'jsoneditor'
+import 'jsoneditor/dist/jsoneditor.min.css'
+import { JsonEditorEmits, JsonEditorExpose, JsonEditorProps } from '../types'
+
+/** 基于 https://github.com/josdejong/jsoneditor 二次封装组件,提供 JSON 编辑器功能。 */
+defineOptions({ name: 'JsonEditor' })
+
+const props = withDefaults(defineProps<JsonEditorProps>(), {
+  mode: 'tree' as JSONEditorMode,
+  height: '400px',
+  showModeSelection: false,
+  showNavigationBar: false,
+  showStatusBar: false,
+  showMainMenuBar: true
+})
+
+const emits = defineEmits<JsonEditorEmits>()
+const jsonObj = useVModel(props, 'modelValue', emits) as Ref<any>
+const jsonEditorContainer = ref<HTMLElement | null>(null)
+let jsonEditor: JSONEditor | null = null
+
+// 设置默认值
+const height = props.height
+
+// 初始化JSONEditor
+const initJsonEditor = () => {
+  if (!jsonEditorContainer.value) return
+
+  // 合并默认配置和用户自定义配置
+  const options: JSONEditorOptions = {
+    mode: props.mode,
+    modes: props.showModeSelection
+      ? (['tree', 'code', 'form', 'text', 'view', 'preview'] as JSONEditorMode[])
+      : undefined,
+    navigationBar: props.showNavigationBar,
+    statusBar: props.showStatusBar,
+    mainMenuBar: props.showMainMenuBar,
+    onChangeJSON: (json: any) => {
+      jsonObj.value = json
+      emits('change', json)
+    },
+    onValidationError: (errors: any) => {
+      emits('error', errors)
+    },
+    ...props.options
+  } as JSONEditorOptions
+
+  // 创建JSONEditor实例
+  jsonEditor = new JSONEditor(jsonEditorContainer.value, options)
+
+  // 设置初始值
+  if (jsonObj.value) {
+    jsonEditor.set(jsonObj.value)
+  }
+}
+
+// 监听数据变化
+watch(
+  () => jsonObj.value,
+  (newValue) => {
+    if (!jsonEditor) return
+
+    try {
+      // 防止无限循环更新
+      const currentJson = jsonEditor.get()
+      if (JSON.stringify(currentJson) !== JSON.stringify(newValue)) {
+        jsonEditor.update(newValue)
+      }
+    } catch (error) {
+      console.error('JSON更新失败:', error)
+    }
+  },
+  { deep: true }
+)
+
+// 生命周期钩子
+onMounted(() => {
+  initJsonEditor()
+})
+
+onBeforeUnmount(() => {
+  if (jsonEditor) {
+    jsonEditor.destroy()
+    jsonEditor = null
+  }
+})
+
+// 暴露方法
+defineExpose<JsonEditorExpose>({
+  // 获取编辑器实例,以便可以调用更多JSONEditor的原生方法
+  getEditor: () => jsonEditor
+})
+</script>
+
+<style lang="scss" scoped>
+/* 隐藏 Ace 编辑器的 powered by ace 标记 */
+:deep(.jsoneditor-menu) {
+  /* 隐藏 powered by ace 标记 */
+  .jsoneditor-poweredBy {
+    display: none !important;
+  }
+}
+</style>

+ 80 - 0
src/components/JsonEditor/types/index.ts

@@ -0,0 +1,80 @@
+import { JSONEditorOptions, JSONEditorMode } from 'jsoneditor'
+
+export interface JsonEditorProps {
+  /**
+   * JSON数据,支持双向绑定
+   */
+  modelValue: any
+  
+  /**
+   * 编辑器模式
+   * @default 'tree'
+   */
+  mode?: JSONEditorMode
+  
+  /**
+   * 编辑器高度
+   * @default '400px'
+   */
+  height?: string
+  
+  /**
+   * 是否显示模式选择下拉菜单
+   * @default false
+   */
+  showModeSelection?: boolean
+  
+  /**
+   * 是否显示导航栏
+   * @default false
+   */
+  showNavigationBar?: boolean
+  
+  /**
+   * 是否显示状态栏
+   * @default true
+   */
+  showStatusBar?: boolean
+  
+  /**
+   * 是否显示主菜单栏
+   * @default true
+   */
+  showMainMenuBar?: boolean
+  
+  /**
+   * JSONEditor配置选项
+   * @see https://github.com/josdejong/jsoneditor/blob/develop/docs/api.md
+   */
+  options?: Partial<JSONEditorOptions>
+}
+
+/**
+ * JsonEditor组件触发的事件
+ */
+export interface JsonEditorEmits {
+  /**
+   * 数据更新时触发
+   */
+  (e: 'update:modelValue', value: any): void
+  
+  /**
+   * 数据变化时触发
+   */
+  (e: 'change', value: any): void
+  
+  /**
+   * 验证错误时触发
+   */
+  (e: 'error', errors: any): void
+}
+
+/**
+ * JsonEditor组件暴露的方法
+ */
+export interface JsonEditorExpose {
+  /**
+   * 获取原始的JSONEditor实例
+   */
+  getEditor: () => any
+}