فهرست منبع

feat: 样式属性支持变量配置

jiaxing.liao 1 ماه پیش
والد
کامیت
b8a502542f

+ 1 - 64
src/renderer/src/views/designer/config/property/CusFormItem.vue

@@ -11,17 +11,9 @@
       :label-position="schema?.labelPosition"
     >
       <VariableBindWrapper
-        :model-value="value"
-        :field-path="schema.field as string"
+        v-model="value"
         :can-bind-variable="!!schema.variableConfig"
         :variable-config="schema.variableConfig"
-        @update:model-value="
-          (val) => {
-            value = val
-          }
-        "
-        @bind="handleOpenVariableSelector"
-        @unbind="handleUnbindVariable"
       >
         <!-- 文本 -->
         <el-input
@@ -282,17 +274,6 @@
     @change-state-style="(field, type) => $emit('changeStateStyle', field, type)"
   />
   <LanguageSelectModal ref="languageModalRef" @select="handleSelectLanguage" />
-  <!-- 变量选择弹窗 -->
-  <VariableSelectorDialog
-    v-model="showVariableSelector"
-    :all-variables="
-      getAllVariables(projectStore.project?.variables || [], projectStore.activePage?.variables)
-    "
-    :current-var-id="currentBoundVarId"
-    :support-type="schema.variableConfig?.type"
-    @confirm="handleVariableBindConfirm"
-    @cancel="showVariableSelector = false"
-  />
 </template>
 
 <script setup lang="tsx">
@@ -331,8 +312,6 @@ import StyleOther from './components/StyleOther.vue'
 import LanguageSelectModal from './components/LanguageSelectModal.vue'
 import { LuLanguages } from 'vue-icons-plus/lu'
 import VariableBindWrapper from './components/VariableBindWrapper.vue'
