Просмотр исходного кода

fix: 修改配置为下拉配置

liaojiaxing 1 неделя назад
Родитель
Сommit
fa78735fd4

+ 100 - 104
src/renderer/src/lvgl-widgets/list/Config.vue

@@ -1,100 +1,105 @@
 <template>
   <div>
-    <el-card class="mb-12px" body-class="pr-0px!">
+    <el-card class="mb-12px" body-class="p-0px!">
       <template #header>
         <div class="flex items-center justify-between">
           <span>内容</span>
-          <span class="flex gap-4px">
+          <span class="flex gap-8px">
             <LuPlus class="cursor-pointer" @click="handleAdd" size="14px" />
             <LuTrash2 class="cursor-pointer" @click="handleClear" size="14px" />
           </span>
         </div>
       </template>
-      <el-scrollbar height="120px">
-        <div
-          v-for="(item, index) in props.values?.value || []"
-          :key="v4()"
-          class="flex items-center pr-12px"
-          @click="handleEdit(item)"
+      <el-scrollbar height="300px">
+        <el-collapse
+          v-model="activeCollapseName"
+          class="box-border border-none!"
+          expand-icon-position="left"
+          accordion
         >
-          <span class="flex-1 truncate text-#00ff00 cursor-pointer">{{ item.text }}</span>
-          <LuTrash2 class="cursor-pointer shrink-0" @click.stop="handleDelete(index)" size="14px" />
-        </div>
-      </el-scrollbar>
-    </el-card>
-    <el-dialog draggable append-to-body v-model="dialogVisible" title="编辑" width="440px">
-      <el-form ref="formRef" :model="formData" label-position="left" label-width="60px">
-        <el-form-item label="类型">
-          <el-select v-model="formData.type">
-            <el-option label="Symbol" value="symbol"></el-option>
-            <el-option label="Image" value="img"></el-option>
-            <el-option label="Text" value="text"></el-option>
-          </el-select>
-        </el-form-item>
-        <el-form-item label="文本">
-          <LanguageInput v-model="formData.text" />
-        </el-form-item>
-        <el-form-item v-if="formData.type === 'symbol'" label="图标">
-          <el-input spellcheck="false" readonly @click="handleShowSymbolModal">
-            <template #prefix>
-              <i class="lvgl-icon not-italic" v-html="getSymbol(formData.img_symbol)"></i>
+          <el-collapse-item
+            v-for="(item, index) in props.values?.value || []"
+            :key="index"
+            :name="index"
+            class="p-12px"
+          >
+            <template #title>
+              <div class="flex items-center justify-between w-full pr-12px">
+                <span class="flex-1 truncate text-#00ff00">{{ item.text }}</span>
+                <LuTrash2
+                  class="cursor-pointer shrink-0"
+                  @click.stop="handleDelete(index)"
+                  size="14px"
+                />
+              </div>
             </template>
-          </el-input>
-        </el-form-item>
-        <template v-if="formData.type === 'img'">
-          <el-form-item label="图片">
-            <ImageSelect v-model="formData.img_id" />
-          </el-form-item>
-          <el-row :gutter="12">
-            <el-col :span="12">
-              <el-form-item label="尺寸">
-                <input-number style="width: 100%" v-model="formData.img_width" :min="0">
-                  <template #prefix>宽度</template>
-                </input-number>
+            <el-form :model="item" label-position="left" label-width="60px">
+              <el-form-item label="类型">
+                <el-select v-model="item.type">
+                  <el-option label="Symbol" value="symbol" />
+                  <el-option label="Image" value="img" />
+                  <el-option label="Text" value="text" />
+                </el-select>
               </el-form-item>
-            </el-col>
-            <el-col :span="12">
-              <el-form-item label-width="0">
-                <input-number style="width: 100%" v-model="formData.img_height" :min="0">
-                  <template #prefix>高度</template>
-                </input-number>
+              <el-form-item label="文本">
+                <LanguageInput v-model="item.text" />
               </el-form-item>
