Skip to content

🎨 useCustomFieldValue - 表单自定义神器

🌟 介绍

想要在表单中使用自己的组件?想要突破传统表单控件的限制?🎨 useCustomFieldValue 就是你的表单自定义神器!

这个强大的 Hook 就像一座桥梁,连接你的创意组件和 Vant 表单系统:

  • 🔗 无缝集成 - 让任何组件都能成为表单项
  • 📊 数据同步 - 自动处理表单数据收集和验证
  • 🎮 完全控制 - 保持组件的独立性和灵活性
  • 🚀 零配置 - 一行代码搞定表单集成

🎯 核心能力:

  • 🎨 自定义表单项 - 任何组件都能成为表单控件
  • 📋 表单数据管理 - 自动参与表单数据收集
  • 验证支持 - 完美支持表单验证机制
  • 🔄 响应式更新 - 数据变化自动同步到表单

🚀 代码演示

🎨 基本用法 - 自定义评分组件

最常见的场景:创建一个星级评分表单项

html
<!-- 🌟 StarRating.vue - 自定义星级评分组件 -->
<template>
  <div class="star-rating">
    <div class="rating-display">
      <span 
        v-for="star in 5" 
        :key="star"
        class="star"
        :class="{ active: star <= currentRating, hover: star <= hoverRating }"
        @click="setRating(star)"
        @mouseenter="hoverRating = star"
        @mouseleave="hoverRating = 0"
      >
        {{ star <= (hoverRating || currentRating) ? '⭐' : '☆' }}
      </span>
    </div>
    
    <div class="rating-info">
      <span class="rating-text">{{ ratingText }}</span>
      <span class="rating-value">({{ currentRating }}/5)</span>
    </div>
    
    <div class="rating-description" v-if="currentRating > 0">
      {{ ratingDescriptions[currentRating - 1] }}
    </div>
  </div>
</template>
js
// 🌟 StarRating.vue
import { ref, computed } from 'vue';
import { useCustomFieldValue } from '@vant/use';

export default {
  name: 'StarRating',
  setup() {
    const currentRating = ref(0);
    const hoverRating = ref(0);
    
    // 🎯 核心:将组件值注册到表单系统
    useCustomFieldValue(() => currentRating.value);
    
    // 🎨 评分描述
    const ratingDescriptions = [
      '😞 很不满意',
      '😐 不太满意', 
      '😊 一般般',
      '😄 比较满意',
      '🤩 非常满意'
    ];
    
    // 🎯 计算评分文本
    const ratingText = computed(() => {
      if (currentRating.value === 0) return '请点击星星评分';
      return ratingDescriptions[currentRating.value - 1];
    });
    
    // 🎮 设置评分
    const setRating = (rating) => {
      currentRating.value = rating;
      console.log(`⭐ 用户评分:${rating}星`);
    };
    
    return {
      currentRating,
      hoverRating,
      ratingText,
      ratingDescriptions,
      setRating
    };
  }
};
html
<!-- 📋 使用自定义评分组件的表单 -->
<template>
  <div class="rating-form-demo">
    <van-form @submit="handleSubmit" ref="formRef">
      <!-- 📝 基本信息 -->
      <van-field
        v-model="formData.productName"
        name="productName"
        label="📦 商品名称"
        placeholder="请输入商品名称"
        :rules="[{ required: true, message: '请输入商品名称' }]"
      />
      
      <!-- 🌟 自定义评分表单项 -->
      <van-field 
        name="rating" 
        label="⭐ 商品评分"
        :rules="[
          { required: true, message: '请为商品评分' },
          { validator: validateRating }
        ]"
      >
        <template #input>
          <star-rating />
        </template>
      </van-field>
      
      <!-- 💬 评价内容 -->
      <van-field
        v-model="formData.comment"
        name="comment"
        label="💬 评价内容"
        type="textarea"
        placeholder="请分享您的使用体验..."
        rows="3"
      />
      
      <!-- 📸 上传图片 -->
      <van-field name="images" label="📸 晒图">
        <template #input>
          <image-uploader />
        </template>
      </van-field>
      
      <!-- 🎯 提交按钮 -->
      <div class="form-actions">
        <van-button 
          type="primary" 
          native-type="submit" 
          block
          :loading="isSubmitting"
        >
          {{ isSubmitting ? '📤 提交中...' : '✅ 提交评价' }}
        </van-button>
      </div>
    </van-form>
    
    <!-- 📊 表单数据预览 -->
    <div class="form-preview" v-if="showPreview">
      <h4>📊 表单数据预览</h4>
      <pre>{{ JSON.stringify(lastSubmitData, null, 2) }}</pre>
    </div>
  </div>
