Browse Source

feat: 添加代码编辑器

liaojiaxing 10 months ago
parent
commit
2aa8bc426a

+ 20 - 25
README.md

@@ -5,34 +5,28 @@
 安装:pnpm install
 运行:pnpm dev
 
-### 任务列表:
-1. 大屏管理页--完成
-2. 页面布局
-  - a. 操作栏 -- 完成
-  - b. 图层管理 -- 完成
-  - c. 组件管理 -- 完成
-  - d. 画布 -- 完成
-  - e. 配置页
-3. 通用协议
-4. 画布
-  - a. 缩放 -- 完成
-  - b. 标尺 -- 完成
-  - c. 拖拽 -- 完成
-  - d. 吸附
-  - e. 辅助线 -- 完成
-  - f. 控制容器组件 -- 完成
-5. 渲染器
-6. 属性面板
-7. 组件属性渲染表单
-8. 预览页
-9. 快捷键
-10. 操作记录
-
-
 
 ### 图表组件开发思路
-- 图表的option参数归一,提供统一的配置,便于后期做主题配置
+- 图表的option参数归一,提供统一的配置,便于后期做主题配置
 - 不同的图表提供可以配置的参数,后期通过归一化处理
 - 组件配置:组件内容、组件样式
 -- 组件样式为容器样式,包括:组件属性(如宽高、xy坐标、整体透明度等)、样式属性(如背景、边框、阴影等)
+-- 组件内容为每个组件单独提供的配置组件,通常为数据源,数据样式等
+
+
+### echarts文件夹结构目录如下:
+```
+components/Charts/
+├── BarChart/ // 柱状图
+│   └── BasicBar // 基础柱状图
+|         ├── index.ts // 暴露组件相关信息
+|         └── src
+|             ├── index.vue // 图表组件
+|             ├── Config.vue // 配置组件
+|             └── props.ts // 组件属性及组件初始数据
+...其他图表组件
+├── hooks
+│   └── useChartOptions.ts // 用于获取组件配置项options数据
+|—— Echarts.vue // 封装echarts组件
+|—— DataConfig.vue // 数据配置组件
+```

+ 4 - 0
package.json

