浏览代码

codeeditor

Alsmile 2 年之前
父节点
当前提交
d0f7b8bd56

+ 2 - 2
package.json

@@ -16,7 +16,7 @@
     "file-saver": "^2.0.5",
     "jszip": "^3.10.0",
     "localforage": "^1.10.0",
-    "monaco-editor": "^0.37.1",
+    "monaco-editor": "^0.38.0",
     "tdesign-vue-next": "^1.3.4",
     "vue": "^3.3.2",
     "vue-router": "^4.2.0"
@@ -24,6 +24,7 @@
   "devDependencies": {
     "@types/file-saver": "^2.0.5",
     "@types/node": "^18.6.4",
+    "@types/offscreencanvas": "^2019.7.0",
     "@types/qrcode": "^1.5.0",
     "@vitejs/plugin-vue": "^4.2.0",
     "@vitejs/plugin-vue-jsx": "^3.0.0",
@@ -34,7 +35,6 @@
     "postcss-nested": "^6.0.1",
     "typescript": "^4.7.4",
     "vite": "^4.3.5",
-    "vite-plugin-monaco-editor": "^1.1.0",
     "vue-tsc": "^1.4.4"
   }
 }

+ 12 - 15
pnpm-lock.yaml

@@ -1,6 +1,9 @@
 lockfileVersion: '6.0'
 
 dependencies:
+  '@types/offscreencanvas':
+    specifier: ^2019.7.0
+    version: 2019.7.0
   axios:
     specifier: ^0.26.0
     version: 0.26.1
@@ -23,8 +26,8 @@ dependencies:
     specifier: ^1.10.0
     version: 1.10.0
   monaco-editor:
-    specifier: ^0.37.1
-    version: 0.37.1
+    specifier: ^0.38.0
+    version: 0.38.0
   tdesign-vue-next:
     specifier: ^1.3.4
     version: 1.3.4(vue@3.3.4)
@@ -72,9 +75,6 @@ devDependencies:
   vite:
     specifier: ^4.3.5
     version: 4.3.7(@types/node@18.16.12)
-  vite-plugin-monaco-editor:
-    specifier: ^1.1.0
-    version: 1.1.0(monaco-editor@0.37.1)
   vue-tsc:
     specifier: ^1.4.4
     version: 1.6.5(typescript@4.9.5)
@@ -663,6 +663,10 @@ packages:
     resolution: {integrity: sha512-tIRrjbY9C277MOfP8M3zjMIhtMlUJ6YVqkGgLjz+74jVsdf4/UjC6Hku4+1N0BS0qyC0JAS6tJLUk9H6JUKviQ==}
     dev: true
 
+  /@types/offscreencanvas@2019.7.0:
+    resolution: {integrity: sha512-PGcyveRIpL1XIqK8eBsmRBt76eFgtzuPiSTyKHZxnGemp2yzGzWpjYKAfK3wIMiU7eH+851yEpiuP8JZerTmWg==}
+    dev: false
+
   /@types/qrcode@1.5.0:
     resolution: {integrity: sha512-x5ilHXRxUPIMfjtM+1vf/GPTRWZ81nqscursm5gMznJeK9M0YnZ1c3bEvRLQ0zSSgedLx1J6MGL231ObQGGhaA==}
     dependencies:
@@ -1517,8 +1521,9 @@ packages:
       minimist: 1.2.8
     dev: false
 
-  /monaco-editor@0.37.1:
-    resolution: {integrity: sha512-jLXEEYSbqMkT/FuJLBZAVWGuhIb4JNwHE9kPTorAVmsdZ4UzHAfgWxLsVtD7pLRFaOwYPhNG9nUCpmFL1t/dIg==}
+  /monaco-editor@0.38.0:
+    resolution: {integrity: sha512-11Fkh6yzEmwx7O0YoLxeae0qEGFwmyPRlVxpg7oF9czOOCB/iCjdJrG5I67da5WiXK3YJCxoz9TJFE8Tfq/v9A==}
+    dev: false
 
   /ms@2.1.2:
     resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==}
@@ -1873,14 +1878,6 @@ packages:
     engines: {node: '>= 0.10'}
     dev: false
 
-  /vite-plugin-monaco-editor@1.1.0(monaco-editor@0.37.1):
-    resolution: {integrity: sha512-IvtUqZotrRoVqwT0PBBDIZPNraya3BxN/bfcNfnxZ5rkJiGcNtO5eAOWWSgT7zullIAEqQwxMU83yL9J5k7gww==}
-    peerDependencies:
-      monaco-editor: '>=0.33.0'
-    dependencies:
-      monaco-editor: 0.37.1
-    dev: true
-
   /vite@4.3.7(@types/node@18.16.12):
     resolution: {integrity: sha512-MTIFpbIm9v7Hh5b0wSBgkcWzSBz7SAa6K/cBTwS4kUiQJfQLFlZZRJRQgqunCVzhTPCk674tW+0Qaqh3Q00dBg==}
     engines: {node: ^14.18.0 || >=16.0.0}

+ 36 - 0
src/styles/app.css

@@ -59,6 +59,10 @@ h5 {
   font-size: 16px;
 }
 
+.small {
+  font-size: 10px;
+}
+
 .bland {
   color: var(--color-bland);
 }