-            </el-col>
-          </el-row>
-          <el-form-item label="图片遮罩" label-position="left" label-width="60px">
-            <div class="w-full flex items-center justify-between">
-              <div class="flex items-center">
-                <ColorPicker
-                  use-type="pure"
-                  picker-type="chrome"
-                  format="hex8"
-                  v-model:pureColor="formData.img_recolor"
-                />
-                <span class="text-text-active">{{ formData.img_recolor }}</span>
-              </div>
-              <el-checkbox v-model="formData.enableCover" />
-            </div>
-          </el-form-item>
-          <el-form-item label="透明度" label-position="left" label-width="60px">
-            <div class="w-full flex gap-20px items-center">
-              <el-slider
-                v-model="formData.img_alpha"
-                :max="255"
-                :min="0"
-                style="flex: 1"
-              ></el-slider>
-              <span class="text-text-active inline w-30px cursor-pointer">
-                {{ formData.img_alpha }}
-              </span>
-              <el-checkbox v-model="formData.enableAlpha" />
-            </div>
-          </el-form-item>
-        </template>
-      </el-form>
-      <template #footer>
-        <el-button type="primary" @click="dialogVisible = false">确定</el-button>
-      </template>
-    </el-dialog>
+              <el-form-item v-if="item.type === 'symbol'" label="图标">
+                <el-input spellcheck="false" readonly @click="handleShowSymbolModal(item)">
+                  <template #prefix>
+                    <i class="lvgl-icon not-italic" v-html="getSymbol(item.img_symbol)"></i>
+                  </template>
+                </el-input>
+              </el-form-item>
+              <template v-if="item.type === 'img'">
+                <el-form-item label="图片">
+                  <ImageSelect v-model="item.img_id" />
+                </el-form-item>
+                <el-row :gutter="12">
+                  <el-col :span="12">
+                    <el-form-item label="尺寸">
+                      <input-number style="width: 100%" v-model="item.img_width" :min="0">
+                        <template #prefix>宽度</template>
+                      </input-number>
+                    </el-form-item>
+                  </el-col>
+                  <el-col :span="12">
+                    <el-form-item label-width="0">
+                      <input-number style="width: 100%" v-model="item.img_height" :min="0">
+                        <template #prefix>高度</template>
+                      </input-number>
+                    </el-form-item>
+                  </el-col>
+                </el-row>
+                <el-form-item label="图片遮罩" label-position="left" label-width="60px">
+                  <div class="w-full flex items-center justify-between">
+                    <div class="flex items-center">
+                      <ColorPicker
+                        use-type="pure"
+                        picker-type="chrome"
+                        format="hex8"
+                        v-model:pureColor="item.img_recolor"
+                      />
+                      <span class="text-text-active">{{ item.img_recolor }}</span>
+                    </div>
+                    <el-checkbox v-model="item.enableCover" />
+                  </div>
+                </el-form-item>
+                <el-form-item label="透明度" label-position="left" label-width="60px">
+                  <div class="w-full flex gap-20px items-center">
+                    <el-slider v-model="item.img_alpha" :max="255" :min="0" style="flex: 1" />
+                    <span class="text-text-active inline w-30px cursor-pointer">
+                      {{ item.img_alpha }}
+                    </span>
+                    <el-checkbox v-model="item.enableAlpha" />
+                  </div>
+                </el-form-item>
+              </template>
+            </el-form>
+          </el-collapse-item>
+        </el-collapse>
+      </el-scrollbar>
+    </el-card>
     <SymbolSelectModal ref="symbolModalRef" @select="handleSelectSymbol" />
   </div>
 </template>
@@ -104,8 +109,8 @@ import type { ListItem } from './data'
 
 import { type Ref, ref } from 'vue'
 import { LuPlus, LuTrash2 } from 'vue-icons-plus/lu'
-import { v4 } from 'uuid'
 import { symbols } from '@/constants'
+import { ColorPicker } from '@/components'
 
 import ImageSelect from '@/views/designer/config/property/components/ImageSelect.vue'
 import SymbolSelectModal from '@/views/designer/config/property/components/SymbolSelectModal.vue'
@@ -115,19 +120,8 @@ const props = defineProps<{
   values: Ref<ListItem[]>
 }>()
 
-const dialogVisible = ref(false)
-const formData = ref<ListItem>({
-  text: '',
-  img_id: '',
-  type: 'symbol',
-  img_width: 0,
-  img_height: 0,
-  img_alpha: 255,
-  img_recolor: '#ffffff00',
-  img_symbol: '',
-  enableCover: true,
-  enableAlpha: true
-})
+const activeCollapseName = ref<number | string>('')
+const activeSymbolItem = ref<ListItem>()
 const symbolModalRef = ref<InstanceType<typeof SymbolSelectModal>>()
 
 /**
@@ -163,6 +157,9 @@ const getSymbol = (symbol: string) => {
  */
 const handleDelete = (index: number) => {
   props.values?.value?.splice(index, 1)
+  if (activeCollapseName.value === index) {
+    activeCollapseName.value = ''
+  }
 }
 
 /**
@@ -170,9 +167,11 @@ const handleDelete = (index: number) => {
  */
 const handleClear = () => {
   props.values.value = []
+  activeCollapseName.value = ''
 }
 