</template>
js
// 📋 表单页面逻辑
import { ref, reactive } from 'vue';
import StarRating from './components/StarRating.vue';
import ImageUploader from './components/ImageUploader.vue';

export default {
  components: {
    StarRating,
    ImageUploader
  },
  setup() {
    const formRef = ref();
    const isSubmitting = ref(false);
    const showPreview = ref(false);
    const lastSubmitData = ref(null);
    
    // 📝 表单数据
    const formData = reactive({
      productName: '',
      comment: ''
    });
    
    // ✅ 评分验证器
    const validateRating = (value) => {
      if (!value || value === 0) {
        return '请为商品评分';
      }
      if (value < 1 || value > 5) {
        return '评分必须在1-5星之间';
      }
      return true;
    };
    
    // 📤 提交表单
    const handleSubmit = async (values) => {
      console.log('📋 表单提交数据:', values);
      
      isSubmitting.value = true;
      
      try {
        // 🌐 模拟API提交
        await new Promise(resolve => setTimeout(resolve, 2000));
        
        // ✅ 提交成功
        lastSubmitData.value = values;
        showPreview.value = true;
        
        console.log('✅ 评价提交成功!', {
          商品名称: values.productName,
          评分: `${values.rating}星`,
          评价内容: values.comment,
          图片数量: values.images?.length || 0
        });
        
        // 🎉 成功提示
        await showSuccessToast('🎉 评价提交成功!感谢您的反馈!');
        
        // 🔄 重置表单
        formRef.value?.resetValidation();
        Object.assign(formData, {
          productName: '',
          comment: ''
        });
        
      } catch (error) {
        console.error('❌ 提交失败:', error);
        showFailToast('❌ 提交失败,请重试');
      } finally {
        isSubmitting.value = false;
      }
    };
    
    return {
      formRef,
      formData,
      isSubmitting,
      showPreview,
      lastSubmitData,
      validateRating,
      handleSubmit
    };
  }
};

🎮 高级用法 - 自定义滑块组件

创建一个带动画效果的价格范围选择器:

html
<!-- 💰 PriceRangeSlider.vue - 价格范围滑块 -->
<template>
  <div class="price-range-slider">
    <div class="price-display">
      <div class="price-item">
        <label>💰 最低价格</label>
        <div class="price-value">¥{{ range.min }}</div>
      </div>
      <div class="price-separator">-</div>
      <div class="price-item">
        <label>💎 最高价格</label>
        <div class="price-value">¥{{ range.max }}</div>
      </div>
    </div>
    
    <div class="slider-container">
      <!-- 🎚️ 双滑块实现 -->
      <div class="slider-track" ref="trackRef">
        <div 
          class="slider-range" 
          :style="rangeStyle"
        ></div>
        
        <div 
          class="slider-thumb min-thumb"
          :style="{ left: minThumbPosition }"
          @mousedown="startDrag('min', $event)"
          @touchstart="startDrag('min', $event)"
        >
          <div class="thumb-tooltip">¥{{ range.min }}</div>
        </div>
        
        <div 
          class="slider-thumb max-thumb"
          :style="{ left: maxThumbPosition }"
          @mousedown="startDrag('max', $event)"
          @touchstart="startDrag('max', $event)"
        >
          <div class="thumb-tooltip">¥{{ range.max }}</div>
        </div>
      </div>
    </div>
    
    <div class="price-presets">
      <span class="preset-label">🎯 快速选择:</span>
      <button 
        v-for="preset in pricePresets"
        :key="preset.label"
        class="preset-btn"
        :class="{ active: isPresetActive(preset) }"
        @click="applyPreset(preset)"
      >
        {{ preset.label }}
      </button>
    </div>
  </div>
</template>
js
// 💰 PriceRangeSlider.vue
import { ref, computed, reactive, onMounted, onUnmounted } from 'vue';
import { useCustomFieldValue } from '@vant/use';