@@ -11,6 +11,9 @@
   "dependencies": {
     "@ant-design/icons-vue": "^7.0.1",
     "@codemirror/lang-javascript": "^6.2.2",
+    "@codemirror/lang-json": "^6.0.1",
+    "@codemirror/language": "^6.10.2",
+    "@codemirror/lint": "^6.8.1",
     "@codemirror/theme-one-dark": "^6.1.2",
     "@vueuse/components": "^10.11.0",
     "@vueuse/core": "^10.10.1",
@@ -19,6 +22,7 @@
     "dayjs": "^1.11.11",
     "echarts": "^5.5.0",
     "element-plus": "^2.7.6",
+    "js-beautify": "^1.14.3",
     "less": "^4.2.0",
     "less-loader": "^12.2.0",
     "lodash": "^4.17.21",

+ 158 - 2
pnpm-lock.yaml

@@ -11,6 +11,15 @@ dependencies:
   '@codemirror/lang-javascript':
     specifier: ^6.2.2
     version: 6.2.2
+  '@codemirror/lang-json':
+    specifier: ^6.0.1
+    version: 6.0.1
+  '@codemirror/language':
+    specifier: ^6.10.2
+    version: 6.10.2
+  '@codemirror/lint':
+    specifier: ^6.8.1
+    version: 6.8.1
   '@codemirror/theme-one-dark':
     specifier: ^6.1.2
     version: 6.1.2
@@ -35,6 +44,9 @@ dependencies:
   element-plus:
     specifier: ^2.7.6
     version: 2.7.6(vue@3.4.27)
+  js-beautify:
+    specifier: ^1.14.3
+    version: 1.14.3
   less:
     specifier: ^4.2.0
     version: 4.2.0
@@ -176,6 +188,13 @@ packages:
       '@lezer/javascript': 1.4.17
     dev: false
 
+  /@codemirror/lang-json@6.0.1:
+    resolution: {integrity: sha512-+T1flHdgpqDDlJZ2Lkil/rLiRy684WMLc74xUnjJH48GQdfJo/pudlTRreZmKwzP8/tGdKf83wlbAdOCzlJOGQ==}
+    dependencies:
+      '@codemirror/language': 6.10.2
+      '@lezer/json': 1.0.2
+    dev: false
+
   /@codemirror/language@6.10.2:
     resolution: {integrity: sha512-kgbTYTo0Au6dCSc/TFy7fK3fpJmgHDv1sG1KNQKJXVi+xBTEeBPY/M30YXiU6mMXeH+YIDLsbrT4ZwNRdtF+SA==}
     dependencies:
@@ -490,6 +509,14 @@ packages:
       '@lezer/lr': 1.4.1
     dev: false
 
+  /@lezer/json@1.0.2:
+    resolution: {integrity: sha512-xHT2P4S5eeCYECyKNPhr4cbEL9tc8w83SPwRC373o9uEdrvGKTZoJVAGxpOsZckMlEh9W23Pc72ew918RWQOBQ==}
+    dependencies:
+      '@lezer/common': 1.2.1
+      '@lezer/highlight': 1.2.0
+      '@lezer/lr': 1.4.1
+    dev: false
+
   /@lezer/lr@1.4.1:
     resolution: {integrity: sha512-CHsKq8DMKBf9b3yXPDIU4DbH+ZJd/sJdYOW2llbW/HudP5u0VS6Bfq1hLYfgU7uAYGFIyGGQIsSOXGPEErZiJw==}
     dependencies:
@@ -913,6 +940,10 @@ packages:
       - vue
     dev: false
 
+  /abbrev@1.1.1:
+    resolution: {integrity: sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==}
+    dev: false
+
   /acorn@8.12.0:
     resolution: {integrity: sha512-RTvkC4w+KNXrM39/lWCUaG0IbRkWdCv7W/IOW9oU6SawyxulvkQy5HQPVTKxEjczcUvapcrw3cFx/60VN/NRNw==}
     engines: {node: '>=0.4.0'}
@@ -981,12 +1012,18 @@ packages:
 
   /balanced-match@1.0.2:
     resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
-    dev: true
 
   /binary-extensions@2.3.0:
     resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==}
     engines: {node: '>=8'}
 
+  /brace-expansion@1.1.11:
+    resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==}
+    dependencies:
+      balanced-match: 1.0.2
+      concat-map: 0.0.1
+    dev: false
+
   /brace-expansion@2.0.1:
     resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==}
     dependencies:
@@ -1059,6 +1096,10 @@ packages:
     resolution: {integrity: sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==}
     engines: {node: '>=18'}
 
+  /commander@2.20.3:
+    resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==}
+    dev: false
+
   /compute-scroll-into-view@1.0.20:
     resolution: {integrity: sha512-UCB0ioiyj8CRjtrvaceBLqqhZCVP+1B8+NWQhmdsm0VXOJtobBCf1dBQmebCCo34qZmUwZfIH2MZLqNHazrfjg==}
     dev: false
@@ -1067,6 +1108,17 @@ packages:
     resolution: {integrity: sha512-7CEBgcMjVmitjYo5q8JTJVra6X5mQ20uTThdK+0kR7UEaDrAWEQcRiBtWJzga4eRpP6afNwwLsX2SET2JhVB1Q==}
     dev: true
 
+  /concat-map@0.0.1:
+    resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==}
+    dev: false
+
+  /config-chain@1.1.13:
+    resolution: {integrity: sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==}
+    dependencies:
+      ini: 1.3.8
+      proto-list: 1.2.4
+    dev: false
+
   /connect@3.7.0:
     resolution: {integrity: sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ==}
     engines: {node: '>= 0.10.0'}
@@ -1161,6 +1213,16 @@ packages:
       zrender: 5.5.0
     dev: false
 