-import VariableSelectorDialog from './components/VariableSelectorDialog.vue'
-import { bindVariableToProperty, getAllVariables } from '@/utils/variableBinding'
 
 defineOptions({
   name: 'CusFormItem'
@@ -363,10 +342,6 @@ const componentProps = computed(() => {
 })
 const expandStyle = ref(true)
 
-const showVariableSelector = ref(false)
-const currentBindField = ref('')
-const currentBindValue = ref<any>(null)
-
 // 绑定数据
 const value = computed({
   get() {
@@ -385,13 +360,6 @@ const value = computed({
   }
 })
 
-const currentBoundVarId = computed(() => {
-  const currentValue = value.value
-  return currentValue && typeof currentValue === 'object' && currentValue.varId
-    ? currentValue.varId
-    : ''
-})
-
 const getTextInputElement = () => {
   const inputInstance = textInputRef.value as any
   return inputInstance?.input || inputInstance?.textarea || null
@@ -455,37 +423,6 @@ const handleSelectLanguage = (languageKey: string) => {
   insertLanguageToken(languageKey)
 }
 
-/**
- * 打开变量选择器
- */
-const handleOpenVariableSelector = () => {
-  currentBindField.value = props.schema.field || ''
-  currentBindValue.value = value.value
-  showVariableSelector.value = true
-}
-
-/**
- * 变量选择确认后的处理
- * @param varId
- */
-const handleVariableBindConfirm = (varId: string) => {
-  const boundValue = bindVariableToProperty(currentBindValue.value, varId)
-  if (props.schema.field && props.formData) {
-    set(props.formData, props.schema.field, boundValue)
-  }
-  showVariableSelector.value = false
-}
-
-/**
- * 解绑变量
- */
-const handleUnbindVariable = () => {
-  const currentValue = value.value
-  if (currentValue && typeof currentValue === 'object' && currentValue.varId) {
-    value.value = currentValue.originValue
-  }
-}
-
 /**
  * 使用表单组件渲染表单项
  */

+ 12 - 8
src/renderer/src/views/designer/config/property/components/StyleAnimation.vue

@@ -1,20 +1,24 @@
 <template>
   <div>
     <el-form-item label="时间" label-position="left" label-width="60px">
-      <input-number
-        placeholder="请输入"
-        v-model="time"
-        :min="0"
-        :max="100000"
-        controls-position="right"
-        style="width: 100%"
-      />
+      <!-- 支持变量 -->
+      <VariableBindWrapper v-model="time" :variable-config="{ type: 'uint32_t' }">
+        <input-number
+          placeholder="请输入"
+          v-model="time"
+          :min="0"
+          :max="100000"
+          controls-position="right"
+          style="width: 100%"
+        />
+      </VariableBindWrapper>
     </el-form-item>
   </div>
 </template>
 
 <script setup lang="ts">
 import { computed } from 'vue'
+import VariableBindWrapper from './VariableBindWrapper.vue'
 
 const modelValue = defineModel<{
   time: number

+ 44 - 30
src/renderer/src/views/designer/config/property/components/StyleBackground.vue

@@ -1,43 +1,56 @@
 <template>
   <div>
     <el-form-item label="背景颜色" label-position="left" label-width="70px">
-      <ColorModal
-        v-model:pureColor="pureColor"
-        v-model:gradientColor="gradientColor"
-        :useType="useType"
-        :width="width"
-        :height="height"
-      />
-      <span class="text-text-active">{{
-        typeof modelValue?.color === 'object' ? '渐变颜色' : modelValue?.color
-      }}</span>
+      <!-- 支持变量 -->
+      <VariableBindWrapper v-model="gradientColor" :variable-config="{ type: 'int32_t' }">
+        <div class="flex items-center">
+          <ColorModal
+            v-model:pureColor="pureColor"
+            v-model:gradientColor="gradientColor"
+            :useType="useType"
+            :width="width"
+            :height="height"
+          />
+          <span class="text-text-active">{{
+            typeof modelValue?.color === 'object' ? '渐变颜色' : modelValue?.color
+          }}</span>
+        </div>
+      </VariableBindWrapper>
     </el-form-item>
     <el-form-item v-if="!onlyColor" label="背景图片" label-position="left" label-width="70px">
       <ImageSelect v-model="image" />
     </el-form-item>
     <el-form-item v-if="!onlyColor" label="图片透明度" label-position="left" label-width="70px">
-      <div class="w-full flex gap-20px items-center">
-        <el-slider
-          v-model="imageAlpha"
-          :disabled="!modelValue?.image"
-          :max="255"
-          :min="0"
-          style="flex: 1"
-        ></el-slider>
-        <span class="text-text-active inline w-30px cursor-pointer">
-          {{ imageAlpha }}
-        </span>
-      </div>
+      <!-- 支持变量 -->
+      <VariableBindWrapper v-model="imageAlpha" :variable-config="{ type: 'uint8_t' }">
+        <div class="w-full flex gap-20px items-center">
+          <el-slider
+            v-model="imageAlpha"
+            :disabled="!modelValue?.image"
+            :max="255"
+            :min="0"
+            style="flex: 1"
+          ></el-slider>
+          <span class="text-text-active inline w-30px cursor-pointer">
+            {{ imageAlpha }}
+          </span>
+        </div>
+      </VariableBindWrapper>
     </el-form-item>
     <el-form-item v-if="!onlyColor" label="图片遮罩" label-position="left" label-width="70px">
-      <ColorPicker
-        use-type="pure"
-        picker-type="chrome"
-        format="hex8"
-        v-model:pureColor="imageColor"
-        :disabled="!modelValue?.image"
-      />
-      <span class="text-text-active">{{ imageColor }}</span>
+      <!-- 支持变量 -->
+      <VariableBindWrapper v-model="imageAlpha" :variable-config="{ type: 'uint8_t' }">
+        <div class="flex items-center">
+          <ColorPicker
+            use-type="pure"
+            picker-type="chrome"
+            format="hex8"
+            v-model:pureColor="imageColor"
+            :disabled="!modelValue?.image"
+          />
+          <span class="text-text-active">{{ imageColor }}</span>
+        </div>
+      </VariableBindWrapper>
     </el-form-item>
   </div>
 </template>
@@ -47,6 +60,7 @@ import { computed } from 'vue'
 import { ColorPicker, ColorModal } from '@/components'
 import ImageSelect from './ImageSelect.vue'
 import { useProjectStore } from '@/store/modules/project'
+import VariableBindWrapper from './VariableBindWrapper.vue'
 
 import type { GradientColor } from '@/lvgl-widgets/type'
 

+ 98 - 62
src/renderer/src/views/designer/config/property/components/StyleBorder.vue

@@ -1,84 +1,119 @@
 <template>
   <div>
     <el-form-item v-if="!onlyRadius" label="颜色" label-position="left" label-width="60px">
-      <ColorPicker v-model:pureColor="color" format="hex8" picker-type="chrome" use-type="pure" />
-      <span class="text-text-active">{{ modelValue?.color }}</span>
+      <!-- 支持变量 -->
+      <VariableBindWrapper v-model="color" :variable-config="{ type: 'int32_t' }">
+        <div class="flex items-center">
+          <ColorPicker
+            v-model:pureColor="color"
+            format="hex8"
+            picker-type="chrome"
+            use-type="pure"
+          />
+          <span class="text-text-active">{{ modelValue?.color }}</span>
+        </div>
+      </VariableBindWrapper>
     </el-form-item>
     <el-row v-if="!onlyRadius" :gutter="12">
       <el-col :span="hideRadius ? 24 : 12">
         <el-form-item label-position="left" label-width="0px">
-          <input-number
-            v-model="width"
-            controls-position="right"
-            style="width: 100%"
-            :min="0"
-            :max="max"
-          >
-            <template #prefix>
-              <span>宽度</span>
-            </template>
-          </input-number>
+          <!-- 支持变量 -->
+          <VariableBindWrapper v-model="width" :variable-config="{ type: 'uint32_t' }">
+            <input-number
+              v-model="width"
+              controls-position="right"
+              style="width: 100%"
+              :min="0"
+              :max="max"
+            >
+              <template #prefix>
+                <span>宽度</span>
+              </template>
+            </input-number>
+          </VariableBindWrapper>
         </el-form-item>
       </el-col>
       <el-col :span="12" v-if="!hideRadius">
         <el-form-item label-position="left" label-width="0px">
-          <input-number
-            v-model="radius"
-            controls-position="right"
-            style="width: 100%"
-            :min="0"
-            :max="max"
-          >
-            <template #prefix>
-              <span>圆角</span>
-            </template>
-          </input-number>
+          <!-- 支持变量 -->
+          <VariableBindWrapper v-model="radius" :variable-config="{ type: 'uint32_t' }">
+            <input-number
+              v-model="radius"
+              controls-position="right"
+              style="width: 100%"
+              :min="0"
+              :max="max"
+            >
+              <template #prefix>
+                <span>圆角</span>
+              </template>
+            </input-number>
+          </VariableBindWrapper>
         </el-form-item>
       </el-col>
     </el-row>
 
     <el-form-item v-if="onlyRadius" label-position="left" label-width="0px">
-      <input-number
-        v-model="radius"
-        controls-position="right"
-        style="width: 100%"
-        :min="0"
-        :max="max"
-      >
-        <template #prefix>
-          <span>圆角</span>
-        </template>
-      </input-number>
+      <!-- 支持变量 -->
+      <VariableBindWrapper v-model="radius" :variable-config="{ type: 'uint32_t' }">
+        <input-number
+          v-model="radius"
+          controls-position="right"
+          style="width: 100%"
+          :min="0"
+          :max="max"
+        >
+          <template #prefix>
+            <span>圆角</span>
+          </template>
+        </input-number>
+      </VariableBindWrapper>
     </el-form-item>
 
     <el-form-item label-position="left" label-width="0px" v-if="!onlyRadius">
-      <div class="flex-1 flex items-center justify-between px-4px mt-8px">
-        <BsBorderOuter
-          class="cursor-pointer"
-          :class="{ 'color-accent-blue': side?.includes('all') }"
-          @click="handleBorder('all')"
-        />
-        <BsBorderTop
-          class="cursor-pointer"
-          :class="{ 'color-accent-blue': side?.includes('top') }"
-          @click="handleBorder('top')"
-        />
-        <BsBorderRight
-          class="cursor-pointer"
-          :class="{ 'color-accent-blue': side?.includes('right') }"
-          @click="handleBorder('right')"
-        />
-        <BsBorderBottom
-          class="cursor-pointer"
-          :class="{ 'color-accent-blue': side?.includes('bottom') }"
-          @click="handleBorder('bottom')"
-        />
-        <BsBorderLeft
-          class="cursor-pointer"
-          :class="{ 'color-accent-blue': side?.includes('left') }"
-          @click="handleBorder('left')"
-        />
-      </div>
+      <!-- 支持变量 -->
+      <VariableBindWrapper
+        v-model="radius"
+        :variable-config="{
+          type: 'enum',
+          enumMap: {
+            0: 'none',
+            1: 'bottom',
+            2: 'top',
+            3: 'left',
+            4: 'right',
+            15: 'all'
+          }
+        }"
+      >
+        <div class="flex-1 flex items-center justify-between px-4px mt-8px">
+          <BsBorderOuter
+            class="cursor-pointer"
+            :class="{ 'color-accent-blue': side?.includes('all') }"
+            @click="handleBorder('all')"
+          />
+          <BsBorderTop
+            class="cursor-pointer"
+            :class="{ 'color-accent-blue': side?.includes('top') }"
+            @click="handleBorder('top')"
+          />
+          <BsBorderRight
+            class="cursor-pointer"
+            :class="{ 'color-accent-blue': side?.includes('right') }"
+            @click="handleBorder('right')"
+          />
+          <BsBorderBottom
+            class="cursor-pointer"
+            :class="{ 'color-accent-blue': side?.includes('bottom') }"
+            @click="handleBorder('bottom')"
+          />
+          <BsBorderLeft
+            class="cursor-pointer"
+            :class="{ 'color-accent-blue': side?.includes('left') }"
+            @click="handleBorder('left')"
+          />
+        </div>
+      </VariableBindWrapper>
     </el-form-item>
   </div>
 </template>
@@ -93,6 +128,7 @@ import {
   BsBorderOuter
 } from 'vue-icons-plus/bs'
 import { useProjectStore } from '@/store/modules/project'
+import VariableBindWrapper from './VariableBindWrapper.vue'
 
 defineProps<{
   hideRadius?: boolean

+ 46 - 22
src/renderer/src/views/designer/config/property/components/StyleFont.vue

@@ -1,21 +1,29 @@
 <template>
   <div>
     <el-form-item label="颜色" label-position="left" label-width="60px">
-      <ColorPicker v-model:pureColor="color" format="hex8" picker-type="chrome" use-type="pure">
-        <template #trigger>
-          <BiFontColor size="22px" :style="{ color }" />
-        </template>
-      </ColorPicker>
-      <span class="text-text-active">{{ modelValue?.color }}</span>
+      <!-- 支持变量 -->
+      <VariableBindWrapper v-model="color" :variable-config="{ type: 'uint32_t' }">
+        <div class="flex items-center">
+          <ColorPicker v-model:pureColor="color" format="hex8" picker-type="chrome" use-type="pure">
+            <template #trigger>
+              <BiFontColor size="22px" :style="{ color }" />
+            </template>
+          </ColorPicker>
+          <span class="text-text-active">{{ modelValue?.color }}</span>
+        </div>
+      </VariableBindWrapper>
     </el-form-item>
     <el-form-item v-if="!onlyColor" label="大小" label-position="left" label-width="60px">
-      <input-number
-        v-model="size"
-        controls-position="right"
-        style="width: 100%"
-        :min="1"
-        :max="1000"
-      />
+      <!-- 支持变量 -->
+      <VariableBindWrapper v-model="size" :variable-config="{ type: 'uint8_t' }">
+        <input-number
+          v-model="size"
+          controls-position="right"
+          style="width: 100%"
+          :min="1"
+          :max="1000"
+        />
+      </VariableBindWrapper>
     </el-form-item>
     <el-form-item v-if="!onlyColor" label="样式" label-position="left" label-width="60px">
       <el-select v-model="family">
@@ -40,7 +48,13 @@
       label-position="left"
       label-width="60px"
     >
-      <el-select-v2 v-model="align" :options="alignOptions" />
+      <!-- 支持变量 -->
+      <VariableBindWrapper
+        v-model="align"
+        :variable-config="{ type: 'enum', enumMap: { 1: 'left', 2: 'center', 3: 'right' } }"
+      >
+        <el-select-v2 v-model="align" :options="alignOptions" />
+      </VariableBindWrapper>
     </el-form-item>
     <el-form-item
       v-if="!hideDecoration && !onlyColor"
@@ -48,15 +62,24 @@
       label-position="left"
       label-width="60px"
     >
-      <el-select-v2
+      <!-- 支持变量 -->
+      <VariableBindWrapper
         v-model="decoration"
-        :options="[
-          { label: 'None', value: 'none' },
-          { label: 'Underline', value: 'underline' },
-          { label: 'Strikethrough', value: 'line-through' },
-          { label: 'Underline&Strikethrough', value: 'underline line-through' }
-        ]"
-      />
+        :variable-config="{
+          type: 'enum',
+          enumMap: { 0: 'none', 1: 'underline', 2: 'line-through', 3: 'underline line-through' }
+        }"
+      >
+        <el-select-v2
+          v-model="decoration"
+          :options="[
+            { label: 'None', value: 'none' },
+            { label: 'Underline', value: 'underline' },
+            { label: 'Strikethrough', value: 'line-through' },
+            { label: 'Underline&Strikethrough', value: 'underline line-through' }
+          ]"
+        />
+      </VariableBindWrapper>
     </el-form-item>
   </div>
 </template>
@@ -66,6 +89,7 @@ import { computed } from 'vue'
 import { ColorPicker } from '@/components'
 import { BiFontColor } from 'vue-icons-plus/bi'
 import { useProjectStore } from '@/store/modules/project'
+import VariableBindWrapper from './VariableBindWrapper.vue'
 
 defineProps<{
   hideAlign?: boolean

+ 20 - 11
src/renderer/src/views/designer/config/property/components/StyleImage.vue

@@ -1,25 +1,34 @@
 <template>
   <el-form-item label="图片遮罩" label-position="left" label-width="60px">
-    <ColorPicker
-      use-type="pure"
-      picker-type="chrome"
-      format="hex8"
-      v-model:pureColor="imageColor"
-    />
-    <span class="text-text-active">{{ imageColor }}</span>
+    <!-- 支持变量 -->
+    <VariableBindWrapper v-model="imageColor" :variable-config="{ type: 'int32_t' }">
+      <div class="flex items-center">
+        <ColorPicker
+          use-type="pure"
+          picker-type="chrome"
+          format="hex8"
+          v-model:pureColor="imageColor"
+        />
+        <span class="text-text-active">{{ imageColor }}</span>
+      </div>
+    </VariableBindWrapper>
   </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="imageAlpha" :max="255" :min="0" style="flex: 1"></el-slider>
-      <span class="text-text-active inline w-30px cursor-pointer">
-        {{ imageAlpha }}
-      </span>
+      <!-- 支持变量 -->
+      <VariableBindWrapper v-model="imageAlpha" :variable-config="{ type: 'uint8_t' }">
+        <el-slider v-model="imageAlpha" :max="255" :min="0" style="flex: 1"></el-slider>
+        <span class="text-text-active inline w-30px cursor-pointer">
+          {{ imageAlpha }}
+        </span>
+      </VariableBindWrapper>
     </div>
   </el-form-item>
 </template>
 
 <script setup lang="ts">
 import { computed } from 'vue'
+import VariableBindWrapper from './VariableBindWrapper.vue'
 
 const modelValue = defineModel<{
   recolor: string

+ 50 - 27
src/renderer/src/views/designer/config/property/components/StyleLine.vue

@@ -1,21 +1,37 @@
 <template>
   <div>
     <el-form-item label="颜色" label-position="left" label-width="60px">
-      <ColorPicker v-model:pureColor="color" format="hex8" picker-type="chrome" use-type="pure" />
-      <span class="text-text-active">{{ modelValue?.color }}</span>
+      <!-- 支持变量 -->
+      <VariableBindWrapper v-model="color" :variable-config="{ type: 'int32_t' }">
+        <div class="flex items-center">
+          <ColorPicker
+            v-model:pureColor="color"
+            format="hex8"
+            picker-type="chrome"
+            use-type="pure"
+          />
+          <span class="text-text-active">{{ modelValue?.color }}</span>
+        </div>
+      </VariableBindWrapper>
     </el-form-item>
     <el-form-item label="宽度" label-position="left" label-width="60px">
-      <input-number
-        placeholder="请输入"
-        v-model="width"
-        controls-position="right"
-        style="width: 100%"
-        :min="widthMin"
-        :max="resolvedWidthMax"
-      />
+      <!-- 支持变量 -->
+      <VariableBindWrapper v-model="width" :variable-config="{ type: 'uint32_t' }">
+        <input-number
+          placeholder="请输入"
+          v-model="width"
+          controls-position="right"
+          style="width: 100%"
+          :min="widthMin"
+          :max="resolvedWidthMax"
+        />
+      </VariableBindWrapper>
     </el-form-item>
     <el-form-item v-if="!hideRadius" label="圆角" label-position="left" label-width="60px">
-      <el-switch v-model="radius" />
+      <!-- 支持变量 -->
+      <VariableBindWrapper v-model="radius" :variable-config="{ type: 'bool' }">
+        <el-switch v-model="radius" />
+      </VariableBindWrapper>
     </el-form-item>
 
     <el-form-item v-if="hasImage" label="图片" label-position="left" label-width="60px">
@@ -23,24 +39,30 @@
     </el-form-item>
 
     <el-form-item v-if="hasDash" label="虚线宽度" label-position="left" label-width="60px">
-      <input-number
-        placeholder="请输入"
-        v-model="dashWidth"
-        controls-position="right"
-        style="width: 100%"
-        :min="0"
-        :max="100"
-      />
+      <!-- 支持变量 -->
+      <VariableBindWrapper v-model="dashWidth" :variable-config="{ type: 'uint32_t' }">
+        <input-number
+          placeholder="请输入"
+          v-model="dashWidth"
+          controls-position="right"
+          style="width: 100%"
+          :min="0"
+          :max="100"
+        />
+      </VariableBindWrapper>
     </el-form-item>
     <el-form-item v-if="hasDash" label="虚线间隔" label-position="left" label-width="60px">
-      <input-number
-        placeholder="请输入"
-        v-model="dashGap"
-        controls-position="right"
-        style="width: 100%"
-        :min="0"
-        :max="100"
-      />
+      <!-- 支持变量 -->
+      <VariableBindWrapper v-model="dashGap" :variable-config="{ type: 'uint32_t' }">
+        <input-number
+          placeholder="请输入"
+          v-model="dashGap"
+          controls-position="right"
+          style="width: 100%"
+          :min="0"
+          :max="100"
+        />
+      </VariableBindWrapper>
     </el-form-item>
   </div>
 </template>
@@ -48,6 +70,7 @@
 <script setup lang="ts">
 import { computed } from 'vue'
 import { useProjectStore } from '@/store/modules/project'
+import VariableBindWrapper from './VariableBindWrapper.vue'
 
 import ImageSelect from './ImageSelect.vue'
 

+ 57 - 44
src/renderer/src/views/designer/config/property/components/StyleMargin.vue

@@ -3,62 +3,74 @@
     <el-row :gutter="12">
       <el-col :span="12">
         <el-form-item label="" label-position="left" label-width="0px">
-          <input-number
-            v-model="top"
-            controls-position="right"
-            style="width: 100%"
-            :min="0"
-            :max="200"
-          >
-            <template #prefix>
-              <span>上</span>
-            </template>
-          </input-number>
+          <!-- 支持变量 -->
+          <VariableBindWrapper v-model="top" :variable-config="{ type: 'uint32_t' }">
+            <input-number
+              v-model="top"
+              controls-position="right"
+              style="width: 100%"
+              :min="0"
+              :max="200"
+            >
+              <template #prefix>
+                <span>上</span>
+              </template>
+            </input-number>
+          </VariableBindWrapper>
         </el-form-item>
       </el-col>
       <el-col :span="12">
         <el-form-item label="" label-position="left" label-width="0px">
-          <input-number
-            v-model="right"
-            controls-position="right"
-            style="width: 100%"
-            :min="0"
-            :max="200"
-          >
-            <template #prefix>
-              <span>右</span>
-            </template>
-          </input-number>
+          <!-- 支持变量 -->
+          <VariableBindWrapper v-model="right" :variable-config="{ type: 'uint32_t' }">
+            <input-number
+              v-model="right"
+              controls-position="right"
+              style="width: 100%"
+              :min="0"
+              :max="200"
+            >
+              <template #prefix>
+                <span>右</span>
+              </template>
+            </input-number>
+          </VariableBindWrapper>
         </el-form-item>
       </el-col>
       <el-col :span="12">
         <el-form-item label="" label-position="left" label-width="0px">
-          <input-number
-            v-model="bottom"
-            controls-position="right"
-            style="width: 100%"
-            :min="0"
-            :max="200"
-          >
-            <template #prefix>
-              <span>下</span>
-            </template>
-          </input-number>
+          <!-- 支持变量 -->
+          <VariableBindWrapper v-model="bottom" :variable-config="{ type: 'uint32_t' }">
+            <input-number
+              v-model="bottom"
+              controls-position="right"
+              style="width: 100%"
+              :min="0"
+              :max="200"
+            >
+              <template #prefix>
+                <span>下</span>
+              </template>
+            </input-number>
+          </VariableBindWrapper>
         </el-form-item>
       </el-col>
       <el-col :span="12">
         <el-form-item label="" label-position="left" label-width="0px">
-          <input-number
-            v-model="left"
-            controls-position="right"
-            style="width: 100%"
-            :min="0"
-            :max="200"
-          >
-            <template #prefix>
-              <span>左</span>
-            </template>
-          </input-number>
+          <!-- 支持变量 -->
+          <VariableBindWrapper v-model="left" :variable-config="{ type: 'uint32_t' }">
+            <input-number
+              v-model="left"
+              controls-position="right"
+              style="width: 100%"
+              :min="0"
+              :max="200"
+            >
+              <template #prefix>
+                <span>左</span>
+              </template>
+            </input-number>
+          </VariableBindWrapper>
         </el-form-item>
       </el-col>
     </el-row>
@@ -67,6 +79,7 @@
 
 <script setup lang="ts">
 import { computed } from 'vue'
+import VariableBindWrapper from './VariableBindWrapper.vue'
 
 const modelValue = defineModel<{
   left: number

+ 11 - 7
src/renderer/src/views/designer/config/property/components/StyleOther.vue

@@ -6,19 +6,23 @@
       label-position="left"
       label-width="60px"
     >
-      <input-number
-        placeholder="请输入"
-        v-model="length"
-        controls-position="right"
-        style="width: 100%"
-        v-bind="componentProps?.['length']"
-      />
+      <!-- 支持变量 -->
+      <VariableBindWrapper v-model="length" :variable-config="{ type: 'uint32_t' }">
+        <input-number
+          placeholder="请输入"
+          v-model="length"
+          controls-position="right"
+          style="width: 100%"
+          v-bind="componentProps?.['length']"
+        />
+      </VariableBindWrapper>
     </el-form-item>
   </div>
 </template>
 
 <script setup lang="ts">
 import { computed } from 'vue'
+import VariableBindWrapper from './VariableBindWrapper.vue'
 
 defineProps<{
   keys: string[]

+ 42 - 25
src/renderer/src/views/designer/config/property/components/StyleOutline.vue

@@ -3,39 +3,55 @@
     <el-row :gutter="12">
       <el-col :span="24">
         <el-form-item label-width="0px">
-          <ColorPicker v-model:pureColor="color" format="hex8" picker-type="chrome" use-type="pure">
-          </ColorPicker>
-          <span class="text-text-active">{{ modelValue?.color }}</span>
+          <!-- 支持变量 -->
+          <VariableBindWrapper v-model="color" :variable-config="{ type: 'int32_t' }">
+            <div class="flex items-center">
+              <ColorPicker
+                v-model:pureColor="color"
+                format="hex8"
+                picker-type="chrome"
+                use-type="pure"
+              >
+              </ColorPicker>
+              <span class="text-text-active">{{ modelValue?.color }}</span>
+            </div>
+          </VariableBindWrapper>
         </el-form-item>
       </el-col>
       <el-col :span="12">
         <el-form-item label="" label-position="left" label-width="0px">
-          <input-number
-            v-model="width"
-            controls-position="right"
-            style="width: 100%"
-            :min="0"
-            :max="1000"
-          >
-            <template #prefix>
-              <span>W</span>
-            </template>
-          </input-number>
+          <!-- 支持变量 -->
+          <VariableBindWrapper v-model="width" :variable-config="{ type: 'uint32_t' }">
+            <input-number
+              v-model="width"
+              controls-position="right"
+              style="width: 100%"
+              :min="0"
+              :max="1000"
+            >
+              <template #prefix>
+                <span>W</span>
+              </template>
+            </input-number>
+          </VariableBindWrapper>
         </el-form-item>
       </el-col>
       <el-col :span="12">
         <el-form-item label="" label-position="left" label-width="0px">
-          <input-number
-            v-model="pad"
-            controls-position="right"
-            style="width: 100%"
-            :min="0"
-            :max="1000"
-          >
-            <template #prefix>
-              <span>Pad</span>
-            </template>
-          </input-number>
+          <!-- 支持变量 -->
+          <VariableBindWrapper v-model="pad" :variable-config="{ type: 'uint32_t' }">
+            <input-number
+              v-model="pad"
+              controls-position="right"
+              style="width: 100%"
+              :min="0"
+              :max="1000"
+            >
+              <template #prefix>
+                <span>Pad</span>
+              </template>
+            </input-number>
+          </VariableBindWrapper>
         </el-form-item>
       </el-col>
     </el-row>
@@ -45,6 +61,7 @@
 <script setup lang="ts">
 import { computed } from 'vue'
 import { ColorPicker } from '@/components'
+import VariableBindWrapper from './VariableBindWrapper.vue'
 
 const modelValue = defineModel<{
   color: string

+ 85 - 66
src/renderer/src/views/designer/config/property/components/StylePadding.vue

@@ -3,92 +3,110 @@
     <el-row :gutter="12">
       <el-col :span="12">
         <el-form-item label="" label-position="left" label-width="0px">
-          <input-number
-            v-model="top"
-            controls-position="right"
-            style="width: 100%"
-            :min="0"
-            :max="200"
-          >
-            <template #prefix>
-              <span>上</span>
-            </template>
-          </input-number>
+          <!-- 支持变量 -->
+          <VariableBindWrapper v-model="top" :variable-config="{ type: 'uint32_t' }">
+            <input-number
+              v-model="top"
+              controls-position="right"
+              style="width: 100%"
+              :min="0"
+              :max="200"
+            >
+              <template #prefix>
+                <span>上</span>
+              </template>
+            </input-number>
+          </VariableBindWrapper>
         </el-form-item>
       </el-col>
       <el-col :span="12">
         <el-form-item label="" label-position="left" label-width="0px">
-          <input-number
-            v-model="left"
-            controls-position="right"
-            style="width: 100%"
-            :min="0"
-            :max="200"
-          >
-            <template #prefix>
-              <span>左</span>
-            </template>
-          </input-number>
+          <!-- 支持变量 -->
+          <VariableBindWrapper v-model="left" :variable-config="{ type: 'uint32_t' }">
+            <input-number
+              v-model="left"
+              controls-position="right"
+              style="width: 100%"
+              :min="0"
+              :max="200"
+            >
+              <template #prefix>
+                <span>左</span>
+              </template>
+            </input-number>
+          </VariableBindWrapper>
         </el-form-item>
       </el-col>
       <el-col :span="12">
         <el-form-item label="" label-position="left" label-width="0px">
-          <input-number
-            v-model="bottom"
-            controls-position="right"
-            style="width: 100%"
-            :min="0"
-            :max="200"
-          >
-            <template #prefix>
-              <span>下</span>
-            </template>
-          </input-number>
+          <!-- 支持变量 -->
+          <VariableBindWrapper v-model="bottom" :variable-config="{ type: 'uint32_t' }">
+            <input-number
+              v-model="bottom"
+              controls-position="right"
+              style="width: 100%"
+              :min="0"
+              :max="200"
+            >
+              <template #prefix>
+                <span>下</span>
+              </template>
+            </input-number>
+          </VariableBindWrapper>
         </el-form-item>
       </el-col>
       <el-col :span="12">
         <el-form-item label="" label-position="left" label-width="0px">
-          <input-number
-            v-model="right"
-            controls-position="right"
-            style="width: 100%"
-            :min="0"
-            :max="200"
-          >
-            <template #prefix>
-              <span>右</span>
-            </template>
-          </input-number>
+          <!-- 支持变量 -->
+          <VariableBindWrapper v-model="right" :variable-config="{ type: 'uint32_t' }">
+            <input-number
+              v-model="right"
+              controls-position="right"
+              style="width: 100%"
+              :min="0"
+              :max="200"
+            >
+              <template #prefix>
+                <span>右</span>
+              </template>
+            </input-number>
+          </VariableBindWrapper>
         </el-form-item>
       </el-col>
       <el-col v-if="hasGap" :span="12">
         <el-form-item label-width="0px">
-          <input-number
-            v-model="row"
-            controls-position="right"
-            style="width: 100%"
-            :min="0"
-            :max="200"
-          >
-            <template #prefix>
-              <span>行</span>
-            </template>
-          </input-number>
+          <!-- 支持变量 -->
+          <VariableBindWrapper v-model="row" :variable-config="{ type: 'uint32_t' }">
+            <input-number
+              v-model="row"
+              controls-position="right"
+              style="width: 100%"
+              :min="0"
+              :max="200"
+            >
+              <template #prefix>
+                <span>行</span>
+              </template>
+            </input-number>
+          </VariableBindWrapper>
         </el-form-item>
       </el-col>
       <el-col v-if="hasGap" :span="12">
         <el-form-item label-width="0px">
-          <input-number
-            v-model="column"
-            controls-position="right"
-            style="width: 100%"
-            :min="0"
-            :max="200"
-          >
-            <template #prefix>
-              <span>列</span>
-            </template>
-          </input-number>
+          <!-- 支持变量 -->
+          <VariableBindWrapper v-model="column" :variable-config="{ type: 'uint32_t' }">
+            <input-number
+              v-model="column"
+              controls-position="right"
+              style="width: 100%"
+              :min="0"
+              :max="200"
+            >
+              <template #prefix>
+                <span>列</span>
+              </template>
+            </input-number>
+          </VariableBindWrapper>
         </el-form-item>
       </el-col>
     </el-row>
@@ -104,6 +122,7 @@
 
 <script setup lang="ts">
 import { computed } from 'vue'
+import VariableBindWrapper from './VariableBindWrapper.vue'
 
 defineProps<{
   allInOne?: boolean

+ 72 - 49
src/renderer/src/views/designer/config/property/components/StyleShadow.vue

@@ -1,72 +1,94 @@
 <template>
   <div>
     <el-form-item label="颜色" label-position="left" label-width="60px">
-      <ColorPicker v-model:pureColor="color" format="hex8" picker-type="chrome" use-type="pure" />
+      <!-- 支持变量 -->
+      <VariableBindWrapper v-model="color" :variable-config="{ type: 'int32_t' }">
+        <div class="flex items-center">
+          <ColorPicker
+            v-model:pureColor="color"
+            format="hex8"
+            picker-type="chrome"
+            use-type="pure"
+          />
+        </div>
+      </VariableBindWrapper>
       <span class="text-text-active">{{ modelValue?.color }}</span>
     </el-form-item>
     <el-row :gutter="12">
       <el-col :span="12">
         <el-form-item label-position="left" label-width="0px">
-          <input-number
-            placeholder="请输入"
-            v-model="x"
-            controls-position="right"
-            style="width: 100%"
-            :min="-100"
-            :max="100"
-          >
-            <template #prefix>
-              <span>X</span>
-            </template>
-          </input-number>
+          <!-- 支持变量 -->
+          <VariableBindWrapper v-model="x" :variable-config="{ type: 'uint32_t' }">
+            <input-number
+              placeholder="请输入"
+              v-model="x"
+              controls-position="right"
+              style="width: 100%"
+              :min="-100"
+              :max="100"
+            >
+              <template #prefix>
+                <span>X</span>
+              </template>
+            </input-number>
+          </VariableBindWrapper>
         </el-form-item>
       </el-col>
       <el-col :span="12">
         <el-form-item label-position="left" label-width="0px">
-          <input-number
-            placeholder="请输入"
-            v-model="y"
-            controls-position="right"
-            style="width: 100%"
-            :min="-100"
-            :max="100"
-          >
-            <template #prefix>
-              <span>Y</span>
-            </template>
-          </input-number>
+          <!-- 支持变量 -->
+          <VariableBindWrapper v-model="y" :variable-config="{ type: 'uint32_t' }">
+            <input-number
+              placeholder="请输入"
+              v-model="y"
+              controls-position="right"
+              style="width: 100%"
+              :min="-100"
+              :max="100"
+            >
+              <template #prefix>
+                <span>Y</span>
+              </template>
+            </input-number>
+          </VariableBindWrapper>
         </el-form-item>
       </el-col>
       <el-col :span="12">
         <el-form-item label-position="left" label-width="0px">
-          <input-number
-            placeholder="请输入"
-            v-model="spread"
-            controls-position="right"
-            style="width: 100%"
-            :min="0"
-            :max="100"
-          >
-            <template #prefix>
-              <span>扩散</span>
-            </template>
-          </input-number>
+          <!-- 支持变量 -->
+          <VariableBindWrapper v-model="spread" :variable-config="{ type: 'uint32_t' }">
+            <input-number
+              placeholder="请输入"
+              v-model="spread"
+              controls-position="right"
+              style="width: 100%"
+              :min="0"
+              :max="100"
+            >
+              <template #prefix>
+                <span>扩散</span>
+              </template>
+            </input-number>
+          </VariableBindWrapper>
         </el-form-item>
       </el-col>
       <el-col :span="12">
         <el-form-item label-position="left" label-width="0px">
-          <input-number
-            placeholder="请输入"
-            v-model="width"
-            controls-position="right"
-            style="width: 100%"
-            :min="0"
-            :max="100"
-          >
-            <template #prefix>
-              <span>宽度</span>
-            </template>
-          </input-number>
+          <!-- 支持变量 -->
+          <VariableBindWrapper v-model="width" :variable-config="{ type: 'uint32_t' }">
+            <input-number
+              placeholder="请输入"
+              v-model="width"
+              controls-position="right"
+              style="width: 100%"
+              :min="0"
+              :max="100"
+            >
+              <template #prefix>
+                <span>宽度</span>
+              </template>
+            </input-number>
+          </VariableBindWrapper>
         </el-form-item>
       </el-col>
     </el-row>
@@ -75,6 +97,7 @@
 
 <script setup lang="ts">
 import { computed } from 'vue'
+import VariableBindWrapper from './VariableBindWrapper.vue'
 
 const modelValue = defineModel<{
   // 阴影颜色 带透明度

+ 29 - 22
src/renderer/src/views/designer/config/property/components/StyleSpace.vue

@@ -2,32 +2,38 @@
   <el-row :gutter="12">
     <el-col v-if="!hideLineHeight" :span="hideLetterSpacing ? 24 : 12">
       <el-form-item label="" label-position="left" label-width="0px">
-        <input-number
-          v-model="lineHeight"
-          controls-position="right"
-          style="width: 100%"
-          :min="-100"
-          :max="100"
-        >
-          <template #prefix>
-            <span>行</span>
-          </template>
-        </input-number>
+        <!-- 支持变量 -->
+        <VariableBindWrapper v-model="lineHeight" :variable-config="{ type: 'uint32_t' }">
+          <input-number
+            v-model="lineHeight"
+            controls-position="right"
+            style="width: 100%"
+            :min="-100"
+            :max="100"
+          >
+            <template #prefix>
+              <span>行</span>
+            </template>
+          </input-number>
+        </VariableBindWrapper>
       </el-form-item>
     </el-col>
     <el-col v-if="!hideLetterSpacing" :span="hideLineHeight ? 24 : 12">
       <el-form-item label="" label-position="left" label-width="0px">
-        <input-number
-          v-model="letterSpacing"
-          controls-position="right"
-          style="width: 100%"
-          :min="-100"
-          :max="100"
-        >
-          <template #prefix>
-            <span>字</span>
-          </template>
-        </input-number>
+        <!-- 支持变量 -->
+        <VariableBindWrapper v-model="letterSpacing" :variable-config="{ type: 'uint32_t' }">
+          <input-number
+            v-model="letterSpacing"
+            controls-position="right"
+            style="width: 100%"
+            :min="-100"
+            :max="100"
+          >
+            <template #prefix>
+              <span>字</span>
+            </template>
+          </input-number>
+        </VariableBindWrapper>
       </el-form-item>
     </el-col>
   </el-row>
@@ -35,6 +41,7 @@
 
 <script setup lang="ts">
 import { computed } from 'vue'
+import VariableBindWrapper from './VariableBindWrapper.vue'
 
 const modelValue = defineModel<{
   letterSpacing: number

+ 113 - 88
src/renderer/src/views/designer/config/property/components/StyleTransform.vue

@@ -3,125 +3,149 @@
     <el-row :gutter="12">
       <el-col :span="12" v-if="showWH">
         <el-form-item label="" label-position="left" label-width="0px">
-          <input-number
-            v-model="width"
-            controls-position="right"
-            style="width: 100%"
-            :min="-10000"
-            :max="10000"
-          >
-            <template #prefix>
-              <span>W</span>
-            </template>
-          </input-number>
+          <!-- 支持变量 -->
+          <VariableBindWrapper v-model="width" :variable-config="{ type: 'uint32_t' }">
+            <input-number
+              v-model="width"
+              controls-position="right"
+              style="width: 100%"
+              :min="-10000"
+              :max="10000"
+            >
+              <template #prefix>
+                <span>W</span>
+              </template>
+            </input-number>
+          </VariableBindWrapper>
         </el-form-item>
       </el-col>
       <el-col :span="12" v-if="showWH">
         <el-form-item label="" label-position="left" label-width="0px">
-          <input-number
-            v-model="height"
-            controls-position="right"
-            style="width: 100%"
-            :min="-10000"
-            :max="10000"
-          >
-            <template #prefix>
-              <span>H</span>
-            </template>
-          </input-number>
+          <!-- 支持变量 -->
+          <VariableBindWrapper v-model="height" :variable-config="{ type: 'uint32_t' }">
+            <input-number
+              v-model="height"
+              controls-position="right"
+              style="width: 100%"
+              :min="-10000"
+              :max="10000"
+            >
+              <template #prefix>
+                <span>H</span>
+              </template>
+            </input-number>
+          </VariableBindWrapper>
         </el-form-item>
       </el-col>
 
       <el-col :span="12">
         <el-form-item label="" label-position="left" label-width="0px">
-          <input-number
-            v-model="translateX"
-            controls-position="right"
-            style="width: 100%"
-            :min="-10000"
-            :max="10000"
-          >
-            <template #prefix>
-              <span>translateX</span>
-            </template>
-          </input-number>
+          <!-- 支持变量 -->
+          <VariableBindWrapper v-model="translateX" :variable-config="{ type: 'uint32_t' }">
+            <input-number
+              v-model="translateX"
+              controls-position="right"
+              style="width: 100%"
+              :min="-10000"
+              :max="10000"
+            >
+              <template #prefix>
+                <span>translateX</span>
+              </template>
+            </input-number>
+          </VariableBindWrapper>
         </el-form-item>
       </el-col>
       <el-col :span="12">
         <el-form-item label="" label-position="left" label-width="0px">
-          <input-number
-            v-model="translateY"
-            controls-position="right"
-            style="width: 100%"
-            :min="-10000"
-            :max="10000"
-          >
-            <template #prefix>
-              <span>translateY</span>
-            </template>
-          </input-number>
+          <!-- 支持变量 -->
+          <VariableBindWrapper v-model="translateY" :variable-config="{ type: 'uint32_t' }">
+            <input-number
+              v-model="translateY"
+              controls-position="right"
+              style="width: 100%"
+              :min="-10000"
+              :max="10000"
+            >
+              <template #prefix>
+                <span>translateY</span>
+              </template>
+            </input-number>
+          </VariableBindWrapper>
         </el-form-item>
       </el-col>
 
       <el-col :span="12">
         <el-form-item label="" label-position="left" label-width="0px">
-          <input-number
-            v-model="originX"
-            controls-position="right"
-            style="width: 100%"
-            :min="-10000"
-            :max="10000"
-          >
-            <template #prefix>
-              <span>originX</span>
-            </template>
-          </input-number>
+          <!-- 支持变量 -->
+          <VariableBindWrapper v-model="originX" :variable-config="{ type: 'uint32_t' }">
+            <input-number
+              v-model="originX"
+              controls-position="right"
+              style="width: 100%"
+              :min="-10000"
+              :max="10000"
+            >
+              <template #prefix>
+                <span>originX</span>
+              </template>
+            </input-number>
+          </VariableBindWrapper>
         </el-form-item>
       </el-col>
       <el-col :span="12">
         <el-form-item label="" label-position="left" label-width="0px">
-          <input-number
-            v-model="originY"
-            controls-position="right"
-            style="width: 100%"
-            :min="-10000"
-            :max="10000"
-          >
-            <template #prefix>
-              <span>originY</span>
-            </template>
-          </input-number>
+          <!-- 支持变量 -->
+          <VariableBindWrapper v-model="originY" :variable-config="{ type: 'uint32_t' }">
+            <input-number
+              v-model="originY"
+              controls-position="right"
+              style="width: 100%"
+              :min="-10000"
+              :max="10000"
+            >
+              <template #prefix>
+                <span>originY</span>
+              </template>
+            </input-number>
+          </VariableBindWrapper>
         </el-form-item>
       </el-col>
 
       <el-col :span="12">
         <el-form-item label="" label-position="left" label-width="0px">
-          <input-number
-            v-model="rotate"
-            controls-position="right"
-            style="width: 100%"
-            :min="-3600"
-            :max="3600"
-          >
-            <template #prefix>
-              <span>rotate</span>
-            </template>
-          </input-number>
+          <!-- 支持变量 -->
+          <VariableBindWrapper v-model="rotate" :variable-config="{ type: 'uint32_t' }">
+            <input-number
+              v-model="rotate"
+              controls-position="right"
+              style="width: 100%"
+              :min="-3600"
+              :max="3600"
+            >
+              <template #prefix>
+                <span>rotate</span>
+              </template>
+            </input-number>
+          </VariableBindWrapper>
         </el-form-item>
       </el-col>
       <el-col :span="12">
         <el-form-item label="" label-position="left" label-width="0px">
-          <input-number
-            v-model="scale"
-            controls-position="right"
-            style="width: 100%"
-            :min="0"
-            :max="10000"
-          >
-            <template #prefix>
-              <span>scale</span>
-            </template>
-          </input-number>
+          <!-- 支持变量 -->
+          <VariableBindWrapper v-model="scale" :variable-config="{ type: 'uint32_t' }">
+            <input-number
+              v-model="scale"
+              controls-position="right"
+              style="width: 100%"
+              :min="0"
+              :max="10000"
+            >
+              <template #prefix>
+                <span>scale</span>
+              </template>
+            </input-number>
+          </VariableBindWrapper>
         </el-form-item>
       </el-col>
     </el-row>
@@ -130,6 +154,7 @@
 
 <script setup lang="ts">
 import { computed } from 'vue'
+import VariableBindWrapper from './VariableBindWrapper.vue'
 
 withDefaults(
   defineProps<{

+ 80 - 21
src/renderer/src/views/designer/config/property/components/VariableBindWrapper.vue

@@ -25,7 +25,7 @@
           class="w-full ml-0!"
           size="small"
           type="primary"
-          @click="emit('bind')"
+          @click="handleOpenVariableSelector"
           ><LuLink size="12" /> 绑定变量</el-button
         >
         <el-button
@@ -33,48 +33,72 @@
           class="w-full ml-0!"
           size="small"
           type="primary"
-          @click="emit('unbind')"
+          @click="handleUnbindVariable"
           ><LuUnlink size="12" /> 解绑变量</el-button
         >
       </div>
     </el-popover>
   </div>
+
+  <!-- 变量选择弹窗 -->
+  <VariableSelectorDialog
+    v-model="showVariableSelector"
+    :all-variables="
+      getAllVariables(projectStore.project?.variables || [], projectStore.activePage?.variables)
+    "
+    :current-var-id="currentBoundVarId"
+    :support-type="variableConfig?.type"
+    @confirm="handleVariableBindConfirm"
+    @cancel="showVariableSelector = false"
+  />
 </template>
 
 <script setup lang="ts">
 import { computed, ref } from 'vue'
 import { ElMessageBox, ElMessage } from 'element-plus'
-import { isVariableBound, findVariableById, getAllVariables } from '@/utils/variableBinding'
+import {
+  isVariableBound,
+  findVariableById,
+  getAllVariables,
+  bindVariableToProperty
+} from '@/utils/variableBinding'
 import { useProjectStore } from '@/store/modules/project'
 import { LuPlus, LuLink, LuUnlink } from 'vue-icons-plus/lu'
 import { v4 } from 'uuid'
 import { TbVariable } from 'vue-icons-plus/tb'
+import VariableSelectorDialog from './VariableSelectorDialog.vue'
 
 import type { Variable, VariableType } from '@/types/variables'
 import type { PopoverInstance } from 'element-plus'
 
-const props = defineProps<{
-  modelValue: any
-  fieldPath: string
-  canBindVariable?: boolean
-  variableConfig?: {
-    type: VariableType
-    enumMap?: Record<number, string>
+const props = withDefaults(
+  defineProps<{
+    /**
+     * 是否允许绑定变量
+     */
+    canBindVariable?: boolean
+    /**
+     * 变量配置
+     */
+    variableConfig?: {
+      type: VariableType
+      enumMap?: Record<number, string>
+    }
+  }>(),
+  {
+    canBindVariable: true
   }
-}>()
+)
 
-const emit = defineEmits<{
-  'update:modelValue': [value: any]
-  bind: []
-  unbind: []
-}>()
+const modelValue = defineModel<any>()
 
 const projectStore = useProjectStore()
 
 const popoverRef = ref<PopoverInstance>()
+const showVariableSelector = ref(false)
 
 const isBound = computed(() => {
-  return isVariableBound(props.modelValue)
+  return isVariableBound(modelValue.value)
 })
 
 const boundVariableName = computed(() => {
@@ -85,7 +109,7 @@ const boundVariableName = computed(() => {
     projectStore.activePage?.variables
   )
 
-  const variable = findVariableById(props.modelValue.varId, allVars)
+  const variable = findVariableById(modelValue.value?.varId, allVars)
   return variable?.name || ''
 })
 
@@ -107,12 +131,12 @@ const addPageVariables = () => {
     inputErrorMessage: '变量名称只能包含字母、数字和下划线,且必须以字母或下划线开头'
   })
     .then(({ value }) => {
-      let val = props.modelValue
+      let val = modelValue.value
       // 如果是枚举
       if (props.variableConfig?.type === 'enum' && props.variableConfig.enumMap) {
         // 尝试将当前值转换为枚举的key
         const enumKey = Object.keys(props.variableConfig.enumMap).find(
-          (key) => props.variableConfig?.enumMap?.[key] === props.modelValue
+          (key) => props.variableConfig?.enumMap?.[key] === modelValue.value
         )
         val = enumKey ?? val
       }
@@ -125,7 +149,7 @@ const addPageVariables = () => {
       }
 
       projectStore.activePage?.variables?.push(newVar)
-      emit('update:modelValue', { varId: newVar.id, originValue: val })
+      modelValue.value = { varId: newVar.id, originValue: val }
     })
     .catch(() => {
       ElMessage({
@@ -133,6 +157,41 @@ const addPageVariables = () => {
       })
     })
 }
+
+/**当前变量绑定的变量id */
+const currentBoundVarId = computed(() => {
+  const currentValue = modelValue.value
+  return currentValue && typeof currentValue === 'object' && currentValue.varId
+    ? currentValue.varId
+    : ''
+})
+
+/**
+ * 变量选择确认后的处理
+ * @param varId
+ */
+const handleVariableBindConfirm = (varId: string) => {
+  const boundValue = bindVariableToProperty(modelValue.value, varId)
+  modelValue.value = boundValue
+  showVariableSelector.value = false
+}
+
+/**
+ * 解绑变量
+ */
+const handleUnbindVariable = () => {
+  const currentValue = modelValue.value
+  if (currentValue && typeof currentValue === 'object' && currentValue.varId) {
+    modelValue.value = currentValue.originValue
+  }
+}
+
+/**
+ * 打开变量选择器
+ */
+const handleOpenVariableSelector = () => {
+  showVariableSelector.value = true
+}
 </script>
 
 <style scoped lang="less">