Parcourir la source

Merge branch 'dev' of https://gitee.com/MTrun/go-view into master-fetch-dev

奔跑的面条 il y a 3 ans
Parent
commit
858ee8515d
73 fichiers modifiés avec 2138 ajouts et 225 suppressions
  1. 2 0
      package.json
  2. 6 0
      src/api/mock/index.ts
  3. 4 74
      src/api/mock/map.json
  4. 20 68
      src/api/mock/test.mock.ts
  5. BIN
      src/assets/images/chart/charts/capsule.png
  6. BIN
      src/assets/images/chart/charts/map_amap.png
  7. BIN
      src/assets/images/chart/informations/iframe.png
  8. BIN
      src/assets/images/chart/informations/text_barrage.png
  9. 3 5
      src/components/GoSystemInfo/index.vue
  10. 1 0
      src/components/Pages/Flipper/index.ts
  11. 3 9
      src/components/Pages/Flipper/index.vue
  12. 6 0
      src/enums/editPageEnum.ts
  13. 6 0
      src/enums/pageEnum.ts
  14. 2 1
      src/hooks/index.ts
  15. 47 0
      src/hooks/useLifeHandler.hook.ts
  16. 0 6
      src/packages/components/Charts/Bars/BarCommon/config.vue
  17. 0 6
      src/packages/components/Charts/Bars/BarCrossrange/config.vue
  18. 25 0
      src/packages/components/Charts/Bars/CapsuleChart/config.ts
  19. 53 0
      src/packages/components/Charts/Bars/CapsuleChart/config.vue
  20. 10 0
      src/packages/components/Charts/Bars/CapsuleChart/data.json
  21. 15 0
      src/packages/components/Charts/Bars/CapsuleChart/index.ts
  22. 228 0
      src/packages/components/Charts/Bars/CapsuleChart/index.vue
  23. 2 1
      src/packages/components/Charts/Bars/index.ts
  24. 0 3
      src/packages/components/Charts/Lines/LineCommon/config.vue
  25. 83 0
      src/packages/components/Charts/Maps/MapAmap/config.ts
  26. 199 0
      src/packages/components/Charts/Maps/MapAmap/config.vue
  27. 19 0
      src/packages/components/Charts/Maps/MapAmap/data.json
  28. 15 0
      src/packages/components/Charts/Maps/MapAmap/index.ts
  29. 130 0
      src/packages/components/Charts/Maps/MapAmap/index.vue
  30. 2 1
      src/packages/components/Charts/Maps/index.ts
  31. 1 1
      src/packages/components/Decorates/Mores/CountDown/config.ts
  32. 1 1
      src/packages/components/Decorates/Mores/CountDown/index.vue
  33. 1 1
      src/packages/components/Decorates/Mores/FlipperNumber/config.ts
  34. 1 1
      src/packages/components/Decorates/Mores/FlipperNumber/index.vue
  35. 20 0
      src/packages/components/Informations/Mores/Iframe/config.ts
  36. 36 0
      src/packages/components/Informations/Mores/Iframe/config.vue
  37. 15 0
      src/packages/components/Informations/Mores/Iframe/index.ts
  38. 49 0
      src/packages/components/Informations/Mores/Iframe/index.vue
  39. 1 2
      src/packages/components/Informations/Mores/Image/index.vue
  40. 1 1
      src/packages/components/Informations/Mores/Video/index.ts
  41. 2 1
      src/packages/components/Informations/Mores/index.ts
  42. 42 0
      src/packages/components/Informations/Texts/TextBarrage/config.ts
  43. 89 0
      src/packages/components/Informations/Texts/TextBarrage/config.vue
  44. 14 0
      src/packages/components/Informations/Texts/TextBarrage/index.ts
  45. 102 0
      src/packages/components/Informations/Texts/TextBarrage/index.vue
  46. 2 1
      src/packages/components/Informations/Texts/index.ts
  47. 12 1
      src/packages/index.d.ts
  48. 2 0
      src/packages/public/publicConfig.ts
  49. 3 0
      src/plugins/icon.ts
  50. 2 1
      src/router/index.ts
  51. 2 1
      src/router/modules/chart.route.ts
  52. 20 0
      src/router/modules/edit.route.ts
  53. 3 1
      src/router/modules/index.ts
  54. 1 1
      src/router/modules/preview.route.ts
  55. 0 1
      src/settings/animations/index.ts
  56. 4 1
      src/settings/designSetting.ts
  57. 9 1
      src/styles/pages/index.scss
  58. 2 1
      src/views/chart/ContentCharts/components/ChartsItemBox/index.vue
  59. 1 0
      src/views/chart/ContentConfigurations/components/ChartData/components/ChartDataRequest/components/RequestHeader/index.vue
  60. 4 0
      src/views/chart/ContentConfigurations/components/ChartData/components/ChartDataRequest/components/RequestTargetConfig/index.vue
  61. 189 0
      src/views/chart/ContentConfigurations/components/ChartEvent/components/ChartEventMonacoEditor/importTemplate.ts
  62. 3 0
      src/views/chart/ContentConfigurations/components/ChartEvent/components/ChartEventMonacoEditor/index.ts
  63. 285 0
      src/views/chart/ContentConfigurations/components/ChartEvent/components/ChartEventMonacoEditor/index.vue
  64. 26 0
      src/views/chart/ContentConfigurations/components/ChartEvent/index.vue
  65. 1 0
      src/views/chart/ContentConfigurations/index.d.ts
  66. 8 1
      src/views/chart/ContentConfigurations/index.vue
  67. 5 0
      src/views/chart/ContentEdit/components/EditBottom/index.vue
  68. 135 29
      src/views/chart/ContentEdit/components/EditTools/index.vue
  69. 128 0
      src/views/edit/index.vue
  70. 2 1
      src/views/preview/components/PreviewRenderGroup/index.vue
  71. 2 1
      src/views/preview/components/PreviewRenderList/index.vue
  72. 25 0
      src/views/preview/wrapper.vue
  73. 6 1
      vite.config.ts

+ 2 - 0
package.json