+  /editorconfig@0.15.3:
+    resolution: {integrity: sha512-M9wIMFx96vq0R4F+gRpY3o2exzb8hEj/n9S8unZtHSvYjibBp/iMufSzvmOcV/laG0ZtuTVGtiJggPOSW2r93g==}
+    hasBin: true
+    dependencies:
+      commander: 2.20.3
+      lru-cache: 4.1.5
+      semver: 5.7.2
+      sigmund: 1.0.1
+    dev: false
+
   /ee-first@1.1.1:
     resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==}
     dev: true
@@ -1322,6 +1384,10 @@ packages:
       mime-types: 2.1.35
     dev: false
 
+  /fs.realpath@1.0.0:
+    resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==}
+    dev: false
+
   /fsevents@2.3.3:
     resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
     engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
@@ -1350,6 +1416,18 @@ packages:
     dependencies:
       is-glob: 4.0.3
 
+  /glob@7.2.3:
+    resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==}
+    deprecated: Glob versions prior to v9 are no longer supported
+    dependencies:
+      fs.realpath: 1.0.0
+      inflight: 1.0.6
+      inherits: 2.0.4
+      minimatch: 3.1.2
+      once: 1.4.0
+      path-is-absolute: 1.0.1
+    dev: false
+
   /gopd@1.0.1:
     resolution: {integrity: sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==}
     dependencies:
@@ -1404,6 +1482,22 @@ packages:
     requiresBuild: true
     optional: true
 
+  /inflight@1.0.6:
+    resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==}
+    deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.
+    dependencies:
+      once: 1.4.0
+      wrappy: 1.0.2
+    dev: false
+
+  /inherits@2.0.4:
+    resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==}
+    dev: false
+
+  /ini@1.3.8:
+    resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==}
+    dev: false
+
   /is-binary-path@2.1.0:
     resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==}
     engines: {node: '>=8'}
@@ -1432,6 +1526,17 @@ packages:
   /is-what@3.14.1:
     resolution: {integrity: sha512-sNxgpk9793nzSs7bA6JQJGeIuRBQhAaNGG77kzYQgMkrID+lS6SlK07K5LaptscDlSaIgH+GPFzf+d75FVxozA==}
 
+  /js-beautify@1.14.3:
+    resolution: {integrity: sha512-f1ra8PHtOEu/70EBnmiUlV8nJePS58y9qKjl4JHfYWlFH6bo7ogZBz//FAZp7jDuXtYnGYKymZPlrg2I/9Zo4g==}
+    engines: {node: '>=10'}
+    hasBin: true
+    dependencies:
+      config-chain: 1.1.13
+      editorconfig: 0.15.3
+      glob: 7.2.3
+      nopt: 5.0.0
+    dev: false
+
   /js-cookie@3.0.5:
     resolution: {integrity: sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==}
     engines: {node: '>=14'}
@@ -1506,6 +1611,13 @@ packages:
       js-tokens: 4.0.0
     dev: false
 
+  /lru-cache@4.1.5:
+    resolution: {integrity: sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==}
+    dependencies:
+      pseudomap: 1.0.2
+      yallist: 2.1.2
+    dev: false
+
   /magic-string@0.30.10:
     resolution: {integrity: sha512-iIRwTIf0QKV3UAnYK4PU8uiEc4SRh5jX0mwpIwETPpHdhVM4f53RSwS/vXvN1JhGX+Cs7B8qIq3d6AH49O5fAQ==}
     dependencies:
@@ -1556,6 +1668,12 @@ packages:
     requiresBuild: true
     optional: true
 
+  /minimatch@3.1.2:
+    resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==}
+    dependencies:
+      brace-expansion: 1.1.11
+    dev: false
+
   /minimatch@9.0.4:
     resolution: {integrity: sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==}
     engines: {node: '>=16 || 14 >=14.17'}
@@ -1600,6 +1718,14 @@ packages:
       sax: 1.4.1
     optional: true
 
+  /nopt@5.0.0:
+    resolution: {integrity: sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==}
+    engines: {node: '>=6'}
+    hasBin: true
+    dependencies:
+      abbrev: 1.1.1
+    dev: false
+
   /normalize-path@3.0.0:
     resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==}
     engines: {node: '>=0.10.0'}
@@ -1620,6 +1746,12 @@ packages:
       ee-first: 1.1.1
     dev: true
 