export default {
  name: 'PriceRangeSlider',
  props: {
    min: { type: Number, default: 0 },
    max: { type: Number, default: 10000 },
    step: { type: Number, default: 100 }
  },
  setup(props) {
    const trackRef = ref();
    const isDragging = ref(false);
    const dragType = ref('');
    
    // 💰 价格范围状态
    const range = reactive({
      min: props.min,
      max: props.max
    });
    
    // 🎯 将价格范围注册到表单
    useCustomFieldValue(() => ({
      min: range.min,
      max: range.max,
      formatted: `¥${range.min} - ¥${range.max}`
    }));
    
    // 🎨 预设价格范围
    const pricePresets = [
      { label: '💸 0-1000', min: 0, max: 1000 },
      { label: '💰 1000-3000', min: 1000, max: 3000 },
      { label: '💎 3000-5000', min: 3000, max: 5000 },
      { label: '👑 5000+', min: 5000, max: 10000 }
    ];
    
    // 📊 计算滑块位置
    const minThumbPosition = computed(() => {
      const percent = (range.min - props.min) / (props.max - props.min) * 100;
      return `${percent}%`;
    });
    
    const maxThumbPosition = computed(() => {
      const percent = (range.max - props.min) / (props.max - props.min) * 100;
      return `${percent}%`;
    });
    
    const rangeStyle = computed(() => {
      const minPercent = (range.min - props.min) / (props.max - props.min) * 100;
      const maxPercent = (range.max - props.min) / (props.max - props.min) * 100;
      return {
        left: `${minPercent}%`,
        width: `${maxPercent - minPercent}%`
      };
    });
    
    // 🎮 拖拽处理
    const startDrag = (type, event) => {
      isDragging.value = true;
      dragType.value = type;
      
      const handleMove = (e) => {
        if (!isDragging.value) return;
        
        const rect = trackRef.value.getBoundingClientRect();
        const clientX = e.clientX || e.touches[0].clientX;
        const percent = Math.max(0, Math.min(1, (clientX - rect.left) / rect.width));
        const value = Math.round((props.min + percent * (props.max - props.min)) / props.step) * props.step;
        
        if (type === 'min') {
          range.min = Math.min(value, range.max - props.step);
        } else {
          range.max = Math.max(value, range.min + props.step);
        }
        
        console.log(`💰 价格范围更新: ¥${range.min} - ¥${range.max}`);
      };
      
      const handleEnd = () => {
        isDragging.value = false;
        dragType.value = '';
        document.removeEventListener('mousemove', handleMove);
        document.removeEventListener('mouseup', handleEnd);
        document.removeEventListener('touchmove', handleMove);
        document.removeEventListener('touchend', handleEnd);
      };
      
      document.addEventListener('mousemove', handleMove);
      document.addEventListener('mouseup', handleEnd);
      document.addEventListener('touchmove', handleMove);
      document.addEventListener('touchend', handleEnd);
      
      event.preventDefault();
    };
    
    // 🎯 应用预设
    const applyPreset = (preset) => {
      range.min = preset.min;
      range.max = preset.max;
      console.log(`🎯 应用预设: ${preset.label}`);
    };
    
    // ✅ 检查预设是否激活
    const isPresetActive = (preset) => {
      return range.min === preset.min && range.max === preset.max;
    };
    
    return {
      trackRef,
      range,
      pricePresets,
      minThumbPosition,
      maxThumbPosition,
      rangeStyle,
      startDrag,
      applyPreset,
      isPresetActive
    };
  }
};

📊 复杂场景 - 多选标签组件

创建一个带搜索功能的多选标签表单项:

