Ver código fonte

feat: 添加代码编辑器、数据类型等

jiaxing.liao 1 mês atrás
pai
commit
0796c7d1b9

+ 1 - 0
package.json

@@ -26,6 +26,7 @@
     "@vueuse/components": "^14.0.0",
     "@vueuse/core": "^14.0.0",
     "element-plus": "^2.11.4",
+    "monaco-editor": "^0.54.0",
     "normalize.css": "^8.0.1",
     "pinia": "^3.0.3",
     "vue-hooks-plus": "^2.4.1",

+ 3 - 0
pnpm-lock.yaml

@@ -23,6 +23,9 @@ importers:
       element-plus:
         specifier: ^2.11.4
         version: 2.11.4(vue@3.5.22(typescript@5.9.3))
+      monaco-editor:
+        specifier: ^0.54.0
+        version: 0.54.0
       normalize.css:
         specifier: ^8.0.1
         version: 8.0.1

+ 62 - 73
project.json5

@@ -11,21 +11,19 @@
     // 项目描述
     "description": "智能HOME项目",
     // 项目类型
-    "type": "1", // 1: 模拟显示 2: 芯片 3: 板卡
+    "type": "chip", // 'analog_display' | 'chip' | 'board' 1: 模拟显示 2: 芯片 3: 板卡
     // 屏幕类型
-    "screenType": "1", // 1:单屏 2:双屏
+    "screenType": "single", // 'single' | 'double' 1:单屏 2:双屏
     // 语言
     "language": "zh-cn",
     // 资源打包方式
-    "resourcePackaging": "1", // 1: C源码 2: C源码+BIN
+    "resourcePackaging": "c_bin", // 'c' | 'c_bin' 1: C源码 2: C源码+BIN
     // BIN数量
     "binNum": "1", // 1~32
     // 芯片配置
     "chip": {
       // 芯片型号
-      "model": "ESP32",
-      // 接口类型
-      "interface": "RGB",
+      "model": "ESP32"
     },
     // 板卡配置
     "board": {
@@ -37,10 +35,12 @@
       {
         // 屏幕类型
         "type": "1", // 1:主屏 2:副屏
+        // 接口类型
+        "interface": "RGB",
         // 屏幕宽
-        "width": 0,
+        "width": 1920,
         // 屏幕高
-        "height": 0,
+        "height": 1080,
         // 颜色深度
         "colorDepth": "16bit",
         // 颜色格式
@@ -64,6 +64,14 @@
     // 修改时间
     "modifyTime": "2025-09-01 12:00:00",
   },
+  // BIN列表
+  "bins": [
+    {
+      "id": "bin_1",
+      "fileName": "bin1.bin",
+      "path": "./src/bin/bin1.bin"
+    }
+  ],
   // 资源
   "resources": {
     // 图片资源
@@ -72,13 +80,10 @@
         "id": "img_1",
         "fileName": "image1.png",
         "fielType": "png",
-        "path": "./src/assets/images/image1.png"
-      },
-      {
-        "id": "img_1",
-        "fileName": "image1.png",
-        "fielType": "png",
-        "path": "./src/assets/images/image1.png"
+        "path": "./src/assets/images/image1.png",
+        "compressFormat": "rle", // 无压缩、RLE压缩、QOI压缩、JPEG压缩、PNG压缩
+        "alpha": 1, // 透明度0-1
+        "bin": "bin_1" // 关联的BIN ID
       }
     ],
     // 字体资源
@@ -88,11 +93,12 @@
         "fileName": "font1.ttf",
         "fielType": "ttf",
         "path": "./src/assets/fonts/font1.ttf",
-        "range": ["1", "2", "3"], // 0: 全部 1:界面文本 2:自定义文本 3: 编码范围
+        "range": ["page", "custom", "range"], // 'all' | 'page' | 'custom' | 'range' 0: 全部 1:界面文本 2:自定义文本 3: 编码范围
         // 编码范围
         "codeRange": ["0x4E00-0x9FA5"],
         // 额外的文本内容
-        "extText": "abcdefghijklmnopqrstuvwxyz"
+        "extText": "abcdefghijklmnopqrstuvwxyz",
+        "bin": "bin_1" // 关联的BIN ID
       }
     ],
     "others": [
@@ -115,10 +121,19 @@
   // 全局变量定义
   "variables": [
     {
-      "id": "var_1",
-      "name": "a",
-      "value": "1",
-      "type": "int"
+      // 组ID
+      "id": "group_1",
+      // 组名称
+      "name": "全局变量",
+      // 变量
+      "variables": [
+        {
+          "id": "var_1",
+          "name": "a",
+          "value": "1",
+          "type": "int"
+        }
+      ]
     }
   ],
   // 主题
@@ -228,23 +243,6 @@
       "action": "static lv_obj_click(void* ctx) { // TODO: 添加事件处理逻辑 }"
     }
   ],