+  /once@1.4.0:
+    resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==}
+    dependencies:
+      wrappy: 1.0.2
+    dev: false
+
   /parse-node-version@1.0.1:
     resolution: {integrity: sha512-3YHlOa/JgH6Mnpr05jP9eDG254US9ek25LyIxZlDItp2iJtwyaXQb57lBYLdT3MowkUFYEV2XXNAYIPlESvJlA==}
     engines: {node: '>= 0.10'}
@@ -1633,6 +1765,11 @@ packages:
     resolution: {integrity: sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==}
     dev: true
 
+  /path-is-absolute@1.0.1:
+    resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==}
+    engines: {node: '>=0.10.0'}
+    dev: false
+
   /path-to-regexp@6.2.2:
     resolution: {integrity: sha512-GQX3SSMokngb36+whdpRXE+3f9V8UzyAorlYvOGx87ufGHehNTn5lCxrKtLyZ4Yl/wEKnNnr98ZzOwwDZV5ogw==}
     dev: true
@@ -1676,6 +1813,10 @@ packages:
       picocolors: 1.0.1
       source-map-js: 1.2.0
 
+  /proto-list@1.2.4:
+    resolution: {integrity: sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==}
+    dev: false
+
   /proxy-from-env@1.1.0:
     resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==}
     dev: false
@@ -1685,6 +1826,10 @@ packages:
     requiresBuild: true
     optional: true
 
+  /pseudomap@1.0.2:
+    resolution: {integrity: sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ==}
+    dev: false
+
   /qs@6.12.1:
     resolution: {integrity: sha512-zWmv4RSuB9r2mYQw3zxQuHWeU+42aKi1wWig/j4ele4ygELZ7PEO6MM7rim9oAQH2A5MWfsAVf/jPvTPgCbvUQ==}
     engines: {node: '>=0.6'}
@@ -1782,7 +1927,6 @@ packages:
     resolution: {integrity: sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==}
     hasBin: true
     requiresBuild: true
-    optional: true
 
   /semver@7.6.2:
     resolution: {integrity: sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==}
@@ -1816,6 +1960,10 @@ packages:
       object-inspect: 1.13.2
     dev: false
 
+  /sigmund@1.0.1:
+    resolution: {integrity: sha512-fCvEXfh6NWpm+YSuY2bpXb/VIihqWA6hLsgboC+0nl71Q7N7o2eaCW8mJa/NLvQhs6jpd3VZV4UiUQlV6+lc8g==}
+    dev: false
+
   /sortablejs@1.14.0:
     resolution: {integrity: sha512-pBXvQCs5/33fdN1/39pPL0NZF20LeRbLQ5jtnheIPN9JQAaufGjKdWduZn4U7wCtVuzKhmRkI0DFYHYRbB2H1w==}
     dev: false
@@ -2104,6 +2252,14 @@ packages:
     resolution: {integrity: sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==}
     dev: false
 
+  /wrappy@1.0.2:
+    resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==}
+    dev: false
+
+  /yallist@2.1.2:
+    resolution: {integrity: sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A==}
+    dev: false
+
   /zrender@5.5.0:
     resolution: {integrity: sha512-O3MilSi/9mwoovx77m6ROZM7sXShR/O/JIanvzTwjN3FORfLSr81PsUGd7jlaYOeds9d8tw82oP44+3YucVo+w==}
     dependencies:

+ 9 - 1
src/components/Charts/Bar/BasicBar/src/Config.vue

@@ -6,7 +6,7 @@
     </ElRadioGroup>
   </div>
 
-  <DataConfig v-if="activeTab === '1'" />
+  <DataConfig v-if="activeTab === '1'" :dataSource="dataSource" @change="handleChange"/>
 </template>
 
 <script setup lang="ts">
@@ -18,6 +18,14 @@ import { basicBarProps } from './props';
 const props = defineProps(basicBarProps);
 const activeTab = ref('1');
 const emit = defineEmits(['change']);
+console.log(props);
+
+const handleChange = (data: any) => {
+  emit('change', {
+    ...props,
+    dataSource: data,
+  });
+}
 </script>
 
 <style lang="less" scoped>