html
<!-- 🏷️ TagSelector.vue - 多选标签组件 -->
<template>
  <div class="tag-selector">
    <!-- 🔍 搜索框 -->
    <div class="search-section">
      <van-field
        v-model="searchKeyword"
        placeholder="🔍 搜索标签..."
        clearable
        @input="handleSearch"
      >
        <template #left-icon>
          <van-icon name="search" />
        </template>
      </van-field>
    </div>
    
    <!-- 🏷️ 已选标签 -->
    <div class="selected-tags" v-if="selectedTags.length > 0">
      <div class="section-title">
        ✅ 已选标签 ({{ selectedTags.length }}/{{ maxSelection }})
      </div>
      <div class="tag-list">
        <van-tag
          v-for="tag in selectedTags"
          :key="tag.id"
          type="primary"
          closeable
          @close="removeTag(tag)"
        >
          {{ tag.emoji }} {{ tag.name }}
        </van-tag>
      </div>
    </div>
    
    <!-- 🎯 可选标签 -->
    <div class="available-tags">
      <div class="section-title">
        🎯 可选标签 ({{ filteredTags.length }})
      </div>
      
      <!-- 📂 分类标签 -->
      <div class="category-tabs">
        <button
          v-for="category in categories"
          :key="category.id"
          class="category-tab"
          :class="{ active: activeCategory === category.id }"
          @click="setActiveCategory(category.id)"
        >
          {{ category.emoji }} {{ category.name }}
        </button>
      </div>
      
      <!-- 🏷️ 标签网格 -->
      <div class="tag-grid">
        <div
          v-for="tag in filteredTags"
          :key="tag.id"
          class="tag-item"
          :class="{ 
            selected: isTagSelected(tag),
            disabled: !canSelectTag(tag)
          }"
          @click="toggleTag(tag)"
        >
          <span class="tag-emoji">{{ tag.emoji }}</span>
          <span class="tag-name">{{ tag.name }}</span>
          <span class="tag-count" v-if="tag.count">({{ tag.count }})</span>
        </div>
      </div>
      
      <!-- 📝 自定义标签 -->
      <div class="custom-tag-section">
        <van-field
          v-model="customTagName"
          placeholder="💡 创建自定义标签..."
          @keyup.enter="addCustomTag"
        >
          <template #button>
            <van-button 
              size="small" 
              type="primary"
              :disabled="!customTagName.trim()"
              @click="addCustomTag"
            >
              ➕ 添加
            </van-button>
          </template>
        </van-field>
      </div>
    </div>
  </div>
</template>
js
// 🏷️ TagSelector.vue
import { ref, computed, reactive } from 'vue';
import { useCustomFieldValue } from '@vant/use';

