Browse Source

feat: 调整项目样式等

jiaxing.liao 2 weeks ago
parent
commit
e8d1c2e544

+ 5 - 0
src/renderer/index.html

@@ -9,6 +9,11 @@
       content="default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:"
     />
     <style>
+      body {
+        width: 100vw;
+        height: 100vh;
+        overflow: hidden;
+      }
       #app {
         width: 100vw;
         height: 100vh;

+ 1 - 1
src/renderer/src/constants/index.ts

@@ -64,7 +64,7 @@ export const stateOptions = [
  */
 
 export const variableType = [
-  { label: 'String', value: 'LV_STRING' },
+  { label: 'STRING', value: 'LV_STRING' },
   { label: 'INT', value: 'LV_INT' },
   { label: 'FLOAT', value: 'LV_FLOAT' },
   { label: 'DOUBLE', value: 'LV_DOUBLE' },

+ 17 - 12
src/renderer/src/lvgl-widgets/button/Button.vue

@@ -38,20 +38,25 @@ const getProps = computed(() => {
       alignItems: 'center',
 
       borderRadius: `${stateStyles?.border.radius}px`,
+      borderStyle: 'solid',
       borderColor: 'transparent',
       borderWidth: `${stateStyles?.border.width}px`,
-      borderTopColor: ['all', 'top'].includes(stateStyles?.border?.side)
-        ? stateStyles?.border?.color
-        : 'transparent',
-      borderRightColor: ['all', 'right'].includes(stateStyles?.border?.side)
-        ? stateStyles?.border?.color
-        : 'transparent',
-      borderBottomColor: ['all', 'bottom'].includes(stateStyles?.border?.side)
-        ? stateStyles?.border?.color
-        : 'transparent',
-      borderLeftColor: ['all', 'left'].includes(stateStyles?.border?.side)
-        ? stateStyles?.border?.color
-        : 'transparent',
+      borderTopColor:
+        stateStyles?.border?.side?.includes('all') || stateStyles?.border?.side?.includes('top')
+          ? stateStyles?.border?.color
+          : 'transparent',
+      borderRightColor:
+        stateStyles?.border?.side?.includes('all') || stateStyles?.border?.side?.includes('right')
+          ? stateStyles?.border?.color
+          : 'transparent',
+      borderBottomColor:
+        stateStyles?.border?.side?.includes('all') || stateStyles?.border?.side?.includes('bottom')
+          ? stateStyles?.border?.color
+          : 'transparent',
+      borderLeftColor:
+        stateStyles?.border?.side?.includes('all') || stateStyles?.border?.side?.includes('left')
+          ? stateStyles?.border?.color
+          : 'transparent',
       /* x 偏移量 | y 偏移量 | 阴影模糊半径 | 阴影扩散半径 | 阴影颜色 */
       boxShodow: stateStyles?.shadow
         ? `${stateStyles?.shadow?.offsetX}px ${stateStyles?.shadow?.offsetY}px ${stateStyles?.shadow?.width}px ${stateStyles?.shadow?.spread}px ${stateStyles?.shadow?.color}`

+ 25 - 16
src/renderer/src/lvgl-widgets/container/Container.vue

@@ -3,7 +3,7 @@
 </template>
 
 <script setup lang="ts">
-import { computed } from 'vue'
+import { computed, CSSProperties } from 'vue'
 import defaultStyle from './style.json'
 
 const props = defineProps<{
@@ -13,10 +13,10 @@ const props = defineProps<{
   state: string
 }>()
 
-const getStyle = computed(() => {
+const getStyle = computed((): CSSProperties => {
   const { width, height, styles } = props
 
-  let stateStyles = styles.find((item) => item.state === props.state)
+  let stateStyles = styles.find((item) => item.part.state === props.state)
 
   // 从默认样式获取样式
   if (!stateStyles && props.state) {
@@ -27,23 +27,32 @@ const getStyle = computed(() => {
     width: `${width}px`,
     height: `${height}px`,
 
+    backgroundColor: stateStyles?.background.color,
+
+    boxSizing: 'border-box',
     borderRadius: `${stateStyles?.border.radius}px`,
+    borderStyle: 'solid',
     borderColor: 'transparent',
     borderWidth: `${stateStyles?.border.width}px`,
-    borderTopColor: ['all', 'top'].includes(stateStyles?.border?.side)
-      ? stateStyles?.border?.color
-      : 'transparent',
-    borderRightColor: ['all', 'right'].includes(stateStyles?.border?.side)
-      ? stateStyles?.border?.color
-      : 'transparent',
-    borderBottomColor: ['all', 'bottom'].includes(stateStyles?.border?.side)
-      ? stateStyles?.border?.color
-      : 'transparent',
-    borderLeftColor: ['all', 'left'].includes(stateStyles?.border?.side)
-      ? stateStyles?.border?.color
-      : 'transparent',
+    borderTopColor:
+      stateStyles?.border?.side?.includes('all') || stateStyles?.border?.side?.includes('top')
+        ? stateStyles?.border?.color
+        : 'transparent',
+    borderRightColor:
+      stateStyles?.border?.side?.includes('all') || stateStyles?.border?.side?.includes('right')
+        ? stateStyles?.border?.color
+        : 'transparent',
+    borderBottomColor:
+      stateStyles?.border?.side?.includes('all') || stateStyles?.border?.side?.includes('bottom')
+        ? stateStyles?.border?.color
+        : 'transparent',
+    borderLeftColor:
+      stateStyles?.border?.side?.includes('all') || stateStyles?.border?.side?.includes('left')
+        ? stateStyles?.border?.color
+        : 'transparent',
+    padding: `${stateStyles?.padding.top}px ${stateStyles?.padding.right}px ${stateStyles?.padding.bottom}px ${stateStyles?.padding.left}px`,
     /* x 偏移量 | y 偏移量 | 阴影模糊半径 | 阴影扩散半径 | 阴影颜色 */
-    boxShodow: stateStyles?.shadow
+    boxShadow: stateStyles?.shadow
       ? `${stateStyles?.shadow?.offsetX}px ${stateStyles?.shadow?.offsetY}px ${stateStyles?.shadow?.width}px ${stateStyles?.shadow?.spread}px ${stateStyles?.shadow?.color}`
       : 'none'
   }

+ 2 - 1
src/renderer/src/lvgl-widgets/container/index.ts

@@ -27,7 +27,8 @@ export default {
       height: 200,
       addFlags: [],
       removeFlags: [],
-      scrollbar: 'on'
+      scrollbar: 'on',
+      state: 'default'
     },
     styles: [
       {

+ 6 - 0
src/renderer/src/lvgl-widgets/container/style.json

@@ -21,6 +21,12 @@
               "radius": 0,
               "side": ["all"]
             },
+            "padding": {
+              "top": 0,
+              "right": 0,
+              "bottom": 0,
+              "left": 0
+            },
             "shadow": {
               "color": "#2092f5ff",
               "x": 0,

+ 17 - 12
src/renderer/src/lvgl-widgets/label/Label.vue

@@ -31,20 +31,25 @@ const getProps = computed(() => {
       alignItems: 'center',
 
       borderRadius: `${stateStyles?.border.radius}px`,
+      borderStyle: 'solid',
       borderColor: 'transparent',
       borderWidth: `${stateStyles?.border.width}px`,
-      borderTopColor: ['all', 'top'].includes(stateStyles?.border?.side)
-        ? stateStyles?.border?.color
-        : 'transparent',
-      borderRightColor: ['all', 'right'].includes(stateStyles?.border?.side)
-        ? stateStyles?.border?.color
-        : 'transparent',
-      borderBottomColor: ['all', 'bottom'].includes(stateStyles?.border?.side)
-        ? stateStyles?.border?.color
-        : 'transparent',
-      borderLeftColor: ['all', 'left'].includes(stateStyles?.border?.side)
-        ? stateStyles?.border?.color
-        : 'transparent',
+      borderTopColor:
+        stateStyles?.border?.side?.includes('all') || stateStyles?.border?.side?.includes('top')
+          ? stateStyles?.border?.color
+          : 'transparent',
+      borderRightColor:
+        stateStyles?.border?.side?.includes('all') || stateStyles?.border?.side?.includes('right')
+          ? stateStyles?.border?.color
+          : 'transparent',
+      borderBottomColor:
+        stateStyles?.border?.side?.includes('all') || stateStyles?.border?.side?.includes('bottom')
+          ? stateStyles?.border?.color
+          : 'transparent',
+      borderLeftColor:
+        stateStyles?.border?.side?.includes('all') || stateStyles?.border?.side?.includes('left')
+          ? stateStyles?.border?.color
+          : 'transparent',
       /* x 偏移量 | y 偏移量 | 阴影模糊半径 | 阴影扩散半径 | 阴影颜色 */
       boxShodow: stateStyles?.shadow
         ? `${stateStyles?.shadow?.offsetX}px ${stateStyles?.shadow?.offsetY}px ${stateStyles?.shadow?.width}px ${stateStyles?.shadow?.spread}px ${stateStyles?.shadow?.color}`

+ 17 - 12
src/renderer/src/lvgl-widgets/span-group/SpanGroup.vue

@@ -42,20 +42,25 @@ const getProps = computed(() => {
 
       // 边框
       borderRadius: `${stateStyles?.border.radius}px`,
+      borderStyle: 'solid',
       borderColor: 'transparent',
       borderWidth: `${stateStyles?.border.width}px`,
-      borderTopColor: ['all', 'top'].includes(stateStyles?.border?.side)
-        ? stateStyles?.border?.color
-        : 'transparent',
-      borderRightColor: ['all', 'right'].includes(stateStyles?.border?.side)
-        ? stateStyles?.border?.color
-        : 'transparent',
-      borderBottomColor: ['all', 'bottom'].includes(stateStyles?.border?.side)
-        ? stateStyles?.border?.color
-        : 'transparent',
-      borderLeftColor: ['all', 'left'].includes(stateStyles?.border?.side)
-        ? stateStyles?.border?.color
-        : 'transparent',
+      borderTopColor:
+        stateStyles?.border?.side?.includes('all') || stateStyles?.border?.side?.includes('top')
+          ? stateStyles?.border?.color
+          : 'transparent',
+      borderRightColor:
+        stateStyles?.border?.side?.includes('all') || stateStyles?.border?.side?.includes('right')
+          ? stateStyles?.border?.color
+          : 'transparent',
+      borderBottomColor:
+        stateStyles?.border?.side?.includes('all') || stateStyles?.border?.side?.includes('bottom')
+          ? stateStyles?.border?.color
+          : 'transparent',
+      borderLeftColor:
+        stateStyles?.border?.side?.includes('all') || stateStyles?.border?.side?.includes('left')
+          ? stateStyles?.border?.color
+          : 'transparent',
       // 阴影
       /* x 偏移量 | y 偏移量 | 阴影模糊半径 | 阴影扩散半径 | 阴影颜色 */
       boxShodow: stateStyles?.shadow

+ 17 - 12
src/renderer/src/lvgl-widgets/textarea/Textarea.vue

@@ -36,20 +36,25 @@ const getProps = computed(() => {
       alignItems: 'center',
 
       borderRadius: `${stateStyles?.border.radius}px`,
+      borderStyle: 'solid',
       borderColor: 'transparent',
       borderWidth: `${stateStyles?.border.width}px`,
-      borderTopColor: ['all', 'top'].includes(stateStyles?.border?.side)
-        ? stateStyles?.border?.color
-        : 'transparent',
-      borderRightColor: ['all', 'right'].includes(stateStyles?.border?.side)
-        ? stateStyles?.border?.color
-        : 'transparent',
-      borderBottomColor: ['all', 'bottom'].includes(stateStyles?.border?.side)
-        ? stateStyles?.border?.color
-        : 'transparent',
-      borderLeftColor: ['all', 'left'].includes(stateStyles?.border?.side)
-        ? stateStyles?.border?.color
-        : 'transparent',
+      borderTopColor:
+        stateStyles?.border?.side?.includes('all') || stateStyles?.border?.side?.includes('top')
+          ? stateStyles?.border?.color
+          : 'transparent',
+      borderRightColor:
+        stateStyles?.border?.side?.includes('all') || stateStyles?.border?.side?.includes('right')
+          ? stateStyles?.border?.color
+          : 'transparent',
+      borderBottomColor:
+        stateStyles?.border?.side?.includes('all') || stateStyles?.border?.side?.includes('bottom')
+          ? stateStyles?.border?.color
+          : 'transparent',
+      borderLeftColor:
+        stateStyles?.border?.side?.includes('all') || stateStyles?.border?.side?.includes('left')
+          ? stateStyles?.border?.color
+          : 'transparent',
       /* x 偏移量 | y 偏移量 | 阴影模糊半径 | 阴影扩散半径 | 阴影颜色 */
       boxShodow: stateStyles?.shadow
         ? `${stateStyles?.shadow?.offsetX}px ${stateStyles?.shadow?.offsetY}px ${stateStyles?.shadow?.width}px ${stateStyles?.shadow?.spread}px ${stateStyles?.shadow?.color}`

+ 2 - 2
src/renderer/src/store/modules/project.ts

@@ -1,7 +1,7 @@
 import type { AppMeta } from '@/types/appMeta'
 import type { Bin } from '@/types/bins'
 import type { ImageResource, OtherResource, FontResource } from '@/types/resource'
-import type { Variable } from '@/types/variables'
+import type { VariableGroup } from '@/types/variables'
 import type { Theme } from '@/types/theme'
 import type { Animation } from '@/types/animation'
 import type { Language } from '@/types/language'
@@ -31,7 +31,7 @@ export interface IProject {
     others: OtherResource[]
   }
   widgets: BaseWidget[]
-  variables: Variable[]
+  variables: VariableGroup[]
   themes: Theme[]
   animations: Animation[]
   languages: Language[]

+ 11 - 11
src/renderer/src/types/appMeta.d.ts

@@ -37,15 +37,15 @@ export type ScreenConfig = {
   colorFormat: string | 'BGR' | 'RGB'
   // 屏幕参数
   params: {
-    PCLK: string
-    VBP: string
-    VFP: string
-    HBP: string
-    HFP: string
-    HSYNC: string
-    VSYNC: string
-    HsyncWidth: string
-    VsyncWidth: string
+    PCLK?: number
+    VBP?: number
+    VFP?: number
+    HBP?: number
+    HFP?: number
+    HSYNC?: number
+    VSYNC?: number
+    HsyncWidth?: number
+    VsyncWidth?: number
   }
 }
 
@@ -71,7 +71,7 @@ export interface AppMeta {
   // 支持视频格式
   videoFormats: string[]
   // 创建时间
-  createTime: string
+  createTime?: string
   // 修改时间
-  modifyTime: string
+  modifyTime?: string
 }

+ 2 - 2
src/renderer/src/types/page.d.ts

@@ -1,5 +1,5 @@
 import { BaseWidget } from './baseWidget'
-import { Variable } from './variables'
+import { VariableGroup } from './variables'
 
 export type ReferenceLine = {
   // ID
@@ -51,7 +51,7 @@ export type Page = {
     function: string
   }[]
   // 页面变量
-  variables: Variable[]
+  variables: VariableGroup[]
   // 子组件
   children: BaseWidget[]
 }

+ 106 - 96
src/renderer/src/views/designer/config/AnimationConfig.vue

@@ -1,12 +1,13 @@
 <template>
-  <el-scrollbar :height="'calc(100vh - 198px)'" class="config pr-10px pl-10px">
+  <el-scrollbar :height="'calc(100vh - 198px)'" class="config">
     <el-form label-position="top">
-      <div class="title flex justify-between items-center h-28px mb-10px">
-        <span class="text-14px">设置动画</span>
-        <el-icon class="cursor-pointer" @click.stop="addAnimation">
-          <Plus />
-        </el-icon>
-      </div>
+      <ViewTitle title="设置动画">
+        <template #right>
+          <el-button type="text" @click="addAnimation"
+            ><el-icon class="cursor-pointer" @click.stop="addAnimation"> <Plus /> </el-icon
+          ></el-button>
+        </template>
+      </ViewTitle>
       <div v-for="item in data" :key="item.id">
         <el-collapse v-model="activeNames">
           <el-collapse-item :name="item.id">
@@ -23,98 +24,103 @@
                 </el-icon>
               </div>
             </template>
-            <el-input v-model="item.name" placeholder="动画名称" class="mt-10px" />
-            <div v-for="(animationItem, index) in item.timeline" :key="index" class="mb-20px">
-              <div class="mt-10px mb-10px">动画属性</div>
-              <div
-                class="flex flex-wrap gap-10px mb-10px pb-20px"
-                style="border-bottom: 1px solid var(--el-border-color-lighter)"
+            <div class="px-10px">
+              <el-input v-model="item.name" placeholder="动画名称" class="my-10px" />
+              <el-card
+                v-for="(animationItem, index) in item.timeline"
+                :key="index"
+                class="mb-10px relative"
               >
-                <el-input-number
-                  v-model="animationItem.start"
-                  placeholder="开始值"
-                  :step="0.1"
-                  :min="0"
-                  style="flex: 0 0 calc(50% - 5px)"
-                />
-                <el-input-number
-                  v-model="animationItem.end"
-                  placeholder="结束值"
-                  :step="0.1"
-                  :min="0"
-                  style="flex: 0 0 calc(50% - 5px)"
-                />
-                <el-input-number
-                  v-model="animationItem.duration"
-                  placeholder="动画时间"
-                  :step="0.1"
-                  :min="0"
-                  style="flex: 0 0 calc(50% - 5px)"
-                />
-                <el-input-number
-                  v-model="animationItem.delay"
-                  placeholder="延迟时间"
-                  :step="0.1"
-                  :min="0"
-                  style="flex: 0 0 calc(50% - 5px)"
-                />
-                <el-select
-                  v-model="animationItem.timingFunction"
-                  placeholder="动画效果"
-                  style="flex: 0 0 calc(50% - 5px)"
+                <el-icon
+                  class="absolute! right-12px bottom-10px cursor-pointer"
+                  @click.stop="handletTimingFunction(item.timeline, 'delete', animationItem)"
                 >
-                  <el-option
-                    v-for="timing in timingFunctions"
-                    :key="timing.value"
-                    :value="timing.value"
-                    :label="timing.label"
+                  <Delete />
+                </el-icon>
+                <div class="mt-10px mb-10px flex justify-between items-center">
+                  <span>动画属性</span>
+                  <el-select
+                    style="width: 100px"
+                    v-model="animationItem.target"
+                    placeholder="目标属性"
+                    size="small"
+                  >
+                    <el-option
+                      v-for="target in animationTargets"
+                      :key="target.value"
+                      :value="target.value"
+                      :label="target.label"
+                    />
+                  </el-select>
+                </div>
+                <div class="flex flex-wrap gap-10px mb-10px pb-20px">
+                  <el-input-number
+                    v-model="animationItem.start"
+                    controls-position="right"
+                    placeholder="开始值"
+                    :step="0.1"
+                    :min="0"
+                    style="flex: 0 0 calc(50% - 5px)"
                   />
-                </el-select>
-                <el-select
-                  v-model="animationItem.iterationCount"
-                  placeholder="循环次数"
-                  style="flex: 0 0 calc(50% - 5px)"
-                >
-                  <el-option
-                    v-for="iteration in iterationCounts"
-                    :key="iteration.value"
-                    :value="iteration.value"
-                    :label="iteration.label"
+                  <el-input-number
+                    v-model="animationItem.end"
+                    controls-position="right"
+                    placeholder="结束值"
+                    :step="0.1"
+                    :min="0"
+                    style="flex: 0 0 calc(50% - 5px)"
                   />
-                </el-select>
-              </div>
-              <div class="flex justify-between items-center gap-10px">
-                <el-select v-model="animationItem.target" placeholder="目标属性">
-                  <el-option
-                    v-for="target in animationTargets"
-                    :key="target.value"
-                    :value="target.value"
-                    :label="target.label"
+                  <el-input-number
+                    v-model="animationItem.duration"
+                    controls-position="right"
+                    placeholder="动画时间"
+                    :step="0.1"
+                    :min="0"
+                    style="flex: 0 0 calc(50% - 5px)"
                   />
-                </el-select>
-                <el-button
-                  v-if="index !== item.timeline.length - 1"
-                  type="info"
-                  style="flex: 0 0 calc(50% - 5px)"
-                  @click="handletTimingFunction(item.timeline, 'delete', animationItem)"
-                  >删除</el-button
-                >
+                  <el-input-number
+                    v-model="animationItem.delay"
+                    controls-position="right"
+                    placeholder="延迟时间"
+                    :step="0.1"
+                    :min="0"
+                    style="flex: 0 0 calc(50% - 5px)"
+                  />
+                  <el-select
+                    v-model="animationItem.timingFunction"
+                    placeholder="动画效果"
+                    style="flex: 0 0 calc(50% - 5px)"
+                  >
+                    <el-option
+                      v-for="timing in timingFunctions"
+                      :key="timing.value"
+                      :value="timing.value"
+                      :label="timing.label"
+                    />
+                  </el-select>
+                  <el-select
+                    v-model="animationItem.iterationCount"
+                    placeholder="循环次数"
+                    style="flex: 0 0 calc(50% - 5px)"
+                  >
+                    <el-option
+                      v-for="iteration in iterationCounts"
+                      :key="iteration.value"
+                      :value="iteration.value"
+                      :label="iteration.label"
+                    />
+                  </el-select>
+                </div>
+              </el-card>
+              <div class="flex justify-between items-center gap-10px">
                 <el-button
-                  v-if="index === item.timeline.length - 1"
                   type="primary"
-                  style="flex: 0 0 calc(50% - 5px)"
+                  style="width: 100%"
                   @click="handletTimingFunction(item.timeline, 'add', '')"
-                  >添加</el-button
+                  >添加属性</el-button
                 >
               </div>
             </div>
-            <el-button
-              v-if="!item.timeline.length"
-              type="primary"
-              style="width: 100%; margin-top: 10px"
-              @click="handletTimingFunction(item.timeline, 'add', '')"
-              >添加</el-button
-            >
           </el-collapse-item>
         </el-collapse>
       </div>
@@ -123,7 +129,7 @@
 </template>
 
 <script setup lang="ts">
-import { ref, watch, defineEmits } from 'vue'
+import { ref, watch } from 'vue'
 import type { Animation } from '@/types/animation'
 import { ArrowRight, Plus, Delete } from '@element-plus/icons-vue'
 import { ElMessageBox } from 'element-plus'
@@ -141,11 +147,13 @@ const data = ref<Animation[]>(props.animation || ({} as Animation[]))
 watch(data, (val) => emit('update:animation', val))
 
 const addAnimation = () => {
+  const id = v4()
   data.value.push({
-    id: v4(),
-    name: '',
+    id,
+    name: `animation_${data.value.length + 1}`,
     timeline: []
   })
+  activeNames.value.push(id)
 }
 
 const handleAnimationRemove = (animation) => {
@@ -165,12 +173,12 @@ const handletTimingFunction = (timeline, name, animationItem) => {
   if (name === 'add') {
     timeline.push({
       target: '',
-      start: 0,
-      end: 0,
-      delay: 0,
-      duration: 0,
+      start: undefined,
+      end: undefined,
+      delay: undefined,
+      duration: undefined,
       timingFunction: '',
-      iterationCount: 0
+      iterationCount: undefined
     })
   } else {
     const index = timeline.findIndex((item) => item.id === animationItem.id)
@@ -195,6 +203,8 @@ const handletTimingFunction = (timeline, name, animationItem) => {
   display: flex;
   align-items: center;
   width: 100%;
+  padding: 0 12px;
+  box-sizing: border-box;
 }
 
 .arrow {

+ 60 - 34
src/renderer/src/views/designer/config/LanguagesConfig.vue

@@ -1,12 +1,13 @@
 <template>
-  <el-scrollbar :height="'calc(100vh - 198px)'" class="config pr-10px pl-10px">
+  <el-scrollbar :height="'calc(100vh - 198px)'" class="config">
+    <ViewTitle title="设置语言">
+      <template #right>
+        <el-button type="text" @click="handleAdd"
+          ><el-icon class="cursor-pointer"> <Plus /> </el-icon
+        ></el-button>
+      </template>
+    </ViewTitle>
     <el-form label-position="top">
-      <div class="title flex justify-between items-center h-28px mb-10px">
-        <span class="text-14px">设置语言</span>
-        <el-icon class="cursor-pointer" @click="handleAdd">
-          <Plus />
-        </el-icon>
-      </div>
       <div v-for="item in data" :key="item.id">
         <el-collapse v-model="activeNames">
           <el-collapse-item :name="item.id">
@@ -23,31 +24,35 @@
                 </el-icon>
               </div>
             </template>
-            <el-input v-model="item.key" class="mt-10px mb-10px" placeholder="语言表示" />
-            <div v-for="(languagesItem, index) in item.values" :key="index" class="mb-10px">
-              <div class="flex flex-wrap gap-5px items-center">
-                <el-select v-model="languagesItem.language" style="flex: 1">
-                  <el-option
-                    v-for="menu in languagesMenu"
-                    :key="menu.value"
-                    :value="menu.value"
-                    :label="menu.label"
-                  />
-                </el-select>
-                <el-input v-model="languagesItem.value" style="flex: 1" />
-                <el-select v-model="languagesItem.font" style="flex: 1" placeholder="字体">
-                </el-select>
-                <el-icon
-                  class="cursor-pointer"
-                  @click.stop="handleRemoveItem(item.values, languagesItem)"
-                >
-                  <Delete />
-                </el-icon>
+            <el-card class="m-10px">
+              <el-input v-model="item.key" class="mt-10px mb-10px" placeholder="语言标识" />
+              <div v-for="(languagesItem, index) in item.values" :key="index" class="mb-10px">
+                <div class="flex flex-wrap gap-5px items-center">
+                  <el-select v-model="languagesItem.language" style="flex: 1">
+                    <el-option
+                      v-for="menu in languagesMenu"
+                      :key="menu.value"
+                      :value="menu.value"
+                      :label="menu.label"
+                    />
+                  </el-select>
+                  <el-input v-model="languagesItem.value" style="flex: 1" />
+                  <el-select v-model="languagesItem.font" style="flex: 1" placeholder="字体">
+                  </el-select>
+                  <el-icon
+                    class="cursor-pointer"
+                    @click.stop="handleRemoveItem(item.values, languagesItem)"
+                  >
+                    <Delete />
+                  </el-icon>
+                </div>
               </div>
-            </div>
-            <el-icon class="cursor-pointer" @click="handleAddlanguagesValues(item.values)">
-              <Plus />
-            </el-icon>
+              <el-tooltip content="添加语言">
+                <el-icon class="cursor-pointer" @click="handleAddlanguagesValues(item.values)">
+                  <Plus />
+                </el-icon>
+              </el-tooltip>
+            </el-card>
           </el-collapse-item>
         </el-collapse>
       </div>
@@ -56,12 +61,13 @@
 </template>
 
 <script setup lang="ts">
-import { ref, watch, defineEmits } from 'vue'
+import { ref, watch } from 'vue'
 import type { LanguagesGroup } from '@/types/languages'
 import { ArrowRight, Plus, Delete } from '@element-plus/icons-vue'
 import { ElMessageBox } from 'element-plus'
 import { languagesMenu } from '@/constants'
 import { v4 } from 'uuid'
+
 interface Emits {
   (e: 'update:languages', val: LanguagesGroup[]): void
 }
@@ -100,11 +106,26 @@ const handleRemoveGroup = (languages) =>
     }
   })
 const handleAdd = () => {
+  const first = data.value[0].values.map((item) => ({
+    ...item,
+    value: ''
+  }))
+
+  const id = v4()
   data.value.push({
-    id: v4(),
+    id,
     key: '',
-    values: []
+    values: first.length
+      ? first
+      : [
+          {
+            language: '',
+            value: '',
+            font: ''
+          }
+        ]
   })
+  activeNames.value.push(id)
 }
 </script>
 
@@ -122,6 +143,8 @@ const handleAdd = () => {
   display: flex;
   align-items: center;
   width: 100%;
+  box-sizing: border-box;
+  padding: 0 12px;
 }
 
 .arrow {
@@ -137,4 +160,7 @@ const handleAdd = () => {
   flex: 1;
   font-weight: 500;
 }
+::v-deep(.el-collapse-item__content) {
+  padding-bottom: 0;
+}
 </style>

+ 37 - 36
src/renderer/src/views/designer/config/VariableConfig.vue

@@ -1,22 +1,14 @@
 <template>
-  <el-scrollbar :height="'calc(100vh - 198px)'" class="config pr-10px pl-10px">
+  <el-scrollbar :height="'calc(100vh - 198px)'" class="config">
+    <ViewTitle title="设置变量">
+      <template #right>
+        <el-button type="text" @click="addVariableGroup"
+          ><el-icon class="cursor-pointer"> <Plus /> </el-icon
+        ></el-button>
+      </template>
+    </ViewTitle>
     <el-form label-position="top">
-      <el-collapse v-for="(item, index) in data" :key="item.id" v-model="activeNames">
-        <el-button
-          v-if="index === 1 && !visibleGroupName"
-          type="primary"
-          class="mt-10px mb-10px"
-          @click="visibleGroupName = !visibleGroupName"
-        >
-          添加局部变量组
-        </el-button>
-        <el-input
-          v-if="index === 1 && visibleGroupName"
-          v-model="variableGroupName"
-          placeholder="请输入组名并回车创建"
-          class="mt-10px mb-10px"
-          @keyup.enter="handleGroup"
-        />
+      <el-collapse v-for="item in data" :key="item.id" v-model="activeNames">
         <el-collapse-item :name="item.name">
           <template #title>
             <div class="collapse-title">
@@ -24,7 +16,14 @@
                 <ArrowRight />
               </el-icon>
 
-              <span class="title-text">{{ item.name }}</span>
+              <span class="title-text mr-12px flex items-center">
+                <el-input
+                  v-model="item.name"
+                  placeholder="请输入组名"
+                  size="small"
+                  class="mt-10px mb-10px"
+                />
+              </span>
 
               <el-icon class="mr-10px" @click.stop="addVariables(item.variables)">
                 <Plus />
@@ -36,9 +35,9 @@
           </template>
           <div class="p-10px">
             <!-- 头部描述 -->
-            <div v-for="(element, indexs) in item.variables" :key="element.id" class="mb-10px">
+            <el-card v-for="(element, index) in item.variables" :key="element.id" class="mb-10px">
               <div class="flex justify-between items-center mb-10px">
-                <span>变量 {{ indexs + 1 }}</span>
+                <span>变量: {{ element.name || index + 1 }}</span>
                 <el-icon
                   class="cursor-pointer"
                   @click="handleVariablesRemove(item.variables, element)"
@@ -58,7 +57,7 @@
                 </el-select>
               </div>
               <el-input v-model="element.value" type="textarea" :rows="3" placeholder="初始值" />
-            </div>
+            </el-card>
           </div>
         </el-collapse-item>
       </el-collapse>
@@ -67,7 +66,7 @@
 </template>
 
 <script setup lang="ts">
-import { ref, watch, defineEmits } from 'vue'
+import { ref, watch } from 'vue'
 import type { VariableGroup } from '@/types/variables'
 import { ArrowRight, Plus, Delete } from '@element-plus/icons-vue'
 import { ElMessageBox } from 'element-plus'
@@ -83,16 +82,13 @@ const props = defineProps<{
 const data = ref<VariableGroup[]>(props.variables || ({} as VariableGroup[]))
 watch(data, (val) => emit('update:variables', val))
 const activeNames = ref<string[]>([])
-// 输入的局部变量组名称
-const variableGroupName = ref('')
-const visibleGroupName = ref(false)
 
 watch(activeNames.value, (value) => console.log(value))
 
 const addVariables = (variables) => {
   variables.push({
     id: v4(),
-    name: '',
+    name: `var_${variables.length + 1}`,
     value: '',
     type: ''
   })
@@ -105,16 +101,6 @@ const handleVariablesRemove = (variables, element) => {
   }
 }
 
-const handleGroup = () => {
-  data.value.push({
-    id: v4(),
-    name: variableGroupName.value,
-    variables: []
-  })
-  visibleGroupName.value = !visibleGroupName.value
-  variableGroupName.value = ''
-}
-
 const removeVariables = (variables) => {
   ElMessageBox.confirm('确定删除?', '提示', {
     confirmButtonText: '确定',
@@ -127,6 +113,16 @@ const removeVariables = (variables) => {
     }
   })
 }
+
+const addVariableGroup = () => {
+  const id = v4()
+  data.value.push({
+    id,
+    name: `vargroup_${data.value.length + 1}`,
+    variables: []
+  })
+  activeNames.value.push(id)
+}
 </script>
 
 <style lang="less" scoped>
@@ -140,9 +136,11 @@ const removeVariables = (variables) => {
   display: none;
 }
 .collapse-title {
+  box-sizing: border-box;
   display: flex;
   align-items: center;
   width: 100%;
+  padding: 0 12px;
 }
 
 .arrow {
@@ -158,4 +156,7 @@ const removeVariables = (variables) => {
   flex: 1;
   font-weight: 500;
 }
+::v-deep(.el-collapse-item__content) {
+  padding-bottom: 0;
+}
 </style>

+ 38 - 161
src/renderer/src/views/designer/config/index.vue

@@ -2,23 +2,23 @@
   <div class="h-full">
     <el-tabs size="small" class="h-full">
       <el-tab-pane label="属性">
-        <property-config v-model:selected="projectStore.activeWidget" />
+        <property-config v-model:selected="activeWidget" />
       </el-tab-pane>
       <el-tab-pane label="变量">
-        <variable-config v-model:variables="data.variables" />
+        <variable-config v-model:variables="variables" />
       </el-tab-pane>
       <el-tab-pane label="动画">
-        <animation-config v-model:animation="data.animation" />
+        <animation-config v-model:animation="animation" />
       </el-tab-pane>
       <el-tab-pane label="多语言">
-        <languages-config v-model:languages="data.languages" />
+        <languages-config v-model:languages="languages" />
       </el-tab-pane>
     </el-tabs>
   </div>
 </template>
 
 <script setup lang="ts">
-import { ref, watch } from 'vue'
+import { computed, watch } from 'vue'
 import PropertyConfig from './PropertyConfig.vue'
 import VariableConfig from './VariableConfig.vue'
 import AnimationConfig from './AnimationConfig.vue'
@@ -26,165 +26,42 @@ import LanguagesConfig from './LanguagesConfig.vue'
 import { useProjectStore } from '@/store/modules/project'
 
 const projectStore = useProjectStore()
-
-const data = ref({
-  childer: [
-    {
-      name: '对象1',
-      type: 'lv_obj',
-      id: 'component-1',
-      x: 0,
-      y: 0,
-      width: 100,
-      height: 100,
-      parent: 'screen-1',
-      text: 'Hello World',
-      align: 'center',
-      state: [],
-      theme: '',
-      styleMain: {
-        border_radius: 0,
-        bg_color: '#ffffff',
-        bg_opa: 255,
-        gradient: '',
-        shade: 0,
-        tint: 0,
-        gradient_direction: 'none',
-        LV_PART_Image: '',
-        border_color: '#000000',
-        border_opa: 1,
-        border_width: 0,
-        border_style: 'solid',
-        box_shadow_spread: 0,
-        box_shadow: '',
-        box_shadow_blur: 0,
-        box_shadow_color: '#000000',
-        box_shadow_alpha: 1,
-        outline_color: '#ff0000',
-        outline_opa: 1,
-        outline_width: 0,
-        outline_style: 'solid',
-        box_shadow_x: 0,
-        box_shadow_y: 0,
-        boxShadow_color: '',
-        boxShadow_opa: 0,
-        boxShadow_x: 0,
-        boxShadow_y: 0,
-        boxShadow_blur: 0,
-        boxShadow_spread: 0,
-        padding_top: 0,
-        padding_bottom: 0,
-        padding_left: 0,
-        padding_right: 0
-      },
-      styleItems: {}
-    }
-  ],
-  events: [
-    {
-      name: '点击',
-      trigger: 'LV_EVENT_CLICKED',
-      actions: [],
-      type: ''
-    }
-  ],
-  animation: [
-    {
-      name: 'fade-move',
-      timeline: [
-        {
-          target: 'opacity',
-          start: 0,
-          end: 1,
-          delay: 0,
-          duration: 500,
-          timingFunction: 'ease',
-          iterationCount: 1
-        },
-        {
-          target: 'translateX',
-          start: -50,
-          end: 0,
-          delay: 0,
-          duration: 500,
-          timingFunction: 'ease-out',
-          iterationCount: 1
-        }
-      ]
-    }
-  ],
-  variables: [
-    // 全局变量组
-    {
-      id: 'page_group_1',
-      name: '全局变量',
-      variables: [
-        {
-          id: '1',
-          name: 'pageName',
-          value: '',
-          type: ''
-        },
-        {
-          id: '2',
-          name: 'pageName1',
-          value: '',
-          type: ''
-        }
-      ]
-    },
-    {
-      id: 'page_local_1',
-      name: '局部变量',
-      variables: [
-        {
-          id: '1',
-          name: 'pageName',
-          value: '',
-          type: ''
-        }
-      ]
-    }
-  ],
-  languages: [
-    {
-      id: 'language_1',
-      // 语言表示
-      key: 'hello',
-      // 语言内容
-      values: [
-        {
-          language: 'zh-CN',
-          value: '你好',
-          font: 'font_1'
-        },
-        {
-          language: 'en-US',
-          value: 'hello',
-          font: 'font_1'
-        },
-        {
-          language: 'ja-JP',
-          value: 'こんにちは'
-        },
-        {
-          language: 'ko-KR',
-          value: '안녕하세요'
-        }
-      ]
-    }
-  ]
+// 属性
+const activeWidget = computed({
+  get() {
+    return projectStore.activeWidget
+  },
+  set(val) {
+    Object.assign(projectStore.activeWidget, val)
+  }
 })
-
-watch(
-  () => data.value,
-  (val) => {
-    console.log('Updated data:', val)
+// 变量
+const variables = computed({
+  get() {
+    return projectStore.project?.variables || []
   },
-  {
-    deep: true
+  set(val) {
+    if (projectStore.project) projectStore.project.variables = val
   }
-)
+})
+// 动画
+const animation = computed({
+  get() {
+    return projectStore.project?.animations || []
+  },
+  set(val) {
+    if (projectStore.project) projectStore.project.animations = val
+  }
+})
+// 多语言
+const languages = computed({
+  get() {
+    return projectStore.project?.languages || []
+  },
+  set(val) {
+    if (projectStore.project) projectStore.project.languages = val
+  }
+})
 </script>
 
 <style scoped lang="less">

+ 44 - 37
src/renderer/src/views/designer/modals/projectModal/index.vue

@@ -6,6 +6,7 @@
     body-class="h-500px overflow-y-auto overflow-x-hidden modal-body-scroll px-12px"
     :modal="false"
     :close-on-click-modal="false"
+    :before-close="close"
     align-center
   >
     <div class="w-full h-full flex">
@@ -28,7 +29,7 @@
           <el-form-item :label="$t('projectPath')" prop="path" required>
             <el-input v-model="formData.path" readonly>
               <template #append>
-                <el-button @click="selectPath('path')"
+                <el-button @click="selectPath('path')" :disabled="mode === 'edit'"
                   ><LuFolder :size="16" :disabled="mode === 'edit'"
                 /></el-button>
               </template>
@@ -394,13 +395,13 @@
           </el-row>
         </el-form>
       </el-scrollbar>
-      <div class="w-200px shrink-0 flex flex-col">
+      <div class="w-200px shrink-0 flex flex-col" v-if="mode === 'add'">
         <Recent @opened="close" />
       </div>
     </div>
 
     <template #footer>
-      <el-button @click="close">{{ $t('cancel') }}</el-button>
+      <el-button @click="close()">{{ $t('cancel') }}</el-button>
       <el-button type="primary" @click="mode === 'add' ? handleSubmit() : handleEdit()">{{
         $t('create')
       }}</el-button>
@@ -444,6 +445,10 @@ import boardConfig from '@/config/board_card_config .json'
 import displayConfig from '@/config/analog_display_config.json'
 import { klona } from 'klona'
 
+const props = defineProps<{
+  initHide?: boolean
+}>()
+
 const { t } = useI18n()
 const mode = ref<'add' | 'edit'>('add')
 const projectStore = useProjectStore()
@@ -483,15 +488,15 @@ const formData = reactive<
       colorDepth: '16',
       colorFormat: 'BGR',
       params: {
-        PCLK: '',
-        VBP: '',
-        VFP: '',
-        HBP: '',
-        HFP: '',
-        HSYNC: '',
-        VSYNC: '',
-        HsyncWidth: '',
-        VsyncWidth: ''
+        PCLK: undefined,
+        VBP: undefined,
+        VFP: undefined,
+        HBP: undefined,
+        HFP: undefined,
+        HSYNC: undefined,
+        VSYNC: undefined,
+        HsyncWidth: undefined,
+        VsyncWidth: undefined
       }
     }
   ],
@@ -501,8 +506,8 @@ const formData = reactive<
   resourcePackaging: 'c',
   binNum: 1,
   language: 'zh-cn',
-  createTime: '',
-  modifyTime: '',
+  createTime: undefined,
+  modifyTime: undefined,
   imageCompress: [],
   videoFormats: []
 })
@@ -516,7 +521,7 @@ watch(
 
 const form = ref<FormInstance>()
 // 显示模态框
-const showModal = ref(false)
+const showModal = ref(!props.initHide)
 // 显示自定义屏幕模态框
 const showScreenModal = ref(false)
 // 自定义屏幕
@@ -544,7 +549,7 @@ const rules = computed(() => {
         trigger: 'blur',
         validator: (_rule, val, callback) => {
           const names = recentProject.recentProjects?.map((item) => item.projectName) || []
-          if (names.includes(val)) {
+          if (names.includes(val) && mode.value === 'add') {
             callback(new Error(t('nameRepeat')))
           } else {
             callback()
@@ -765,15 +770,15 @@ const handleChangeScreenTypeByBoard = (board: any) => {
       colorFormat: screen?.color_format,
       interface: '',
       params: {
-        PCLK: '',
-        VBP: '',
-        VFP: '',
-        HBP: '',
-        HFP: '',
-        HSYNC: '',
-        VSYNC: '',
-        HsyncWidth: '',
-        VsyncWidth: ''
+        PCLK: undefined,
+        VBP: undefined,
+        VFP: undefined,
+        HBP: undefined,
+        HFP: undefined,
+        HSYNC: undefined,
+        VSYNC: undefined,
+        HsyncWidth: undefined,
+        VsyncWidth: undefined
       }
     })
   })
@@ -816,15 +821,15 @@ const handleChangeScreenTypeByAnalog = (type: any) => {
       colorFormat: screen?.color_format?.default,
       interface: '',
       params: {
-        PCLK: '',
-        VBP: '',
-        VFP: '',
-        HBP: '',
-        HFP: '',
-        HSYNC: '',
-        VSYNC: '',
-        HsyncWidth: '',
-        VsyncWidth: ''
+        PCLK: undefined,
+        VBP: undefined,
+        VFP: undefined,
+        HBP: undefined,
+        HFP: undefined,
+        HSYNC: undefined,
+        VSYNC: undefined,
+        HsyncWidth: undefined,
+        VsyncWidth: undefined
       }
     })
   })
@@ -849,8 +854,10 @@ const handleEdit = async () => {
 }
 
 // 关闭弹窗
-const close = () => {
-  console.log('close')
+const close = (done?: () => void) => {
+  if (!projectStore.project) return
+
+  done?.()
   form.value?.resetFields()
   showModal.value = false
 }
@@ -869,7 +876,7 @@ defineExpose({
       formData[key] = value
     })
     mode.value = 'edit'
-    showModal.value = false
+    showModal.value = true
   }
 })
 </script>

+ 7 - 1
src/renderer/src/views/designer/sidebar/Libary.vue

@@ -13,7 +13,7 @@
         :title="group.label"
         :name="group.label"
       >
-        <div class="px-2 pb-2 pt-1 grid grid-cols-[repeat(auto-fill,70px)] gap-2">
+        <div class="px-2 pb-2 pt-1 grid grid-cols-[repeat(auto-fill,minmax(70px,1fr))] gap-row-2">
           <LibaryItem v-for="item in group.items" :key="item.key" :comp="item" />
         </div>
       </el-collapse-item>
@@ -71,3 +71,9 @@ const getGroups = computed(() => {
   return list
 })
 </script>
+
+<style scoped lang="less">
+::v-deep(.el-collapse-item__content) {
+  padding: 0;
+}
+</style>

+ 1 - 1
src/renderer/src/views/designer/sidebar/components/LibaryItem.vue

@@ -3,7 +3,7 @@
     ref="libaryItemRef"
     :key="comp.key"
     draggable="true"
-    class="w-60px h-60px border border-solid border-border rounded-4px flex flex-col items-center text-text-secondary justify-center cursor-move hover:bg-bg-secondary hover:text-text-active"
+    class="place-self-center w-60px h-60px border border-solid border-border rounded-4px flex flex-col items-center text-text-secondary justify-center cursor-move hover:bg-bg-secondary hover:text-text-active"
   >
     <div class="w-32px h-32px flex items-center justify-center">
       <img width="32px" :src="comp.icon" draggable="false" />

+ 14 - 1
src/renderer/src/views/designer/sidebar/index.vue

@@ -34,7 +34,12 @@
                 <span><LuSettings2 /></span>
               </template>
               <div class="text-12px" @click="showPopoverMenu = false">
-                <div class="leading-24px px-12px cursor-pointer hover:bg-accent-blue">项目设置</div>
+                <div
+                  class="leading-24px px-12px cursor-pointer hover:bg-accent-blue"
+                  @click="handleProjectSetting"
+                >
+                  项目设置
+                </div>
                 <div
                   class="leading-24px px-12px cursor-pointer hover:bg-accent-blue"
                   @click="appStore.showGeneralModal = true"
@@ -72,6 +77,7 @@
     </div>
   </div>
   <GeneralSettingModal />
+  <ProjectSettingModal ref="projectSettingModalRef" init-hide />
 </template>
 
 <script setup lang="ts">
@@ -87,6 +93,7 @@ import Schema from './Schema.vue'
 import Resource from './Resource.vue'
 import Method from './Method.vue'
 import GeneralSettingModal from '../modals/settingModal/GeneralSettingModal.vue'
+import ProjectSettingModal from '../modals/projectModal/index.vue'
 
 const { t } = useI18n()
 
@@ -120,6 +127,12 @@ const sidebarMenu = computed(() => {
 const activeMenu = ref('widget')
 const appStore = useAppStore()
 const showPopoverMenu = ref(false)
+const projectSettingModalRef = ref<InstanceType<typeof ProjectSettingModal>>()
+
+// 项目设置
+const handleProjectSetting = () => {
+  projectSettingModalRef.value?.edit()
+}
 </script>
 
 <style scoped lang="less">

+ 8 - 2
src/renderer/src/views/designer/workspace/index.vue

@@ -67,15 +67,21 @@ import MonacoEditor from '@/components/MonacoEditor/index.vue'
 import Stage from './stage/index.vue'
 import Composite from './composite/index.vue'
 
+type TabItem = {
+  label: string
+  name: string
+  component: any
+}
+
 const projectStore = useProjectStore()
 const appStore = useAppStore()
 
 const content = ref('')
 const activeTab = ref<TabPaneName>('design')
 // tab pane列表
-const tabPaneList = shallowRef([
+const tabPaneList = shallowRef<TabItem[]>([
   {
-    label: '项目预览',
+    label: '文件代码',
     name: '111',
     component: MonacoEditor
   }

+ 17 - 2
src/renderer/src/views/designer/workspace/stage/Node.vue

@@ -57,6 +57,8 @@
       @render="onRender"
       @drag="onDrag"
       @resize="onResize"
+      @dragStart="onDragStart"
+      @dragEnd="onDragEnd"
     />
   </div>
   <!-- 右键菜单 -->
@@ -108,6 +110,8 @@ const projectStore = useProjectStore()
 const widget = computed(() => LvglWidgets[props.schema.type]?.component)
 // 节点容器放置ref
 const nodeRef = ref<HTMLDivElement>()
+// 当前层级
+const zIndex = ref(props.zIndex)
 // 判断当前节点是否选中
 const selected = computed(() =>
   projectStore.activeWidgets.map((item) => item.id).includes(props.schema.id)
@@ -115,7 +119,7 @@ const selected = computed(() =>
 
 // 组件样式
 const getStyle = computed((): CSSProperties => {
-  const { style = {}, schema, zIndex } = props
+  const { style = {}, schema } = props
 
   return {
     position: 'absolute',
@@ -124,7 +128,7 @@ const getStyle = computed((): CSSProperties => {
     transform: `translate(${schema.props.x}px, ${schema.props.y}px)`,
     width: schema.props.width + 'px',
     height: schema.props.height + 'px',
-    zIndex: zIndex,
+    zIndex: zIndex.value,
     ...style
   }
 })
@@ -175,6 +179,7 @@ useMutationObserver(
 
 // 拖拽放置事件处理
 useDrop(nodeRef, {
+  // 元素放置
   onDom: (content, event) => {
     if (!content) return
     // 创建控件
@@ -203,6 +208,16 @@ const handleSelect = (e) => {
   contentMenuRef.value?.handleClose()
 }
 
+// 拖拽开始
+const onDragStart = (e) => {
+  zIndex.value = 9999
+}
+
+// 拖拽结束
+const onDragEnd = (e) => {
+  zIndex.value = props.zIndex
+}
+
 // 渲染节点拖拽
 const onDrag = (e) => {
   // 当前选中节点整体移动