+ 1 - 0
src/components/Charts/Bar/BasicBar/src/index.vue

@@ -9,6 +9,7 @@ import { basicBarProps } from "./props";
 import { useChartOptions } from "@/components/Charts/hooks/useChartOptions";
 
 const props = defineProps(basicBarProps);
+
 const { options } = useChartOptions(props);
 
 </script>

+ 33 - 22
src/components/Charts/Bar/BasicBar/src/props.ts

@@ -2,6 +2,7 @@ import { type PropType } from "vue";
 import { EChartsOption } from "echarts";
 import { getNormalizedChart } from "@/utils/common";
 import { dataSource } from "@/utils/common";
+import { DataSourceType } from "@/enum";
 
 export const basicBarProps = {
   width: {
@@ -55,34 +56,44 @@ export const basicBarProps = {
   }
 };
 
+const chartOptions = getNormalizedChart({
+  title: {
+    text: "柱状图标题",
+  },
+  xAxis: {
+    data: ['轴标签A', '轴标签B', '轴标签C', '轴标签D']
+  },
+})
+
 export const defaultPropsValue: EChartsOption = {
+  // 组件容器默认属性
   container: {
     props: {
       width: 400,
       height: 260,
     },
   },
-  props: getNormalizedChart({
-    title: {
-      text: "柱状图标题",
-    },
-    xAxis: {
-      data: ['轴标签A', '轴标签B', '轴标签C', '轴标签D']
-    },
-  }),
-  dataSource: {
-    xData: ['轴标签A', '轴标签B', '轴标签C', '轴标签D'],
-    series: [
-      {
-        type: 'bar',
-        name: '系列1',
-        data: [89.3, 92.1, 94.4, 85.4]
-      },
-      {
-        type: 'bar',
-        name: '系列2',
-        data: [95.8, 89.4, 91.2, 76.9]
+  // 图表默认属性
+  props: {
+    // 数据源
+    dataSource: {
+      sourceType: DataSourceType.STATIC,
+      data: {
+        xData: ['轴标签A', '轴标签B', '轴标签C', '轴标签D'],
+        series: [
+          {
+            type: 'bar',
+            name: '系列1',
+            data: [89.3, 92.1, 94.4, 85.4]
+          },
+          {
+            type: 'bar',
+            name: '系列2',
+            data: [95.8, 89.4, 91.2, 76.9]
+          },
+        ]
       },
-    ]
-  }
+    },
+    ...chartOptions
+  },
 };

+ 102 - 36
src/components/Charts/DataConfig.vue

@@ -1,6 +1,11 @@
 <template>
-  <Form size="small" layout="horizontal" :model="formModel" :label-col="{span: 6}">
-    <Form.Item label="类型">
+  <Form
+    size="small"
+    layout="horizontal"
+    :model="formModel"
+    :label-col="{ span: 6 }"
+  >
+    <Form.Item label="类型" name="sourceType">
       <Select v-model:value="formModel.sourceType">
         <SelectOption :value="DataSourceType.STATIC">静态数据</SelectOption>
         <SelectOption :value="DataSourceType.API">动态数据</SelectOption>
@@ -8,45 +13,82 @@
     </Form.Item>
     <!-- 静态数据 -->
     <template v-if="formModel.sourceType === DataSourceType.STATIC">
-      <Form.Item label="数据">
-        <Button type="default" size="small" @click="handleStaticData">编辑</Button>
+      <Form.Item label="数据" name="data">
+        <Button type="default" size="small" @click="handleEditData('data')"
+          >编辑</Button
+        >
       </Form.Item>
     </template>
     <!-- 接口 -->
     <template v-else-if="formModel.sourceType === DataSourceType.API">
-      <Form.Item label="接口地址" prop="url">
-        <Input.TextArea placeholder="请输入接口地址"></Input.TextArea>
+      <Form.Item label="接口地址" name="url">
+        <Input.TextArea
+          :auto-size="{ minRows: 5 }"
+          placeholder="请输入接口地址"
+          v-model:value="formModel.url"
+        ></Input.TextArea>
       </Form.Item>
-      <Form.Item label="请求方式" prop="method">
-        <RadioGroup v-model="formModel.method">
+      <Form.Item label="请求方式" name="method">
+        <RadioGroup v-model:value="formModel.method">
           <Radio value="GET">GET</Radio>
           <Radio value="POST">POST</Radio>
         </RadioGroup>
       </Form.Item>
-      <Form.Item label="请求参数">
-        <Button type="default" size="small">编辑</Button>
+      <Form.Item label="刷新时间" name="refreshTime">
+        <InputNumber
+          v-model:value="formModel.refreshTime"
+          :min="0"
+          :step="1"
+          :max="60"
+          style="width: 100%"
+        >
+          <template #addonAfter>
+            <span class="text-gray-500">秒</span>
+          </template>
+        </InputNumber>
       </Form.Item>
-      <Form.Item label="请求头">
-        <Button type="default" size="small">编辑</Button>
+      <Form.Item label="请求参数" name="params">
+        <Button type="default" size="small" @click="handleEditData('params')"
+          >编辑</Button
+        >
       </Form.Item>
-      <Form.Item label="刷新时间">
-        <InputNumber :min="0" :max="1000" />
+      <Form.Item label="请求头" name="headers">
+        <Button type="default" size="small" @click="handleEditData('headers')"
+          >编辑</Button
+        >
       </Form.Item>
-      <Form.Item label="数据处理">
-        <Button type="default" size="small">编辑</Button>
+      <Form.Item label="数据处理" name="dataProcess">
+        <Button
+          type="default"
+          size="small"
+          @click="handleEditData('dataProcess')"
+          >编辑</Button
+        >
       </Form.Item>
     </template>
   </Form>
-  <CodeEditorModal ref="codeEditorRef" title="编辑数据"/>
+  <CodeEditorModal ref="codeEditorRef" title="编辑数据" @ok="handleCodeSave" />
 </template>
 
 <script setup lang="ts">
-import { ref } from 'vue';
-import type { Ref } from 'vue';
-import { Form, RadioGroup, Radio, Input, Button, InputNumber, Select, SelectOption } from 'ant-design-vue';
-import { DataSourceType } from '@/enum';
-import { CodeEditorModal, type CodeEditorModalInstance } from '@/components/CodeEditor';
-
+import type { DataSource } from "#/echart";
+import type { Ref } from "vue";
+import { ref, defineProps, watch, defineEmits } from "vue";
+import {
+  Form,
+  Input,
+  Button,
+  InputNumber,
+  Select,
+  SelectOption,
+  RadioGroup,
+  Radio
+} from "ant-design-vue";
+import { DataSourceType } from "@/enum";
+import {
+  CodeEditorModal,
+  type CodeEditorModalInstance,
+} from "@/components/CodeEditor";
 
 /**
  * 通用数据data约定内容结构
@@ -66,26 +108,50 @@ import { CodeEditorModal, type CodeEditorModalInstance } from '@/components/Code
  *  ]
  * }
  */
+const emit = defineEmits(["change"]);
+const props = defineProps<{
+  dataSource: DataSource;
+}>();
 const formModel = ref({
   sourceType: DataSourceType.STATIC,
   // 静态数据相关
-  data: [],
+  data: "",
   // 接口相关
-  url: '',
-  method: 'GET',
-  params: '',
-  headers: '',
+  url: location.origin + "/api/get/example/bar",
+  method: "GET",
+  params: {},
+  headers: {},
   refreshTime: 0,
   // 数据处理
-  dataProcess: () => {},
-})
+  dataProcess: "() => {}",
+});
 
