You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Vue 3现有表单如何将文本输入框替换为标签输入组件并提交到Firestore

实现方案

1. 先封装通用标签输入组件

在项目src/components目录下新建TagInput.vue文件,代码如下:

<template>
  <div class="tag-input-wrapper">
    <div class="tag-list">
      <span v-for="(tag, index) in localTags" :key="index" class="tag-item">
        {{ tag }}
        <button type="button" @click="removeTag(index)" class="tag-remove">×</button>
      </span>
    </div>
    <input
      type="text"
      v-model="inputValue"
      @keydown.enter.prevent="addTag"
      @keydown.comma.prevent="addTag"
      placeholder="输入后按回车或逗号添加标签"
      class="tag-input"
    />
  </div>
</template>

<script>
import { ref, watch } from 'vue'

export default {
  props: {
    modelValue: {
      type: Array,
      default: () => []
    },
    maxTags: {
      type: Number,
      default: 3 // 适配你要求的3个标签限制
    }
  },
  emits: ['update:modelValue'],
  setup(props, { emit }) {
    const inputValue = ref('')
    const localTags = ref([...props.modelValue])

    // 同步父组件绑定值
    watch(() => props.modelValue, (newVal) => {
      localTags.value = [...newVal]
    }, { deep: true })

    const addTag = () => {
      const trimmedVal = inputValue.value.trim()
      if (!trimmedVal || localTags.value.includes(trimmedVal) || localTags.value.length >= props.maxTags) return
      localTags.value.push(trimmedVal)
      emit('update:modelValue', [...localTags.value])
      inputValue.value = ''
    }

    const removeTag = (index) => {
      localTags.value.splice(index, 1)
      emit('update:modelValue', [...localTags.value])
    }

    return { inputValue, localTags, addTag, removeTag }
  }
}
</script>

<style scoped>
.tag-input-wrapper {
  border: 1px solid #ddd;
  padding: 8px;
  border-radius: 4px;
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  align-items: center;
}
.tag-list {
  display: flex;
  gap: 6px;
  flex-wrap: wrap;
}
.tag-item {
  background: #e0f2fe;
  padding: 4px 8px;
  border-radius: 4px;
  display: flex;
  align-items: center;
  gap: 4px;
}
.tag-remove {
  background: none;
  border: none;
  cursor: pointer;
  color: #0ea5e9;
  font-size: 16px;
  padding: 0 2px;
}
.tag-input {
  border: none;
  outline: none;
  flex: 1;
  min-width: 120px;
}
</style>

2. 修改现有表单页面代码

2.1 替换template中对应输入框

找到原来的优点、缺点输入框部分,替换为标签组件:

<!-- 优点部分改造后 -->
<div>
  <h3>优点</h3>
  <div for="petPros">这个宠物的3个优点是什么?</div>
  <TagInput id="petPros" v-model="petPros" :max-tags="3" />
  <label for="petPros">最多添加3个标签</label>
</div>

<hr>

<!-- 缺点部分改造后 -->
<div> 
  <h3>缺点</h3>
  <div for="petCons">这个宠物的3个缺点是什么?</div>
  <TagInput id="petCons" v-model="petCons" :max-tags="3" />
  <label for="petCons">最多添加3个标签</label>
</div>

2.2 修改script部分逻辑

首先引入组件,然后把原来字符串类型的petPros、petCons改成数组类型:

<script>
import { ref } from 'vue'
import useStorage from '@/composables/useStorage'
import useCollection from '@/composables/useCollection'
// 引入标签组件
import TagInput from '@/components/TagInput.vue'

export default {
  // 注册组件
  components: { TagInput },
  setup() {
    const { filePath, url, uploadImage } = useStorage()
    const { error, addDoc } = useCollection('reviews')
    const { user } = getUser()
    const router = useRouter()

    const pet_type = ref('')
    // 改为数组类型默认值
    const petPros = ref([])
    const petCons = ref([])
    const reviewPrivacy = ref('')

    const file = ref(null)
    const fileError = ref(null)
    const isPending = ref(false)

    const handleSubmit = async () => {
      // 可加标签非空校验
      if (petPros.value.length === 0 || petCons.value.length === 0) {
        alert('请填写至少1个优点和1个缺点')
        return
      }
      if (file.value) {
        isPending.value = true
        await uploadImage(file.value)
        const res = await addDoc({
          pet_type: pet_type.value,
          // Firestore原生支持数组类型,直接存即可
          petPros: petPros.value,
          petCons: petCons.value,
          // 如果需要兼容原来的字符串存储格式,改成如下代码即可
          // petPros: petPros.value.join(','),
          // petCons: petCons.value.join(','),
          reviewPrivacy: reviewPrivacy.value,
          userId: user.value.uid,
          userName: user.value.displayName,
          createdAt: timestamp()
        })
        isPending.value = false
        if (!error.value) {
          router.push({ name: 'ReviewDetails', params: { id: res.id }})
        }
      }
    }

    // 允许的文件类型
    const types = ['image/png', 'image/jpeg']

    const handleChange = (e) => {
      let selected = e.target.files[0]
      console.log(selected)

      if (selected && types.includes(selected.type)) {
        file.value = selected
        fileError.value = null
      } else {
        file.value = null
        fileError.value = '请选择图片文件(png、jpg或jpeg格式)'
      }
    }
    
    return { pet_type, petPros, petCons, reviewPrivacy, handleSubmit, fileError, handleChange, isPending }  
  }
}
</script>

说明

  • Firestore原生支持数组类型字段,直接存储标签数组即可,不需要额外转换,后续查询过滤也更灵活
  • 组件默认限制最多3个标签,符合你的业务需求,可以通过max-tags属性调整限制数量
  • 你可以根据需要自行调整组件的样式,匹配现有表单的UI风格

内容的提问来源于stack exchange,提问作者Mark Gavagan

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.01 19:15:03