-const handleShowSymbolModal = () => {
+const handleShowSymbolModal = (item: ListItem) => {
+  activeSymbolItem.value = item
   symbolModalRef.value?.open()
 }
 
@@ -180,12 +179,9 @@ const handleShowSymbolModal = () => {
  * 选择图标
  */
 const handleSelectSymbol = (val: string) => {
-  formData.value.img_symbol = val
-}
-
-const handleEdit = (record: ListItem) => {
-  formData.value = record
-  dialogVisible.value = true
+  if (activeSymbolItem.value) {
+    activeSymbolItem.value.img_symbol = val
+  }
 }
 </script>
 

+ 122 - 114
src/renderer/src/lvgl-widgets/menu/Config.vue

@@ -1,6 +1,6 @@
 <template>
   <div>
-    <el-card class="mb-12px" body-class="pr-0px!">
+    <el-card class="mb-12px" body-class="p-0px!">
       <template #header>
         <div class="flex items-center justify-between">
           <span class="flex items-center gap-4px">
@@ -23,13 +23,13 @@
           </el-tooltip>
         </div>
       </template>
-      <el-scrollbar height="120px">
+      <el-scrollbar height="300px">
         <div
           v-for="(section, sectionIndex) in props.values?.value.children || []"
-          :key="v4()"
-          class="border-border border-solid border-1px p-4px bg-gray-800 mb-12px mr-12px rounded-4px"
+          :key="sectionIndex"
+          class="border-border border-solid border-1px p-8px bg-gray-800 mb-12px mx-12px rounded-4px"
         >
-          <div class="flex items-center gap-8px">
+          <div class="flex items-center gap-8px mb-6px">
             <LanguageInput
               v-model="section.name"
               type="textarea"
@@ -47,98 +47,112 @@
               size="14px"
             />
           </div>
-          <div
-            v-for="(item, index) in section.children || []"
-            :key="v4()"
-            class="flex items-center justify-between"
-            @click="handleEdit(item)"
+          <el-collapse
+            v-model="activeCollapseName"
+            class="box-border border-none!"
+            expand-icon-position="left"
+            accordion
           >
-            <span class="flex items-center">
-              <el-radio
-                v-model="activeIndex"
-                :value="`[${sectionIndex}].children.[${index}]`"
-                class="mr-0!"
-                @click.stop
-              />
-              <span class="flex-1 truncate text-#00ff00 cursor-pointer">{{ item.name }}</span>
-            </span>
-
-            <LuTrash2
-              class="cursor-pointer shrink-0"
-              @click.stop="handleDeleteItem(sectionIndex, index)"
-              size="14px"
-            />
-          </div>
+            <el-collapse-item
+              v-for="(item, index) in section.children || []"
+              :key="index"
+              :name="getCollapseName(sectionIndex, index)"
+              class="py-4px"
+            >
+              <template #title>
+                <div class="flex items-center justify-between w-full pr-12px gap-4px">
+                  <el-radio
+                    v-model="activeIndex"
+                    :value="`[${sectionIndex}].children.[${index}]`"
+                    class="mr-0!"
+                    @click.stop
+                  />
+                  <span class="flex-1 truncate text-#00ff00">{{ item.name }}</span>
+                  <LuTrash2
+                    class="cursor-pointer shrink-0"
+                    @click.stop="handleDeleteItem(sectionIndex, index)"
+                    size="14px"
+                  />
+                </div>
+              </template>
+              <el-form :model="item" label-position="left" label-width="60px" class="px-8px">
+                <el-form-item label="类型">
+                  <el-select v-model="item.titleIcon.type">
+                    <el-option label="Symbol" value="symbol" />
+                    <el-option label="Image" value="img" />
+                    <el-option label="Text" value="text" />
+                  </el-select>
+                </el-form-item>
+                <el-form-item label="文本">
+                  <LanguageInput v-model="item.name" />
+                </el-form-item>
+                <el-form-item v-if="item.titleIcon.type === 'symbol'" label="图标">
+                  <el-input spellcheck="false" readonly @click="handleShowSymbolModal(item)">
+                    <template #prefix>
+                      <i
+                        class="lvgl-icon not-italic"
+                        v-html="getSymbol(item.titleIcon.img_symbol)"
+                      ></i>
+                    </template>
+                  </el-input>
+                </el-form-item>
+                <template v-if="item.titleIcon.type === 'img'">
+                  <el-form-item label="图片">
+                    <ImageSelect v-model="item.titleIcon.img_id" />
+                  </el-form-item>
+                  <el-row :gutter="12">
+                    <el-col :span="12">
+                      <el-form-item label="尺寸">
+                        <input-number
+                          style="width: 100%"
+                          v-model="item.titleIcon.img_width"
+                          :min="0"
+                        >
+                          <template #prefix>宽度</template>
+                        </input-number>
+                      </el-form-item>
+                    </el-col>
+                    <el-col :span="12">
+                      <el-form-item label-width="0">
+                        <input-number
+                          style="width: 100%"
+                          v-model="item.titleIcon.img_height"
+                          :min="0"
+                        >
+                          <template #prefix>高度</template>
+                        </input-number>
+                      </el-form-item>
+                    </el-col>
+                  </el-row>
+                  <el-form-item label="图片遮罩" label-position="left" label-width="60px">
+                    <ColorPicker
+                      use-type="pure"
+                      picker-type="chrome"
+                      format="hex8"
+                      v-model:pureColor="item.titleIcon.img_recolor"
+                    />
+                    <span class="text-text-active">{{ item.titleIcon.img_recolor }}</span>
+                  </el-form-item>
+                  <el-form-item label="透明度" label-position="left" label-width="60px">
+                    <div class="w-full flex gap-20px items-center">
+                      <el-slider
+                        v-model="item.titleIcon.img_alpha"
+                        :max="255"
+                        :min="0"
+                        style="flex: 1"
+                      />
+                      <span class="text-text-active inline w-30px cursor-pointer">
+                        {{ item.titleIcon.img_alpha }}
+                      </span>
+                    </div>
+                  </el-form-item>
+                </template>
+              </el-form>
+            </el-collapse-item>
+          </el-collapse>
         </div>
       </el-scrollbar>
     </el-card>
-    <el-dialog draggable append-to-body v-model="dialogVisible" title="编辑" width="440px">
-      <el-form ref="formRef" :model="formData" label-position="left" label-width="60px">
-        <el-form-item label="类型">
-          <el-select v-model="formData.titleIcon.type">
-            <el-option label="Symbol" value="symbol"></el-option>
-            <el-option label="Image" value="img"></el-option>
-            <el-option label="Text" value="text"></el-option>
-          </el-select>
-        </el-form-item>
-        <el-form-item label="文本">
-          <LanguageInput v-model="formData.name" />
-        </el-form-item>
-        <el-form-item v-if="formData.type === 'symbol'" label="图标">
-          <el-input spellcheck="false" readonly @click="handleShowSymbolModal">
-            <template #prefix>
-              <i class="lvgl-icon not-italic" v-html="getSymbol(formData.titleIcon.img_symbol)"></i>
-            </template>
-          </el-input>
-        </el-form-item>
-        <template v-if="formData.titleIcon.type === 'img'">
-          <el-form-item label="图片">
-            <ImageSelect v-model="formData.titleIcon.img_id" />
-          </el-form-item>
-          <el-row :gutter="12">
-            <el-col :span="12">
-              <el-form-item label="尺寸">
-                <input-number style="width: 100%" v-model="formData.titleIcon.img_width" :min="0">
-                  <template #prefix>宽度</template>
-                </input-number>
-              </el-form-item>
-            </el-col>
-            <el-col :span="12">
-              <el-form-item label-width="0">
-                <input-number style="width: 100%" v-model="formData.titleIcon.img_height" :min="0">
-                  <template #prefix>高度</template>
-                </input-number>
-              </el-form-item>
-            </el-col>
-          </el-row>
-          <el-form-item label="图片遮罩" label-position="left" label-width="60px">
-            <ColorPicker
-              use-type="pure"
-              picker-type="chrome"
-              format="hex8"
-              v-model:pureColor="formData.titleIcon.img_recolor"
-            />
-            <span class="text-text-active">{{ formData.titleIcon.img_recolor }}</span>
-          </el-form-item>
-          <el-form-item label="透明度" label-position="left" label-width="60px">
-            <div class="w-full flex gap-20px items-center">
-              <el-slider
-                v-model="formData.titleIcon.img_alpha"
-                :max="255"
-                :min="0"
-                style="flex: 1"
-              ></el-slider>
-              <span class="text-text-active inline w-30px cursor-pointer">
-                {{ formData.titleIcon.img_alpha }}
-              </span>
-            </div>
-          </el-form-item>
-        </template>
-      </el-form>
-      <template #footer>
-        <el-button type="primary" @click="dialogVisible = false">确定</el-button>
-      </template>
-    </el-dialog>
     <SymbolSelectModal ref="symbolModalRef" @select="handleSelectSymbol" />
   </div>
 </template>
@@ -148,9 +162,9 @@ import type { MenuItem } from './data'
 
 import { ref, computed, type Ref } from 'vue'
 import { LuPlus, LuTrash2, LuArrowRightToLine, LuArrowLeftToLine } from 'vue-icons-plus/lu'
-import { v4 } from 'uuid'
 import { symbols } from '@/constants'
 import { getNextIndex } from '@/utils'
+import { ColorPicker } from '@/components'
 
 import ImageSelect from '@/views/designer/config/property/components/ImageSelect.vue'
 import SymbolSelectModal from '@/views/designer/config/property/components/SymbolSelectModal.vue'
@@ -160,19 +174,8 @@ const props = defineProps<{
   values: Ref<any>
 }>()
 
-const dialogVisible = ref(false)
-const formData = ref<MenuItem>({
-  name: '',
-  titleIcon: {
-    img_id: '',
-    type: 'symbol',
-    img_width: 0,
-    img_height: 0,
-    img_alpha: 255,
-    img_recolor: '#ffffff00',
-    img_symbol: ''
-  }
-})
+const activeCollapseName = ref<number | string>('')
+const activeSymbolItem = ref<MenuItem>()
 const symbolModalRef = ref<InstanceType<typeof SymbolSelectModal>>()
 
 // 当前激活
@@ -244,17 +247,25 @@ const handleAddItem = (sectionIndex: number | string) => {
  */
 const handleDeleteItem = (sectionIndex: number | string, index: number | string) => {
   props.values?.value.children?.[sectionIndex]?.children?.splice(index, 1)
+  if (activeCollapseName.value === getCollapseName(sectionIndex, index)) {
+    activeCollapseName.value = ''
+  }
 }
 
 const handleChangeInPage = () => {
   inPage.value = !inPage.value
 }
 
+const getCollapseName = (sectionIndex: number | string, index: number | string) => {
+  return `${sectionIndex}-${index}`
+}
+
 const getSymbol = (symbol: string) => {
   return symbols.find((item) => item.label === symbol)?.value
 }
 
-const handleShowSymbolModal = () => {
+const handleShowSymbolModal = (item: MenuItem) => {
+  activeSymbolItem.value = item
   symbolModalRef.value?.open()
 }
 
@@ -262,12 +273,9 @@ const handleShowSymbolModal = () => {
  * 选择图标
  */
 const handleSelectSymbol = (val: string) => {
-  formData.value.titleIcon.img_symbol = val
-}
-
-const handleEdit = (record: MenuItem) => {
-  formData.value = record
-  dialogVisible.value = true
+  if (activeSymbolItem.value) {
+    activeSymbolItem.value.titleIcon.img_symbol = val
+  }
 }
 </script>
 

+ 121 - 112
src/renderer/src/lvgl-widgets/span-group/Config.vue

@@ -1,114 +1,130 @@
 <template>
   <div>
-    <el-card class="mb-12px" body-class="pr-0px!">
+    <el-card class="mb-12px" body-class="p-0px!">
       <template #header>
         <div class="flex items-center justify-between">
           <span>内容</span>
-          <span class="flex gap-4px">
+          <span class="flex gap-8px">
             <LuPlus class="cursor-pointer" @click="handleAdd" size="14px" />
             <LuTrash2 class="cursor-pointer" @click="handleClear" size="14px" />
           </span>
         </div>
       </template>
-      <el-scrollbar height="120px">
-        <div
-          v-for="(item, index) in props.values?.value || []"
-          :key="v4()"
-          class="flex items-center pr-12px"
-          @click="handleEdit(item)"
+
+      <!-- 调整了高度以适应展开的表单 -->
+      <el-scrollbar height="300px">
+        <el-collapse
+          v-model="activeCollapseName"
+          class="box-border border-none!"
+          expand-icon-position="left"
+          accordion
         >
-          <span class="flex-1 truncate text-#00ff00 cursor-pointer">{{ item.text }}</span>
-          <LuTrash2 class="cursor-pointer shrink-0" @click.stop="handleDelete(index)" size="14px" />
-        </div>
-      </el-scrollbar>
-    </el-card>
-    <el-dialog draggable append-to-body v-model="dialogVisible" title="编辑文本" width="440px">
-      <el-form ref="formRef" :model="formData" label-position="left" label-width="60px">
-        <el-form-item label="字体颜色">
-          <ColorPicker
-            v-model:pureColor="formData.text_color"
-            format="hex8"
-            picker-type="chrome"
-            use-type="pure"
+          <el-collapse-item
+            v-for="(item, index) in props.values?.value || []"
+            :key="index"
+            :name="index"
+            class="p-12px"
           >
-            <template #trigger>
-              <BiFontColor size="22px" :style="{ color: formData.text_color }" />
-            </template>
-          </ColorPicker>
-          <span class="text-text-active">{{ formData.text_color }}</span>
-        </el-form-item>
-        <el-form-item label="字体样式" label-width="60px">
-          <el-select v-model="formData.font_family" placeholder="请选择">
-            <template #label="{ label }">
-              <div :style="{ fontFamily: `'${label}'` }">{{ label }}</div>
-            </template>
-            <el-option
-              v-for="item in fontOptions"
-              :key="item.value"
-              :value="item.value"
-              :label="item.label"
-            >
-              <div :style="{ fontFamily: `'${item.label}'` }">
-                {{ item.label }}
+            <!-- 折叠面板标题 -->
+            <template #title>
+              <div class="flex items-center justify-between w-full pr-12px">
+                <span class="flex-1 truncate text-#00ff00">{{ item.text }}</span>
+                <!-- 使用 @click.stop 阻止冒泡,避免触发折叠/展开 -->
+                <LuTrash2
+                  class="cursor-pointer shrink-0"
+                  @click.stop="handleDelete(index)"
+                  size="14px"
+                />
               </div>
-            </el-option>
-          </el-select>
-        </el-form-item>
-        <el-form-item label="字体大小">
-          <input-number
-            v-model="formData.font_size"
-            controls-position="right"
-            placeholder="请输入"
-            style="width: 100%"
-          />
-        </el-form-item>
-        <el-form-item label="">
-          <VariableBindWrapper
-            v-model="formData.text_decor"
-            :variable-config="{
-              type: 'enum',
-              enumMap: {
-                0: LineEnum.LV_TEXT_DECOR_NONE,
-                1: LineEnum.LV_TEXT_DECOR_UNDERLINE,
-                2: LineEnum.LV_TEXT_DECOR_STRIKETHROUGH,
-                3: LineEnum['LV_TEXT_DECOR_UNDERLINE | LV_TEXT_DECOR_STRIKETHROUGH']
-              }
-            }"
-          >
-            <el-radio-group v-model="formData.text_decor">
-              <el-radio-button :value="LineEnum.LV_TEXT_DECOR_NONE">
-                <el-tooltip content="none">
-                  <MdNotInterested size="14px" />
-                </el-tooltip>
-              </el-radio-button>
-              <el-radio-button :value="LineEnum.LV_TEXT_DECOR_UNDERLINE">
-                <el-tooltip content="underline">
-                  <RxUnderline size="14px" />
-                </el-tooltip>
-              </el-radio-button>
-              <el-radio-button :value="LineEnum.LV_TEXT_DECOR_STRIKETHROUGH">
-                <el-tooltip content="strikethrough">
-                  <RxStrikethrough size="14px" />
-                </el-tooltip>
-              </el-radio-button>
-              <el-radio-button
-                :value="LineEnum['LV_TEXT_DECOR_UNDERLINE | LV_TEXT_DECOR_STRIKETHROUGH']"
-              >
-                <el-tooltip content="strikethrough and underline">
-                  <GoStrikethrough size="14px" />
-                </el-tooltip>
-              </el-radio-button>
-            </el-radio-group>
-          </VariableBindWrapper>
-        </el-form-item>
-        <el-form-item label="文本">
-          <LanguageInput type="textarea" v-model="formData.text" placeholder="请输入文本" />
-        </el-form-item>
-      </el-form>
-      <template #footer>
-        <el-button type="primary" @click="dialogVisible = false">确定</el-button>
-      </template>
-    </el-dialog>
+            </template>
+
+            <!-- 展开的配置表单,直接绑定 item -->
+            <el-form :model="item" label-position="left" label-width="60px">
+              <el-form-item label="字体颜色">
+                <ColorPicker
+                  v-model:pureColor="item.text_color"
+                  format="hex8"
+                  picker-type="chrome"
+                  use-type="pure"
+                >
+                  <template #trigger>
+                    <BiFontColor size="22px" :style="{ color: item.text_color }" />
+                  </template>
+                </ColorPicker>
+                <span class="text-text-active">{{ item.text_color }}</span>
+              </el-form-item>
+              <el-form-item label="字体样式">
+                <el-select v-model="item.font_family" placeholder="请选择">
+                  <template #label="{ label }">
+                    <div :style="{ fontFamily: `'${label}'` }">{{ label }}</div>
+                  </template>
+                  <el-option
+                    v-for="font in fontOptions"
+                    :key="font.value"
+                    :value="font.value"
+                    :label="font.label"
+                  >
+                    <div :style="{ fontFamily: `'${font.label}'` }">
+                      {{ font.label }}
+                    </div>
+                  </el-option>
+                </el-select>
+              </el-form-item>
+              <el-form-item label="字体大小">
+                <input-number
+                  v-model="item.font_size"
+                  controls-position="right"
+                  placeholder="请输入"
+                  style="width: 100%"
+                />
+              </el-form-item>
+              <el-form-item label="">
+                <VariableBindWrapper
+                  v-model="item.text_decor"
+                  :variable-config="{
+                    type: 'enum',
+                    enumMap: {
+                      0: LineEnum.LV_TEXT_DECOR_NONE,
+                      1: LineEnum.LV_TEXT_DECOR_UNDERLINE,
+                      2: LineEnum.LV_TEXT_DECOR_STRIKETHROUGH,
+                      3: LineEnum['LV_TEXT_DECOR_UNDERLINE | LV_TEXT_DECOR_STRIKETHROUGH']
+                    }
+                  }"
+                >
+                  <el-radio-group v-model="item.text_decor">
+                    <el-radio-button :value="LineEnum.LV_TEXT_DECOR_NONE">
+                      <el-tooltip content="none">
+                        <MdNotInterested size="14px" />
+                      </el-tooltip>
+                    </el-radio-button>
+                    <el-radio-button :value="LineEnum.LV_TEXT_DECOR_UNDERLINE">
+                      <el-tooltip content="underline">
+                        <RxUnderline size="14px" />
+                      </el-tooltip>
+                    </el-radio-button>
+                    <el-radio-button :value="LineEnum.LV_TEXT_DECOR_STRIKETHROUGH">
+                      <el-tooltip content="strikethrough">
+                        <RxStrikethrough size="14px" />
+                      </el-tooltip>
+                    </el-radio-button>
+                    <el-radio-button
+                      :value="LineEnum['LV_TEXT_DECOR_UNDERLINE | LV_TEXT_DECOR_STRIKETHROUGH']"
+                    >
+                      <el-tooltip content="strikethrough and underline">
+                        <GoStrikethrough size="14px" />
+                      </el-tooltip>
+                    </el-radio-button>
+                  </el-radio-group>
+                </VariableBindWrapper>
+              </el-form-item>
+              <el-form-item label="文本">
+                <LanguageInput type="textarea" v-model="item.text" placeholder="请输入文本" />
+              </el-form-item>
+            </el-form>
+          </el-collapse-item>
+        </el-collapse>
+      </el-scrollbar>
+    </el-card>
   </div>
 </template>
 
@@ -117,7 +133,6 @@ import { type SpanItem, LineEnum } from './data'
 
 import { computed, type Ref, ref } from 'vue'
 import { LuPlus, LuTrash2 } from 'vue-icons-plus/lu'
-import { v4 } from 'uuid'
 import { ColorPicker } from '@/components'
 import { BiFontColor } from 'vue-icons-plus/bi'
 import { MdNotInterested } from 'vue-icons-plus/md'
@@ -131,14 +146,8 @@ const props = defineProps<{
   values: Ref<SpanItem[]>
 }>()
 
-const dialogVisible = ref(false)
-const formData = ref<SpanItem>({
-  text: '',
-  text_color: '',
-  font_family: 'montserratMedium',
-  font_size: 0,
-  text_decor: LineEnum.LV_TEXT_DECOR_NONE
-})
+// 控制折叠面板展开的项 (使用 accordion 模式,所以是单个值)
+const activeCollapseName = ref<number | string>('')
 
 const projectStore = useProjectStore()
 // 字体选项
@@ -170,6 +179,10 @@ const handleAdd = () => {
  */
 const handleDelete = (index: number) => {
   props.values?.value?.splice(index, 1)
+  // 如果删除的是当前展开的项,则关闭折叠面板
+  if (activeCollapseName.value === index) {
+    activeCollapseName.value = ''
+  }
 }
 
 /**
@@ -177,11 +190,7 @@ const handleDelete = (index: number) => {
  */
 const handleClear = () => {
   props.values.value = []
-}
-
-const handleEdit = (record: SpanItem) => {
-  formData.value = record
-  dialogVisible.value = true
+  activeCollapseName.value = ''
 }
 </script>
 

+ 39 - 61
src/renderer/src/lvgl-widgets/tabview/Config.vue

@@ -1,70 +1,59 @@
 <template>
-  <el-card class="mb-12px" body-class="p-8px!">
+  <el-card class="mb-12px" body-class="p-0px!">
     <template #header>
       <div class="flex items-center justify-between">
         <span>标签</span>
         <LuPlus class="cursor-pointer" size="16px" @click="handleAddRow" />
       </div>
     </template>
-    <el-scrollbar max-height="120px">
-      <div class="box-border pr-12px mb-4px" v-for="(_, index) in children || []" :key="index">
-        <div class="w-full flex items-center gap-4px relative group/item">
-          <el-radio v-model="activeIndex" :value="index" class="mr-0!" />
-          <el-input
-            spellcheck="false"
-            v-model="children[index].name"
-            readonly
-            @click="handleEdit(children[index], index)"
-          />
-          <div class="cursor-pointer" @click="handleDeleteItem(index)">
-            <LuTrash2 size="14px" />
-          </div>
-        </div>
-      </div>
+    <el-scrollbar height="300px">
+      <el-collapse
+        v-model="activeCollapseName"
+        class="box-border border-none!"
+        expand-icon-position="left"
+        accordion
+      >
+        <el-collapse-item
+          v-for="(item, index) in children || []"
+          :key="index"
+          :name="index"
+          class="p-12px"
+        >
+          <template #title>
+            <div class="flex items-center justify-between w-full pr-12px gap-4px">
+              <el-radio v-model="activeIndex" :value="index" class="mr-0!" @click.stop />
+              <span class="flex-1 truncate text-#00ff00">{{ item.name }}</span>
+              <LuTrash2
+                class="cursor-pointer shrink-0"
+                @click.stop="handleDeleteItem(index)"
+                size="14px"
+              />
+            </div>
+          </template>
+          <el-form :model="item" label-position="left" label-width="60px">
+            <el-form-item label="名称" prop="name">
+              <LanguageInput v-model="item.name" />
+            </el-form-item>
+            <el-form-item label="文本" prop="text">
+              <LanguageInput type="textarea" :rows="2" v-model="item.text" />
+            </el-form-item>
+          </el-form>
+        </el-collapse-item>
+      </el-collapse>
     </el-scrollbar>
   </el-card>
-  <el-dialog
-    draggable
-    append-to-body
-    title="编辑选项"
-    width="440px"
-    v-model="open"
-    close-on-click-modal
-  >
-    <el-form ref="form" :model="formData" :rules="rules" hide-required-asterisk>
-      <el-form-item label="名称" prop="name">
-        <LanguageInput v-model="formData.name" />
-      </el-form-item>
-      <el-form-item label="文本" prop="text">
-        <LanguageInput type="textarea" :rows="2" v-model="formData.text" />
-      </el-form-item>
-    </el-form>
-    <template #footer>
-      <el-button type="primary" @click="handleSubmit">确定</el-button>
-    </template>
-  </el-dialog>
 </template>
 
 <script setup lang="ts">
 import { ref, computed, type Ref } from 'vue'
 import { LuPlus, LuTrash2 } from 'vue-icons-plus/lu'
-import { FormInstance } from 'element-plus'
-import { klona } from 'klona'
 import LanguageInput from '../components/LanguageInput.vue'
 
 const props = defineProps<{
   values: Ref<any>
 }>()
 
-const open = ref(false)
-const form = ref<FormInstance>()
-const formData = ref({
-  name: '',
-  text: ''
-})
-const rules = {
-  name: [{ required: true, message: '请输入名称', trigger: 'blur' }]
-}
+const activeCollapseName = ref<number | string>('')
 
 // 子项
 const children = computed({
@@ -96,6 +85,9 @@ const activeIndex = computed({
  */
 const handleDeleteItem = (index: number) => {
   children.value.splice(index, 1)
+  if (activeCollapseName.value === index) {
+    activeCollapseName.value = ''
+  }
 }
 
 /**
@@ -109,20 +101,6 @@ const handleAddRow = () => {
     children: []
   })
 }
-
-let tempIndex
-const handleEdit = (record, idx: number | string) => {
-  tempIndex = idx
-  open.value = true
-  formData.value = klona(record)
-}
-
-const handleSubmit = () => {
-  form.value?.validate().then(() => {
-    children.value[tempIndex] = klona(formData.value)
-    open.value = false
-  })
-}
 </script>
 
 <style scoped></style>

+ 6 - 8
src/renderer/src/views/designer/config/VariableConfig.vue

@@ -27,7 +27,7 @@
                 <el-select
                   v-model="row.type"
                   placeholder="请选择"
-                  :disabled="isVariableUsed(row.id)"
+                  :disabled="isVariableTypeChangeDisabled(row.id)"
                 >
                   <el-option
                     v-for="type in variableType"
@@ -109,7 +109,7 @@
                       <el-select
                         v-model="row.type"
                         placeholder="请选择"
-                        :disabled="isVariableUsed(row.id)"
+                        :disabled="isVariableTypeChangeDisabled(row.id)"
                       >
                         <el-option
                           v-for="type in variableType"
@@ -244,15 +244,13 @@ const addPageVariables = () => {
 }
 
 /**
- * 变量是否被使用
+ * 变量类型是否禁止修改
  */
-const isVariableUsed = (varId: string) => {
+const isVariableTypeChangeDisabled = (varId: string) => {
   const pages = projectStore.project?.screens.flatMap((item) => item.pages) || []
-  const usageCount = findVariableUsages(varId, [
-    { children: pages }
-  ] as unknown as BaseWidget[]).length
+  const usages = findVariableUsages(varId, [{ children: pages }] as unknown as BaseWidget[])
 
-  return usageCount > 0
+  return usages.some((usage) => usage.fieldPath === 'props' || usage.fieldPath.startsWith('props.'))
 }
 
 /**