@@ -11,6 +11,8 @@
     "lint:fix": "eslint --ext .js,.jsx,.ts,.tsx,.vue src --fix"
   },
   "dependencies": {
+    "@amap/amap-jsapi-loader": "^1.0.1",
+    "@amap/amap-jsapi-types": "^0.0.8",
     "@types/color": "^3.0.3",
     "@types/crypto-js": "^4.1.1",
     "@types/keymaster": "^1.6.30",

+ 6 - 0
src/api/mock/index.ts

@@ -15,6 +15,7 @@ export const radarUrl = '/mock/radarData'
 export const heatMapUrl = '/mock/heatMapData'
 export const scatterBasicUrl = '/mock/scatterBasic'
 export const mapUrl = '/mock/map'
+export const capsuleUrl = '/mock/capsule'
 export const wordCloudUrl = '/mock/wordCloud'
 export const treemapUrl = '/mock/treemap'
 export const threeEarth01Url = '/mock/threeEarth01Data'
@@ -82,6 +83,11 @@ const mockObject: MockMethod[] = [
     method: RequestHttpEnum.GET,
     response: () => test.fetchMap
   },
+  {
+    url: capsuleUrl,
+    method: RequestHttpEnum.GET,
+    response: () => test.fetchCapsule
+  },
   {
     url: wordCloudUrl,
     method: RequestHttpEnum.GET,

+ 4 - 74
src/api/mock/map.json

@@ -1,79 +1,9 @@
 {
-  "point": [
+  "markers|50": [
     {
-      "name": "北京",
-      "value": [116.405285, 39.904989, 200]
-    },
-    {
-      "name": "郑州",
-      "value": [113.665412, 34.757975, 888]
-    },
-    {
-      "name": "青海",
-      "value": [101.778916, 36.623178, 666]
-    },
-    {
-      "name": "宁夏回族自治区",
-      "value": [106.278179, 38.46637, 66]
-    },
-    {
-      "name": "哈尔滨市",
-      "value": [126.642464, 45.756967, 101]
-    }
-  ],
-  "map": [
-    {
-      "name": "北京市",
-      "value": "@integer(0, 1000)"
-    },
-    {
-      "name": "河北省",
-      "value": "@integer(0, 1000)"
-    },
-
-    {
-      "name": "江苏省",
-      "value": "@integer(0, 1000)"
-    },
-    {
-      "name": "福建省",
-      "value": "@integer(0, 1000)"
-    },
-    {
-      "name": "山东省",
-      "value": "@integer(0, 1000)"
-    },
-    {
-      "name": "河南省",
-      "value": "@integer(0, 1000)"
-    },
-    {
-      "name": "湖北省",
-      "value": "@integer(0, 1000)"
-    },
-    {
-      "name": "广西壮族自治区",
-      "value": "@integer(0, 1000)"
-    },
-    {
-      "name": "海南省",
-      "value": "@integer(0, 1000)"
-    },
-    {
-      "name": "青海省",
-      "value": "@integer(0, 1000)"
-    },
-    {
-      "name": "新疆维吾尔自治区",
-      "value": "@integer(0, 1000)"
+      "name": "某某地市",
+      "value": "@integer(2, 20)",
+      "position": ["@float(115, 117, 1, 6)", "@float(38, 40, 1, 6)"]
     }
-  ],
-  "pieces": [
-    { "gte": 1000, "label": ">1000" },
-    { "gte": 600, "lte": 999, "label": "600-999" },
-    { "gte": 200, "lte": 599, "label": "200-599" },
-    { "gte": 50, "lte": 199, "label": "49-199" },
-    { "gte": 10, "lte": 49, "label": "10-49" },
-    { "lte": 9, "label": "<9" }
   ]
 }

+ 20 - 68
src/api/mock/test.mock.ts

@@ -11,27 +11,7 @@ export default {
     msg: '请求成功',
     data: {
       dimensions: ['product', 'dataOne'],
-      source: [
-        {
-          product: '@name',
-          'dataOne|0-900': 3
-        },
-        {
-          product: '@name',
-          'dataOne|0-900': 3
-        },
-        {
-          product: '@name',
-          'dataOne|0-900': 3
-        },
-        {
-          product: '@name',
-          'dataOne|0-900': 3
-        },
-        {
-          product: '@name',
-          'dataOne|0-900': 3
-        },
+      'source|50': [
         {
           product: '@name',
           'dataOne|0-900': 3
@@ -39,6 +19,22 @@ export default {
       ]
     }
   },
+  // 胶囊图
+  fetchCapsule: {
+    code: 0,
+    status: 200,
+    msg: '请求成功',
+    data: {
+      dimensions: ['name', 'value'],
+      source: [
+        { name: '厦门', 'value|0-40': 20 },
+        { name: '南阳', 'value|20-60': 40 },
+        { name: '北京', 'value|40-80': 60 },
+        { name: '上海', 'value|60-100': 80 },
+        { name: '新疆', value: 100 }
+      ]
+    }
+  },
   // 图表
   fetchMockData: {
     code: 0,
@@ -46,32 +42,7 @@ export default {
     msg: '请求成功',
     data: {
       dimensions: ['product', 'dataOne', 'dataTwo'],
-      source: [
-        {
-          product: '@name',
-          'dataOne|100-900': 3,
-          'dataTwo|100-900': 3
-        },
-        {
-          product: '@name',
-          'dataOne|100-900': 3,
-          'dataTwo|100-900': 3
-        },
-        {
-          product: '@name',
-          'dataOne|100-900': 3,
-          'dataTwo|100-900': 3
-        },
-        {
-          product: '@name',
-          'dataOne|100-900': 3,
-          'dataTwo|100-900': 3
-        },
-        {
-          product: '@name',
-          'dataOne|100-900': 3,
-          'dataTwo|100-900': 3
-        },
+      'source|50': [
         {
           product: '@name',
           'dataOne|100-900': 3,
@@ -85,21 +56,7 @@ export default {
     code: 0,
     status: 200,
     msg: '请求成功',
-    data: [
-      { name: '@name', 'value|100-900': 5 },
-      { name: '@name', 'value|100-900': 5 },
-      { name: '@name', 'value|100-900': 5 },
-      { name: '@name', 'value|100-900': 5 },
-      { name: '@name', 'value|100-900': 5 },
-      { name: '@name', 'value|100-900': 5 },
-      { name: '@name', 'value|100-900': 5 },
-      { name: '@name', 'value|100-900': 5 },
-      { name: '@name', 'value|100-900': 5 },
-      { name: '@name', 'value|100-900': 5 },
-      { name: '@name', 'value|100-900': 5 },
-      { name: '@name', 'value|100-900': 5 },
-      { name: '@name', 'value|100-900': 5 }
-    ]
+    'data|50': [{ name: '@name', 'value|100-900': 5 }]
   },
   // 轮播表格
   fetchScrollBoard: {
@@ -262,12 +219,7 @@ export default {
     data: [
       {
         startArray: { name: '@name', N: '@integer(10, 100)', E: '@integer(10, 100)' },
-        endArray: [
-          { name: '@name', N: '@integer(10, 100)', E: '@integer(10, 100)' },
-          { name: '@name', N: '@integer(10, 100)', E: '@integer(10, 100)' },
-          { name: '@name', N: '@integer(10, 100)', E: '@integer(10, 100)' },
-          { name: '@name', N: '@integer(10, 100)', E: '@integer(10, 100)' }
-        ]
+        'endArray|10': [{ name: '@name', N: '@integer(10, 100)', E: '@integer(10, 100)' }]
       }
     ]
   }

BIN
src/assets/images/chart/charts/capsule.png


BIN
src/assets/images/chart/charts/map_amap.png


BIN
src/assets/images/chart/informations/iframe.png


BIN
src/assets/images/chart/informations/text_barrage.png


+ 3 - 5
src/components/GoSystemInfo/index.vue

@@ -11,7 +11,7 @@
       </template>
 
       <n-list-item>
-        <n-space :size="20">
+        <n-space class="go-my-2" :size="20">
           <n-text class="item-left">版权声明:</n-text>
           <n-text>
             GoView 版权属于
@@ -21,8 +21,7 @@
       </n-list-item>
 
       <n-list-item>
-        <n-divider style="margin-top: 0" />
-        <n-space :size="20">
+        <n-space class="go-my-2" :size="20">
           <n-text class="item-left">协议备注:</n-text>
           <n-text>
             请遵守开源 MIT 协议,以上声明 <n-text type="error">不可删除</n-text>,否则视作侵权行为,后果自负!
@@ -31,8 +30,7 @@
       </n-list-item>
 
       <n-list-item>
-        <n-divider style="margin-top: 0" />
-        <n-space :size="20">
+        <n-space  class="go-mt-2" :size="20">
           <n-text class="item-left">商业授权:</n-text>
           <n-text>
             若不想保留版权声明,请通过仓库/交流群 联系项目作者,进行授权

+ 1 - 0
src/components/Flipper/index.ts → src/components/Pages/Flipper/index.ts

@@ -1,4 +1,5 @@
 import Flipper from './index.vue'
+
 type FlipType = 'up' | 'down'
 
 export { Flipper, FlipType }

+ 3 - 9
src/components/Flipper/index.vue → src/components/Pages/Flipper/index.vue

@@ -1,19 +1,13 @@
 <template>
-  <div class="M-Flipper" :class="[flipType, { go: isFlipping }]">
+  <div class="go-Flipper" :class="[flipType, { go: isFlipping }]">
     <div class="digital front" :data-front="frontTextFromData"></div>
     <div class="digital back" :data-back="backTextFromData"></div>
   </div>
 </template>
 
-<script lang="ts">
-export default {
-  name: 'Flipper'
-}
-</script>
-
 <script lang="ts" setup>
 import { ref, PropType, watch } from 'vue'
-import { FlipType } from '.'
+import { FlipType }  from './index'
 
 const props = defineProps({
   flipType: {
@@ -131,7 +125,7 @@ $lineColor: #4a9ef8;
 }
 // #endregion
 
-.M-Flipper {
+.go-Flipper {
   display: inline-block;
   position: relative;
   width: $width;

+ 6 - 0
src/enums/editPageEnum.ts

@@ -9,6 +9,12 @@ export enum DragKeyEnum {
   DRAG_KEY = 'ChartData'
 }
 
+// 不同页面保存操作
+export enum SavePageEnum {
+  CHART = 'SaveChart',
+  JSON = 'SaveJSON'
+}
+
 // 操作枚举
 export enum MenuEnum {
   // 移动

+ 6 - 0
src/enums/pageEnum.ts

@@ -12,6 +12,12 @@ export enum PreviewEnum {
   CHART_PREVIEW_NAME = 'ChartPreview',
 }
 
+export enum EditEnum {
+  //  图表JSON编辑
+  CHART_EDIT = '/chart/edit/:id(.*)*',
+  CHART_EDIT_NAME = 'ChartEdit',
+}
+
 export enum PageEnum {
   // 登录
   BASE_LOGIN = '/login',

+ 2 - 1
src/hooks/index.ts

@@ -2,4 +2,5 @@ export * from '@/hooks/useTheme.hook'
 export * from '@/hooks/usePreviewScale.hook'
 export * from '@/hooks/useCode.hook'
 export * from '@/hooks/useChartDataFetch.hook'
-export * from '@/hooks/useSystemInit.hook'
+export * from '@/hooks/useSystemInit.hook'
+export * from '@/hooks/useLifeHandler.hook'

+ 47 - 0
src/hooks/useLifeHandler.hook.ts

@@ -0,0 +1,47 @@
+import { CreateComponentType, EventLife } from '@/packages/index.d'
+import * as echarts from 'echarts'
+
+// 所有图表组件集合对象
+const components: { [K in string]?: any } = {}
+
+// 项目提供的npm 包变量
+export const npmPkgs = { echarts }
+
+export const useLifeHandler = (chartConfig: CreateComponentType) => {
+  const events = chartConfig.events || {}
+  // 生成生命周期事件
+  const lifeEvents = {
+    [EventLife.BEFORE_MOUNT](e: any) {
+      // 存储组件
+      components[chartConfig.id] = e.component
+      const fnStr = (events[EventLife.BEFORE_MOUNT] || '').trim()
+      generateFunc(fnStr, e)
+    },
+    [EventLife.MOUNTED](e: any) {
+      const fnStr = (events[EventLife.MOUNTED] || '').trim()
+      generateFunc(fnStr, e)
+    }
+  }
+  return lifeEvents
+}
+
+/**
+ *
+ * @param fnStr 用户方法体代码
+ * @param e 执行生命周期的动态组件实例
+ */
+function generateFunc(fnStr: string, e: any) {
+  try {
+    // npmPkgs 便于拷贝 echarts 示例时设置option 的formatter等相关内容
+    Function(`
+      "use strict";
+      return (
+        async function(e, components, node_modules){
+          const {${Object.keys(npmPkgs).join()}} = node_modules;
+          ${fnStr}
+        }
+      )`)().bind(e?.component)(e, components, npmPkgs)
+  } catch (error) {
+    console.error(error)
+  }
+}

+ 0 - 6
src/packages/components/Charts/Bars/BarCommon/config.vue

@@ -3,12 +3,6 @@
   <global-setting :optionData="optionData"></global-setting>
   <CollapseItem v-for="(item, index) in seriesList" :key="index" :name="`柱状图-${index + 1}`" :expanded="true">
     <SettingItemBox name="图形">
-      <SettingItem name="颜色">
-        <n-color-picker size="small" :modes="['hex']" v-model:value="item.itemStyle.color"></n-color-picker>
-      </SettingItem>
-      <SettingItem>
-        <n-button size="small" @click="item.itemStyle.color = null"> 恢复默认 </n-button>
-      </SettingItem>
       <SettingItem name="宽度">
         <n-input-number
           v-model:value="item.barWidth"

+ 0 - 6
src/packages/components/Charts/Bars/BarCrossrange/config.vue

@@ -3,12 +3,6 @@
   <global-setting :optionData="optionData"></global-setting>
   <CollapseItem v-for="(item, index) in seriesList" :key="index" :name="`柱状图-${index+1}`" :expanded="true">
     <SettingItemBox name="图形">
-      <SettingItem name="颜色">
-        <n-color-picker size="small" :modes="['hex']" v-model:value="item.itemStyle.color"></n-color-picker>
-      </SettingItem>
-      <SettingItem>
-        <n-button size="small" @click="item.itemStyle.color = null">恢复默认</n-button>
-      </SettingItem>
       <SettingItem name="宽度">
           <n-input-number
           v-model:value="item.barWidth"

+ 25 - 0
src/packages/components/Charts/Bars/CapsuleChart/config.ts

@@ -0,0 +1,25 @@
+import { PublicConfigClass } from '@/packages/public'
+import { CapsuleChartConfig } from './index'
+import { CreateComponentType } from '@/packages/index.d'
+import { chartInitConfig } from '@/settings/designSetting'
+
+import cloneDeep from 'lodash/cloneDeep'
+import dataJson from './data.json'
+
+export const option = {
+  dataset: dataJson,
+  colors: ['#c4ebad', '#6be6c1', '#a0a7e6', '#96dee8', '#3fb1e3' ],
+  unit: '',
+  itemHeight: 10,
+  valueFontSize: 16,
+  paddingRight: 50,
+  paddingLeft: 50,
+  showValue: true
+}
+
+export default class Config extends PublicConfigClass implements CreateComponentType {
+  public key: string = CapsuleChartConfig.key
+  public attr = { ...chartInitConfig, zIndex: -1 }
+  public chartConfig = cloneDeep(CapsuleChartConfig)
+  public option = cloneDeep(option)
+}

+ 53 - 0
src/packages/components/Charts/Bars/CapsuleChart/config.vue

@@ -0,0 +1,53 @@
+<template>
+  <!-- Echarts 全局设置 -->
+  <global-setting :optionData="optionData"> </global-setting>
+  <!-- 胶囊柱图 -->
+  <collapse-item name="胶囊柱图" expanded>
+    <SettingItemBox name="布局">
+      <setting-item name="左侧边距">
+        <n-input-number v-model:value="optionData.paddingLeft" :min="10" :step="1" size="small"></n-input-number>
+      </setting-item>
+      <setting-item name="右侧边距">
+        <n-input-number v-model:value="optionData.paddingRight" :min="10" :step="1" size="small"></n-input-number>
+      </setting-item>
+      <setting-item name="每块高度(px)">
+        <n-input-number v-model:value="optionData.itemHeight" :min="0" :step="1" size="small"></n-input-number>
+      </setting-item>
+    </SettingItemBox>
+    <SettingItemBox name="文本">
+      <setting-item name="所有文字大小">
+        <n-input-number v-model:value="optionData.valueFontSize" :min="0" :step="1" size="small"></n-input-number>
+      </setting-item>
+      <setting-item name="单位">
+        <n-input v-model:value="optionData.unit" size="small"></n-input>
+      </setting-item>
+
+      <SettingItem>
+        <n-space>
+          <n-switch v-model:value="optionData.showValue" size="small"></n-switch>
+          <n-text>显示数值</n-text>
+        </n-space>
+      </SettingItem>
+    </SettingItemBox>
+    <SettingItemBox name="颜色">
+      <setting-item v-for="(item, index) in optionData.colors" :key="index" :name="`颜色${index}`">
+        <n-color-picker v-model:value="optionData.colors[index]" size="small" :modes="['hex']"></n-color-picker>
+      </setting-item>
+    </SettingItemBox>
+  </collapse-item>
+</template>
+
+<script setup lang="ts">
+import { PropType, computed } from 'vue'
+import { GlobalSetting, CollapseItem, SettingItemBox, SettingItem } from '@/components/Pages/ChartItemSetting'
+import { GlobalThemeJsonType } from '@/settings/chartThemes/index'
+
+import { option } from './config'
+
+const props = defineProps({
+  optionData: {
+    type: Object as PropType<typeof option & GlobalThemeJsonType>,
+    required: true
+  }
+})
+</script>

+ 10 - 0
src/packages/components/Charts/Bars/CapsuleChart/data.json

@@ -0,0 +1,10 @@
+{
+  "dimensions": ["name", "value"],
+  "source": [
+    { "name": "厦门", "value": 20 },
+    { "name": "南阳", "value": 40 },
+    { "name": "北京", "value": 60 },
+    { "name": "上海", "value": 80 },
+    { "name": "新疆", "value": 100 }
+  ]
+}

+ 15 - 0
src/packages/components/Charts/Bars/CapsuleChart/index.ts

@@ -0,0 +1,15 @@
+import image from '@/assets/images/chart/charts/capsule.png'
+import { ConfigType, PackagesCategoryEnum, ChartFrameEnum } from '@/packages/index.d'
+import { ChatCategoryEnum, ChatCategoryEnumName } from '../../index.d'
+
+export const CapsuleChartConfig: ConfigType = {
+  key: 'CapsuleChart',
+  chartKey: 'VCapsuleChart',
+  conKey: 'VCCapsuleChart',
+  title: '胶囊柱图',
+  category: ChatCategoryEnum.BAR,
+  categoryName: ChatCategoryEnumName.BAR,
+  package: PackagesCategoryEnum.CHARTS,
+  chartFrame: ChartFrameEnum.COMMON,
+  image
+}

+ 228 - 0
src/packages/components/Charts/Bars/CapsuleChart/index.vue

@@ -0,0 +1,228 @@
+<template>
+  <div
+    v-if="state.mergedConfig"
+    class="go-dv-capsule-chart"
+    :style="{ 
+      fontSize: numberSizeHandle(state.mergedConfig.valueFontSize),
+      paddingLeft: numberSizeHandle(state.mergedConfig.paddingLeft),
+      paddingRight: numberSizeHandle(state.mergedConfig.paddingRight)
+    }"
+  >
+    <div class="label-column">
+      <div
+        v-for="item in state.mergedConfig.dataset.source"
+        :key="item[state.mergedConfig.dataset.dimensions[0]]"
+        :style="{ height: state.capsuleItemHeight, lineHeight: state.capsuleItemHeight }"
+      >
+        {{ item[state.mergedConfig.dataset.dimensions[0]] }}
+      </div>
+      <div class="laset">&nbsp;</div>
+    </div>
+
+    <div class="capsule-container">
+      <div
+        v-for="(capsule, index) in state.capsuleLength"
+        :key="index"
+        class="capsule-item"
+        :style="{ height: state.capsuleItemHeight }"
+      >
+        <div
+          class="capsule-item-column"
+          :style="`width: ${capsule * 100}%; background-color: ${
+            state.mergedConfig.colors[index % state.mergedConfig.colors.length]
+          };height:calc(100% - ${2}px);`"
+        >
+          <div v-if="state.mergedConfig.showValue" class="capsule-item-value">
+            {{ state.capsuleValue[index] }}
+          </div>
+        </div>
+      </div>
+
+      <div class="unit-label">
+        <div v-for="(label, index) in state.labelData" :key="label + index">
+          {{ label }}
+        </div>
+      </div>
+    </div>
+
+    <div v-if="state.mergedConfig.unit" class="unit-text">
+      {{ state.mergedConfig.unit }}
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { onMounted, watch, reactive, PropType } from 'vue'
+import merge from 'lodash/merge'
+import cloneDeep from 'lodash/cloneDeep'
+import { useChartDataFetch } from '@/hooks'
+import { useChartEditStore } from '@/store/modules/chartEditStore/chartEditStore'
+import config, { option } from './config'
+
+type DataProps = {
+  name: string | number
+  value: string | number
+  [key: string]: string | number
+}
+
+interface StateProps {
+  defaultConfig: {
+    dataset: {
+      dimensions: Array<string>
+      source: Array<DataProps>
+    }
+    colors: Array<string>
+    unit: string
+    showValue: boolean
+    itemHeight: number
+    valueFontSize: number
+    paddingLeft: number
+    paddingRight: number
+  }
+  mergedConfig: any
+  capsuleLength: Array<number>
+  capsuleValue: Array<string | Object>
+  labelData: Array<number>
+  capsuleItemHeight: string
+}
+
+const props = defineProps({
+  chartConfig: {
+    type: Object as PropType<config>,
+    default: () => ({})
+  }
+})
+
+const state = reactive<StateProps>({
+  defaultConfig: option,
+  mergedConfig: null,
+  capsuleLength: [],
+  capsuleValue: [],
+  labelData: [],
+  capsuleItemHeight: ''
+})
+
+watch(
+  () => props.chartConfig.option,
+  newVal => {
+    calcData(newVal)
+  },
+  {
+    deep: true
+  }
+)
+
+const calcData = (data: any) => {
+  mergeConfig(props.chartConfig.option)
+
+  calcCapsuleLengthAndLabelData()
+}
+
+const mergeConfig = (data: any) => {
+  state.mergedConfig = merge(cloneDeep(state.defaultConfig), data || {})
+}
+
+// 数据解析
+const calcCapsuleLengthAndLabelData = () => {
+  const { source } = state.mergedConfig.dataset
+  if (!source.length) return
+
+  state.capsuleItemHeight = numberSizeHandle(state.mergedConfig.itemHeight)
+  const capsuleValue = source.map((item: DataProps) => item[state.mergedConfig.dataset.dimensions[1]])
+
+  const maxValue = Math.max(...capsuleValue)
+
+  state.capsuleValue = capsuleValue
+
+  state.capsuleLength = capsuleValue.map((v: any) => (maxValue ? v / maxValue : 0))
+
+  const oneFifth = maxValue / 5
+
+  const labelData = Array.from(new Set(new Array(6).fill(0).map((v, i) => Math.ceil(i * oneFifth))))
+
+  state.labelData = labelData
+}
+
+const numberSizeHandle = (val: string | number) => {
+  return val + 'px'
+}
+
+onMounted(() => {
+  calcData(props.chartConfig.option)
+})
+
+// 预览
+useChartDataFetch(props.chartConfig, useChartEditStore, (newData: any) => {
+  calcData(newData)
+})
+</script>
+
+<style lang="scss" scoped>
+@include go('dv-capsule-chart') {
+  position: relative;
+  display: flex;
+  flex-direction: row;
+  box-sizing: border-box;
+  padding: 20px;
+  padding-right: 50px;
+  color: #b9b8cc;
+
+  .label-column {
+    display: flex;
+    flex-direction: column;
+    justify-content: space-between;
+    box-sizing: border-box;
+    padding-right: 10px;
+    text-align: right;
+    > div:not(:last-child) {
+      margin: 5px 0;
+    }
+  }
+
+  .capsule-container {
+    flex: 1;
+    display: flex;
+    flex-direction: column;
+    justify-content: space-between;
+  }
+
+  .capsule-item {
+    box-shadow: 0 0 3px #999;
+    height: 10px;
+    margin: 5px 0px;
+    border-radius: 5px;
+
+    .capsule-item-column {
+      position: relative;
+      height: 8px;
+      margin-top: 1px;
+      border-radius: 5px;
+      transition: all 0.3s;
+      display: flex;
+      justify-content: flex-end;
+      align-items: center;
+
+      .capsule-item-value {
+        padding-left: 10px;
+        transform: translateX(100%);
+      }
+    }
+  }
+
+  .unit-label {
+    height: 20px;
+    position: relative;
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+  }
+
+  .unit-text {
+    text-align: right;
+    display: flex;
+    align-items: flex-end;
+    line-height: 20px;
+    margin-left: 10px;
+  }
+}
+</style>

+ 2 - 1
src/packages/components/Charts/Bars/index.ts

@@ -1,4 +1,5 @@
 import { BarCommonConfig } from './BarCommon/index'
 import { BarCrossrangeConfig } from './BarCrossrange/index'
+import { CapsuleChartConfig } from './CapsuleChart/index'
 
-export default [BarCommonConfig, BarCrossrangeConfig]
+export default [BarCommonConfig, BarCrossrangeConfig, CapsuleChartConfig]

+ 0 - 3
src/packages/components/Charts/Lines/LineCommon/config.vue

@@ -3,9 +3,6 @@
   <global-setting :optionData="optionData"></global-setting>
   <CollapseItem v-for="(item, index) in seriesList" :key="index" :name="`折线图-${index + 1}`" :expanded="true">
     <SettingItemBox name="线条">
-      <setting-item name="颜色">
-        <n-color-picker size="small" :modes="['hex']" v-model:value="item.lineStyle.color"></n-color-picker>
-      </setting-item>
       <SettingItem name="宽度">
         <n-input-number
           v-model:value="item.lineStyle.width"

+ 83 - 0
src/packages/components/Charts/Maps/MapAmap/config.ts

@@ -0,0 +1,83 @@
+import { PublicConfigClass } from '@/packages/public'
+import { CreateComponentType } from '@/packages/index.d'
+import { MapAmapConfig } from './index'
+import { chartInitConfig } from '@/settings/designSetting'
+import cloneDeep from 'lodash/cloneDeep'
+import dataJson from './data.json'
+
+export enum ThemeEnum {
+  NORMAL = 'normal',
+  DARK = 'dark',
+  LIGHT = 'light',
+  WHITES_MOKE = 'whitesmoke',
+  FRESH = 'fresh',
+  GREY = 'grey',
+  GRAFFITI = 'graffiti',
+  MACARON = 'macaron',
+  BLUE = 'blue',
+  DARKBLUE = 'darkblue',
+  WINE = 'wine'
+}
+
+export enum LangEnum {
+  ZH_CN = 'zh_cn',
+  EN = 'en',
+  ZH_EN = 'zh_en'
+}
+
+export enum ViewModeEnum {
+  PLANE = '2D',
+  STEREOSCOPIC = '3D'
+}
+
+export enum FeaturesEnum {
+  BG = 'bg',
+  POINT = 'point',
+  ROAD = 'road',
+  BUILDING = 'building'
+}
+
+export enum MarkerEnum {
+  // 圆圈
+  CIRCLE_MARKER = 'CircleMarker',
+  // 定位标点
+  MARKER = 'Marker',
+  // 暂无
+  NONE = 'none'
+}
+
+export const option = {
+  dataset: dataJson,
+  mapOptions: {
+    pitch: 60,
+    skyColor: '#53A9DE',
+    amapKey: 'd5f3e16589dbecae64d05fe90e2ba4f2',
+    amapStyleKey: ThemeEnum.DARK,
+    amapStyleKeyCustom: '',
+    amapLon: 116.397428,
+    amapLat: 39.90923,
+    amapZindex: 11,
+    marker: {
+      fillColor: '#E98984FF',
+      fillOpacity: 0.5,
+      strokeColor: 'white',
+      strokeWeight: 2,
+      strokeOpacity: 0.5,
+      zIndex: 10,
+      bubble: true,
+      cursor: 'pointer',
+      clickable: true
+    },
+    mapMarkerType: MarkerEnum.CIRCLE_MARKER,
+    viewMode: ViewModeEnum.PLANE,
+    lang: LangEnum.ZH_CN,
+    features: [FeaturesEnum.BG, FeaturesEnum.POINT, FeaturesEnum.ROAD, FeaturesEnum.BUILDING]
+  }
+}
+
+export default class Config extends PublicConfigClass implements CreateComponentType {
+  public key = MapAmapConfig.key
+  public attr = { ...chartInitConfig, w: 1000, h: 800, zIndex: -1 }
+  public chartConfig = cloneDeep(MapAmapConfig)
+  public option = cloneDeep(option)
+}

+ 199 - 0
src/packages/components/Charts/Maps/MapAmap/config.vue

@@ -0,0 +1,199 @@
+<template>
+  <collapse-item name="基础" :expanded="true">
+    <setting-item-box name="语言类型" :alone="true">
+      <setting-item>
+        <n-select size="small" v-model:value="optionData.mapOptions.lang" :options="langOptions" />
+      </setting-item>
+    </setting-item-box>
+    <setting-item-box name="Key" :alone="true">
+      <setting-item name="请务必使用自己的高德应用 key">
+        <n-input v-model:value="optionData.mapOptions.amapKey" size="small"></n-input>
+      </setting-item>
+    </setting-item-box>
+    <setting-item-box name="自定义地图样式ID" :alone="true">
+      <setting-item>
+        <n-input size="small" v-model:value="optionData.mapOptions.amapStyleKeyCustom" />
+      </setting-item>
+    </setting-item-box>
+  </collapse-item>
+  <collapse-item name="地图" :expanded="true">
+    <setting-item-box name="主题">
+      <setting-item>
+        <n-select size="small" v-model:value="optionData.mapOptions.amapStyleKey" :options="themeOptions" />
+      </setting-item>
+    </setting-item-box>
+    <setting-item-box name="内容" :alone="true">
+      <n-checkbox-group v-model:value="optionData.mapOptions.features">
+        <n-space item-style="display: flex;">
+          <n-checkbox :value="item.value" :label="item.label" v-for="(item, index) in featuresOptions" :key="index" />
+        </n-space>
+      </n-checkbox-group>
+    </setting-item-box>
+    <setting-item-box name="位置">
+      <setting-item name="经度">
+        <n-input-number v-model:value="optionData.mapOptions.amapLon" :show-button="false" size="small">
+          <template #suffix>°</template>
+        </n-input-number>
+      </setting-item>
+      <setting-item name="纬度">
+        <n-input-number v-model:value="optionData.mapOptions.amapLat" :show-button="false" size="small">
+          <template #suffix>°</template>
+        </n-input-number>
+      </setting-item>
+      <setting-item name="初始缩放">
+        <n-input-number v-model:value="optionData.mapOptions.amapZindex" :min="0" size="small"></n-input-number>
+      </setting-item>
+    </setting-item-box>
+    <setting-item-box name="模式" :alone="true">
+      <setting-item>
+        <n-radio-group v-model:value="optionData.mapOptions.viewMode" name="radiogroup">
+          <n-space>
+            <n-radio v-for="song in viewModeOptions" :key="song.value" :value="song.value">
+              {{ song.label }}
+            </n-radio>
+          </n-space>
+        </n-radio-group>
+      </setting-item>
+    </setting-item-box>
+    <template v-if="optionData.mapOptions.viewMode === '3D'">
+      <setting-item-box>
+        <setting-item name="天空色">
+          <n-color-picker size="small" :modes="['hex']" v-model:value="optionData.mapOptions.skyColor"></n-color-picker>
+        </setting-item>
+        <setting-item name="俯仰角">
+          <n-input-number v-model:value="optionData.mapOptions.pitch" :min="0" :max="83" size="small"></n-input-number>
+        </setting-item>
+      </setting-item-box>
+    </template>
+  </collapse-item>
+  <collapse-item name="标记" :expanded="true">
+    <setting-item-box name="样式">
+      <setting-item name="类型">
+        <n-select size="small" v-model:value="optionData.mapOptions.mapMarkerType" :options="MarkerOptions" />
+      </setting-item>
+      <setting-item name="颜色">
+        <n-color-picker v-model:value="optionData.mapOptions.marker.fillColor" size="small"></n-color-picker>
+      </setting-item>
+    </setting-item-box>
+  </collapse-item>
+</template>
+
+<script setup lang="ts">
+import { PropType } from 'vue'
+import { option, MarkerEnum, ThemeEnum, LangEnum, ViewModeEnum, FeaturesEnum } from './config'
+import { CollapseItem, SettingItemBox, SettingItem } from '@/components/Pages/ChartItemSetting'
+
+defineProps({
+  optionData: {
+    type: Object as PropType<typeof option>,
+    required: true
+  }
+})
+
+const themeOptions = [
+  {
+    value: ThemeEnum.NORMAL,
+    label: '标准'
+  },
+  {
+    value: ThemeEnum.DARK,
+    label: '幻影黑'
+  },
+  {
+    value: ThemeEnum.LIGHT,
+    label: '月光银'
+  },
+  {
+    value: ThemeEnum.WHITES_MOKE,
+    label: '远山黛'
+  },
+  {
+    value: ThemeEnum.FRESH,
+    label: '草色青'
+  },
+  {
+    value: ThemeEnum.GREY,
+    label: '雅士灰'
+  },
+  {
+    value: ThemeEnum.GRAFFITI,
+    label: '涂鸦'
+  },
+  {
+    value: ThemeEnum.MACARON,
+    label: '马卡龙'
+  },
+  {
+    value: ThemeEnum.BLUE,
+    label: '靛青蓝'
+  },
+  {
+    value: ThemeEnum.DARKBLUE,
+    label: '极夜蓝'
+  },
+  {
+    value: ThemeEnum.WINE,
+    label: '酱籽'
+  }
+]
+
+const langOptions = [
+  {
+    value: LangEnum.ZH_CN,
+    label: '中文简体'
+  },
+  {
+    value: LangEnum.EN,
+    label: '英文'
+  },
+  {
+    value: LangEnum.ZH_EN,
+    label: '中英文对照'
+  }
+]
+
+const viewModeOptions = [
+  {
+    value: ViewModeEnum.PLANE,
+    label: '2D'
+  },
+  {
+    value: ViewModeEnum.STEREOSCOPIC,
+    label: '3D'
+  }
+]
+
+const featuresOptions = [
+  {
+    value: FeaturesEnum.BG,
+    label: '显示地图背景'
+  },
+  {
+    value: FeaturesEnum.POINT,
+    label: '显示标识'
+  },
+  {
+    value: FeaturesEnum.ROAD,
+    label: '显示道路'
+  },
+  {
+    value: FeaturesEnum.BUILDING,
+    label: '显示建筑'
+  }
+]
+
+const MarkerOptions = [
+  {
+    value: MarkerEnum.CIRCLE_MARKER,
+    label: '圆形标点'
+  },
+  {
+    value: MarkerEnum.MARKER,
+    label: '定位标点'
+  },
+  {
+    value: MarkerEnum.NONE,
+    label: '隐藏标点'
+  }
+]
+</script>

+ 19 - 0
src/packages/components/Charts/Maps/MapAmap/data.json

@@ -0,0 +1,19 @@
+{
+  "markers": [
+    {
+      "name": "某某地市",
+      "value": 10,
+      "position": [116.300467, 39.907761]
+    },
+    {
+      "name": "某某地市",
+      "value": 15,
+      "position": [116.400567, 39.908761]
+    },
+    {
+      "name": "某某地市",
+      "value": 20,
+      "position": [116.200467, 39.937761]
+    }
+  ]
+}

+ 15 - 0
src/packages/components/Charts/Maps/MapAmap/index.ts

@@ -0,0 +1,15 @@
+import { ConfigType, PackagesCategoryEnum, ChartFrameEnum } from '@/packages/index.d'
+import image from '@/assets/images/chart/charts/map_amap.png'
+import { ChatCategoryEnum, ChatCategoryEnumName } from '../../index.d'
+
+export const MapAmapConfig: ConfigType = {
+  key: 'MapAmap',
+  chartKey: 'VMapAmap',
+  conKey: 'VCMapAmap',
+  title: '高德地图',
+  category: ChatCategoryEnum.MAP,
+  categoryName: ChatCategoryEnumName.MAP,
+  package: PackagesCategoryEnum.CHARTS,
+  chartFrame: ChartFrameEnum.COMMON,
+  image
+}

+ 130 - 0
src/packages/components/Charts/Maps/MapAmap/index.vue

@@ -0,0 +1,130 @@
+<template>
+  <div ref="vChartRef"></div>
+</template>
+
+<script setup lang="ts">
+import { ref, PropType, toRefs, watch } from 'vue'
+import AMapLoader from '@amap/amap-jsapi-loader'
+import { CreateComponentType } from '@/packages/index.d'
+import { useChartDataFetch } from '@/hooks'
+import { useChartEditStore } from '@/store/modules/chartEditStore/chartEditStore'
+import { MarkerEnum } from './config'
+import { isArray } from '@/utils'
+
+const props = defineProps({
+  chartConfig: {
+    type: Object as PropType<CreateComponentType>,
+    required: true
+  }
+})
+let {
+  amapKey,
+  amapStyleKey,
+  amapLon,
+  amapLat,
+  amapZindex,
+  mapMarkerType,
+  lang,
+  amapStyleKeyCustom,
+  features,
+  viewMode,
+  pitch,
+  skyColor,
+  marker
+} = toRefs(props.chartConfig.option.mapOptions)
+
+let mapIns: any = null
+let markers: any = []
+let AMapIns: any = null
+const vChartRef = ref<HTMLElement>()
+
+const initMap = (newData: any) => {
+  // 初始化
+  AMapLoader.load({
+    key: amapKey.value, //api服务key--另外需要在public中使用安全密钥!!!
+    version: '1.4.8', // 指定要加载的 JSAPI 的版本,缺省时默认为 1.4.15
+    plugins: ['AMap.PlaceSearch', 'AMap.AutoComplete'] // 需要使用的的插件列表
+  })
+    .then(AMap => {
+      AMapIns = AMap
+      mapIns = new AMap.Map(vChartRef.value, {
+        resizeEnable: true,
+        zoom: amapZindex.value, // 地图显示的缩放级别
+        center: [amapLon.value, amapLat.value],
+        mapStyle: `amap://styles/${amapStyleKeyCustom.value !== '' ? amapStyleKeyCustom.value : amapStyleKey.value}`, //自定义地图的显示样式
+        lang: lang.value,
+        features: features.value,
+        pitch: pitch.value, // 地图俯仰角度,有效范围 0 度- 83 度
+        skyColor: skyColor.value,
+        viewMode: viewMode.value, // 地图模式
+        willReadFrequently: true
+      })
+      dataHandle(props.chartConfig.option.dataset)
+    })
+    .catch(e => {})
+}
+
+const dataHandle = (newData: any) => {
+  if (!mapIns && !AMapIns) {
+    initMap(props.chartConfig.option)
+    return
+  }
+  if (isArray(newData.markers)) {
+    // 先清除旧标记
+    mapIns.remove(markers)
+    markers = []
+    // 记录新标记
+    if (mapMarkerType.value === MarkerEnum.MARKER) {
+      newData.markers.forEach((markerItem: any) => {
+        const markerInstance = new AMapIns.Marker({
+          position: [markerItem.position[0], markerItem.position[1]],
+          offset: new AMapIns.Pixel(-13, -30)
+        })
+        markers.push(markerInstance)
+        markerInstance.setMap(mapIns)
+      })
+    } else if (mapMarkerType.value === MarkerEnum.CIRCLE_MARKER) {
+      newData.markers.forEach((markerItem: any) => {
+        const markerInstance = new AMapIns.CircleMarker({
+          center: [markerItem.position[0], markerItem.position[1]],
+          radius: markerItem.value,
+          ...marker.value
+        })
+        markers.push(markerInstance)
+        markerInstance.setMap(mapIns)
+      })
+    }
+  }
+}
+
+const stopWatch = watch(
+  () => props.chartConfig.option.mapOptions,
+  option => {
+    initMap(option)
+  },
+  {
+    immediate: true,
+    deep: true
+  }
+)
+
+watch(
+  () => props.chartConfig.option.dataset,
+  newData => {
+    try {
+      dataHandle(newData)
+    } catch (error) {
+      console.log(error)
+    }
+  },
+  {
+    deep: false
+  }
+)
+
+// 预览
+useChartDataFetch(props.chartConfig, useChartEditStore, (newData: any) => {
+  stopWatch()
+  dataHandle(newData)
+})
+</script>

+ 2 - 1
src/packages/components/Charts/Maps/index.ts

@@ -1,3 +1,4 @@
 import { MapBaseConfig } from './MapBase/index'
+import { MapAmapConfig } from './MapAmap/index'
 
-export default [ MapBaseConfig ]
+export default [MapBaseConfig, MapAmapConfig]

+ 1 - 1
src/packages/components/Decorates/Mores/CountDown/config.ts

@@ -3,7 +3,7 @@ import { CreateComponentType } from '@/packages/index.d'
 import { CountDownConfig } from './index'
 import cloneDeep from 'lodash/cloneDeep'
 import { chartInitConfig } from '@/settings/designSetting'
-import { FlipType } from '@/components/Flipper'
+import { FlipType } from '@/components/Pages/Flipper'
 
 type STYLE = '时分秒' | '冒号'
 

+ 1 - 1
src/packages/components/Decorates/Mores/CountDown/index.vue

@@ -76,7 +76,7 @@
 <script setup lang="ts">
 import { PropType, toRefs, watch, ref, onMounted } from 'vue'
 import { CreateComponentType } from '@/packages/index.d'
-import { Flipper } from '@/components/Flipper'
+import { Flipper } from '@/components/Pages/Flipper'
 import { OptionType } from './config'
 import { CountdownInst, CountdownProps } from 'naive-ui/es/countdown/src/Countdown'
 

+ 1 - 1
src/packages/components/Decorates/Mores/FlipperNumber/config.ts

@@ -3,7 +3,7 @@ import { CreateComponentType } from '@/packages/index.d'
 import { FlipperNumberConfig } from './index'
 import cloneDeep from 'lodash/cloneDeep'
 import { chartInitConfig } from '@/settings/designSetting'
-import { FlipType } from '@/components/Flipper'
+import { FlipType } from '@/components/Pages/Flipper'
 
 export interface OptionType {
   dataset: number | string

+ 1 - 1
src/packages/components/Decorates/Mores/FlipperNumber/index.vue

@@ -21,7 +21,7 @@ import { PropType, toRefs, watch, ref } from 'vue'
 import { CreateComponentType } from '@/packages/index.d'
 import { useChartEditStore } from '@/store/modules/chartEditStore/chartEditStore'
 import { useChartDataFetch } from '@/hooks'
-import { Flipper } from '@/components/Flipper'
+import { Flipper } from '@/components/Pages/Flipper'
 import { OptionType } from './config'
 
 const props = defineProps({

+ 20 - 0
src/packages/components/Informations/Mores/Iframe/config.ts

@@ -0,0 +1,20 @@
+import { PublicConfigClass } from '@/packages/public'
+import { CreateComponentType } from '@/packages/index.d'
+import { chartInitConfig } from '@/settings/designSetting'
+import { IframeConfig } from './index'
+import cloneDeep from 'lodash/cloneDeep'
+
+export const option = {
+  // 网站路径
+  dataset: "https://cn.vuejs.org/",
+  // 圆角
+  borderRadius: 10
+}
+
+export default class Config extends PublicConfigClass implements CreateComponentType
+{
+  public key = IframeConfig.key
+  public attr = { ...chartInitConfig, w: 800, h: 800, zIndex: -1 }
+  public chartConfig = cloneDeep(IframeConfig)
+  public option = cloneDeep(option)
+}

+ 36 - 0
src/packages/components/Informations/Mores/Iframe/config.vue

@@ -0,0 +1,36 @@
+<template>
+  <collapse-item name="属性" :expanded="true">
+    <setting-item-box name="路径" :alone="true">
+      <setting-item name="请填写 https 协议的网址">
+        <n-input v-model:value="optionData.dataset" size="small"></n-input>
+      </setting-item>
+    </setting-item-box>
+    <setting-item-box name="样式">
+      <setting-item name="圆角">
+        <n-input-number
+          v-model:value="optionData.borderRadius"
+          size="small"
+          :min="0"
+          placeholder="圆角"
+        ></n-input-number>
+      </setting-item>
+    </setting-item-box>
+  </collapse-item>
+</template>
+
+<script setup lang="ts">
+import { PropType } from "vue";
+import { option } from "./config";
+import {
+  CollapseItem,
+  SettingItemBox,
+  SettingItem,
+} from "@/components/Pages/ChartItemSetting";
+
+const props = defineProps({
+  optionData: {
+    type: Object as PropType<typeof option>,
+    required: true,
+  },
+});
+</script>

+ 15 - 0
src/packages/components/Informations/Mores/Iframe/index.ts

@@ -0,0 +1,15 @@
+import image from '@/assets/images/chart/informations/iframe.png'
+import { ConfigType, PackagesCategoryEnum, ChartFrameEnum } from '@/packages/index.d'
+import { ChatCategoryEnum,ChatCategoryEnumName } from '../../index.d'
+
+export const IframeConfig: ConfigType = {
+  key: 'Iframe',
+  chartKey: 'VIframe',
+  conKey: 'VCIframe',
+  title: '远程网页',
+  category: ChatCategoryEnum.MORE,
+  categoryName: ChatCategoryEnumName.MORE,
+  package: PackagesCategoryEnum.INFORMATIONS,
+  chartFrame: ChartFrameEnum.COMMON,
+  image
+}

+ 49 - 0
src/packages/components/Informations/Mores/Iframe/index.vue

@@ -0,0 +1,49 @@
+<template>
+  <div :style="getStyle(borderRadius)">
+    <iframe :src="option.dataset" :width="w" :height="h" style="border-width: 0"></iframe>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { PropType, shallowReactive, watch, toRefs } from 'vue'
+import { useChartDataFetch } from '@/hooks'
+import { CreateComponentType } from '@/packages/index.d'
+import { useChartEditStore } from '@/store/modules/chartEditStore/chartEditStore'
+
+const props = defineProps({
+  chartConfig: {
+    type: Object as PropType<CreateComponentType>,
+    required: true
+  }
+})
+
+const { w, h } = toRefs(props.chartConfig.attr)
+const { borderRadius } = toRefs(props.chartConfig.option)
+
+const option = shallowReactive({
+  dataset: ''
+})
+
+const getStyle = (radius: number) => {
+  return {
+    borderRadius: `${radius}px`,
+    overflow: 'hidden'
+  }
+}
+
+// 编辑更新
+watch(
+  () => props.chartConfig.option.dataset,
+  (newData: string) => {
+    option.dataset = newData
+  },
+  {
+    immediate: true
+  }
+)
+
+// 预览更新
+useChartDataFetch(props.chartConfig, useChartEditStore, (newData: string) => {
+  option.dataset = newData
+})
+</script>

+ 1 - 2
src/packages/components/Informations/Mores/Image/index.vue

@@ -46,8 +46,7 @@ watch(
     option.dataset = newData
   },
   {
-    immediate: true,
-    deep: false
+    immediate: true
   }
 )
 

+ 1 - 1
src/packages/components/Informations/Mores/Video/index.ts

@@ -10,6 +10,6 @@ export const VideoConfig: ConfigType = {
   category: ChatCategoryEnum.MORE,
   categoryName: ChatCategoryEnumName.MORE,
   package: PackagesCategoryEnum.INFORMATIONS,
-  chartFrame: ChartFrameEnum.ECHARTS,
+  chartFrame: ChartFrameEnum.COMMON,
   image
 }

+ 2 - 1
src/packages/components/Informations/Mores/index.ts

@@ -1,5 +1,6 @@
 import { ImageConfig } from './Image/index'
+import { IframeConfig } from './Iframe/index'
 import { VideoConfig } from './Video/index'
 import { WordCloudConfig } from './WordCloud/index'
 
-export default [ImageConfig, VideoConfig, WordCloudConfig]
+export default [WordCloudConfig, ImageConfig, VideoConfig, IframeConfig]

+ 42 - 0
src/packages/components/Informations/Texts/TextBarrage/config.ts

@@ -0,0 +1,42 @@
+import { PublicConfigClass } from '@/packages/public'
+import { CreateComponentType } from '@/packages/index.d'
+import { TextBarrageConfig } from './index'
+import { chartInitConfig } from '@/settings/designSetting'
+import cloneDeep from 'lodash/cloneDeep'
+
+export enum FontWeightEnum {
+  NORMAL = '常规',
+  BOLD = '加粗',
+}
+
+export const FontWeightObject = {
+  [FontWeightEnum.NORMAL]: 'normal',
+  [FontWeightEnum.BOLD]: 'bold',
+}
+
+export const option = {
+  
+  dataset: '让数字化看得见',
+  fontSize: 32,
+  fontColor: '#ffffff',
+  fontWeight: 'normal',
+  // 字间距
+  letterSpacing: 5,
+  //阴影
+  showShadow:  true,
+  boxShadow: 'none',
+  hShadow: 0,
+  vShadow: 0,
+  blurShadow: 8,
+  colorShadow: '#0075ff',
+  //动画
+  animationTime: 0,
+  animationSpeed: 50,
+}
+
+export default class Config extends PublicConfigClass implements CreateComponentType {
+  public key = TextBarrageConfig.key
+  public attr = { ...chartInitConfig, w: 500, h: 70, zIndex: -1 }
+  public chartConfig = cloneDeep(TextBarrageConfig)
+  public option = cloneDeep(option)
+}

+ 89 - 0
src/packages/components/Informations/Texts/TextBarrage/config.vue

@@ -0,0 +1,89 @@
+<template>
+  <collapse-item name="信息" :expanded="true">
+    <setting-item-box name="文字" :alone="true">
+      <setting-item>
+        <n-input v-model:value="optionData.dataset" size="small"></n-input>
+      </setting-item>
+    </setting-item-box>
+  </collapse-item>
+
+  <collapse-item name="样式" :expanded="true">
+    <setting-item-box name="文字">
+      <setting-item name="颜色">
+        <n-color-picker size="small" :modes="['hex']" v-model:value="optionData.fontColor"></n-color-picker>
+      </setting-item>
+      <setting-item name="字体大小">
+        <n-input-number v-model:value="optionData.fontSize" size="small" placeholder="字体大小"></n-input-number>
+      </setting-item>
+      <setting-item name="字体粗细">
+        <n-select v-model:value="optionData.fontWeight" size="small" :options="fontWeightOptions" />
+      </setting-item>
+
+      <setting-item name="字间距">
+        <n-input-number v-model:value="optionData.letterSpacing" size="small" placeholder="输入字间距"></n-input-number>
+      </setting-item>
+    </setting-item-box>
+    <setting-item-box name="阴影">
+      <setting-item>
+        <n-space>
+          <n-switch v-model:value="optionData.showShadow" size="small" />
+          <n-text>展示阴影</n-text>
+        </n-space>
+      </setting-item>
+      <setting-item name="颜色">
+        <n-color-picker size="small" :modes="['hex']" v-model:value="optionData.colorShadow"></n-color-picker
+      ></setting-item>
+      <setting-item name="x">
+        <n-input-number v-model:value="optionData.hShadow" size="small"></n-input-number
+      ></setting-item>
+      <setting-item name="y">
+        <n-input-number v-model:value="optionData.vShadow" size="small"></n-input-number
+      ></setting-item>
+      <setting-item name="模糊">
+        <n-input-number v-model:value="optionData.blurShadow" size="small"></n-input-number
+      ></setting-item>
+    </setting-item-box>
+
+    <setting-item-box name="动画">
+      <setting-item name="动画速度">
+        <n-input-number
+          v-model:value="optionData.animationSpeed"
+          size="small"
+          placeholder="动画速度"
+          :min="0"
+        ></n-input-number>
+      </setting-item>
+      <setting-item name="动画间隔">
+        <n-input-number
+          v-model:value="optionData.animationTime"
+          size="small"
+          placeholder="动画间隔"
+          :min="0"
+        ></n-input-number>
+      </setting-item>
+    </setting-item-box>
+  </collapse-item>
+</template>
+
+<script setup lang="ts">
+import { PropType } from 'vue'
+import { option, FontWeightEnum, FontWeightObject } from './config'
+import { CollapseItem, SettingItemBox, SettingItem } from '@/components/Pages/ChartItemSetting'
+const props = defineProps({
+  optionData: {
+    type: Object as PropType<typeof option>,
+    required: true
+  }
+})
+
+const fontWeightOptions = [
+  {
+    label: FontWeightEnum.NORMAL,
+    value: FontWeightObject[FontWeightEnum.NORMAL]
+  },
+  {
+    label: FontWeightEnum.BOLD,
+    value: FontWeightObject[FontWeightEnum.BOLD]
+  }
+]
+</script>

+ 14 - 0
src/packages/components/Informations/Texts/TextBarrage/index.ts

@@ -0,0 +1,14 @@
+import image from '@/assets/images/chart/informations/text_barrage.png'
+import { ConfigType, PackagesCategoryEnum } from '@/packages/index.d'
+import { ChatCategoryEnum,ChatCategoryEnumName } from '../../index.d'
+
+export const TextBarrageConfig: ConfigType = {
+  key: 'TextBarrage',
+  chartKey: 'VTextBarrage',
+  conKey: 'VCTextBarrage',
+  title: '弹幕文字',
+  category: ChatCategoryEnum.TEXT,
+  categoryName: ChatCategoryEnumName.TEXT,
+  package: PackagesCategoryEnum.INFORMATIONS,
+  image
+}

+ 102 - 0
src/packages/components/Informations/Texts/TextBarrage/index.vue

@@ -0,0 +1,102 @@
+<template>
+  <div class="go-text-box">
+    <div class="content">
+      <span>
+        {{ option.dataset }}
+      </span>
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { PropType, toRefs, shallowReactive, watch, computed, ref } from 'vue'
+import { CreateComponentType } from '@/packages/index.d'
+import { useChartDataFetch } from '@/hooks'
+import { useChartEditStore } from '@/store/modules/chartEditStore/chartEditStore'
+import { option as configOption } from './config'
+import { values } from 'lodash'
+
+const props = defineProps({
+  chartConfig: {
+    type: Object as PropType<CreateComponentType & typeof option>,
+    required: true
+  }
+})
+
+const { w } = toRefs(props.chartConfig.attr)
+
+const { fontColor, fontSize, letterSpacing, fontWeight, animationTime, animationSpeed, boxShadow } = toRefs(
+  props.chartConfig.option
+)
+
+const option = shallowReactive({
+  dataset: configOption.dataset
+})
+
+// 手动更新
+watch(
+  () => props.chartConfig.option.dataset,
+  (newData: any) => {
+    option.dataset = newData
+  },
+  {
+    immediate: true,
+    deep: false
+  }
+)
+
+//阴影
+watch(
+  props.chartConfig.option,
+  () => {
+    try {
+      if (props.chartConfig.option.showShadow) {
+        boxShadow.value = `${props.chartConfig.option.hShadow}px ${props.chartConfig.option.vShadow}px ${props.chartConfig.option.blurShadow}px ${props.chartConfig.option.colorShadow}`
+      } else {
+        boxShadow.value = 'none'
+      }
+    } catch (error) {
+      console.log(error)
+    }
+  },
+  {
+    immediate: true
+  }
+)
+
+const transitionDuration = computed(() => {
+  return Math.floor((w.value as any) / (animationSpeed.value as any))
+})
+
+// 预览更新
+useChartDataFetch(props.chartConfig, useChartEditStore, (newData: string) => {
+  option.dataset = newData
+})
+</script>
+
+<style lang="scss" scoped>
+@include go('text-box') {
+  display: flex;
+  align-items: center;
+  .content {
+    width: 100%;
+    color: v-bind('fontColor');
+    font-size: v-bind('fontSize + "px"');
+    letter-spacing: v-bind('letterSpacing + "px"');
+    font-weight: v-bind('fontWeight');
+    text-shadow: v-bind('boxShadow');
+    position: absolute;
+    animation: barrage v-bind('transitionDuration + "s"') linear v-bind('animationTime + "s"') infinite;
+  }
+  @keyframes barrage {
+    from {
+      left: 100%;
+      transform: translateX(0);
+    }
+    to {
+      left: 0;
+      transform: translateX(-100%);
+    }
+  }
+}
+</style>

+ 2 - 1
src/packages/components/Informations/Texts/index.ts

@@ -1,4 +1,5 @@
 import { TextCommonConfig } from './TextCommon/index'
+import { TextBarrageConfig } from './TextBarrage/index'
 import { TextGradientConfig } from './TextGradient/index'
 
-export default [TextCommonConfig, TextGradientConfig]
+export default [TextCommonConfig, TextGradientConfig, TextBarrageConfig]

+ 12 - 1
src/packages/index.d.ts

@@ -90,6 +90,14 @@ export const BlendModeEnumList = [
   { label: '亮度', value: 'luminosity' }
 ]
 
+// vue3 生命周期事件
+export enum EventLife {
+  // 渲染之后
+  MOUNTED = 'vnodeMounted',
+  // 渲染之前
+  BEFORE_MOUNT = 'vnodeBeforeMount',
+}
+
 // 组件实例类
 export interface PublicConfigType {
   id: string
@@ -115,12 +123,15 @@ export interface PublicConfigType {
   }
   filter?: string
   status: StatusType
+  events?: {
+    [K in EventLife]?: string
+  }
 }
 
 export interface CreateComponentType extends PublicConfigType, requestConfig {
   key: string
   chartConfig: ConfigType
-  option: GlobalThemeJsonType
+  option: GlobalThemeJsonType,
 }
 
 // 组件成组实例类

+ 2 - 0
src/packages/public/publicConfig.ts

@@ -81,6 +81,8 @@ export class PublicConfigClass implements PublicConfigType {
   public request = cloneDeep(requestConfig)
   // 数据过滤
   public filter = undefined
+  // 事件
+  public events = undefined
 }
 
 // 多选成组类

+ 3 - 0
src/plugins/icon.ts

@@ -27,6 +27,7 @@ import {
   LockClosedOutline as LockClosedOutlineIcon,
   HelpCircleOutline as HelpOutlineIcon,
   CodeSlash as CodeSlashIcon,
+  Create as CreateIcon,
   Rocket as RocketIcon,
   Duplicate as DuplicateIcon,
   DuplicateOutline as DuplicateOutlineIcon,
@@ -108,6 +109,8 @@ const ionicons5 = {
   DuplicateOutlineIcon,
   // 代码
   CodeSlashIcon,
+  // 修改代码
+  CreateIcon,
   // 事件(火箭)
   RocketIcon,
   // 退出

+ 2 - 1
src/router/index.ts

@@ -21,7 +21,8 @@ const RootRoute: Array<RouteRecordRaw> = [
       ...RedirectRoute,
       modules.projectRoutes,
       modules.chartRoutes,
-      modules.previewRoutes
+      modules.previewRoutes,
+      modules.editRoutes
     ]
   }
 ]

+ 2 - 1
src/router/modules/chart.route.ts

@@ -12,7 +12,8 @@ const chartRoutes: RouteRecordRaw = {
   component: importPath['ChartEnum.CHART_HOME_NAME'],
   meta: {
     title: '工作空间',
-    isRoot: true
+    isRoot: true,
+    noKeepAlive: true,
   }
 }
 

+ 20 - 0
src/router/modules/edit.route.ts

@@ -0,0 +1,20 @@
+import { RouteRecordRaw } from 'vue-router'
+import { EditEnum } from '@/enums/pageEnum'
+
+// 引入路径
+const importPath = {
+  [EditEnum.CHART_EDIT_NAME]: () => import('@/views/edit/index.vue')
+}
+
+const chartRoutes: RouteRecordRaw = {
+  path: EditEnum.CHART_EDIT,
+  name: EditEnum.CHART_EDIT_NAME,
+  component: importPath[EditEnum.CHART_EDIT_NAME],
+  meta: {
+    title: '编辑',
+    isRoot: true
+  }
+}
+
+
+export default chartRoutes

+ 3 - 1
src/router/modules/index.ts

@@ -1,9 +1,11 @@
 import projectRoutes from './project.router'
 import chartRoutes from './chart.route'
 import previewRoutes from './preview.route'
+import editRoutes from './edit.route'
 
 export default {
   projectRoutes,
   chartRoutes,
-  previewRoutes
+  previewRoutes,
+  editRoutes
 }

+ 1 - 1
src/router/modules/preview.route.ts

@@ -3,7 +3,7 @@ import { PreviewEnum } from '@/enums/pageEnum'
 
 // 引入路径
 const importPath = {
-  'PreviewEnum.CHART_PREVIEW_NAME': () => import('@/views/preview/index.vue')
+  'PreviewEnum.CHART_PREVIEW_NAME': () => import('@/views/preview/wrapper.vue')
 }
 
 const chartRoutes: RouteRecordRaw = {

+ 0 - 1
src/settings/animations/index.ts

@@ -11,7 +11,6 @@ export const animations = [
       { label: '放大晃动缩小', value: 'tada' },
       { label: '扇形摇摆', value: 'wobble' },
       { label: '左右上下晃动', value: 'jello' },
-      { label: 'Y轴旋转', value: 'flip' }
     ]
   },
   {

+ 4 - 1
src/settings/designSetting.ts

@@ -55,7 +55,10 @@ export const backgroundImageSize = 5
 // 预览展示方式
 export const previewScaleType = PreviewScaleEnum.FIT
 
-// 数据请求间隔(s)
+// 编辑工作台同步到 JSON 的轮询间隔(5S)
+export const editToJsonInterval = 5000
+
+// 数据请求间隔
 export const requestInterval = 30
 
 // 工作台自动保存间隔(s)

+ 9 - 1
src/styles/pages/index.scss

@@ -1 +1,9 @@
-// 页面全局样式
+// 页面全局样式
+// 去除高德地图 logo
+.amap-logo {
+  display: none !important;
+  opacity: 0 !important;
+}
+.amap-copyright {
+  opacity: 0 !important;
+}

+ 2 - 1
src/views/chart/ContentCharts/components/ChartsItemBox/index.vue

@@ -116,7 +116,8 @@ $centerHeight: 100px;
       height: $centerHeight;
       overflow: hidden;
       .list-img {
-        height: 100%;
+        height: 100px;
+        width: 140px;
         border-radius: 6px;
         @extend .go-transition;
       }

+ 1 - 0
src/views/chart/ContentConfigurations/components/ChartData/components/ChartDataRequest/components/RequestHeader/index.vue

@@ -73,6 +73,7 @@
         <n-text>SQL 类型不支持 Get 请求,请使用其它方式</n-text>
       </template>
       <template v-else>
+        <n-tag type="warning">需要后台提供专门处理 sql 的接口</n-tag>
         <setting-item-box name="键名">
           <n-tag type="primary" :bordered="false" style="width: 40px; font-size: 16px"> sql </n-tag>
         </setting-item-box>

+ 4 - 0
src/views/chart/ContentConfigurations/components/ChartData/components/ChartDataRequest/components/RequestTargetConfig/index.vue

@@ -75,6 +75,7 @@ import {
   heatMapUrl,
   scatterBasicUrl,
   mapUrl,
+  capsuleUrl,
   wordCloudUrl,
   treemapUrl,
   threeEarth01Url
@@ -122,6 +123,9 @@ const apiList = [
   {
     value: `【地图数据】${mapUrl}`
   },
+  {
+    value: `【胶囊柱图】${capsuleUrl}`
+  },
   {
     value: `【词云】${wordCloudUrl}`
   },

+ 189 - 0
src/views/chart/ContentConfigurations/components/ChartEvent/components/ChartEventMonacoEditor/importTemplate.ts

@@ -0,0 +1,189 @@
+// 获取实例
+const eTemplateString = `
+console.log(e)
+`
+// 获取全局 echarts 实例
+const echartsTemplateString = `
+console.log(echarts)
+`
+
+// 获取当前组件图表集合
+const componentsTemplateString = `
+console.log(components)
+`
+
+// 获取 nodeModules 实例
+const nodeModulesTemplateString = `
+console.log(node_modules)
+`
+
+// 添加点击事件
+const addClickTemplateString = `
+// 在渲染之后才能获取 dom 实例
+e.el.addEventListener('click', () => {
+  alert('我触发拉~');
+}, false)
+`
+
+// 异步引入
+const importTemplateString = `
+await import('https://lf3-cdn-tos.bytecdntp.com/cdn/expire-1-M/lodash.js/4.17.21/lodash.js')
+
+// lodash 默认赋值给 "_"
+console.log('isEqual', _.isEqual(['1'], ['1']))
+`
+
+// 修改图表 tooltip
+const tooltipTemplateString =
+  `
+// 获取echart实例
+const chart = this.refs.vChartRef.chart
+
+// 图表设置tooltip
+chart.setOption({
+  tooltip: {
+    trigger: 'axis', //item
+    enterable: true, 
+    formatter (params) {
+      return` +
+  '`' +
+  `
+        <div>
+          <img src="https://portrait.gitee.com/uploads/avatars/user/1654/4964818_MTrun_1653229420.png!avatar30">
+          <b><a href="https://gitee.com/dromara/go-view">《这是一个自定义的tooltip》</a></b>
+        <div>
+        <div style='border-radius:35px;color:#666'>
+        ` +
+  '$' +
+  `{Object.entries(params[0].value).map(kv => ` +
+  '`' +
+  `<div>` +
+  '$' +
+  `{kv[0]}:` +
+  '$' +
+  `{kv[1]}</div>` +
+  '`' +
+  `).join('')}
+        </div>
+      ` +
+  '`;' +
+  `
+    },
+  }
+})
+`
+
+// 添加【轮播列表】样式
+const addStyleString =
+  `
+// 组件样式作用域标识
+const scoped = this.subTree.scopeId
+function loadStyleString(css){
+	let style = document.createElement('style')
+	style.type = 'text/css'
+	style.appendChild(document.createTextNode(css))
+	let head = document.getElementsByTagName('head')[0]
+	head.appendChild(style)
+}
+loadStyleString(` +
+  '`' +
+  `
+.dv-scroll-board[` +
+  '$' +
+  `{scoped}] {
+  position: relative;
+  overflow: hidden;
+}
+.dv-scroll-board[` +
+  '$' +
+  `{scoped}]::before {
+  content: '';
+  display: block;
+  position: absolute;
+  top: -20%;
+  left: -100%;
+  width: 550px;
+  height: 60px;
+  transform: rotate(-45deg);
+  background-image: linear-gradient(rgba(0, 0, 0, 0), rgba(255, 255, 255, 0.3), rgba(0, 0, 0, 0));
+  animation: cross 2s infinite;
+}
+@keyframes cross{
+  to{
+    top: 80%;
+    left: 100%;
+    transform: rotate(-45deg);
+  }
+}
+` +
+  '`' +
+  `)
+`
+
+// 修改地图原点大小
+const editMapPointString = `
+const chart = this.refs.vChartRef.chart
+// 定义地图原点大小 同理可自定义标签等等内容
+this.props.chartConfig.option.series[0].symbolSize = (val) => {
+  return Math.sqrt(val[2]) / 3;
+}
+this.setupState.vEchartsSetOption();
+let i = 0; // 当前轮播索引
+const len = 3; // 轮播部分提示
+(function showTips() {
+  const action = (type, dataIndex) => {
+    chart.dispatchAction({
+      type,
+      dataIndex,
+      seriesIndex: 0,
+    });
+  }
+  setInterval(() => {
+    action("downplay", i);
+    action("hideTip", i);
+    if (i === len) i = 0;
+    i++;
+    action("highlight", i);
+    action("showTip", i);
+  }, 2000);
+})()
+`
+
+export const templateList = [
+  {
+    description: '获取当前组件实例',
+    code: eTemplateString
+  },
+  {
+    description: '获取全局 echarts 实例',
+    code: echartsTemplateString
+  },
+  {
+    description: '获取组件图表集合',
+    code: componentsTemplateString
+  },
+  {
+    description: '获取 nodeModules 实例',
+    code: nodeModulesTemplateString
+  },
+  {
+    description: '获取远程 CDN 库',
+    code: importTemplateString
+  },
+  {
+    description: '设置文字组件点击事件',
+    code: addClickTemplateString
+  },
+  {
+    description: '修改图表 tooltip',
+    code: tooltipTemplateString
+  },
+  {
+    description: '添加【轮播列表】样式',
+    code: addStyleString
+  },
+  {
+    description: '修改【地图】圆点,新增提示自动轮播',
+    code: editMapPointString
+  }
+]

+ 3 - 0
src/views/chart/ContentConfigurations/components/ChartEvent/components/ChartEventMonacoEditor/index.ts

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

+ 285 - 0
src/views/chart/ContentConfigurations/components/ChartEvent/components/ChartEventMonacoEditor/index.vue

@@ -0,0 +1,285 @@
+<template>
+  <n-collapse-item title="高级事件配置" name="2">
+    <template #header-extra>
+      <n-button type="primary" tertiary size="small" @click.stop="showModal = true">
+        <template #icon>
+          <n-icon>
+            <pencil-icon />
+          </n-icon>
+        </template>
+        编辑
+      </n-button>
+    </template>
+    <n-card>
+      <!-- 函数体 -->
+      <div v-for="eventName in EventLife" :key="eventName">
+        <p>
+          <span class="func-keyword">async {{ eventName }}</span> (e, components, echarts, node_modules) {
+        </p>
+        <p class="go-ml-4"><n-code :code="(targetData.events || {})[eventName]" language="typescript"></n-code></p>
+        <p>}<span>,</span></p>
+      </div>
+    </n-card>
+  </n-collapse-item>
+
+  <!-- 弹窗 -->
+  <n-modal class="go-chart-data-monaco-editor" v-model:show="showModal" :mask-closable="false">
+    <n-card :bordered="false" role="dialog" size="small" aria-modal="true" style="width: 1200px; height: 700px">
+      <template #header>
+        <n-space>
+          <n-text>高级事件编辑器(配合源码使用)</n-text>
+        </n-space>
+      </template>
+      <template #header-extra> </template>
+      <n-layout has-sider sider-placement="right">
+        <n-layout style="height: 580px; padding-right: 20px">
+          <n-tabs v-model:value="editTab" type="card" tab-style="min-width: 100px;">
+            <!-- 提示 -->
+            <template #suffix>
+              <n-text class="tab-tip" type="warning">tips: {{ EventLifeTip[editTab] }}</n-text>
+            </template>
+            <n-tab-pane
+              v-for="(eventName, index) in EventLife"
+              :key="index"
+              :tab="`${EventLifeName[eventName]}-${eventName}`"
+              :name="eventName"
+            >
+              <!-- 函数名称 -->
+              <p class="go-pl-3">
+                <span class="func-keyword">async function &nbsp;&nbsp;</span>
+                <span class="func-keyNameWord">{{ eventName }}(e, components, echarts, node_modules)&nbsp;&nbsp;{</span>
+              </p>
+              <!-- 编辑主体 -->
+              <monaco-editor v-model:modelValue="events[eventName]" height="480px" language="javascript" />
+              <!-- 函数结束 -->
+              <p class="go-pl-3 func-keyNameWord">}</p>
+            </n-tab-pane>
+          </n-tabs>
+        </n-layout>
+        <n-layout-sider
+          :collapsed-width="14"
+          :width="340"
+          show-trigger="bar"
+          collapse-mode="transform"
+          content-style="padding: 12px 12px 0px 12px;margin-left: 3px;"
+        >
+          <n-tabs default-value="1" justify-content="space-evenly" type="segment">
+            <!-- 验证结果 -->
+            <n-tab-pane tab="验证结果" name="1" size="small">
+              <n-scrollbar trigger="none" style="max-height: 505px">
+                <n-collapse class="go-px-3" arrow-placement="right" :default-expanded-names="[1, 2, 3]">
+                  <template v-for="error in [validEvents()]" :key="error">
+                    <n-collapse-item title="错误函数" :name="1">
+                      <n-text depth="3">{{ error.errorFn || '暂无' }}</n-text>
+                    </n-collapse-item>
+                    <n-collapse-item title="错误信息" :name="2">
+                      <n-text depth="3">{{ error.name || '暂无' }}</n-text>
+                    </n-collapse-item>
+                    <n-collapse-item title="堆栈信息" :name="3">
+                      <n-text depth="3">{{ error.message || '暂无' }}</n-text>
+                    </n-collapse-item>
+                  </template>
+                </n-collapse>
+              </n-scrollbar>
+            </n-tab-pane>
+            <!-- 辅助说明 -->
+            <n-tab-pane tab="变量说明" name="2">
+              <n-scrollbar trigger="none" style="max-height: 505px">
+                <n-collapse class="go-px-3" arrow-placement="right" :default-expanded-names="[1, 2, 3, 4]">
+                  <n-collapse-item title="e" :name="1">
+                    <n-text depth="3">触发对应生命周期事件时接收的参数</n-text>
+                  </n-collapse-item>
+                  <n-collapse-item title="this" :name="2">
+                    <n-text depth="3">图表组件实例</n-text>
+                    <br />
+                    <n-tag class="go-m-1" v-for="prop in ['refs', 'setupState', 'ctx', 'props', '...']" :key="prop">{{
+                      prop
+                    }}</n-tag>
+                  </n-collapse-item>
+                  <n-collapse-item title="components" :name="3">
+                    <n-text depth="3"
+                      >当前大屏内所有组件的集合id 图表组件中的配置id,可以获取其他图表组件进行控制</n-text
+                    >
+                    <n-code :code="`{\n  [id]: component\n}`" language="typescript"></n-code>
+                  </n-collapse-item>
+                  <n-collapse-item title="node_modules" :name="4">
+                    <n-text depth="3">以下是内置在代码环境中可用的包变量</n-text>
+                    <br />
+                    <n-tag class="go-m-1" v-for="pkg in Object.keys(npmPkgs || {})" :key="pkg">{{ pkg }}</n-tag>
+                  </n-collapse-item>
+                </n-collapse>
+              </n-scrollbar>
+            </n-tab-pane>
+            <!-- 介绍案例 -->
+            <n-tab-pane tab="介绍案例" name="3">
+              <n-scrollbar trigger="none" style="max-height: 505px">
+                <n-collapse arrow-placement="right">
+                  <n-collapse-item
+                    v-for="(item, index) in templateList"
+                    :key="index"
+                    :title="`案例${index + 1}:${item.description}`"
+                    :name="index"
+                  >
+                    <n-code :code="item.code" language="typescript"></n-code>
+                  </n-collapse-item>
+                </n-collapse>
+              </n-scrollbar>
+            </n-tab-pane>
+          </n-tabs>
+        </n-layout-sider>
+      </n-layout>
+
+      <template #action>
+        <n-space justify="space-between">
+          <div class="go-flex-items-center">
+            <n-tag :bordered="false" type="primary">
+              <template #icon>
+                <n-icon :component="DocumentTextIcon" />
+              </template>
+              提示
+            </n-tag>
+            <n-text class="go-ml-2" depth="2">通过提供的参数可为图表增加定制化的tooltip、交互事件等等</n-text>
+          </div>
+
+          <n-space>
+            <n-button size="medium" @click="closeEvents">取消</n-button>
+            <n-button size="medium" type="primary" @click="saveEvents">保存</n-button>
+          </n-space>
+        </n-space>
+      </template>
+    </n-card>
+  </n-modal>
+</template>
+
+<script lang="ts" setup>
+import { ref, computed, watch, toRefs, toRaw } from 'vue'
+import { MonacoEditor } from '@/components/Pages/MonacoEditor'
+import { useTargetData } from '../../../hooks/useTargetData.hook'
+import { templateList } from './importTemplate'
+import { npmPkgs } from '@/hooks'
+import { icon } from '@/plugins'
+import { goDialog, toString } from '@/utils'
+import { CreateComponentType, EventLife } from '@/packages/index.d'
+import { Script } from 'vm'
+
+const { targetData, chartEditStore } = useTargetData()
+const { DocumentTextIcon, ChevronDownIcon, PencilIcon } = icon.ionicons5
+
+const EventLifeName = {
+  [EventLife.BEFORE_MOUNT]: '渲染之前',
+  [EventLife.MOUNTED]: '渲染之后'
+}
+
+const EventLifeTip = {
+  [EventLife.BEFORE_MOUNT]: '此时组件 DOM 还未存在',
+  [EventLife.MOUNTED]: '此时组件 DOM 已经存在'
+}
+
+// 受控弹窗
+const showModal = ref(false)
+// 编辑区域控制
+const editTab = ref(EventLife.MOUNTED)
+// events 函数模板
+let events = ref({ ...targetData.value.events })
+// 事件错误标识
+const errorFlag = ref(false)
+
+// 验证语法
+const validEvents = () => {
+  let errorFn = ''
+  let message = ''
+  let name = ''
+
+  errorFlag.value = Object.entries(events.value).every(([eventName, str]) => {
+    try {
+      // 支持await,验证语法
+      const AsyncFunction = Object.getPrototypeOf(async function () {}).constructor
+      new AsyncFunction(str)
+      return true
+    } catch (error: any) {
+      message = error.message
+      name = error.name
+      errorFn = eventName
+      return false
+    }
+  })
+  return {
+    errorFn,
+    message,
+    name
+  }
+}
+
+// 关闭事件
+const closeEvents = () => {
+  showModal.value = false
+}
+
+// 新增事件
+const saveEvents = () => {
+  if (validEvents().errorFn) {
+    window['$message'].error('事件函数错误,无法进行保存')
+    return
+  }
+  if (Object.values(events.value).join('').trim() === '') {
+    // 清空事件
+    targetData.value.events = undefined
+  } else {
+    targetData.value.events = { ...events.value }
+  }
+  closeEvents()
+}
+
+watch(
+  () => showModal.value,
+  (newData: boolean) => {
+    if (newData) {
+      events.value = { ...targetData.value.events }
+    }
+  }
+)
+</script>
+
+<style lang="scss" scoped>
+/* 外层也要使用 */
+.func-keyword {
+  color: #b478cf;
+}
+
+@include go('chart-data-monaco-editor') {
+  .func-keyNameWord {
+    color: #70c0e8;
+  }
+  .tab-tip {
+    font-size: 12px;
+  }
+  &.n-card.n-modal,
+  .n-card {
+    @extend .go-background-filter;
+  }
+}
+@include deep() {
+  .n-layout,
+  .n-layout-sider {
+    background-color: transparent;
+  }
+  .go-editor-area {
+    max-height: 530px;
+  }
+  .checkbox--hidden:checked {
+    & + label {
+      .n-icon {
+        transition: all 0.3s;
+        transform: rotate(180deg);
+      }
+    }
+    & ~ .go-editor-area {
+      display: none;
+    }
+  }
+  // 优化代码换行
+  .n-code > pre {
+    white-space: break-spaces;
+  }
+}
+</style>

+ 26 - 0
src/views/chart/ContentConfigurations/components/ChartEvent/index.vue

@@ -0,0 +1,26 @@
+<template>
+  <!-- 事件配置 -->
+  <n-collapse class="go-mt-3" arrow-placement="right" :default-expanded-names="['1', '2']">
+    <n-text depth="3">
+      组件 id:
+      <n-text>{{ targetData.id }}</n-text>
+    </n-text>
+    <n-collapse-item title="基础事件配置" name="1">
+      <div class="go-event">
+        <n-text depth="3">【单击、双击、移入、移出】在开发中,即将上线!</n-text>
+        <br/>
+        <n-text depth="3">(备注:高级事件模块可自行实现上述功能)</n-text>
+      </div>
+    </n-collapse-item>
+    <chart-event-monaco-editor></chart-event-monaco-editor>
+  </n-collapse>
+</template>
+
+<script setup lang="ts">
+import { ref } from 'vue'
+import { ChartEventMonacoEditor } from './components/ChartEventMonacoEditor'
+import { useTargetData } from '../hooks/useTargetData.hook'
+
+const { targetData } = useTargetData()
+const showModal = ref(false)
+</script>

+ 1 - 0
src/views/chart/ContentConfigurations/index.d.ts

@@ -3,4 +3,5 @@ export enum TabsEnum {
   CHART_SETTING = 'chartSetting',
   CHART_ANIMATION = 'chartAnimation',
   CHART_DATA = 'chartData',
+  CHART_EVENT = 'chartEvent'
 }

+ 8 - 1
src/views/chart/ContentConfigurations/index.vue

@@ -75,12 +75,13 @@ const { getDetails } = toRefs(useChartLayoutStore())
 const { setItem } = useChartLayoutStore()
 const chartEditStore = useChartEditStore()
 
-const { ConstructIcon, FlashIcon, DesktopOutlineIcon, LeafIcon } = icon.ionicons5
+const { ConstructIcon, FlashIcon, DesktopOutlineIcon, LeafIcon, RocketIcon } = icon.ionicons5
 
 const ContentEdit = loadAsyncComponent(() => import('../ContentEdit/index.vue'))
 const CanvasPage = loadAsyncComponent(() => import('./components/CanvasPage/index.vue'))
 const ChartSetting = loadAsyncComponent(() => import('./components/ChartSetting/index.vue'))
 const ChartData = loadAsyncComponent(() => import('./components/ChartData/index.vue'))
+const ChartEvent = loadAsyncComponent(() => import('./components/ChartEvent/index.vue'))
 const ChartAnimation = loadAsyncComponent(() => import('./components/ChartAnimation/index.vue'))
 
 const collapsed = ref<boolean>(getDetails.value)
@@ -148,6 +149,12 @@ const chartsTabList = [
     title: '数据',
     icon: FlashIcon,
     render: ChartData
+  },
+  {
+    key: TabsEnum.CHART_EVENT,
+    title: '事件',
+    icon: RocketIcon,
+    render: ChartEvent
   }
 ]
 </script>

+ 5 - 0
src/views/chart/ContentEdit/components/EditBottom/index.vue

@@ -138,7 +138,12 @@ watchEffect(() => {
 
 <style lang="scss" scoped>
 $min-width: 500px;
+$max-width: 670px;
 @include go('edit-bottom') {
+  width: 100%;
+  min-width: $min-width;
+  min-width: $max-width;
+  padding: 0 10px;
   display: flex;
   align-items: center;
   justify-content: space-between;

+ 135 - 29
src/views/chart/ContentEdit/components/EditTools/index.vue

@@ -7,7 +7,11 @@
     @mouseleave="toolsMouseoutHandle"
   >
     <!-- PawIcon -->
-    <n-icon v-show="settingStore.getChartToolsStatus === ToolsStatusEnum.ASIDE && isMiniComputed " class="asideLogo" size="22">
+    <n-icon
+      v-show="settingStore.getChartToolsStatus === ToolsStatusEnum.ASIDE && isMiniComputed"
+      class="asideLogo"
+      size="22"
+    >
       <PawIcon></PawIcon>
     </n-icon>
 
@@ -58,17 +62,28 @@
 </template>
 
 <script setup lang="ts">
-import { ref, computed, h } from 'vue'
+import { ref, computed, h, watch } from 'vue'
 import { useSettingStore } from '@/store/modules/settingStore/settingStore'
 import { ToolsStatusEnum } from '@/store/modules/settingStore/settingStore.d'
+import { useChartEditStore } from '@/store/modules/chartEditStore/chartEditStore'
+import { fetchPathByName, routerTurnByPath, setSessionStorage, getLocalStorage } from '@/utils'
+import { editToJsonInterval } from '@/settings/designSetting'
+import { EditEnum, ChartEnum } from '@/enums/pageEnum'
+import { StorageEnum } from '@/enums/storageEnum'
+import { useRoute } from 'vue-router'
+import { useSync } from '@/views/chart/hooks/useSync.hook'
+import { SavePageEnum } from '@/enums/editPageEnum'
 import { GoSystemSet } from '@/components/GoSystemSet/index'
 import { exportHandle } from './utils'
 import { useFile } from './hooks/useFile.hooks'
 import { BtnListType, TypeEnum } from './index.d'
 import { icon } from '@/plugins'
 
-const { DownloadIcon, ShareIcon, PawIcon, SettingsSharpIcon } = icon.ionicons5
+const { DownloadIcon, ShareIcon, PawIcon, SettingsSharpIcon, CreateIcon } = icon.ionicons5
 const settingStore = useSettingStore()
+const chartEditStore = useChartEditStore()
+const routerParamsInfo = useRoute()
+const { updateComponent } = useSync()
 
 // 鼠标悬停定时器
 let mouseTime: any = null
@@ -80,38 +95,16 @@ const isMini = ref<boolean>(true)
 const asideTootipDis = ref(true)
 // 文件上传
 const { importUploadFileListRef, importCustomRequest, importBeforeUpload } = useFile()
-// 配置列表
-const btnList: BtnListType[] = [
-  {
-    key: 'export',
-    type: TypeEnum.BUTTON,
-    name: '导出',
-    icon: ShareIcon,
-    handle: exportHandle
-  },
-  {
-    key: 'import',
-    type: TypeEnum.IMPORTUPLOAD,
-    name: '导入',
-    icon: DownloadIcon
-  },
-  {
-    key: 'setting',
-    type: TypeEnum.BUTTON,
-    name: '设置',
-    icon: SettingsSharpIcon,
-    handle: () => {
-      globalSettingModel.value = true
-    }
-  }
-]
 
 // 是否是侧边栏
 const isAside = computed(() => settingStore.getChartToolsStatus === ToolsStatusEnum.ASIDE)
+
 // 是否隐藏(悬浮展示)
 const isHide = computed(() => settingStore.getChartToolsStatusHide)
+
 // 是否展示最小化(与全局配置相关)
 const isMiniComputed = computed(() => isMini.value && isHide.value)
+
 // 页面渲染配置
 const btnListComputed = computed(() => {
   if (!isAside.value) return btnList
@@ -142,6 +135,119 @@ const toolsMouseoutHandle = () => {
     isMini.value = true
   }
 }
+
+// 编辑处理
+const editHandle = () => {
+  window['$message'].warning('将开启失焦更新与 5 秒同步更新!')
+  setTimeout(() => {
+    // 获取id路径
+    const path = fetchPathByName(EditEnum.CHART_EDIT_NAME, 'href')
+    if (!path) return
+    let { id } = routerParamsInfo.params as any
+    id = typeof id === 'string' ? id : id[0]
+    updateToSession(id)
+    routerTurnByPath(path, [id], undefined, true)
+  }, 1000)
+}
+
+// 把内存中的数据同步到SessionStorage 便于传递给新窗口初始化数据
+const updateToSession = (id: string) => {
+  const storageInfo = chartEditStore.getStorageInfo
+  const sessionStorageInfo = getLocalStorage(StorageEnum.GO_CHART_STORAGE_LIST) || []
+
+  if (sessionStorageInfo?.length) {
+    const repeateIndex = sessionStorageInfo.findIndex((e: { id: string }) => e.id === id)
+    // 重复替换
+    if (repeateIndex !== -1) {
+      sessionStorageInfo.splice(repeateIndex, 1, { ...storageInfo, id })
+      setSessionStorage(StorageEnum.GO_CHART_STORAGE_LIST, sessionStorageInfo)
+    } else {
+      sessionStorageInfo.push({ ...storageInfo, id })
+      setSessionStorage(StorageEnum.GO_CHART_STORAGE_LIST, sessionStorageInfo)
+    }
+  } else {
+    setSessionStorage(StorageEnum.GO_CHART_STORAGE_LIST, [{ ...storageInfo, id }])
+  }
+}
+
+// 侦听器更新
+const useSyncUpdate = () => {
+  // 定义侦听器变量
+  let timer: any
+  const updateFn = (e: any) => updateComponent(e!.detail, true, false)
+  const syncData = () => {
+    if (routerParamsInfo.name == ChartEnum.CHART_HOME_NAME) {
+      dispatchEvent(new CustomEvent(SavePageEnum.CHART, { detail: chartEditStore.getStorageInfo }))
+    }
+  }
+
+  // 开启侦听
+  const use = () => {
+    // 1、定时同步数据
+    timer = setInterval(() => {
+      // 窗口激活并且处于工作台
+      document.hasFocus() && syncData()
+    }, editToJsonInterval)
+    // 2、失焦同步数据
+    addEventListener('blur', syncData)
+
+    // 【监听JSON代码 刷新工作台图表】
+    addEventListener(SavePageEnum.JSON, updateFn)
+  }
+
+  // 关闭侦听
+  const unUse = () => {
+    clearInterval(timer)
+    removeEventListener(SavePageEnum.JSON, updateFn)
+    removeEventListener('blur', syncData)
+  }
+
+  // 路由变更时处理
+  const watchHandler = (toName: any, fromName: any) => {
+    if (fromName == ChartEnum.CHART_HOME_NAME) {
+      unUse()
+    }
+    if (toName == ChartEnum.CHART_HOME_NAME) {
+      use()
+    }
+  }
+  return watchHandler
+}
+
+watch(() => routerParamsInfo.name, useSyncUpdate(), { immediate: true })
+
+// 配置列表
+const btnList: BtnListType[] = [
+  {
+    key: 'export',
+    type: TypeEnum.BUTTON,
+    name: '导出',
+    icon: ShareIcon,
+    handle: exportHandle
+  },
+  {
+    key: 'import',
+    type: TypeEnum.IMPORTUPLOAD,
+    name: '导入',
+    icon: DownloadIcon
+  },
+  {
+    key: 'edit',
+    type: TypeEnum.BUTTON,
+    name: '编辑JSON',
+    icon: CreateIcon,
+    handle: editHandle
+  },
+  {
+    key: 'setting',
+    type: TypeEnum.BUTTON,
+    name: '设置',
+    icon: SettingsSharpIcon,
+    handle: () => {
+      globalSettingModel.value = true
+    }
+  }
+]
 </script>
 
 <style lang="scss" scoped>
@@ -168,7 +274,7 @@ $asideBottom: 70px;
     flex-direction: column-reverse;
     height: auto;
     right: 20px;
-    padding: 20px 8px;
+    padding: 30px 8px;
     bottom: $asideBottom;
     overflow: hidden;
     transition: height ease 0.4s;

+ 128 - 0
src/views/edit/index.vue

@@ -0,0 +1,128 @@
+<template>
+  <div class="go-edit">
+    <n-layout>
+      <n-layout-header class="go-edit-header go-px-5 go-flex-items-center" bordered>
+        <div>
+          <n-text class="go-edit-title go-mr-4">页面在线编辑器</n-text>
+          <n-button v-if="showOpenFilePicker" class="go-mr-3" size="medium" @click="importJSON">
+            <template #icon>
+              <n-icon>
+                <download-icon></download-icon>
+              </n-icon>
+            </template>
+            导入
+          </n-button>
+        </div>
+        <n-space>
+          <n-tag :bordered="false" type="warning"> 「页面失焦保存」 </n-tag>
+          <n-tag :bordered="false" type="warning"> 「ctrl + s 保存」 </n-tag>
+        </n-space>
+      </n-layout-header>
+      <n-layout-content>
+        <monaco-editor
+          v-model:modelValue="content"
+          language="json"
+          :editorOptions="{
+            lineNumbers: 'on',
+            minimap: { enabled: true }
+          }"
+      />
+      </n-layout-content>
+    </n-layout>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { computed, ref, watch } from 'vue'
+import { MonacoEditor } from '@/components/Pages/MonacoEditor'
+import { SavePageEnum } from '@/enums/editPageEnum'
+import { getSessionStorageInfo } from '../preview/utils'
+import type { ChartEditStorageType } from '../preview/index.d'
+import { setSessionStorage } from '@/utils'
+import { StorageEnum } from '@/enums/storageEnum'
+import { icon } from '@/plugins'
+
+const { ChevronBackOutlineIcon, DownloadIcon } = icon.ionicons5
+const showOpenFilePicker: Function = (window as any).showOpenFilePicker
+let content = ref('')
+
+// 从sessionStorage 获取数据
+function getDataBySession() {
+  const localStorageInfo: ChartEditStorageType = getSessionStorageInfo() as ChartEditStorageType
+  content.value = JSON.stringify(localStorageInfo, undefined, 2)
+}
+getDataBySession()
+
+// 返回父窗口
+function back() {
+  opener.name = Date.now()
+  window.open(opener.location.href, opener.name)
+}
+
+// 导入json文本
+async function importJSON() {
+  const files = await showOpenFilePicker()
+  const file = await files[0].getFile()
+  const fr = new FileReader()
+  fr.readAsText(file)
+  fr.onloadend = () => {
+    content.value = (fr.result || '').toString()
+  }
+}
+
+// 同步 [画布页失去焦点时同步数据到JSON页,JSON页Ctrl+S 时同步数据到画布页]
+opener.addEventListener(SavePageEnum.CHART, (e: any) => {
+  setSessionStorage(StorageEnum.GO_CHART_STORAGE_LIST, [e.detail])
+  content.value = JSON.stringify(e.detail, undefined, 2)
+})
+
+// 窗口失焦 + 保存 => 同步数据
+document.addEventListener('keydown', function (e) {
+  if (e.keyCode == 83 && (navigator.platform.match('Mac') ? e.metaKey : e.ctrlKey)) {
+    e.preventDefault()
+    updateSync()
+  }
+})
+addEventListener('blur', updateSync)
+
+// 同步更新
+function updateSync() {
+  if (!opener) {
+    return window['$message'].error('源窗口已关闭,视图同步失败')
+  }
+  try {
+    const detail = JSON.parse(content.value)
+    delete detail.id
+    // 保持id不变
+    opener.dispatchEvent(new CustomEvent(SavePageEnum.JSON, { detail }))
+  } catch (e) {
+    window['$message'].error('内容格式有误')
+    console.log(e)
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.go-edit {
+  display: flex;
+  flex-direction: column;
+  height: 100vh;
+  .go-edit-header {
+    display: flex;
+    align-items: center;
+    height: 60px;
+    justify-content: space-between;
+    .go-edit-title {
+      position: relative;
+      bottom: 3px;
+      font-size: 18px;
+      font-weight: bold;
+    }
+  }
+  @include deep() {
+    .go-editor-area {
+      height: calc(100vh - 60px) !important;
+    }
+  }
+}
+</style>

+ 2 - 1
src/views/preview/components/PreviewRenderGroup/index.vue

@@ -18,6 +18,7 @@
       :themeSetting="themeSetting"
       :themeColor="themeColor"
       :style="{ ...getSizeStyle(item.attr) }"
+      v-on="useLifeHandler(item)"
     ></component>
   </div>
 </template>
@@ -27,7 +28,7 @@ import { PropType } from 'vue'
 import { CreateComponentGroupType } from '@/packages/index.d'
 import { animationsClass, getFilterStyle, getTransformStyle, getBlendModeStyle } from '@/utils'
 import { getSizeStyle, getComponentAttrStyle, getStatusStyle } from '../../utils'
-
+import { useLifeHandler } from '@/hooks'
 const props = defineProps({
   groupData: {
     type: Object as PropType<CreateComponentGroupType>,

+ 2 - 1
src/views/preview/components/PreviewRenderList/index.vue

@@ -29,6 +29,7 @@
       :themeSetting="themeSetting"
       :themeColor="themeColor"
       :style="{ ...getSizeStyle(item.attr) }"
+      v-on="useLifeHandler(item)"
     ></component>
   </div>
 </template>
@@ -41,7 +42,7 @@ import { CreateComponentGroupType } from '@/packages/index.d'
 import { chartColors } from '@/settings/chartThemes/index'
 import { animationsClass, getFilterStyle, getTransformStyle, getBlendModeStyle } from '@/utils'
 import { getSizeStyle, getComponentAttrStyle, getStatusStyle } from '../../utils'
-
+import { useLifeHandler } from '@/hooks'
 const props = defineProps({
   localStorageInfo: {
     type: Object as PropType<ChartEditStorageType>,

+ 25 - 0
src/views/preview/wrapper.vue

@@ -0,0 +1,25 @@
+<template>
+  <Preview :key="key"></Preview>
+</template>
+
+<script setup lang="ts">
+import { getSessionStorageInfo } from './utils'
+import type { ChartEditStorageType } from './index.d'
+import { SavePageEnum } from '@/enums/editPageEnum'
+import { setSessionStorage } from '@/utils'
+import { StorageEnum } from '@/enums/storageEnum'
+import { ref } from 'vue'
+import Preview from './index.vue'
+
+let key = ref(Date.now())
+let localStorageInfo: ChartEditStorageType = getSessionStorageInfo() as ChartEditStorageType
+
+// 数据变更 -> 同步sessionStorage -> reload页面 (重新执行Mounted)
+;[SavePageEnum.JSON, SavePageEnum.CHART].forEach((saveEvent: string) => {
+  opener.addEventListener(saveEvent, (e: any) => {
+    setSessionStorage(StorageEnum.GO_CHART_STORAGE_LIST, [{ ...e.detail, id: localStorageInfo.id }])
+    key.value = Date.now()
+  })
+})
+
+</script>

+ 6 - 1
vite.config.ts

@@ -23,6 +23,10 @@ export default ({ mode }) => defineConfig({
       {
         find: '@',
         replacement: pathResolve('src')
+      },
+      {
+        find: 'vue-i18n',
+        replacement: 'vue-i18n/dist/vue-i18n.cjs.js', //解决i8n警告
       }
     ],
     dedupe: ['vue']
@@ -79,7 +83,8 @@ export default ({ mode }) => defineConfig({
   build: {
     target: 'es2015',
     outDir: OUTPUT_DIR,
-    terserOptions: terserOptions,
+    // minify: 'terser', // 如果需要用terser混淆,可打开这两行
+    // terserOptions: terserOptions,
     rollupOptions: rollupOptions,
     brotliSize: brotliSize,
     chunkSizeWarningLimit: chunkSizeWarningLimit