+/* =====================编辑代码======================= */
+let pathKey: "data" | "params" | "headers" | "dataProcess";
 const codeEditorRef = ref<Ref<CodeEditorModalInstance> | null>(null);
-const handleStaticData = () => {
-  codeEditorRef.value?.open();
-}
-</script>
+const handleEditData = (key: "data" | "params" | "headers" | "dataProcess") => {
+  pathKey = key;
+  codeEditorRef.value?.open(JSON.stringify(formModel.value[key]));
+};
+const handleCodeSave = (code: string) => {
+  formModel.value[pathKey] = JSON.parse(code);
+};
 
-<style scoped>
+watch(
+  () => props.dataSource,
+  (val) => {
+    Object.assign(formModel.value, val || {});
+  },
+  { immediate: true }
+);
+
+watch(
+  () => formModel.value,
+  (val) => {
+    emit("change", val);
+  },
+  { deep: true }
+);
+</script>
 
-</style>
+<style scoped></style>

+ 1 - 0
src/components/Charts/Echarts.vue

@@ -37,6 +37,7 @@ watch(
   },
   {
     immediate: true,
+    deep: true,
   }
 );
 </script>

+ 62 - 8
src/components/Charts/hooks/useChartOptions.ts

@@ -1,15 +1,69 @@
+import type { EChartsOption } from "echarts";
 import { computed, watch, ref } from "vue";