-  // BIN列表
-  "bins": [
-    {
-      "id": "bin_1",
-      // BIN名称
-      "name": "img",
-      // 包含文件
-      "files": ["img_1", "img_1"],
-    },
-    {
-      "id": "bin_2",
-      // BIN名称
-      "name": "font",
-      // 包含文件
-      "files": ["font_1", "font_2"],
-    }
-  ],
   // 屏幕
   "screens": [
     {
@@ -252,12 +250,10 @@
       "name": "主屏",
       // 类型
       "type": "screen",
-      // 背景颜色 未设置读取通用配置
-      "backgroundColor": "#ffffff",
       // 屏幕宽 未设置取通用配置
-      "width": 0,
+      "width": 1920,
       // 屏幕高 未设置取通用配置
-      "height": 0,
+      "height": 1080,
       // 隐藏
       "hidden": false,
       // 锁定
@@ -291,15 +287,9 @@
           // 样式
           "style": {},
           // 事件
-          "events": [
-            {
-              "id": "e_1",
-              "event": "init",
-              "action": ""
-            }
-          ],
+          "events": [],
           // 页面变量
-          "variable": [
+          "variables": [
             {
               "id": "page_1_var_1",
               "name": "page_1_a",
@@ -307,21 +297,12 @@
               "type": "int"
             }
           ],
-          // 方法
-          "method": [
-            {
-              "id": "page_1_method_1",
-              "name": "page_1_method_1",
-              "value": "lv_obj_click"
-            }
-          ],
           // 子组件
           "children": [
             {
               "id": "lv_obj_01",
+              // 控件名称
               "name": "obj_01",
-              // 父级id
-              "parentId": "page_1",
               // 类型
               "type": "widget",
               // 控件类型
@@ -387,25 +368,33 @@
                   // 触发事件
                   "trigger": "click",
                   // 动作类型
-                  "actionType": "function", // function: 执行函数
-                  // 动作
-                  "action": "lv_obj_click"
+                  "type": "function", // 'play_animation' | 'function' -> play_animation: 播放动画 function: 执行函数
+                  // 动画ID
+                  animation: "",
+                  // 动画播放前函数ID
+                  animationPlayerBeforeEvent: "",
+                  // 动画播放后函数ID
+                  animationPlayerAfterEvent: "",
+                  // 函数ID
+                  function: "method_1"
                 },
                 // 加载完毕播放动画
                 {
-                  "id": "event_2",
+                  "id": "event_1",
                   // 事件名称
-                  "name": "lv_obj_loaded_event",
+                  "name": "lv_obj_click_event",
                   // 触发事件
-                  "trigger": "loaded",
+                  "trigger": "click",
                   // 动作类型
-                  "actionType": "play_animation", // play_animation: 播放动画
-                  // 动作
-                  "action": "move_x_animation",
-                  // 动画播放前回调
-                  "before": "",
-                  // 动画播放后回调
-                  "after": ""
+                  "type": "play_animation", // 'play_animation' | 'function' -> play_animation: 播放动画 function: 执行函数
+                  // 动画ID
+                  animation: "animation_1",
+                  // 动画播放前函数ID
+                  animationPlayerBeforeEvent: "method_animation_1",
+                  // 动画播放后函数ID
+                  animationPlayerAfterEvent: "method_animation_2",
+                  // 函数ID
+                  function: ""
                 }
               ],
               // 子对象

+ 5 - 0
src/renderer/components.d.ts

@@ -11,6 +11,9 @@ export {}
 /* prettier-ignore */
 declare module 'vue' {
   export interface GlobalComponents {
+    CodeEditor: typeof import('./src/components/CodeEditor/index.vue')['default']
+    ElCollapse: typeof import('element-plus/es')['ElCollapse']
+    ElCollapseItem: typeof import('element-plus/es')['ElCollapseItem']
     ElConfigProvider: typeof import('element-plus/es')['ElConfigProvider']
     ElContainer: typeof import('element-plus/es')['ElContainer']
     ElHeader: typeof import('element-plus/es')['ElHeader']
@@ -20,6 +23,8 @@ declare module 'vue' {
     ElTabPane: typeof import('element-plus/es')['ElTabPane']
     ElTabs: typeof import('element-plus/es')['ElTabs']
     ElTooltip: typeof import('element-plus/es')['ElTooltip']
+    ElTree: typeof import('element-plus/es')['ElTree']
+    MonacoEditor: typeof import('./src/components/MonacoEditor/index.vue')['default']
     RouterLink: typeof import('vue-router')['RouterLink']
     RouterView: typeof import('vue-router')['RouterView']
     SplitterCollapse: typeof import('./src/components/SplitterCollapse/index.vue')['default']

+ 187 - 0
src/renderer/src/components/MonacoEditor/index.vue

@@ -0,0 +1,187 @@
+<script setup lang="ts">
+import type { editor } from 'monaco-editor'
+
+import { nextTick, onMounted, ref, watch } from 'vue'
+import { useAppStore } from '@/store/modules/app'
+import * as monaco from 'monaco-editor'
+
+const props = withDefaults(
+  defineProps<{
+    allowFullscreen?: boolean
+    autoToggleTheme?: boolean
+    bordered?: boolean
+    config?: editor.IStandaloneEditorConstructionOptions
+    language?: string
+    lineNumbers?: 'off' | 'on'
+    modelValue?: any
+    readOnly?: boolean
+    theme?: 'hc-black' | 'vs-dark' | 'vs-light'
+    valueFormat?: string
+  }>(),
+  {
+    allowFullscreen: true,
+    config: () => ({
+      minimap: {
+        enabled: false
+      },
+      selectOnLineNumbers: true
+    }),
+    autoToggleTheme: true,
+    language: 'json',
+    lineNumbers: 'on',
+    readOnly: false,
+    theme: 'vs-light',
+    valueFormat: 'string'
+  }
+)
+
+const { theme } = useAppStore()
+
+const emit = defineEmits(['update:modelValue'])
+
+const isFullScreen = ref(false)
+
+const fullScreenStyle = `position: fixed;
+  top: 0;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  z-index: 2999;`
+
+const editContainer = ref<HTMLElement | null>(null)
+
+let monacoEditor: monaco.editor.IStandaloneCodeEditor | null = null
+
+/**
+ * 设置文本
+ * @param text
+ */
+function setValue(text: string) {
+  monacoEditor?.setValue(text || '')
+}
+
+/**
+ * 光标处插入文本
+ * @param text
+ */
+function insertText(text: string) {
+  // 获取光标位置
+  const position = monacoEditor?.getPosition()
+  // 未获取到光标位置信息
+  if (!position) {
+    return
+  }
+  // 插入
+  monacoEditor?.executeEdits('', [
+    {
+      range: new monaco.Range(
+        position.lineNumber,
+        position.column,
+        position.lineNumber,
+        position.column
+      ),
+      text
+    }
+  ])
+  // 设置新的光标位置
+  monacoEditor?.setPosition({
+    ...position,
+    column: position.column + text.length
+  })
+  // 重新聚焦
+  monacoEditor?.focus()
+}
+
+onMounted(() => {
+  monacoEditor = monaco.editor.create(editContainer.value as HTMLElement, {
+    value: getValue(),
+    ...props.config,
+    automaticLayout: true,
+    language: props.language,
+    lineNumbers: props.lineNumbers,
+    readOnly: props.readOnly,
+    scrollBeyondLastLine: false,
+    theme: props.theme
+  })
+
+  function handleToggleTheme() {
+    console.log('theme', theme)
+    if (theme === 'dark') {
+      monaco.editor.setTheme('vs-dark')
+    } else {
+      monaco.editor.setTheme('vs-light')
+    }
+  }
+
+  // 自动切换主题
+  if (props.autoToggleTheme) {
+    watch(
+      () => theme,
+      () => {
+        nextTick(() => handleToggleTheme())
+      },
+      {
+        immediate: true
+      }
+    )
+  }
+
+  // 获取值
+  function getValue() {
+    // valueFormat 为json 格式,需要转换处理
+    if (props.valueFormat === 'json' && props.modelValue) {
+      return JSON.stringify(props.modelValue, null, 2)
+    }
+    return props.modelValue ?? ''
+  }
+
+  // 监听值变化
+  monacoEditor.onDidChangeModelContent(() => {
+    const currenValue = monacoEditor?.getValue()
+
+    // valueFormat 为json 格式,需要转换处理
+    if (props.valueFormat === 'json' && currenValue) {
+      emit('update:modelValue', JSON.parse(currenValue))
+      return
+    }
+
+    emit('update:modelValue', currenValue ?? '')
+  })
+})
+
+defineExpose({
+  insertText,
+  setValue
+})
+</script>
+<template>
+  <div
+    ref="editContainer"
+    :class="{ bordered: props.bordered }"
+    :style="isFullScreen ? fullScreenStyle : ''"
+    class="code-editor w-full h-full relative"
+  >
+    <div
+      class="z-999 text-$epic-text-helper absolute right-4 top-2 cursor-pointer text-xl"
+      @click="isFullScreen = !isFullScreen"
+      v-if="props.allowFullscreen"
+    >
+      <EpicIcon
+        :name="isFullScreen ? `icon--epic--close-fullscreen` : `icon--epic--open-fullscreen`"
+      />
+    </div>
+  </div>
+</template>
+<style lang="less" scoped>
+.code-editor {
+  width: 100%;
+  min-height: 150px;
+  :deep(.monaco-editor) {
+    height: 100%;
+  }
+
+  &.bordered {
+    border: 1px solid var(--epic-border-color);
+  }
+}
+</style>

+ 1 - 1
src/renderer/src/components/SplitterCollapse/SplitterCollapseItem.vue

@@ -17,7 +17,7 @@
       </div>
     </div>
     <!-- content -->
-    <div class="flex-1">
+    <div class="flex-1" v-show="opened">
       <slot></slot>
     </div>
   </div>

+ 0 - 303
src/renderer/src/store/modules/action.ts

@@ -1,303 +0,0 @@
-import { defineStore } from 'pinia'
-import { AlignEnum } from '@/enum/alignEnum'
-import { LayerEnum } from '@/enum/layerEnum'
-import { useProjectStore } from '@/store/modules/project'
-import { cloneDeep } from 'lodash'
-import { CustomElement } from '#/project'
-import { uuid } from '@/utils'
-
-type ActionState = {
-  // 操作记录--最大记录10条
-  records: string[]
-  // 当前操作索引
-  activeIndex: number
-  appKey: number
-  copyCache: any
-}
-export const useAcionStore = defineStore('action', {
-  state(): ActionState {
-    return {
-      records: [],
-      activeIndex: -1,
-      appKey: 0,
-      copyCache: null
-    }
-  },
-  getters: {
-    projectStore: () => useProjectStore(),
-    undoDisabled: (state) => state.activeIndex <= 0,
-    redoDisabled: (state) => state.activeIndex === state.records.length - 1
-  },
-  actions: {
-    initRecord() {
-      this.records = [JSON.stringify(this.projectStore.projectInfo)]
-      this.activeIndex = 0
-    },
-    // addRecord({type, info }: RecordItem & { snapshot?: ProjectInfo}) {
-    addRecord() {
-      // 新增如果当前索引不是最后一条, 覆盖后面的记录
-      if (this.activeIndex < this.records.length - 1) {
-        this.records.splice(this.activeIndex + 1, this.records.length)
-      }
-
-      this.records.push(JSON.stringify(this.projectStore.projectInfo))
-
-      // 新增如果超过10条记录,删除最早的一条
-      if (this.records.length > 10) {
-        this.records.shift()
-        this.activeIndex--
-      }
-
-      this.activeIndex = this.records.length - 1
-    },
-    /* 撤销 */
-    actionUndo() {
-      if (this.activeIndex <= 0) return
-      --this.activeIndex
-      const projectInfo = JSON.parse(this.records[this.activeIndex])
-      this.projectStore.updateProjectInfo(projectInfo)
-      this.appKey++
-    },
-    /* 重做 */
-    actionRedo() {
-      ++this.activeIndex
-      const projectInfo = JSON.parse(this.records[this.activeIndex])
-      this.projectStore.updateProjectInfo(projectInfo)
-      this.appKey++
-    },
-    actionClear() {},
-    /* 对齐 */
-    actionAlign(type: AlignEnum) {
-      const activeElements = this.projectStore.currentSelectedElements
-      switch (type) {
-        case AlignEnum.Bottom: {
-          const maxY = Math.max(
-            ...activeElements.map((item) => item.container.props.y + item.container.props.height)
-          )
-          activeElements.forEach((item) => {
-            this.projectStore.updateElement(
-              item.key,
-              'container.props.y',
-              maxY - item.container.props.height
-            )
-          })
-          break
-        }
-        case AlignEnum.HorizontalCenter: {
-          const maxX = Math.max(
-            ...activeElements.map((item) => item.container.props.x + item.container.props.width)
-          )
-          const minX = Math.min(...activeElements.map((item) => item.container.props.x))
-          const centerX = minX + (maxX - minX) / 2
-          activeElements.forEach((item) => {
-            this.projectStore.updateElement(
-              item.key,
-              'container.props.x',
-              centerX - item.container.props.width / 2
-            )
-          })
-          break
-        }
-        case AlignEnum.VerticalCenter: {
-          const maxY = Math.max(
-            ...activeElements.map((item) => item.container.props.y + item.container.props.height)
-          )
-          const minY = Math.min(...activeElements.map((item) => item.container.props.y))
-          const centerY = minY + (maxY - minY) / 2
-          activeElements.forEach((item) => {
-            this.projectStore.updateElement(
-              item.key,
-              'container.props.y',
-              centerY - item.container.props.height / 2
-            )
-          })
-          break
-        }
-        case AlignEnum.Left: {
-          const minX = Math.min(...activeElements.map((item) => item.container.props.x))
-          activeElements.forEach((item) => {
-            this.projectStore.updateElement(item.key, 'container.props.x', minX)
-          })
-          break
-        }
-        case AlignEnum.Right: {
-          const maxX = Math.max(
-            ...activeElements.map((item) => item.container.props.x + item.container.props.width)
-          )
-          activeElements.forEach((item) => {
-            this.projectStore.updateElement(
-              item.key,
-              'container.props.x',
-              maxX - item.container.props.width
-            )
-          })
-          break
-        }
-        case AlignEnum.Top: {
-          const minY = Math.min(...activeElements.map((item) => item.container.props.y))
-          activeElements.forEach((item) => {
-            this.projectStore.updateElement(item.key, 'container.props.y', minY)
-          })
-          break
-        }
-        default:
-      }
-      this.addRecord()
-    },
-    /* 图层调整 */
-    actionLayer(type: LayerEnum) {
-      const activeElements = this.projectStore.currentSelectedElements
-      const elements = cloneDeep(
-        this.projectStore.elements.sort((a, b) => a.zIndex - b.zIndex)
-      ) as CustomElement[]
-
-      switch (type) {
-        case LayerEnum.UP: {
-          activeElements.forEach((item) => {
-            const index = elements.findIndex((element) => element.key === item.key)
-            if (item.zIndex === elements.length) return
-            elements.splice(index, 1)
-            elements.splice(index + 1, 0, { ...item })
-          })
-          elements.forEach((item, index) => {
-            item.zIndex = index + 1
-          })
-          elements.forEach((item) => {
-            this.projectStore.updateElement(item.key, 'zIndex', item.zIndex)
-          })
-          break
-        }
-        case LayerEnum.DOWN: {
-          activeElements.forEach((item) => {
-            const index = elements.findIndex((element) => element.key === item.key)
-            if (item.zIndex === 1) return
-            elements.splice(index, 1)
-            elements.splice(index - 1, 0, { ...item })
-          })
-          elements.forEach((item, index) => {
-            item.zIndex = index + 1
-          })
-          elements.forEach((item) => {
-            this.projectStore.updateElement(item.key, 'zIndex', item.zIndex)
-          })
-          break
-        }
-        case LayerEnum.TOP: {
-          activeElements.forEach((item) => {
-            const index = elements.findIndex((element) => element.key === item.key)
-            if (item.zIndex === elements.length) return
-            elements.splice(index, 1)
-            elements.push({ ...item })
-          })
-          elements.forEach((item, index) => {
-            item.zIndex = index + 1
-          })
-          elements.forEach((item) => {
-            this.projectStore.updateElement(item.key, 'zIndex', item.zIndex)
-          })
-          break
-        }
-        case LayerEnum.BOTTOM: {
-          activeElements.forEach((item) => {
-            const index = elements.findIndex((element) => element.key === item.key)
-            if (item.zIndex === 1) return
-            elements.splice(index, 1)
-            elements.unshift({ ...item })
-          })
-          elements.forEach((item, index) => {
-            item.zIndex = index + 1
-          })
-          elements.forEach((item) => {
-            this.projectStore.updateElement(item.key, 'zIndex', item.zIndex)
-          })
-          break
-        }
-      }
-      this.addRecord()
-    },
-    /* 添加组合 */
-    actionGroup() {
-      const elements = this.projectStore.currentSelectedElements
-      const key = uuid()
-      // 1、移除元素
-      elements.forEach((element) => {
-        this.projectStore.removeElement(element.key)
-      })
-      const { minX, minY, maxX, maxY, maxZIndex } = calcComboInfo(elements)
-      const groupIndex =
-        this.projectStore.elements.filter((item) => item.componentType === 'group').length + 1
-      // 重新计算子元素位置
-      elements.forEach((item) => {
-        item.container.props.x -= minX
-        item.container.props.y -= minY
-        item.parentKey = key
-      })
-      const group: CustomElement = {
-        key,
-        name: '组合' + groupIndex,
-        componentType: 'group',
-        visible: true,
-        locked: false,
-        zIndex: maxZIndex,
-        container: {
-          style: {},
-          props: {
-            width: maxX - minX,
-            height: maxY - minY,
-            x: minX,
-            y: minY
-          }
-        },
-        children: elements,
-        collapsed: false,
-        events: [],
-        animations: [],
-        props: {}
-      }
-      // 2、添加组合元素
-      this.projectStore.addElement(group)
-    },
-    /* 拆分组合元素 */
-    actionUngroup() {
-      const group = this.projectStore.currentSelectedElements[0]
-      // 1、取出子元素
-      const elements = group.children?.map((item) => {
-        // 2、计算子元素位置
-        item.container.props.x += group.container.props.x
-        item.container.props.y += group.container.props.y
-        delete item.parentKey
-        return item
-      })
-
-      // 3、移除组
-      this.projectStore.removeElement(group.key)
-      // 4、添加子元素
-      elements?.forEach((item) => {
-        this.projectStore.addElement(item, undefined, true)
-      })
-    },
-    /* 复制 */
-    actionCopy() {
-      const elements = this.projectStore.currentSelectedElements
-      this.copyCache = JSON.stringify(elements)
-    },
-    /* 粘贴 */
-    actionPaste() {
-      if (!this.copyCache) return
-
-      try {
-        const elements = JSON.parse(this.copyCache)
-        const offsetX = 10
-        const offsetY = 10
-        elements.forEach((element: CustomElement) => {
-          element.key = uuid()
-          element.container.props.x += offsetX
-          element.container.props.y += offsetY
-          this.projectStore.addElement(element)
-        })
-      } catch (error) {
-        console.log(error)
-      }
-    }
-  }
-})

+ 1 - 1
src/renderer/src/store/modules/app.ts

@@ -13,7 +13,7 @@ export const useAppStore = defineStore('app', {
       // 语言
       lang: localStorage.getItem('lang') || 'zh_CN',
       // 主题
-      theme: localStorage.getItem('theme') || 'light',
+      theme: localStorage.getItem('theme') || 'dark',
       // 编辑器
       editor: {
         fontSize: localStorage.getItem('editorFontSize') || 16

+ 1 - 8
src/renderer/src/store/modules/project.ts

@@ -2,13 +2,6 @@ import { defineStore } from 'pinia'
 
 export const useProjectStore = defineStore('project', {
   state: () => ({
-    project: {
-      id: 1,
-      name: 'Project 1',
-      description: 'This is a project description',
-      status: 'in progress',
-      dueDate: '2021-09-01',
-      assignedTo: 'John Doe'
-    }
+    project: {}
   })
 })

+ 23 - 0
src/renderer/src/types/animation.d.ts

@@ -0,0 +1,23 @@
+export type AnimationTimeline = {
+  // 目标属性
+  target: string
+  // 开始值
+  start: number
+  // 结束值
+  end: number
+  // 延迟时间
+  delay: number
+  // 动画时间
+  duration: number
+  // 动画效果
+  timingFunction: string
+  // 动画次数
+  iterationCount: number | 'infinite' // 数值或infinite
+}
+
+export type Animation = {
+  // 动画名称
+  name: string
+  // 动画时间线
+  timeline: AnimationTimeline[]
+}

+ 61 - 0
src/renderer/src/types/appMeta.d.ts

@@ -0,0 +1,61 @@
+export type AppType = 'analog_display' | 'chip' | 'board'
+export type ScreenType = 'single' | 'double'
+export type ResourcePackaging = 'c' | 'c_bin'
+
+export type ChipConfig = {
+  // 芯片型号
+  model: string
+  // 接口类型
+  interface: string
+}
+
+export type BoardConfig = {
+  // 板卡型号
+  model: string
+}
+
+export type ScreenConfig = {
+  // 屏幕类型
+  type: 1 | 2 // 1:主屏 2:副屏
+  // 屏幕宽
+  width: number
+  // 屏幕高
+  height: number
+  // 颜色深度
+  colorDepth: string
+  // 颜色格式
+  colorFormat: 'BGR' | 'RGB'
+  // 屏幕参数
+  params: {
+    PCLK: string
+    VBP: string
+    VFP: string
+    HBP: string
+    HFP: string
+    HSYNC: string
+    VSYNC: string
+    HSYNC_WIDTH: string
+    HSYNC_WIDTH: string
+  }
+}
+
+export interface AppMeta {
+  name: string // 项目名称
+  version: string // 项目版本号
+  description: string // 项目描述
+  type: AppType // 项目类型 1: 模拟显示 2: 芯片 3: 板卡
+  screenType: string //  屏幕类型 1:单屏 2:双屏
+  language: string // 语言
+  resourcePackaging: ResourcePackaging // 资源打包方式1: C源码 2: C源码+BIN
+  binNum: number // // BIN数量 1~32
+  // 芯片配置
+  chip: ChipConfig
+  // 板卡配置
+  board: BoardConfig
+  // 屏幕配置
+  screens: ScreenConfig[]
+  // 创建时间
+  createTime: string
+  // 修改时间
+  modifyTime: string
+}

+ 0 - 0
src/renderer/src/types/baseWidget.d.ts


+ 8 - 0
src/renderer/src/types/bins.d.ts

@@ -0,0 +1,8 @@
+export type Bin = {
+  // ID
+  id: string
+  // 名称
+  name: string
+  // 地址
+  path: string
+}

+ 17 - 0
src/renderer/src/types/language.d.ts

@@ -0,0 +1,17 @@
+export type LanguageItem = {
+  // 语言
+  language: string
+  // 内容
+  value: string
+  // 补充字体
+  font: string
+}
+
+export type Language = {
+  // ID
+  id: string
+  // 语言表示
+  key: string
+  // 语言内容
+  values: LanguageItem[]
+}

+ 8 - 0
src/renderer/src/types/method.d.ts

@@ -0,0 +1,8 @@
+export type Method = {
+  // ID
+  id: string
+  // 名称
+  name: string
+  // 内容
+  action: string
+}

+ 54 - 0
src/renderer/src/types/page.d.ts

@@ -0,0 +1,54 @@
+import { Variable } from './variables'
+
+export type ReferenceLine = {
+  // ID
+  id: string
+  // 垂直 水平
+  layout: 'horizontal' | 'vertical'
+  // 位置
+  position: number
+  // 显示
+  visible: boolean
+}
+
+export type Page = {
+  // 页面ID
+  id: string
+  // 页面名称
+  name: string
+  // 类型
+  type: 'page'
+  // 隐藏
+  hidden: boolean
+  // 锁定
+  locked: boolean
+  // 参考线
+  referenceLine: ReferenceLine[]
+  // 属性
+  props: Record<string, any>
+  // 样式
+  style: Record<string, any>
+  // 事件
+  events: {
+    // 事件ID
+    id: string
+    // 事件名称
+    name: string
+    // 触发器
+    trigger: string
+    // 执行类型
+    type: 'play_animation' | 'function'
+    // 动画ID
+    animation: string
+    // 动画播放前函数ID
+    animationPlayerBeforeEvent: string
+    // 动画播放后函数ID
+    animationPlayerAfterEvent: string
+    // 函数ID
+    function: string
+  }[]
+  // 页面变量
+  variables: Variable[]
+  // 子组件
+  children: any[]
+}

+ 52 - 0
src/renderer/src/types/resource.d.ts

@@ -0,0 +1,52 @@
+export type CompressFormat = 'none' | 'rle' | 'qoi' | 'jpeg' | 'png' // 无压缩、RLE压缩、QOI压缩、JPEG压缩、PNG压缩
+export type ImageResource = {
+  // ID
+  id: string
+  // 文件名
+  fileName: string
+  // 文件类型
+  fielType: string
+  // 文件路径
+  path: string
+  // 压缩格式
+  compressFormat: CompressFormat
+  // 透明度 0-1
+  alpha: number
+  // 绑定bin
+  bin: number
+}
+
+export type TextRange = 'all' | 'page' | 'custom' | 'range'
+export type FontResource = {
+  // ID
+  id: string
+  // 文件名
+  fileName: string
+  // 文件类型
+  fielType: string
+  // 文件路径
+  path: string
+  // 字体范围类型
+  range: TextRange[] // 0: 全部 1:界面文本 2:自定义文本 3: 编码范围
+  // 编码范围
+  codeRange: string[]
+  // 额外的文本内容
+  extText: string
+  // 绑定bin
+  bin: number
+}
+
+export type OtherResource = {
+  // ID
+  id: string
+  // 文件名
+  fileName: string
+  // 文件类型
+  fielType: string
+  // 文件路径
+  path: string
+  // 绑定bin
+  bin: number
+}
+
+export type Resource = ImageResource | FontResource | OtherResource

+ 18 - 0
src/renderer/src/types/screen.d.ts

@@ -0,0 +1,18 @@
+export type Screen = {
+  // ID
+  id: string
+  // 名称
+  name: string
+  // 类型
+  type: 'screen'
+  // 屏幕宽 未设置取通用配置
+  width: number
+  // 屏幕高 未设置取通用配置
+  height: number
+  // 隐藏
+  hidden: boolean
+  // 锁定
+  locked: boolean
+  // 页面
+  pages: []
+}

+ 20 - 0
src/renderer/src/types/theme.d.ts

@@ -0,0 +1,20 @@
+export type ThemeColor = {
+  // ID
+  id: string
+  // 颜色名称
+  name: string
+  // 颜色值
+  value: string
+  // 透明度
+  alpha: number
+}
+export type Theme = {
+  // ID
+  id: string
+  // 主题名称
+  name: string
+  // 默认主题
+  default: boolean
+  // 颜色集
+  colors: ThemeColor[]
+}

+ 19 - 0
src/renderer/src/types/variables.d.ts

@@ -0,0 +1,19 @@
+export type Variable = {
+  // ID
+  id: string
+  // 名称
+  name: string
+  // 值
+  value: any
+  // 数据类型
+  type: string
+}
+
+export type VariableGroup = {
+  // ID
+  id: string
+  // 名称
+  name: string
+  // 变量
+  variables: Variable[]
+}

+ 208 - 2
src/renderer/src/views/designer/sidebar/Hierarchy.vue

@@ -1,12 +1,218 @@
 <template>
   <SplitterCollapse>
-    <SplitterCollapseItem title="屏幕页面" name="page" :min="200"> </SplitterCollapseItem>
-    <SplitterCollapseItem title="图层" name="hierarchy" :min="200"> </SplitterCollapseItem>
+    <SplitterCollapseItem title="屏幕页面" name="page" :min="200">
+      <el-tree
+        ref="treeRef"
+        style="max-width: 600px"
+        :data="screenData"
+        default-expand-all
+        node-key="id"
+        highlight-current
+        :props="{ label: 'name', children: 'pages' }"
+        :render-content="renderScreen"
+      />
+    </SplitterCollapseItem>
+    <SplitterCollapseItem title="图层" name="hierarchy" :min="200">
+      <el-tree
+        ref="treeRef"
+        style="max-width: 600px"
+        :data="widgetData"
+        default-expand-all
+        node-key="id"
+        highlight-current
+        :props="{ label: 'name' }"
+        :render-content="renderPage"
+      />
+    </SplitterCollapseItem>
   </SplitterCollapse>
 </template>
 
 <script setup lang="ts">
 import { SplitterCollapse, SplitterCollapseItem } from '@/components/SplitterCollapse'
+import {
+  LuTrash2,
+  LuLock,
+  LuUnlock,
+  LuEye,
+  LuEyeOff,
+  LuMonitor,
+  LuPanelsTopLeft,
+  LuBox
+} from 'vue-icons-plus/lu'
+import type { RenderContentFunction } from 'element-plus'
+// 屏幕页面数据
+const screenData = [
+  {
+    id: 'screen_1',
+    name: '主屏',
+    type: 'screen',
+    backgroundColor: '#ffffff',
+    width: 0,
+    height: 0,
+    hidden: false,
+    locked: false,
+    pages: [
+      {
+        id: 'page_1',
+        name: '启动页',
+        type: 'page',
+        hidden: false,
+        locked: false,
+        referenceLine: [
+          {
+            id: 'r_1',
+            layout: 'horizontal',
+            position: 0,
+            visible: true
+          }
+        ],
+        props: {},
+        style: {},
+        events: [],
+        variable: [],
+        method: [],
+        children: []
+      },
+      {
+        id: 'page_2',
+        name: '详情页',
+        type: 'page',
+        hidden: false,
+        locked: false,
+        referenceLine: [
+          {
+            id: 'r_1',
+            layout: 'horizontal',
+            position: 0,
+            visible: true
+          }
+        ],
+        props: {},
+        style: {},
+        events: [],
+        variable: [],
+        method: [],
+        children: []
+      }
+    ]
+  }
+]
+
+// 图层数据
+const widgetData = [
+  {
+    id: 'widget_1',
+    name: '按钮',
+    type: 'widget',
+    hidden: false,
+    locked: false,
+    props: {},
+    style: {},
+    events: [],
+    variable: [],
+    method: []
+  }
+]
+
+const renderScreen: RenderContentFunction = (h, { node, data }) => {
+  return h(
+    'div',
+    {
+      style: {
+        width: '100%',
+        display: 'flex',
+        'justify-content': 'space-between',
+        'align-items': 'center'
+      }
+    },
+    [
+      h(
+        'div',
+        {
+          style: {
+            display: 'flex',
+            'align-items': 'center',
+            gap: '8px',
+            paddingRight: '8px'
+          }
+        },
+        [
+          h(data.type === 'screen' ? LuMonitor : LuPanelsTopLeft, {
+            style: { color: 'var(--text-secondary)' },
+            size: 14
+          }),
+          h('span', null, node.label)
+        ]
+      ),
+      h(
+        'div',
+        {
+          style: {
+            display: 'flex',
+            'align-items': 'center',
+            gap: '8px',
+            paddingRight: '8px'
+          }
+        },
+        [
+          h(LuTrash2, { style: { color: 'var(--text-secondary)' }, size: 14 }),
+          h(LuEye, { style: { color: 'var(--text-secondary)' }, size: 14 }),
+          h(LuLock, { style: { color: 'var(--text-secondary)' }, size: 14 })
+        ]
+      )
+    ]
+  )
+}
+
+const renderPage: RenderContentFunction = (h, { node, data }) => {
+  return h(
+    'div',
+    {
+      style: {
+        width: '100%',
+        display: 'flex',
+        'justify-content': 'space-between',
+        'align-items': 'center'
+      }
+    },
+    [
+      h(
+        'div',
+        {
+          style: {
+            display: 'flex',
+            'align-items': 'center',
+            gap: '8px',
+            paddingRight: '8px'
+          }
+        },
+        [
+          h(data.type === 'page' ? LuPanelsTopLeft : LuBox, {
+            style: { color: 'var(--text-secondary)' },
+            size: 14
+          }),
+          h('span', null, node.label)
+        ]
+      ),
+      h(
+        'div',
+        {
+          style: {
+            display: 'flex',
+            'align-items': 'center',
+            gap: '8px',
+            paddingRight: '8px'
+          }
+        },
+        [
+          h(LuTrash2, { style: { color: 'var(--text-secondary)' }, size: 14 }),
+          h(LuEye, { style: { color: 'var(--text-secondary)' }, size: 14 }),
+          h(LuLock, { style: { color: 'var(--text-secondary)' }, size: 14 })
+        ]
+      )
+    ]
+  )
+}
 </script>
 
 <style scoped></style>

+ 15 - 0
src/renderer/src/views/designer/sidebar/Libary.vue

@@ -0,0 +1,15 @@
+<template>
+  <SplitterCollapse>
+    <SplitterCollapseItem title="基础" name="base" :min="200"> </SplitterCollapseItem>
+    <SplitterCollapseItem title="表单" name="form" :min="200"> </SplitterCollapseItem>
+    <SplitterCollapseItem title="容器" name="coniner" :min="200"> </SplitterCollapseItem>
+    <SplitterCollapseItem title="图文" name="image_text" :min="200"> </SplitterCollapseItem>
+    <SplitterCollapseItem title="媒体" name="media" :min="200"> </SplitterCollapseItem>
+  </SplitterCollapse>
+</template>
+
+<script setup lang="ts">
+import { SplitterCollapse, SplitterCollapseItem } from '@/components/SplitterCollapse'
+</script>
+
+<style scoped></style>

+ 9 - 0
src/renderer/src/views/designer/sidebar/Schema.vue

@@ -0,0 +1,9 @@
+<template>
+  <MonacoEditor />
+</template>
+
+<script setup lang="ts">
+import MonacoEditor from '@/components/MonacoEditor/index.vue'
+</script>
+
+<style scoped></style>

+ 17 - 17
src/renderer/src/views/designer/sidebar/index.vue

@@ -29,13 +29,12 @@
       </div>
     </div>
     <!-- 目录大纲 -->
-    <div class="flex-1" v-show="activeMenu === 'file'"></div>
+    <div class="flex-1" v-show="activeMenu === 'file'">
+      <Hierarchy />
+    </div>
     <!-- 控件库 -->
     <div class="flex-1" v-show="activeMenu === 'widget'">
-      <SplitterCollapse>
-        <SplitterCollapseItem title="屏幕页面" name="page" :min="200"> </SplitterCollapseItem>
-        <SplitterCollapseItem title="页面大纲" name="hierarchy"> </SplitterCollapseItem>
-      </SplitterCollapse>
+      <Libary />
     </div>
     <!-- 资源管理 -->
     <div class="flex-1" v-show="activeMenu === 'resource'">
@@ -53,42 +52,43 @@
       </SplitterCollapse>
     </div>
     <!-- json预览 -->
-    <div class="flex-1" v-show="activeMenu === 'json'">
-      <SplitterCollapse>
-        <SplitterCollapseItem title="屏幕页面" name="page" :min="200"> </SplitterCollapseItem>
-        <SplitterCollapseItem title="页面大纲" name="hierarchy"> </SplitterCollapseItem>
-      </SplitterCollapse>
+    <div class="flex-1 overflow-auto" v-show="activeMenu === 'json'">
+      <Schema />
     </div>
   </div>
 </template>
 
 <script setup lang="ts">
 import { ref, h } from 'vue'
-import { SplitterCollapse, SplitterCollapseItem } from '@/components/SplitterCollapse'
-import { LuFiles, LuBoxes, LuFileStack, LuSquareCode, LuSettings2 } from 'vue-icons-plus/lu'
 import { useI18n } from 'vue-i18n'
 
+import { SplitterCollapse, SplitterCollapseItem } from '@/components/SplitterCollapse'
+import { LuFiles, LuBoxes, LuSquareCode, LuSettings2, LuInbox } from 'vue-icons-plus/lu'
+import Hierarchy from './Hierarchy.vue'
+import Libary from './Libary.vue'
+import Schema from './Schema.vue'
+
 const { t } = useI18n()
 
 const sidebarMenu = [
   {
     key: 'file',
-    title: LuFiles,
+    title: h('span', null, h(LuFiles)),
     tooltip: t('directory')
   },
   {
     key: 'widget',
-    title: LuBoxes,
+    title: h('span', null, h(LuBoxes)),
     tooltip: t('widgetLibrary')
   },
   {
     key: 'resource',
-    title: LuFileStack,
+    title: h('span', null, h(LuInbox)),
     tooltip: t('resourceManager')
   },
   {
     key: 'code',
-    title: LuSquareCode,
+    title: h('span', null, h(LuSquareCode)),
     tooltip: t('codeView')
   },
   {
@@ -110,7 +110,7 @@ const activeMenu = ref('file')
 }
 .sidebar-menu-item {
   width: 100%;
-  height: 40px;
+  height: 48px;
   display: flex;
   align-items: center;
   justify-content: center;