@@ -208,6 +212,10 @@ a.hover:hover {
   &.between {
     justify-content: space-between;
   }
+
+  .shrink-0 {
+    flex-shrink: 0;
+  }
 }
 
 .flex-grow {
@@ -274,6 +282,10 @@ a.hover:hover {
   margin-bottom: 12px;
 }
 
+.mr-12 {
+  margin-right: 12px;
+}
+
 .mt-16 {
   margin-top: 16px;
 }
@@ -282,6 +294,26 @@ a.hover:hover {
   margin-bottom: 16px;
 }
 
+.px-4 {
+  padding-left: 4px;
+  padding-right: 4px;
+}
+
+.py-4 {
+  padding-top: 4px;
+  padding-bottom: 4px;
+}
+
+.px-8 {
+  padding-left: 8px;
+  padding-right: 8px;
+}
+
+.py-8 {
+  padding-top: 8px;
+  padding-bottom: 8px;
+}
+
 .px-12 {
   padding-left: 12px;
   padding-right: 12px;
@@ -311,6 +343,10 @@ a.hover:hover {
   padding: 16px;
 }
 
+.border {
+  border: 1px solid var(--color-sub-border);
+}
+
 .transparent {
   background-color: transparent;
   background-image: linear-gradient(

+ 4 - 6
src/styles/props.css

@@ -130,12 +130,10 @@
     border-color: var(--color-desc);
 
     &::after {
-      width: 6px;
-      height: 6px;
-      left: 2px;
-      top: 2px;
-      margin: 0;
-      transform: none;
+      width: 12px;
+      height: 12px;
+      margin-top: -6px;
+      margin-left: -6px;
     }
   }
   .t-radio__label {

+ 16 - 0
src/styles/tdesign.css

@@ -328,6 +328,15 @@
 }
 
 .t-radio-group {
+  .t-radio {
+    &:last-child {
+      margin-right: 0;
+    }
+  }
+  .t-radio__input {
+    background: none;
+  }
+
   .t-radio-button {
     border-color: var(--color-border-input);
     &:last-child {
@@ -448,9 +457,16 @@
       margin-left: -20px;
       padding: 0 20px;
       overflow: auto;
+      &::-webkit-scrollbar {
+        width: 3px !important;
+      }
     }
   }
 
+  .t-dialog__footer {
+    padding: 0;
+  }
+
   .t-dialog__close {
     margin-right: -6px;
     &:hover {

+ 7 - 4
src/styles/var.css

@@ -34,7 +34,7 @@
   --color-background-popup-hover: #454f64;
 
   --color-border: #000000;
-  --color-sub-border: #f7f7f7;
+  --color-sub-border: var(--color-background-input);
   --color-border-input: #535f79;
   --color-border-input-hover: #454f64;
 
@@ -48,9 +48,9 @@
   --shadow-panel: 0px 2px 6px 0px rgb(0 10 38 / 4%);
 
   --td-radius-medium: 4px;
-  --td-bg-color-container: #1e2430;
+  --td-bg-color-container: var(--color-background);
   --td-component-border: #42516c;
-  --td-bg-color-secondarycontainer: #42516c;
+  --td-bg-color-secondarycontainer: var(--td-component-border);
   --td-comp-size-xxl: 40px;
   --td-text-color-primary: var(--color);
   --td-text-color-secondary: var(--color-desc);
@@ -63,7 +63,10 @@
   --td-brand-color: var(--color-primary);
   --td-bg-color-component: var(--color-border-input);
   --td-brand-color-light: var(--color-border-input);
-  --td-mask-disabled: #1e2430;
+  --td-mask-disabled: var(--color-background);
 
   --color-dialog-border: transparent;
+
+  --color-background-editor: #181b24;
+  --color-border-editor: #1e2430;
 }

+ 8 - 8
src/views/Index.vue

@@ -5,7 +5,7 @@
     <div class="design-body">
       <Graphics />
       <View />
-      <div style="border-left: 1px solid var(--color-border); z-index: 3">
+      <div style="border-left: 1px solid var(--color-border); z-index: 7">
         <FileProps v-if="selections.mode === SelectionMode.File" />
         <PenProps v-else-if="selections.mode === SelectionMode.Pen" />
         <PensProps v-else />
@@ -15,14 +15,14 @@
 </template>
 
 <script lang="ts" setup>
-import Header from "./components/Header.vue";
-import Graphics from "./components/Graphics.vue";
-import View from "./components/View.vue";
-import FileProps from "./components/FileProps.vue";
-import PenProps from "./components/PenProps.vue";
-import PensProps from "./components/PensProps.vue";
+import Header from './components/Header.vue';
+import Graphics from './components/Graphics.vue';
+import View from './components/View.vue';
+import FileProps from './components/FileProps.vue';
+import PenProps from './components/PenProps.vue';
+import PensProps from './components/PensProps.vue';
 
-import { useSelection, SelectionMode } from "@/services/selections";
+import { useSelection, SelectionMode } from '@/services/selections';
 
 const { selections } = useSelection();
 </script>

+ 14 - 15
src/views/Preview.vue

@@ -5,23 +5,22 @@
 </template>
 
 <script setup lang="ts">
-import { ref, onMounted, watch, onUnmounted } from "vue";
-import localforage from "localforage";
-import { localMeta2dDataName } from "@/services/utils";
-import { registerNormalShape } from "../components/register";
-import { defaultFormat } from "@/services/defaults";
-import { useRouter, useRoute } from "vue-router";
-import { Meta2d, Options, Pen } from "@meta2d/core";
-import { registerBasicDiagram } from "@/services/register";
-import { cdn } from "@/services/api";
+import { ref, onMounted, watch, onUnmounted } from 'vue';
+import localforage from 'localforage';
+import { localMeta2dDataName } from '@/services/utils';
+import { defaultFormat } from '@/services/defaults';
+import { useRouter, useRoute } from 'vue-router';
+import { Meta2d, Options, Pen } from '@meta2d/core';
+import { registerBasicDiagram } from '@/services/register';
+import { cdn, getLe5le2d } from '@/services/api';
 const route = useRoute();
 
-const meta2dDom = ref("");
+const meta2dDom = ref('');
 
 const meta2dOptions: Options = {
   cdn,
   //   rule: true,
-  background: "#1e2430",
+  background: '#1e2430',
   x: 10,
   y: 10,
   width: 1920,
@@ -33,7 +32,7 @@ onMounted(() => {
   meta2d = new Meta2d(meta2dDom.value, meta2dOptions);
   registerBasicDiagram();
   open();
-  meta2d.on("opened", opened);
+  meta2d.on('opened', opened);
 });
 
 const watcher = watch(
@@ -45,13 +44,13 @@ const watcher = watch(
 
 const open = async () => {
   if (route.query.id) {
-    const ret: any = getLe5le2d(route.query.id + "");
+    const ret: any = getLe5le2d(route.query.id + '');
     ret && meta2d.open(ret);
   } else {
     const data: any = JSON.parse(
       await localforage.getItem(localMeta2dDataName)
     );
-    data&&meta2d.open(data);
+    data && meta2d.open(data);
   }
 };
 
@@ -62,7 +61,7 @@ const opened = () => {
 onUnmounted(() => {
   watcher();
   if (meta2d) {
-    meta2d.off("opened", opened);
+    meta2d.off('opened', opened);
 
     meta2d.destroy();
   }

+ 53 - 49
src/views/components/FileProps.vue

@@ -125,18 +125,18 @@
                   <label>初始化动作</label>
                   <t-button
                     variant="outline"
-                    style="padding: 0 6px; margin: 2px 8px"
-                    @click="clickInit"
+                    style="padding: 0 4px; margin: 2px 8px"
+                    @click="showInitFnDialog"
                   >
                     <t-icon name="ellipsis" />
                   </t-button>
                 </div>
                 <div class="form-item">
-                  <label>数据格式转换</label>
+                  <label>数据监听</label>
                   <t-button
                     variant="outline"
-                    style="padding: 0 6px; margin: 2px 8px"
-                    @click="clickFormat"
+                    style="padding: 0 4px; margin: 2px 8px"
+                    @click="showDataTransformation"
                   >
                     <t-icon name="ellipsis" />
                   </t-button>
@@ -150,20 +150,30 @@
         <ElementTree />
       </t-tab-panel>
     </t-tabs>
-    <MonacoModal
-      v-model:visible="initVisible"
-      :code="initCode"
-      title="初始化动作"
-      language="javascript"
-      @changeCode="changeInitCode"
-    />
-    <MonacoModal
-      v-model:visible="formatVisible"
-      :code="formatCode"
-      title="数据格式转换"
-      language="javascript"
-      @changeCode="changeformatCode"
-    />
+    <t-dialog
+      v-if="initFnDialog.show"
+      :visible="true"
+      header="初始化动作"
+      @confirm="onOkInitFn"
+      @close="initFnDialog.show = false"
+      :width="700"
+    >
+      <CodeEditor v-model="initFnDialog.data" style="height: 300px" />
+    </t-dialog>
+
+    <t-dialog
+      v-if="dataTransformationDialog.show"
+      :visible="true"
+      header="数据监听"
+      @confirm="onOkDataTransformation"
+      @close="dataTransformationDialog.show = false"
+      :width="700"
+    >
+      <CodeEditor
+        v-model="dataTransformationDialog.data"
+        style="height: 300px"
+      />
+    </t-dialog>
   </div>
 </template>
 
@@ -171,7 +181,7 @@
 import { onMounted, reactive, onUnmounted, ref } from 'vue';
 import { getCookie } from '@/services/cookie';
 import ElementTree from './ElementTree.vue';
-import MonacoModal from './common/MonacoModal.vue';
+import CodeEditor from '@/views/components/common/CodeEditor.vue';
 
 const headers = {
   Authorization: 'Bearer ' + (localStorage.token || getCookie('token') || ''),
@@ -232,6 +242,16 @@ const screenList = reactive([
   },
 ]);
 
+const initFnDialog = reactive<any>({
+  show: false,
+  data: '',
+});
+
+const dataTransformationDialog = reactive<any>({
+  show: false,
+  data: '',
+});
+
 const selectedSreen = (item: any) => {
   meta2d.store.data.width = item.width;
   meta2d.store.data.height = item.height;
@@ -262,7 +282,6 @@ const changeValue = (e: any, key: string) => {
 };
 
 onMounted(() => {
-  // initMeta2dCanvas();
   openData();
   meta2d.on('opened', openData);
 });
@@ -271,18 +290,6 @@ onUnmounted(() => {
   meta2d.off('opened', openData);
 });
 
-function initMeta2dCanvas() {
-  if (!meta2d.store.data.width) {
-    meta2d.store.data.width = 1920;
-  }
-  if (!meta2d.store.data.height) {
-    meta2d.store.data.height = 1080;
-  }
-  if (!meta2d.store.data.background) {
-    meta2d.store.data.background = '#ffffff';
-  }
-}
-
 function openData() {
   data.meta2dData = Object.assign({}, meta2d.store.data);
   if (meta2d.store.data.bkImage) {
@@ -295,27 +302,24 @@ function openData() {
   }
 }
 
-// const modal = ref<InstanceType<typeof MyModal> | null>(null)
-const initVisible = ref(false);
-const formatVisible = ref(false);
-const initCode = ref('');
-const formatCode = ref('');
-
-const clickInit = () => {
-  initVisible.value = true;
+const showInitFnDialog = () => {
+  initFnDialog.data = meta2d.store.data.initJs;
+  initFnDialog.show = true;
 };
 
-const clickFormat = () => {
-  formatVisible.value = true;
+const onOkInitFn = () => {
+  meta2d.store.data.initJs = initFnDialog.data;
+  initFnDialog.show = false;
 };
-const changeInitCode = (code: string) => {
-  initCode.value = code;
-  meta2d.store.data.initJs = code;
+
+const showDataTransformation = () => {
+  dataTransformationDialog.data = meta2d.store.data.socketCbJs;
+  dataTransformationDialog.show = true;
 };
 
-const changeformatCode = (code: string) => {
-  formatCode.value = code;
-  meta2d.store.data.socketCbJs = code;
+const onOkDataTransformation = () => {
+  meta2d.store.data.socketCbJs = dataTransformationDialog.data;
+  dataTransformationDialog.show = false;
 };
 </script>
 <style lang="postcss" scoped>

+ 383 - 19
src/views/components/PenDatas.vue

@@ -172,7 +172,7 @@
     :visible="true"
     class="data-link-dialog"
     header="动态数据绑定"
-    @cancel="
+    @close="
       dataBindDialog.data.binds = dataBindDialog.bkBinds;
       dataBindDialog.show = false;
     "
@@ -227,28 +227,181 @@
     :visible="true"
     class="data-events-dialog"
     header="数据触发器"
-    @cancel="triggersDialog.show = false"
     @confirm="triggersDialog.show = false"
+    @close="triggersDialog.show = false"
     :width="700"
   >
     <div class="body">
-      <div class="mb-12" v-for="(trigger, i) in triggersDialog.data.triggers">
-        <div class="flex middle between">
-          <div class="title">触发器{{ i + 1 }}</div>
+      <t-collapse
+        v-model="triggersDialog.openedCollapses"
+        :borderless="true"
+        :expand-on-row-click="false"
+      >
+        <t-collapse-panel
+          v-for="(trigger, i) in triggersDialog.data.triggers"
+          :value="i"
+        >
+          <template #header>
+            <t-input v-model="trigger.name" class="mr-12" />
+          </template>
+          <template #headerRightContent>
+            <t-popconfirm
+              content="确认删除该触发器吗?"
+              @confirm="triggersDialog.data.triggers.splice(i, 1)"
+            >
+              <t-icon name="delete" class="hover" />
+            </t-popconfirm>
+          </template>
+          <section>
+            <div class="form-item banner">
+              <label>触发条件</label>
+              <div class="w-full flex middle between">
+                <div></div>
+                <t-radio-group v-model="trigger.conditionType">
+                  <t-radio value="and"> 满足全部条件 </t-radio>
+                  <t-radio value="or"> 满足任意条件 </t-radio>
+                </t-radio-group>
+              </div>
+            </div>
+            <div v-for="(c, index) in trigger.conditions" class="mb-12">
+              <div class="flex middle between head">
+                <div class="flex middle">
+                  <t-icon name="arrow-right" class="mr-4" />
+                  条件{{ index + 1 }}
+                </div>
+                <t-icon
+                  name="close"
+                  class="hover"
+                  @click="trigger.conditions.splice(index, 1)"
+                />
+              </div>
+              <div class="px-16 py-4">
+                <div class="form-item mt-4">
+                  <label>条件类型</label>
+                  <t-radio-group v-model="c.type">
+                    <t-radio value=""> 关系条件 </t-radio>
+                    <t-radio value="fn"> 高阶条件 </t-radio>
+                  </t-radio-group>
+                </div>
+                <template v-if="!c.type">
+                  <div class="form-item mt-8">
+                    <label>比较条件</label>
+                    <div class="flex middle">
+                      <label class="shrink-0 mr-8">数据</label>
+                      <t-select
+                        v-model="c.operator"
+                        placeholder="关系运算"
+                        :options="operatorOptions"
+                        class="shrink-0 mr-8"
+                        style="width: 80px"
+                      />
+                      <t-select
+                        v-model="c.valueType"
+                        class="shrink-0 mr-8"
+                        style="width: 110px"
+                        placeholder="固定值"
+                      >
+                        <t-option key="" value="" label="固定值">
+                          固定值
+                        </t-option>
+                        <t-option key="prop" value="prop" label="对象属性值">
+                          对象属性值
+                        </t-option>
+                      </t-select>
+                      <template v-if="!c.valueType">
+                        <t-input
+                          v-model="c.value"
+                          class="shrink-0"
+                          style="width: 320px"
+                        />
+                      </template>
+                      <template v-else>
+                        <t-tree-select
+                          v-model="c.target"
+                          :data="penTree"
+                          filterable
+                          placeholder="对象"
+                          class="shrink-0 mr-8"
+                          style="width: 160px"
+                          @change="onChangeTriggerTarget(c)"
+                        />
+                        <t-select-input
+                          v-model:inputValue="c.value"
+                          :value="c.value"
+                          v-model:popupVisible="triggersDialog.popupVisible"
+                          allow-input
+                          @focus="triggersDialog.popupVisible = true"
+                          @blur="triggersDialog.popupVisible = false"
+                          class="shrink-0"
+                          style="width: 152px"
+                        >
+                          <template #panel>
+                            <ul style="padding: 8px 12px">
+                              <li
+                                v-for="item in triggersDialog.targetProps"
+                                :key="item.value"
+                                @click="c.value = item.value"
+                              >
+                                {{ item.label }}
+                              </li>
+                            </ul>
+                          </template>
+                        </t-select-input>
+                      </template>
+                    </div>
+                  </div>
+                </template>
+                <template v-else>
+                  <div>function condition(pen) {</div>
+                  <CodeEditor class="mt-4" v-model="c.fnJs" />
+                  <div class="mt-4">}</div>
+                </template>
+              </div>
+            </div>
+            <div class="mt-8">
+              <a @click="addTriggerCondition(trigger)"> + 添加条件 </a>
+            </div>
 
-          <t-popconfirm
-            content="确认删除该触发器吗?"
-            @confirm="triggersDialog.data.triggers.splice(i, 1)"
-          >
-            <t-icon name="close" class="hover" />
-          </t-popconfirm>
-        </div>
-      </div>
-      <div>
-        <a @click="triggersDialog.data.triggers.push({})">
-          <t-icon name="add" />
-          添加触发器
-        </a>
+            <div class="form-item banner mt-16">
+              <label>执行动作</label>
+            </div>
+            <div v-for="(a, index) in trigger.actions" class="mb-12">
+              <div class="flex middle between head">
+                <div class="flex middle">
+                  <t-icon name="arrow-right" class="mr-4" />
+                  动作{{ index + 1 }}
+                </div>
+                <t-icon
+                  name="close"
+                  class="hover"
+                  @click="trigger.actions.splice(index, 1)"
+                />
+              </div>
+              <div class="px-16 py-4">
+                <div class="form-item mt-4">
+                  <label>动作类型</label>
+                  <div>
+                    <t-select v-model="a.action" placeholder="请选择">
+                      <t-option
+                        v-for="option in actionOptions"
+                        :key="option.value"
+                        :value="option.value"
+                        :label="option.label"
+                      />
+                    </t-select>
+                  </div>
+                </div>
+              </div>
+            </div>
+            <div class="mt-8">
+              <a @click="trigger.actions.push({})"> + 添加动作 </a>
+            </div>
+          </section>
+        </t-collapse-panel>
+      </t-collapse>
+
+      <div class="mt-8">
+        <a @click="onAddTrigger"> + 添加触发器 </a>
       </div>
     </div>
   </t-dialog>
@@ -267,8 +420,10 @@ import { useRoute, useRouter } from 'vue-router';
 import { MessagePlugin } from 'tdesign-vue-next';
 import axios from 'axios';
 import { debounce } from '@/services/debouce';
+import { getPenTree } from '@/services/common';
+import { updatePen } from './pen';
 
-import { updatePen } from './pen.ts';
+import CodeEditor from '@/views/components/common/CodeEditor.vue';
 
 const route = useRoute();
 const router = useRouter();
@@ -365,6 +520,69 @@ const typeOptions = [
   },
 ];
 
+const actionOptions = [
+  {
+    label: '打开链接',
+    value: 0,
+  },
+  {
+    label: '打开视图',
+    value: 13,
+  },
+  {
+    label: '播放动画',
+    value: 2,
+  },
+  {
+    label: '暂停动画',
+    value: 3,
+  },
+  {
+    label: '停止动画',
+    value: 4,
+  },
+  {
+    label: '更改属性',
+    value: 1,
+  },
+  {
+    label: '打开弹框',
+    value: 14,
+  },
+  {
+    label: '发送消息',
+    value: 7,
+  },
+  {
+    label: '发送MQTT',
+    value: 15,
+  },
+  {
+    label: '发送Websocket',
+    value: 16,
+  },
+  {
+    label: '发送HTTP(s)',
+    value: 17,
+  },
+  {
+    label: '播放视频',
+    value: 8,
+  },
+  {
+    label: '暂停视频',
+    value: 9,
+  },
+  {
+    label: '停止视频',
+    value: 10,
+  },
+  {
+    label: '自定义函数',
+    value: 18,
+  },
+];
+
 const addDataDialog = reactive<any>({
   show: false,
   data: undefined,
@@ -400,6 +618,17 @@ const dataSetColumns = [
   },
 ];
 
+const operatorOptions = ref<any>([
+  { label: '=', value: '=' },
+  { label: '!=', value: '!=' },
+  { label: '>', value: '>' },
+  { label: '<', value: '<' },
+  { label: '>=', value: '>=' },
+  { label: '<=', value: '<=' },
+  { label: '包含', value: '[)' },
+  { label: '不包含', value: '![)' },
+]);
+
 const query = reactive<{
   current: number;
   pageSize: number;
@@ -417,6 +646,8 @@ const triggersDialog = reactive<any>({
   data: undefined,
 });
 
+const penTree: any = ref([]);
+
 let timer: any;
 
 onBeforeMount(() => {
@@ -613,8 +844,88 @@ const onTrigger = (item: any) => {
   if (!item.triggers) {
     item.triggers = [];
   }
+  triggersDialog.openedCollapses = [0];
   triggersDialog.data = item;
+  triggersDialog.targetProps = [];
   triggersDialog.show = true;
+
+  penTree.value = getPenTree();
+};
+
+const onAddTrigger = () => {
+  const i = triggersDialog.data.triggers.length;
+  triggersDialog.data.triggers.push({
+    name: `触发器${i + 1}`,
+    conditionType: 'and',
+    conditions: [],
+    actions: [],
+  });
+
+  triggersDialog.openedCollapses.push(i);
+};
+
+const addTriggerCondition = (trigger: any) => {
+  trigger.conditions.push({
+    type: '',
+    operator: '=',
+    valueType: '',
+  });
+};
+
+const onChangeTriggerTarget = (c: any) => {
+  triggersDialog.targetProps = [
+    {
+      value: 'x',
+      label: 'X',
+    },
+    {
+      value: 'y',
+      label: 'Y',
+    },
+    {
+      value: 'width',
+      label: '宽',
+    },
+    {
+      value: 'height',
+      label: '高',
+    },
+    {
+      value: 'visible',
+      label: '显示',
+    },
+    {
+      value: 'text',
+      label: '文字',
+    },
+    {
+      value: 'progress',
+      label: '进度',
+    },
+    {
+      value: 'showChild',
+      label: '状态',
+    },
+    {
+      value: 'rotate',
+      label: '旋转',
+    },
+  ];
+
+  const target: any = meta2d.findOne(c.target);
+  if (target) {
+    for (const item of target.realTimes) {
+      const found = triggersDialog.targetProps.findIndex(
+        (elem: any) => elem.value === item.key
+      );
+      if (found < 0) {
+        triggersDialog.targetProps.push({
+          value: item.key,
+          label: item.label,
+        });
+      }
+    }
+  }
 };
 
 onUnmounted(() => {
@@ -633,6 +944,10 @@ onUnmounted(() => {
       background: var(--color-background-input);
       line-height: 36px;
       margin-bottom: 6px;
+
+      .title {
+        line-height: 36px;
+      }
     }
   }
 
@@ -685,4 +1000,53 @@ onUnmounted(() => {
     overflow: auto;
   }
 }
+
+.body {
+  :deep(.t-collapse.t--border-less) {
+    .t-collapse-panel__header {
+      border-top: none;
+      border-bottom: 1px solid var(--td-border-level-1-color);
+      padding: 8px 0;
+
+      .t-input {
+        border: none;
+        padding-left: 0;
+        font-size: 14px;
+      }
+    }
+
+    .t-collapse-panel__content {
+      padding: 8px 0;
+    }
+  }
+
+  .title {
+    position: relative;
+    margin: 8px 0;
+
+    :deep(.t-input) {
+      border-color: var(--color-background-input);
+      border-radius: 0;
+      border-left: none;
+      border-top: none;
+      border-right: none;
+      padding-left: 0;
+      padding-bottom: 8px;
+      font-size: 14px;
+
+      &:hover {
+        border-color: var(--color-border-input);
+      }
+    }
+  }
+
+  .head {
+    margin-top: 10px;
+  }
+
+  .banner {
+    background-color: var(--color-background-input);
+    padding: 0 12px;
+  }
+}
 </style>

+ 3 - 1
src/views/components/PenEvents.vue

@@ -27,7 +27,7 @@ import { MessagePlugin } from 'tdesign-vue-next';
 import axios from 'axios';
 import { debounce } from '@/services/debouce';
 
-import { updatePen } from './pen.ts';
+import { updatePen } from './pen';
 
 const route = useRoute();
 const router = useRouter();
@@ -85,6 +85,8 @@ const options = ref<any>([
 
 onBeforeMount(() => {});
 
+const addEvent = () => {};
+
 onUnmounted(() => {});
 </script>
 <style lang="postcss" scoped>

+ 57 - 41
src/views/components/PenProps.vue

@@ -926,17 +926,43 @@
               shape="square"
               variant="outline"
               style="width: 24px"
-              @click="showTitle"
+              @click="showTooltip"
             >
               <t-icon name="ellipsis" slot="icon"
             /></t-button>
           </div>
-          <MonacoModal
-            v-model:visible="titleVisible"
-            :options="titleOptions"
-            title="鼠标提示"
-            @changeOptions="changeTitleCode"
-          />
+
+          <t-dialog
+            v-if="tooltipDialog.show"
+            :visible="true"
+            header="鼠标提示"
+            @confirm="onOkTooltip"
+            @close="tooltipDialog.show = false"
+            :width="700"
+          >
+            <t-radio-group v-model="tooltipDialog.type">
+              <t-radio value="1">文字</t-radio>
+              <t-radio value="2">函数</t-radio>
+            </t-radio-group>
+
+            <div class="py-8">
+              <CodeEditor
+                v-show="tooltipDialog.type == 1"
+                v-model="tooltipDialog.title"
+                style="height: 300px"
+              />
+              <div v-show="tooltipDialog.type == 2">
+                <div>function tooltip(pen) {</div>
+                <CodeEditor
+                  v-model="tooltipDialog.titleFnJs"
+                  class="mt-4"
+                  style="height: 248px"
+                />
+                <div class="mt-4">}</div>
+              </div>
+            </div>
+            <div class="gray" style="font-size: 12px">支持Markdown格式</div>
+          </t-dialog>
           <t-space />
         </t-space>
       </t-tab-panel>
@@ -957,13 +983,12 @@
 import { onBeforeMount, onUnmounted, reactive, ref } from 'vue';
 import { getCookie } from '@/services/cookie';
 import { useSelection } from '@/services/selections';
-import MonacoModal from './common/MonacoModal.vue';
-import { monacoOption } from './common/MonacoModal.vue';
 
+import CodeEditor from '@/views/components/common/CodeEditor.vue';
 import PenAnimates from './PenAnimates.vue';
 import PenDatas from './PenDatas.vue';
 import PenEvents from './PenEvents.vue';
-import { updatePen } from './pen.ts';
+import { updatePen } from './pen';
 
 const headers = {
   Authorization: 'Bearer ' + (localStorage.token || getCookie('token') || ''),
@@ -1000,6 +1025,10 @@ const fonts = [
 
 const { selections } = useSelection();
 
+const tooltipDialog = reactive<any>({
+  show: false,
+});
+
 onBeforeMount(() => {
   const d = meta2d.store.data as any;
   if (!d.groups) {
@@ -1041,7 +1070,8 @@ onBeforeMount(() => {
     data.pen.bkType = 0;
   }
 
-  // 测试代码
+  // 示例代码
+  /*
   data.pen.props.custom = [
     { label: '数字', key: 'a', type: 'number', placeholder: '输入提示' },
     { label: '文字', key: 'b' },
@@ -1058,6 +1088,7 @@ onBeforeMount(() => {
       placeholder: '输入提示',
     },
   ];
+  */
   // end
   data.pen.shadow = !!data.pen.shadowColor;
   getRect();
@@ -1139,39 +1170,24 @@ const onChangeInputTag = (currentTags: any, context: any) => {
   data.tagPopupVisible = false;
 };
 
-const titleVisible = ref(false);
-const titleOptions: monacoOption[] = [
-  {
-    key: 'title',
-    value: 0,
-    name: '文字',
-    tip: '支持markdown',
-    code: '',
-    language: 'markdown',
-  },
-  {
-    key: 'titleFnJs',
-    value: 1,
-    name: '高级',
-    tip: 'mark函数',
-    code: '',
-    language: 'javascript',
-    example: '//例如: return `${pen.name}<br/>${pen.text}`;',
-  },
-];
+const showTooltip = () => {
+  tooltipDialog.title = data.pen.title || '';
+  tooltipDialog.titleFnJs =
+    data.pen.titleFnJs || '// 例如:return `${pen.name}<br/>${pen.text}`;';
+  tooltipDialog.type = data.pen.titleFnJs ? '2' : '1';
 
-const showTitle = () => {
-  titleOptions.forEach((item) => {
-    item.code = data.pen[item.key] || '';
-  });
-  titleVisible.value = true;
+  tooltipDialog.show = true;
 };
 
-const changeTitleCode = (options: monacoOption[]) => {
-  options.forEach((item) => {
-    data.pen[item.key] = item.code || '';
-    changeValue(item.key);
-  });
+const onOkTooltip = () => {
+  if (tooltipDialog.type === '1') {
+    data.pen.title = tooltipDialog.title;
+    data.pen.titleFnJs = '';
+  } else {
+    data.pen.title = '';
+    data.pen.titleFnJs = tooltipDialog.titleFnJs;
+  }
+  tooltipDialog.show = false;
 };
 
 onUnmounted(() => {

+ 1 - 1
src/views/components/View.vue

@@ -1271,7 +1271,7 @@ const importDataSet = async () => {
     },
   ];
   // dsData.value = [];
-  let data = await importExcel(columns);
+  let data: any = await importExcel(columns);
   console.log('data', data);
   // setTimeout(() => {
   dsData.value = data;

+ 145 - 0
src/views/components/common/CodeEditor.vue

@@ -0,0 +1,145 @@
+<template>
+  <div ref="dom" class="code-editor"></div>
+</template>
+<script lang="ts" setup>
+import { onMounted, onUnmounted, ref, watch } from 'vue';
+
+//按需引入
+import * as monaco from 'monaco-editor/esm/vs/editor/editor.api';
+import 'monaco-editor/esm/vs/editor/browser/widget/codeEditorWidget.js';
+import 'monaco-editor/esm/vs/editor/contrib/bracketMatching/browser/bracketMatching.js';
+import 'monaco-editor/esm/vs/editor/contrib/caretOperations/browser/caretOperations.js';
+import 'monaco-editor/esm/vs/editor/contrib/caretOperations/browser/transpose.js';
+import 'monaco-editor/esm/vs/editor/contrib/clipboard/browser/clipboard.js';
+import 'monaco-editor/esm/vs/editor/contrib/codeAction/browser/codeActionContributions.js';
+import 'monaco-editor/esm/vs/editor/contrib/copyPaste/browser/copyPasteContribution.js';
+import 'monaco-editor/esm/vs/editor/contrib/comment/browser/comment.js';
+import 'monaco-editor/esm/vs/editor/contrib/contextmenu/browser/contextmenu.js';
+import 'monaco-editor/esm/vs/editor/contrib/cursorUndo/browser/cursorUndo.js';
+import 'monaco-editor/esm/vs/editor/contrib/find/browser/findController.js';
+import 'monaco-editor/esm/vs/editor/contrib/folding/browser/folding.js';
+import 'monaco-editor/esm/vs/editor/contrib/format/browser/formatActions.js';
+import 'monaco-editor/esm/vs/editor/contrib/documentSymbols/browser/documentSymbols.js';
+import 'monaco-editor/esm/vs/editor/contrib/inlineCompletions/browser/inlineCompletions.contribution.js';
+import 'monaco-editor/esm/vs/editor/contrib/hover/browser/hover.js';
+import 'monaco-editor/esm/vs/editor/contrib/indentation/browser/indentation.js';
+import 'monaco-editor/esm/vs/editor/contrib/inlayHints/browser/inlayHintsContribution.js';
+import 'monaco-editor/esm/vs/editor/contrib/inPlaceReplace/browser/inPlaceReplace.js';
+import 'monaco-editor/esm/vs/editor/contrib/lineSelection/browser/lineSelection.js';
+import 'monaco-editor/esm/vs/editor/contrib/linesOperations/browser/linesOperations.js';
+import 'monaco-editor/esm/vs/editor/contrib/linkedEditing/browser/linkedEditing.js';
+import 'monaco-editor/esm/vs/editor/contrib/links/browser/links.js';
+import 'monaco-editor/esm/vs/editor/contrib/longLinesHelper/browser/longLinesHelper.js';
+import 'monaco-editor/esm/vs/editor/contrib/multicursor/browser/multicursor.js';
+import 'monaco-editor/esm/vs/editor/contrib/parameterHints/browser/parameterHints.js';
+import 'monaco-editor/esm/vs/editor/contrib/rename/browser/rename.js';
+import 'monaco-editor/esm/vs/editor/contrib/semanticTokens/browser/documentSemanticTokens.js';
+import 'monaco-editor/esm/vs/editor/contrib/semanticTokens/browser/viewportSemanticTokens.js';
+import 'monaco-editor/esm/vs/editor/contrib/smartSelect/browser/smartSelect.js';
+import 'monaco-editor/esm/vs/editor/contrib/snippet/browser/snippetController2.js';
+import 'monaco-editor/esm/vs/editor/contrib/stickyScroll/browser/stickyScrollContribution.js';
+import 'monaco-editor/esm/vs/editor/contrib/suggest/browser/suggestController.js';
+import 'monaco-editor/esm/vs/editor/contrib/suggest/browser/suggestInlineCompletions.js';
+import 'monaco-editor/esm/vs/editor/contrib/tokenization/browser/tokenization.js';
+import 'monaco-editor/esm/vs/editor/contrib/toggleTabFocusMode/browser/toggleTabFocusMode.js';
+import 'monaco-editor/esm/vs/editor/contrib/unicodeHighlighter/browser/unicodeHighlighter.js';
+import 'monaco-editor/esm/vs/editor/contrib/unusualLineTerminators/browser/unusualLineTerminators.js';
+import 'monaco-editor/esm/vs/editor/contrib/wordHighlighter/browser/wordHighlighter.js';
+import 'monaco-editor/esm/vs/editor/contrib/wordOperations/browser/wordOperations.js';
+import 'monaco-editor/esm/vs/editor/contrib/wordPartOperations/browser/wordPartOperations.js';
+
+import editorWorker from 'monaco-editor/esm/vs/editor/editor.worker?worker';
+import jsonWorker from 'monaco-editor/esm/vs/language/json/json.worker?worker';
+import tsWorker from 'monaco-editor/esm/vs/language/typescript/ts.worker?worker';
+
+import 'monaco-editor/esm/vs/language/json/monaco.contribution';
+import 'monaco-editor/esm/vs/basic-languages/javascript/javascript.contribution';
+import 'monaco-editor/esm/vs/basic-languages/markdown/markdown.contribution';
+
+const dom = ref<any>();
+
+const { modelValue, language, options } = defineProps<{
+  modelValue: any;
+  language?: string;
+  options?: any;
+}>();
+
+const emit = defineEmits(['update:modelValue', 'change']);
+
+self.MonacoEnvironment = {
+  getWorker(_: any, label: string) {
+    if (label === 'json') {
+      return new jsonWorker();
+    }
+    if (label === 'typescript' || label === 'javascript') {
+      return new tsWorker();
+    }
+    return new editorWorker();
+  },
+};
+
+let editor: monaco.editor.IStandaloneCodeEditor;
+
+onMounted(() => {
+  editor = monaco.editor.create(dom.value, {
+    value: modelValue,
+    automaticLayout: true,
+    minimap: { enabled: false },
+    language: language || 'javascript',
+    theme: 'vs-dark',
+    ...options,
+  });
+  editor.onDidChangeModelContent(() => {
+    const currenValue = editor.getValue();
+    emit('update:modelValue', currenValue);
+    emit('change', currenValue);
+  });
+});
+
+watch(
+  () => modelValue,
+  (newValue) => {
+    if (editor) {
+      const value = editor.getValue();
+      if (newValue !== value) {
+        editor.setValue(newValue);
+      }
+    }
+  }
+);
+
+watch(
+  () => options,
+  (newValue) => {
+    editor.updateOptions(newValue);
+  },
+  { deep: true }
+);
+
+watch(
+  () => language,
+  (newValue) => {
+    monaco.editor.setModelLanguage(editor.getModel()!, newValue);
+  }
+);
+
+onUnmounted(() => {
+  editor?.dispose();
+});
+</script>
+<style lang="postcss" scoped>
+.code-editor {
+  border: 1px solid var(--color-sub-border);
+  width: 100%;
+  min-height: 160px;
+
+  :deep(.monaco-editor) {
+    --vscode-editorGutter-background: var(--color-background-editor);
+    --vscode-editor-background: var(--color-background-editor);
+
+    .view-overlays .current-line {
+      border: 1px solid var(--color-border-editor);
+    }
+  }
+}
+</style>

+ 0 - 0
src/views/components/common/MonacoModal.vue → src/views/components/common/MonacoModal.vue_


+ 0 - 9
src/views/components/common/customMonaco.ts

@@ -1,9 +0,0 @@
-import * as monaco from 'monaco-editor/esm/vs/editor/editor.api.js';
-import 'monaco-editor/esm/vs/basic-languages/javascript/javascript.contribution';
-import 'monaco-editor/esm/vs/basic-languages/markdown/markdown.contribution';
-import 'monaco-editor/esm/vs/editor/editor.all.js';
-
-import 'monaco-editor/esm/vs/language/typescript/monaco.contribution';
-import 'monaco-editor/esm/vs/language/json/monaco.contribution';
-
-export { monaco };

+ 0 - 1
tsconfig.json

@@ -5,7 +5,6 @@
     "useDefineForClassFields": true,
     "module": "esnext",
     "moduleResolution": "node",
-    "strict": true,
     "jsx": "preserve",
     "sourceMap": true,
     "resolveJsonModule": true,

+ 8 - 2
vite.config.ts

@@ -2,13 +2,12 @@ import { defineConfig, Plugin, ViteDevServer } from 'vite';
 import vue from '@vitejs/plugin-vue';
 import vueJsx from '@vitejs/plugin-vue-jsx';
 import * as path from 'path';
-import monacoEditorPlugin from 'vite-plugin-monaco-editor';
 import * as fs from 'fs';
 import formidable from 'formidable';
 
 // https://vitejs.dev/config/
 export default defineConfig({
-  plugins: [vue(), vueJsx(), monacoEditorPlugin({}), fileList()],
+  plugins: [vue(), vueJsx(), fileList()],
   resolve: {
     alias: {
       '@': path.resolve(__dirname, './src/'),
@@ -17,6 +16,13 @@ export default defineConfig({
   },
   build: {
     outDir: 'v',
+    rollupOptions: {
+      output: {
+        manualChunks: {
+          monaco: [`monaco-editor`],
+        },
+      },
+    },
   },
   server: {
     proxy: {