-import { omit } from 'lodash';
-import { EChartsOption } from "echarts";
+import { omit, defaultsDeep } from 'lodash';
 import { useRequest } from "vue-hooks-plus";
+import { DataSourceType } from '@/enum';
 
-export const useChartOptions = (props: Record<string, any>) => {
-  const options = ref<EChartsOption>(omit(props, ['width', 'height', 'dataSource']) as EChartsOption);
+export const useChartOptions = (chartProps: Record<string, any>) => {
+  const dataSource = chartProps.dataSource || {}
+  const xAxis = ref<EChartsOption["xAxis"]>({data: dataSource?.data?.xData});
+  const yAxis = ref<EChartsOption["yAxis"]>({data: dataSource?.data?.yData});
+  const series = ref<EChartsOption["series"]>(dataSource?.data?.series);
 
-  const { run, refresh, cancel, data, loading} = useRequest(props.dataSource.url, {
-    defaultParams: props.dataSource.params,
-    manual: false
-  })
+  // 请求数据
+  const { run, refresh, cancel, data, loading} = useRequest(chartProps.dataSource.url, {
+    defaultParams: chartProps.dataSource.params,
+    manual: true,
+    pollingInterval: (chartProps.dataSource?.refreshTime || 0) * 1000,
+  });
+
+  /* 初始请求 */
+  if(chartProps.dataSource.sourceType === DataSourceType.API) {
+    run();
+  }
+
+  watch(
+    () => data.value,
+    (val) => {
+      if(val && chartProps.dataSource.sourceType === DataSourceType.API) {
+        const { xData, yData, series } = data.value as any;
+        // TODO 数据处理成通用格式
+        xAxis.value = {data: xData};
+        yAxis.value = {data: yData};
+        series.value = series;
+      }
+    },{
+      deep: true
+    }
+  )
+
+  watch(
+    () => chartProps.dataSource,
+    (val) => {
+      if(val === DataSourceType.API) {
+        refresh();
+      } else {
+        cancel();
+        const dataSource = chartProps.dataSource || {};
+        xAxis.value = {data: dataSource?.data?.xData};
+        yAxis.value = {data: dataSource?.data?.yData};
+        series.value = dataSource?.data?.series;
+      }
+    },
+    {
+      deep: true
+    }
+  )
+
+  const options = computed((): EChartsOption => {
+    const opt = omit(chartProps, ['width', 'height', 'dataSource']) as EChartsOption;
+    
+    return defaultsDeep({
+      xAxis: xAxis.value,
+      yAxis: yAxis.value,
+      series: series.value,
+    }, opt);
+  });
 
   return {
     options,

+ 1 - 1
src/components/CodeEditor/index.ts

@@ -1,7 +1,7 @@
 import CodeEditorModal from './src/index.vue';
 
 export type CodeEditorModalInstance = {
-  open: () => void;
+  open: (code: string) => void;
   close: () => void;
   getCode: () => string;
 };

+ 39 - 6
src/components/CodeEditor/src/Editor.vue

@@ -2,23 +2,56 @@
   <Codemirror
     ref="editorRef"
     placeholder="请输入"
-    style="height: 300px;"
+    style="height: 500px;"
+    :model-value="modelValue"
     :tab-size="2"
     :auto-focus="true"
-    :indent-with-tab="true"
-    :extensions="[javascript(), oneDark]"
-    :model-value="modelValue"
+    :indent-with-tabs="true"
+    :extensions="[
+      oneDark,
+      javascript(),
+      json()
+    ]"
+    @change="handleCodeChange"
   />
 </template>
 
 <script setup lang="ts">
-import { ref } from 'vue';
+import { ref, defineProps, watch, defineEmits } from 'vue';
 import { Codemirror } from 'vue-codemirror';
 import { oneDark } from '@codemirror/theme-one-dark';
 import { javascript } from '@codemirror/lang-javascript';
+import { json } from '@codemirror/lang-json';
+import jsBeautify from 'js-beautify';
 
+const props = defineProps({
+  code: {
+    type: String,
+    default: ''
+  }
+});
+const emit = defineEmits(['update:code', 'change']);
 const editorRef = ref(null);
-const modelValue = ref('');
+const modelValue = ref(props.code);
+
+watch(
+  () => props.code,
+  (val) => {
+    modelValue.value = jsBeautify.js(val, { indent_size: 2 });
+  },
+  { immediate: true }
+)
+
+const handleCodeChange = (val: string) => {
+  try {
+    JSON.stringify(JSON.parse(val), null, 2);
+    emit('update:code', val);
+    emit('change', val);
+  } catch (error) {
+    console.error(error);
+    return;
+  }
+}
 </script>
 
 <style scoped>

+ 22 - 10
src/components/CodeEditor/src/index.vue

@@ -2,9 +2,10 @@
   <Modal
     v-model:open="open"
     :title="title"
-    :width="800"
+    :width="width"
+    @ok="handleOk"
   >
-    <Editor code="hello world"/>
+    <Editor v-model:code="code"/>
   </Modal>
 </template>
 
@@ -15,20 +16,31 @@ import Editor from './Editor.vue';
 
 interface IProp {
   title?: string;
+  code?: string;
+  width?: number;
 }
-const props = withDefaults(defineProps<IProp>(), {
+withDefaults(defineProps<IProp>(), {
   title: '编辑',
-  code: ''
+  code: '',
+  width: 800,
 });
-const emit = defineEmits(['update:modelValue']);
+const emit = defineEmits(['ok']);
 const open = ref(false);
+const code = ref('');
+
+const handleOk = () => {
+  emit('ok', code.value);
+  open.value = false;
+};
 
 defineExpose({
-  open: () => {open.value = true},
-  close: () => {open.value = false},
-  getCode: () => {
-    return props.code;
-  }
+  open: (codeStr: string) => {
+    open.value = true;
+    code.value = codeStr;
+  },
+  close: () => {
+    open.value = false;
+  },
 });
 </script>
 

+ 1 - 1
src/mock/index.ts

@@ -15,7 +15,7 @@ export default [
     },
   },
   {
-    url: '/api/get/example/bar/list',
+    url: '/api/get/example/bar',
     method: 'post',
     timeout: 2000,
     response: {

+ 1 - 0
src/views/designer/component/Configurator.vue

@@ -68,6 +68,7 @@ const handleConfigChange = (config: any) => {
 .configurator {
   width: 300px;
   overflow-y: auto;
+  padding: 0 12px;
   .config-content {
     padding: 12px;
     padding-top: 0;

+ 6 - 1
types/echart.d.ts

@@ -1,11 +1,16 @@
 import { DataSourceType } from "@/enum/index";
 
+export interface ChartData {
+  xData: string[];
+  yData: number[] | string[];
+  series: any[];
+}
 // chart数据源
 export interface DataSource {
   // 类型
   sourceType: DataSourceType.STATIC,
   // 数据
-  data?: any[],
+  data?: ChartData,
   // 接口相关
   url?: string,
   // 请求方式

+ 2 - 1
types/index.d.ts

@@ -2,4 +2,5 @@ declare interface Fn<T = any, R = T> {
   (...arg: T[]): R;
 }
 
-declare module 'lodash';
+declare module 'lodash';
+declare module 'js-beautify';