Ver Fonte

数据面板

Alsmile há 2 anos atrás
pai
commit
556083b8b0

+ 1 - 1
package.json

@@ -17,7 +17,7 @@
     "jszip": "^3.10.0",
     "localforage": "^1.10.0",
     "monaco-editor": "^0.37.1",
-    "tdesign-vue-next": "^1.3.3",
+    "tdesign-vue-next": "^1.3.4",
     "vue": "^3.3.2",
     "vue-router": "^4.2.0"
   },

Diff do ficheiro suprimidas por serem muito extensas
+ 201 - 178
pnpm-lock.yaml


+ 19 - 18
src/services/utils.ts

@@ -1,12 +1,12 @@
-import { FormItem, Pen, Meta2d, Meta2dData } from "@meta2d/core";
-import { MessagePlugin, NotifyPlugin, Button } from "tdesign-vue-next";
-import { h, ref } from "vue";
-import { cdn } from "./api";
+import { FormItem, Pen, Meta2d, Meta2dData } from '@meta2d/core';
+import { MessagePlugin, NotifyPlugin, Button } from 'tdesign-vue-next';
+import { h, ref } from 'vue';
+import { cdn } from './api';
 
 const market = import.meta.env.VITE_MARKET;
 
-export const noLoginTip = "请先登录,否则无法保存!";
-export const localMeta2dDataName = "meta2dData";
+export const noLoginTip = '请先登录,否则无法保存!';
+export const localMeta2dDataName = 'meta2dData';
 
 export interface Meta2dBackData extends Meta2dData {
   id?: string;
@@ -47,7 +47,7 @@ export function showNotification(title: string): Promise<boolean> {
     };
     if (!notification.value) {
       notification.value = NotifyPlugin.info({
-        title: "提示",
+        title: '提示',
         content: title,
         closeBtn: true,
         onCloseBtnClick: () => {
@@ -56,18 +56,19 @@ export function showNotification(title: string): Promise<boolean> {
           resolve(false);
         },
         // duration: 1000000,
+        // @ts-ignore
         footer: h(
           Button,
           {
-            theme: "primary",
-            size: "small",
+            theme: 'primary',
+            size: 'small',
             style: {
-              "margin-top": "16px",
-              "margin-left": "256px",
+              'margin-top': '16px',
+              'margin-left': '256px',
             },
             onClick: btnClick,
           },
-          "确定"
+          '确定'
         ),
       });
     }
@@ -108,23 +109,23 @@ export async function dealwithFormatbeforeOpen(data: Meta2dBackData) {
 
 export function gotoAccount() {
   MessagePlugin.info({
-    content: "请开通vip,即将跳转到开通页面...",
+    content: '请开通vip,即将跳转到开通页面...',
     duration: 3,
   });
   setTimeout(() => {
     if (market) {
-      window.open("/account?unVip=true");
+      window.open('/account?unVip=true');
     } else {
-      let arr = location.host.split(".");
-      arr[0] = "http://account";
-      let accountUrl = arr.join(".");
+      let arr = location.host.split('.');
+      arr[0] = 'http://account';
+      let accountUrl = arr.join('.');
       window.open(`${accountUrl}?unVip=true`);
     }
   }, 3000);
 }
 
 export function isGif(url: string): boolean {
-  return url.endsWith(".gif");
+  return url.endsWith('.gif');
 }
 
 /**

+ 17 - 0
src/styles/app.css

@@ -270,10 +270,18 @@ a.hover:hover {
   margin-top: 12px;
 }
 
+.mb-12 {
+  margin-bottom: 12px;
+}
+
 .mt-16 {
   margin-top: 16px;
 }
 
+.mb-16 {
+  margin-bottom: 16px;
+}
+
 .px-12 {
   padding-left: 12px;
   padding-right: 12px;
@@ -325,6 +333,15 @@ a.hover:hover {
   background-position: 0 0, 3px 3px;
 }
 
+.vip-label {
+  font-size: 10px;
+  background-color: #ff400030;
+  color: var(--color-bland);
+  padding: 0 6px;
+  margin-left: 4px;
+  border-radius: 2px;
+}
+
 *::placeholder {
   color: var(--color-gray);
 }

+ 24 - 4
src/styles/tdesign.css

@@ -389,7 +389,8 @@
 }
 
 .t-popup[data-popper-placement^='top'] .t-popup__arrow::before {
-  top: -4px;
+  top: -8px;
+  border-top-left-radius: 0;
 }
 
 .t-button--variant-base {
@@ -438,12 +439,31 @@
     font-size: 14px;
   }
 
-  .t-dialog__close:hover {
-    background: none;
-    color: var(--color-primary);
+  .t-dialog__body {
+    overflow: initial;
+
+    & > .body {
+      width: calc(100% + 40px);
+      height: 380px;
+      margin-left: -20px;
+      padding: 0 20px;
+      overflow: auto;
+    }
+  }
+
+  .t-dialog__close {
+    margin-right: -6px;
+    &:hover {
+      background: none;
+      color: var(--color-primary);
+    }
   }
 
   .t-table__empty {
     color: var(--color-gray);
   }
 }
+
+.t-checkbox__label {
+  font-size: 12px;
+}

+ 159 - 20
src/views/components/PenDatas.vue

@@ -1,13 +1,12 @@
 <template>
   <div class="props">
-    <div class="px-16 py-16" v-if="pen.realTimes && pen.realTimes.length">
-      <div class="grid" style="line-height: 30px">
+    <div class="real-times" v-if="pen.realTimes && pen.realTimes.length">
+      <div class="grid head">
         <div class="title">数据名</div>
-        <div class="title ml-8">值</div>
-        <div>
-          <div style="text-align: right; padding-right: 2px">
-            <t-icon name="more" />
-          </div>
+        <div class="title">值</div>
+        <div class="title">触发器</div>
+        <div class="actions">
+          <t-icon name="more" />
         </div>
       </div>
       <div class="grid" v-for="(item, i) in pen.realTimes">
@@ -15,12 +14,51 @@
           <label class="label">{{ item.label }}</label>
         </t-tooltip>
         <div class="value">
-          <t-input v-model="item.value" />
+          <t-tooltip :content="getBindsDesc(item)" placement="top">
+            <t-icon
+              name="link"
+              class="hover"
+              :class="{ primary: item.binds?.length }"
+              @click="onBind(item)"
+            />
+          </t-tooltip>
+          <t-input
+            v-if="item.type === 'number'"
+            v-model="pen[item.key]"
+            placeholder="数字"
+            @change="changeValue(item.key)"
+          />
+          <t-switch
+            v-else-if="item.type === 'bool'"
+            v-model="pen[item.key]"
+            class="ml-8"
+            size="small"
+            @change="changeValue(item.key)"
+          />
+          <t-input
+            v-else
+            v-model="pen[item.key]"
+            placeholder="字符串"
+            @change="changeValue(item.key)"
+          />
         </div>
-        <div class="actions">
-          <t-tooltip content="变量绑定" placement="top">
-            <t-icon name="link" class="hover" @click="onBind(item)" />
+        <div>
+          <t-tooltip :content="item.triggers?.length || '触发器'">
+            <t-badge
+              :count="item.triggers?.length"
+              size="small"
+              dot
+              :offset="[0, 5]"
+            >
+              <t-icon
+                name="relativity"
+                class="hover"
+                @click="onTrigger(item)"
+              />
+            </t-badge>
           </t-tooltip>
+        </div>
+        <div class="actions">
           <t-dropdown
             :options="moreOptions"
             @click="onMenuMore($event, item, i)"
@@ -36,7 +74,7 @@
           @click="addRealTime"
           :minColumnWidth="150"
         >
-          <a> <t-icon name="add-rectangle" /> 添加动态数据 </a>
+          <a class="ml-12"> <t-icon name="add-rectangle" /> 添加动态数据 </a>
         </t-dropdown>
       </div>
     </div>
@@ -133,7 +171,7 @@
     v-if="dataBindDialog.show"
     :visible="true"
     class="data-link-dialog"
-    header="变量绑定"
+    header="动态数据绑定"
     @cancel="
       dataBindDialog.data.binds = dataBindDialog.bkBinds;
       dataBindDialog.show = false;
@@ -148,7 +186,6 @@
           v-for="(tag, index) in dataBindDialog.data.binds"
           :key="index"
           :content="tag.id"
-          trigger="click"
         >
           <t-tag class="mr-8 mb-8" closable @close="onRemoveBind(index)">
             {{ tag.label }}
@@ -184,18 +221,62 @@
     >
     </t-table>
   </t-dialog>
+
+  <t-dialog
+    v-if="triggersDialog.show"
+    :visible="true"
+    class="data-events-dialog"
+    header="数据触发器"
+    @cancel="triggersDialog.show = false"
+    @confirm="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-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>
+    </div>
+  </t-dialog>
 </template>
 
 <script lang="ts" setup>
-import { onBeforeMount, reactive, ref, toRaw } from 'vue';
+import {
+  getCurrentInstance,
+  onBeforeMount,
+  onUnmounted,
+  reactive,
+  ref,
+  toRaw,
+} from 'vue';
 import { useRoute, useRouter } from 'vue-router';
 import { MessagePlugin } from 'tdesign-vue-next';
 import axios from 'axios';
 import { debounce } from '@/services/debouce';
 
+import { updatePen } from './pen.ts';
+
 const route = useRoute();
 const router = useRouter();
 
+const {
+  proxy: { $forceUpdate },
+}: any = getCurrentInstance();
+
 const { pen } = defineProps<{
   pen: any;
 }>();
@@ -308,7 +389,7 @@ const dataSetColumns = [
   },
   {
     colKey: 'label',
-    title: '变量名称',
+    title: '动态数据名称',
     width: 220,
     ellipsis: { theme: 'light', trigger: 'context-menu' },
   },
@@ -331,11 +412,21 @@ const query = reactive<{
   range: [],
 });
 
+const triggersDialog = reactive<any>({
+  show: false,
+  data: undefined,
+});
+
+let timer: any;
+
 onBeforeMount(() => {
+  // realTimesOptions - 扩展的动态数据下拉列表
   if (pen.realTimesOptions) {
     options.value[options.value.length - 1].divider = true;
     options.value.push(...pen.realTimesOptions);
   }
+
+  timer = setInterval($forceUpdate, 1000);
 });
 
 const addRealTime = (e: any) => {
@@ -499,13 +590,50 @@ const onRemoveBind = (index: number) => {
     dataBindDialog.selectedIds.push(i.id);
   }
 };
+
+const getBindsDesc = (item: any) => {
+  if (!item.binds || !item.binds.length) {
+    return '绑定动态数据';
+  }
+  let desc = '';
+  for (const i of item.binds) {
+    desc += i.label + ',';
+  }
+  if (desc && desc.length > 1) {
+    desc = desc.substring(0, desc.length - 1);
+  }
+  return desc;
+};
+
+const changeValue = (prop: string) => {
+  updatePen(pen, prop);
+};
+
+const onTrigger = (item: any) => {
+  if (!item.triggers) {
+    item.triggers = [];
+  }
+  triggersDialog.data = item;
+  triggersDialog.show = true;
+};
+
+onUnmounted(() => {
+  clearInterval(timer);
+});
 </script>
 <style lang="postcss" scoped>
 .props {
   height: 100%;
 
   .grid {
-    grid-template-columns: 80px 154px 40px;
+    grid-template-columns: 60px 140px 54px 30px;
+    padding: 0 12px;
+
+    &.head {
+      background: var(--color-background-input);
+      line-height: 36px;
+      margin-bottom: 6px;
+    }
   }
 
   .blank {
@@ -525,8 +653,20 @@ const onRemoveBind = (index: number) => {
 
   .value {
     padding-right: 8px;
+    display: flex;
+    align-items: center;
+
+    svg {
+      flex-shrink: 0;
+      margin-right: 4px;
+    }
+
+    div {
+      width: 110px;
+    }
 
     :deep(.t-input) {
+      padding-left: 4px;
       height: 26px;
       border-color: transparent;
       &:hover {
@@ -536,9 +676,8 @@ const onRemoveBind = (index: number) => {
   }
 
   .actions {
-    svg {
-      margin-left: 6px;
-    }
+    text-align: right;
+    padding-right: 2px;
   }
 
   .data-list {

+ 132 - 0
src/views/components/PenEvents.vue

@@ -0,0 +1,132 @@
+<template>
+  <div class="props">
+    <div class="px-16 py-16" v-if="pen.events && pen.events.length"></div>
+    <div class="flex column center blank" v-else>
+      <img src="/img/blank.png" />
+      <div class="gray center">还没有交互事件</div>
+      <div class="mt-8">
+        <t-dropdown :options="options" @click="addEvent" :minColumnWidth="150">
+          <t-button style="height: 30px"> 添加交互事件 </t-button>
+        </t-dropdown>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script lang="ts" setup>
+import {
+  getCurrentInstance,
+  onBeforeMount,
+  onUnmounted,
+  reactive,
+  ref,
+  toRaw,
+} from 'vue';
+import { useRoute, useRouter } from 'vue-router';
+import { MessagePlugin } from 'tdesign-vue-next';
+import axios from 'axios';
+import { debounce } from '@/services/debouce';
+
+import { updatePen } from './pen.ts';
+
+const route = useRoute();
+const router = useRouter();
+
+const {
+  proxy: { $forceUpdate },
+}: any = getCurrentInstance();
+
+const { pen } = defineProps<{
+  pen: any;
+}>();
+
+const options = ref<any>([
+  {
+    value: 'click',
+    content: '单击',
+  },
+  {
+    value: 'dblclick',
+    content: '双击',
+    divider: true,
+  },
+  {
+    value: 'enter',
+    content: '鼠标移入',
+  },
+  {
+    value: 'leave',
+    content: '鼠标移出',
+    divider: true,
+  },
+  {
+    value: 'active',
+    content: '获得焦点',
+  },
+  {
+    value: 'inactive',
+    content: '失去焦点',
+    divider: true,
+  },
+  {
+    value: 'mousedown',
+    content: '鼠标按下',
+  },
+  {
+    value: 'mouseup',
+    content: '鼠标抬起',
+    divider: true,
+  },
+  {
+    value: 'message',
+    content: '消息',
+  },
+]);
+
+onBeforeMount(() => {});
+
+onUnmounted(() => {});
+</script>
+<style lang="postcss" scoped>
+.props {
+  height: 100%;
+
+  .blank {
+    height: 70%;
+    img {
+      padding: 16px;
+      opacity: 0.9;
+    }
+  }
+
+  .label {
+    width: fit-content;
+    font-size: 10px;
+    line-height: 28px;
+    color: var(--color-desc);
+  }
+
+  .value {
+    padding-right: 8px;
+
+    :deep(.t-input) {
+      height: 26px;
+      border-color: transparent;
+      &:hover {
+        border-color: var(--color-primary);
+      }
+    }
+  }
+
+  .actions {
+    svg {
+      margin-left: 6px;
+    }
+  }
+
+  .data-list {
+    height: 300px;
+    overflow: auto;
+  }
+}
+</style>

+ 6 - 33
src/views/components/PenProps.vue

@@ -946,7 +946,9 @@
       <t-tab-panel :value="3" label="数据">
         <PenDatas :pen="data.pen" />
       </t-tab-panel>
-      <t-tab-panel :value="4" label="交互"> </t-tab-panel>
+      <t-tab-panel :value="4" label="交互">
+        <PenEvents :pen="data.pen" />
+      </t-tab-panel>
     </t-tabs>
   </div>
 </template>
@@ -960,6 +962,8 @@ import { monacoOption } from './common/MonacoModal.vue';
 
 import PenAnimates from './PenAnimates.vue';
 import PenDatas from './PenDatas.vue';
+import PenEvents from './PenEvents.vue';
+import { updatePen } from './pen.ts';
 
 const headers = {
   Authorization: 'Bearer ' + (localStorage.token || getCookie('token') || ''),
@@ -1078,38 +1082,7 @@ const decimalRound = (val: number) => {
 };
 
 const changeValue = (prop: string) => {
-  const v: any = { id: data.pen.id };
-  v[prop] = data.pen[prop];
-  if (prop === 'x') {
-    v.x = data.rect.x;
-  } else if (prop === 'y') {
-    v.y = data.rect.y;
-  } else if (prop === 'width') {
-    v.height = (data.rect.width / data.pen.width) * data.pen.height;
-  } else if (prop === 'height') {
-    v.width = (data.rect.height / data.pen.height) * data.pen.width;
-  } else if (prop === 'shadow') {
-    if (v[prop]) {
-      !v.shadowOffsetX && (v.shadowOffsetX = 0);
-      !v.shadowOffsetY && (v.shadowOffsetY = 0);
-      !v.shadowBlur && (v.shadowBlur = 0);
-    } else {
-      v.shadowColor = '';
-    }
-  } else if (prop === 'lineGradientColors') {
-    //@ts-ignore
-    if (meta2d.store.active[0].name === 'line') {
-      //@ts-ignore
-      meta2d.store.active[0].calculative.gradientColorStop = null;
-    } else {
-      //@ts-ignore
-      meta2d.store.active[0].calculative.lineGradient = null;
-    }
-    //不同模式切换不同的系统配色
-  } else if (prop === 'titleFnJs') {
-    v.titleFn = null;
-  }
-  meta2d.setValue(v);
+  updatePen(data.pen, prop);
 };
 
 const onFontPopupVisible = (val: boolean) => {

Diff do ficheiro suprimidas por serem muito extensas
+ 408 - 402
src/views/components/View.vue


+ 38 - 35
src/views/components/common/MonacoModal.vue

@@ -7,32 +7,36 @@
     :on-cancel="cancel"
     :on-close="cancel"
   >
-  <t-radio-group
+    <t-radio-group
       v-if="options"
       size="small"
       v-model="currentValue"
       :default-value="0"
       @change="changeOption"
     >
-    <t-tooltip  v-for="option in options" :content="option.tip" placement="top">
-      <t-radio-button  :value="option.value">  {{ option.name }}</t-radio-button>
+      <t-tooltip
+        v-for="option in options"
+        :content="option.tip"
+        placement="top"
+      >
+        <t-radio-button :value="option.value">
+          {{ option.name }}</t-radio-button
+        >
       </t-tooltip>
     </t-radio-group>
     <div ref="monacoDom" class="monaco"></div>
   </t-dialog>
 </template>
 <script lang="ts">
-
 export interface monacoOption {
   key: string;
   value?: number | string;
   name?: string;
   tip?: string;
   code?: string;
-  language: "javascript" | "json" | "markdown";
+  language: 'javascript' | 'json' | 'markdown';
   example?: string;
 }
-
 </script>
 <script setup lang="ts">
 import {
@@ -44,8 +48,8 @@ import {
   reactive,
   ref,
   watch,
-} from "vue";
-import { monaco } from "./customMonaco";
+} from 'vue';
+import { monaco } from './customMonaco';
 
 const props = defineProps({
   visible: {
@@ -55,31 +59,31 @@ const props = defineProps({
   title: {
     type: String,
     default: () => {
-      return "JavaScript";
+      return 'JavaScript';
     },
   },
   code: {
     type: String,
     default: () => {
-      return "";
+      return '';
     },
   },
   language: {
     type: String,
     default: () => {
-      return "javascript";
+      return 'javascript';
     },
     validator: (value: string) => {
       // 这个值必须匹配下列字符串中的一个
-      return ["javascript", "json", "markdown"].includes(value);
+      return ['javascript', 'json', 'markdown'].includes(value);
     },
   },
   options: {
-    type:Array as PropType<monacoOption[]>
-  }
+    type: Array as PropType<monacoOption[]>,
+  },
 });
 
-const emit = defineEmits(["update:visible", "changeCode","changeOptions"]);
+const emit = defineEmits(['update:visible', 'changeCode', 'changeOptions']);
 
 let editor: any = null;
 
@@ -88,19 +92,19 @@ function handleOk() {
   if (props.options) {
     props.options[currentValue.value].code = editor.getValue();
     emit('changeOptions', props.options);
-    emit("update:visible", false);
+    emit('update:visible', false);
   } else {
     const code = editor.getValue();
-    emit("changeCode", code);
-    emit("update:visible", false);
+    emit('changeCode', code);
+    emit('update:visible', false);
   }
 }
 
 function cancel() {
-  emit("update:visible", false);
+  emit('update:visible', false);
 }
 
-const curTheme = "vs-dark"; // 暗主题
+const curTheme = 'vs-dark'; // 暗主题
 const monacoDom: any = ref(null);
 
 const currentValue = ref(0);
@@ -111,10 +115,13 @@ const changeOption = (e: number) => {
       props.options[beforeValue].code = editor.getValue();
       beforeValue = e;
     }
-    monaco.editor.setModelLanguage(editor.getModel(), props.options[e].language || 'javascript');
-    editor.setValue(props.options[e].code || props.options[e].example ||"");
+    monaco.editor.setModelLanguage(
+      editor.getModel(),
+      props.options[e].language || 'javascript'
+    );
+    editor.setValue(props.options[e].code || props.options[e].example || '');
   }
-}
+};
 
 watch(
   () => props.visible,
@@ -131,17 +138,17 @@ watch(
         }
         if (props.options) {
           const option = props.options[currentValue.value];
-          editor.setValue(option.code||option.example||'');
+          editor.setValue(option.code || option.example || '');
           monaco.editor.setModelLanguage(editor.getModel(), option.language);
         } else {
           // 可见状态
           editor.setValue(props.code);
           monaco.editor.setModelLanguage(editor.getModel(), props.language);
         }
-          // 格式化
-          setTimeout(() => {
-            editor.getAction(["editor.action.formatDocument"])._run();
-          }, 300);
+        // 格式化
+        setTimeout(() => {
+          editor.getAction(['editor.action.formatDocument'])._run();
+        }, 300);
       });
     }
   }
@@ -152,12 +159,8 @@ onUnmounted(() => {
 });
 </script>
 
-<style lang="postcss">
-.t-dialog__body {
-  /* padding: 0; */
-  overflow: hidden;
-  .monaco {
-    height: 400px;
-  }
+<style lang="postcss" scoped>
+.monaco {
+  height: 400px;
 }
 </style>

+ 36 - 0
src/views/components/pen.ts

@@ -0,0 +1,36 @@
+export const updatePen = (pen: any, prop: string) => {
+  const v: any = { id: pen.id };
+  const rect: any = meta2d.getPenRect(pen);
+
+  v[prop] = pen[prop];
+  if (prop === 'x') {
+    v.x = rect.x;
+  } else if (prop === 'y') {
+    v.y = rect.y;
+  } else if (prop === 'width') {
+    v.height = (rect.width / pen.width) * pen.height;
+  } else if (prop === 'height') {
+    v.width = (rect.height / pen.height) * pen.width;
+  } else if (prop === 'shadow') {
+    if (v[prop]) {
+      !v.shadowOffsetX && (v.shadowOffsetX = 0);
+      !v.shadowOffsetY && (v.shadowOffsetY = 0);
+      !v.shadowBlur && (v.shadowBlur = 0);
+    } else {
+      v.shadowColor = '';
+    }
+  } else if (prop === 'lineGradientColors') {
+    //@ts-ignore
+    if (meta2d.store.active[0].name === 'line') {
+      //@ts-ignore
+      meta2d.store.active[0].calculative.gradientColorStop = null;
+    } else {
+      //@ts-ignore
+      meta2d.store.active[0].calculative.lineGradient = null;
+    }
+    //不同模式切换不同的系统配色
+  } else if (prop === 'titleFnJs') {
+    v.titleFn = null;
+  }
+  meta2d.setValue(v);
+};

Alguns ficheiros não foram mostrados porque muitos ficheiros mudaram neste diff