export default {
  name: 'TagSelector',
  props: {
    maxSelection: { type: Number, default: 5 },
    allowCustom: { type: Boolean, default: true }
  },
  setup(props) {
    const searchKeyword = ref('');
    const activeCategory = ref('all');
    const customTagName = ref('');
    const selectedTags = ref([]);
    
    // 🎯 将选中的标签注册到表单
    useCustomFieldValue(() => ({
      tags: selectedTags.value,
      tagIds: selectedTags.value.map(tag => tag.id),
      tagNames: selectedTags.value.map(tag => tag.name),
      count: selectedTags.value.length
    }));
    
    // 📂 标签分类
    const categories = [
      { id: 'all', name: '全部', emoji: '🌟' },
      { id: 'tech', name: '技术', emoji: '💻' },
      { id: 'design', name: '设计', emoji: '🎨' },
      { id: 'business', name: '商业', emoji: '💼' },
      { id: 'life', name: '生活', emoji: '🌱' }
    ];
    
    // 🏷️ 预设标签
    const allTags = ref([
      // 技术类
      { id: 1, name: 'Vue.js', emoji: '💚', category: 'tech', count: 1234 },
      { id: 2, name: 'React', emoji: '⚛️', category: 'tech', count: 2345 },
      { id: 3, name: 'TypeScript', emoji: '🔷', category: 'tech', count: 987 },
      { id: 4, name: 'Node.js', emoji: '🟢', category: 'tech', count: 1567 },
      
      // 设计类
      { id: 5, name: 'UI设计', emoji: '🎨', category: 'design', count: 876 },
      { id: 6, name: 'UX体验', emoji: '✨', category: 'design', count: 654 },
      { id: 7, name: '原型设计', emoji: '📐', category: 'design', count: 432 },
      
      // 商业类
      { id: 8, name: '产品管理', emoji: '📊', category: 'business', count: 789 },
      { id: 9, name: '市场营销', emoji: '📈', category: 'business', count: 567 },
      { id: 10, name: '数据分析', emoji: '📉', category: 'business', count: 345 },
      
      // 生活类
      { id: 11, name: '健身运动', emoji: '💪', category: 'life', count: 234 },
      { id: 12, name: '美食烹饪', emoji: '🍳', category: 'life', count: 456 },
      { id: 13, name: '旅行摄影', emoji: '📸', category: 'life', count: 678 }
    ]);
    
    // 🔍 过滤标签
    const filteredTags = computed(() => {
      let tags = allTags.value;
      
      // 按分类过滤
      if (activeCategory.value !== 'all') {
        tags = tags.filter(tag => tag.category === activeCategory.value);
      }
      
      // 按搜索关键词过滤
      if (searchKeyword.value.trim()) {
        const keyword = searchKeyword.value.toLowerCase();
        tags = tags.filter(tag => 
          tag.name.toLowerCase().includes(keyword) ||
          tag.emoji.includes(keyword)
        );
      }
      
      return tags;
    });
    
    // 🎮 标签操作
    const toggleTag = (tag) => {
      if (!canSelectTag(tag)) return;
      
      if (isTagSelected(tag)) {
        removeTag(tag);
      } else {
        addTag(tag);
      }
    };
    
    const addTag = (tag) => {
      if (selectedTags.value.length >= props.maxSelection) {
        showToast(`最多只能选择 ${props.maxSelection} 个标签`);
        return;
      }
      
      selectedTags.value.push(tag);
      console.log(`✅ 添加标签: ${tag.emoji} ${tag.name}`);
    };
    
    const removeTag = (tag) => {
      const index = selectedTags.value.findIndex(t => t.id === tag.id);
      if (index > -1) {
        selectedTags.value.splice(index, 1);
        console.log(`❌ 移除标签: ${tag.emoji} ${tag.name}`);
      }
    };
    
    const isTagSelected = (tag) => {
      return selectedTags.value.some(t => t.id === tag.id);
    };
    
    const canSelectTag = (tag) => {
      return !isTagSelected(tag) && selectedTags.value.length < props.maxSelection;
    };
    
    // 📂 分类切换
    const setActiveCategory = (categoryId) => {
      activeCategory.value = categoryId;
      console.log(`📂 切换分类: ${categoryId}`);
    };
    
    // 🔍 搜索处理
    const handleSearch = (value) => {
      console.log(`🔍 搜索标签: ${value}`);
    };
    
    // 💡 添加自定义标签
    const addCustomTag = () => {
      const name = customTagName.value.trim();
      if (!name) return;
      
      // 检查是否已存在
      const exists = allTags.value.some(tag => 
        tag.name.toLowerCase() === name.toLowerCase()
      );
      
      if (exists) {
        showToast('该标签已存在');
        return;
      }
      
      // 创建新标签
      const newTag = {
        id: Date.now(),
        name,
        emoji: '💡',
        category: 'custom',
        count: 0,
        isCustom: true
      };
      
      allTags.value.push(newTag);
      addTag(newTag);
      customTagName.value = '';
      
      console.log(`💡 创建自定义标签: ${name}`);
    };
    
    return {
      searchKeyword,
      activeCategory,
      customTagName,
      selectedTags,
      categories,
      filteredTags,
      toggleTag,
      removeTag,
      isTagSelected,
      canSelectTag,
      setActiveCategory,
      handleSearch,
      addCustomTag
    };
  }
};

📚 API 参考

🔧 类型定义

ts
// 🎯 自定义表单值函数
function useCustomFieldValue(customValue: () => unknown): void;

// 💡 使用示例类型
type CustomValue = 
  | string          // 🔤 简单字符串值
  | number          // 🔢 数字值
  | boolean         // ✅ 布尔值
  | object          // 📦 复杂对象
  | Array<any>      // 📋 数组数据
  | null            // 🚫 空值
  | undefined;      // ❓ 未定义

// 🎨 常见自定义组件值类型
interface RatingValue {
  rating: number;           // ⭐ 评分值
  description?: string;     // 📝 评分描述
}

interface PriceRangeValue {
  min: number;             // 💰 最小价格
  max: number;             // 💎 最大价格
  formatted: string;       // 🎨 格式化显示
}

interface TagsValue {
  tags: Tag[];             // 🏷️ 标签数组
  tagIds: number[];        // 🆔 标签ID数组
  tagNames: string[];      // 📝 标签名称数组
  count: number;           // 📊 标签数量
}

📋 参数说明

参数说明类型默认值
customValue🎯 获取表单项值的函数
💡 返回值将作为表单项的值参与表单数据收集和验证
() => unknown-

🎮 使用要点

✅ 正确用法

  1. 🎯 在组件内部调用

    js
    // ✅ 在自定义组件的 setup 中调用
    export default {
      setup() {
        const value = ref('');
        useCustomFieldValue(() => value.value);
        return { value };
      }
    };
  2. 📊 返回响应式数据

    js
    // ✅ 返回响应式数据,自动同步更新
    const data = reactive({ count: 0, name: '' });
    useCustomFieldValue(() => data);
  3. 🔄 动态计算值

    js
    // ✅ 返回计算属性或动态值
    const items = ref([]);
    useCustomFieldValue(() => ({
      items: items.value,
      count: items.value.length,
      isEmpty: items.value.length === 0
    }));

❌ 避免的用法

  1. 🚫 在组件外部调用

    js
    // ❌ 不要在组件外部调用
    const value = ref('');
    useCustomFieldValue(() => value.value); // 错误!
    
    export default {
      setup() {
        return { value };
      }
    };
  2. 🚫 返回非响应式数据

    js
    // ❌ 返回静态值,无法响应变化
    useCustomFieldValue(() => 'static value');

🎯 实际应用场景

🛒 电商场景

js
// 🌟 商品评分组件
const ratingComponent = () => {
  const rating = ref(0);
  useCustomFieldValue(() => ({
    rating: rating.value,
    text: getRatingText(rating.value)
  }));
};

// 💰 价格范围选择器
const priceRangeComponent = () => {
  const range = reactive({ min: 0, max: 1000 });
  useCustomFieldValue(() => range);
};

📱 移动应用

js
// 📸 图片上传组件
const imageUploaderComponent = () => {
  const images = ref([]);
  useCustomFieldValue(() => ({
    images: images.value,
    count: images.value.length,
    urls: images.value.map(img => img.url)
  }));
};

// 📍 地址选择器
const addressPickerComponent = () => {
  const address = reactive({
    province: '',
    city: '',
    district: '',
    detail: ''
  });
  useCustomFieldValue(() => address);
};

🏢 企业应用

js
// 👥 人员选择器
const memberSelectorComponent = () => {
  const selectedMembers = ref([]);
  useCustomFieldValue(() => ({
    members: selectedMembers.value,
    memberIds: selectedMembers.value.map(m => m.id),
    count: selectedMembers.value.length
  }));
};

// 📊 数据图表组件
const chartComponent = () => {
  const chartData = ref(null);
  useCustomFieldValue(() => ({
    data: chartData.value,
    type: 'chart',
    timestamp: Date.now()
  }));
};

💡 最佳实践

✅ 推荐做法

  1. 🎯 明确的数据结构

    js
    // ✅ 返回结构化数据,便于表单处理
    useCustomFieldValue(() => ({
      value: currentValue.value,
      displayText: getDisplayText(),
      isValid: validateValue(),
      metadata: getMetadata()
    }));
  2. 🔄 响应式数据同步

    js
    // ✅ 使用响应式数据,确保实时同步
    const formValue = computed(() => ({
      ...baseData.value,
      computed: calculateValue()
    }));
    useCustomFieldValue(() => formValue.value);
  3. ✅ 数据验证支持

    js
    // ✅ 配合表单验证使用
    const validateCustomValue = (value) => {
      if (!value || !value.required) {
        return '请完成必填项';
      }
      return true;
    };

❌ 避免的做法

  1. 🚫 频繁的复杂计算

    js
    // ❌ 避免在返回函数中进行复杂计算
    useCustomFieldValue(() => {
      // 复杂的计算逻辑...
      return heavyCalculation(); // 可能影响性能
    });
  2. 🚫 副作用操作

    js
    // ❌ 不要在返回函数中执行副作用
    useCustomFieldValue(() => {
      console.log('value changed'); // 副作用
      updateOtherState(); // 副作用
      return value.value;
    });

🛠️ 调试技巧

🔍 数据监控

js
// 📊 监控表单值变化
const debugCustomValue = () => {
  const value = ref('');
  
  useCustomFieldValue(() => {
    const currentValue = value.value;
    console.log('🎯 自定义表单值更新:', {
      value: currentValue,
      type: typeof currentValue,
      timestamp: new Date().toISOString()
    });
    return currentValue;
  });
  
  return { value };
};

🧪 表单集成测试

js
// 🧪 测试表单数据收集
const testFormIntegration = () => {
  const testValue = ref({ test: true });
  
  useCustomFieldValue(() => {
    console.log('📋 表单收集数据:', testValue.value);
    return testValue.value;
  });
  
  // 🎮 模拟数据变化
  setTimeout(() => {
    testValue.value = { test: false, updated: true };
  }, 1000);
};

📚 相关文档

📋 表单相关

🎮 状态管理

🛠️ 开发工具

💡 实战案例

基於Vant構建的企業級移動端解決方案