Vuetify Combobox 添加新项目作为对象而不是字符串

Vuetify Combobox add new item as object instead of string

我正在使用 Vuetify(v2.6.3) 构建一个 new/edit 项目表单。我的表单有多个组合框,它们将它们的项目从后端 API 作为对象拉出。我希望能够添加组合框能够做到的新项目。但是,当我添加新项目时,它会作为字符串添加。有没有办法将该新项目添加为对象?

我只需要新对象有一个name键,以便将它发送到API待创建

想要 New 项来自 v-combobox:

{
  name: 'New Item',
}

从 API 返回并可作为组合框选项的简化项目列表:

[
  { 
    id: 1, 
    name: 'Item 1', 
    createdAt: '2020-01-01', 
    updatedAt: '2020-01-01' 
  },
  { 
    id: 2, 
    name: 'Item 2', 
    createdAt: '2020-01-01', 
    updatedAt: '2020-01-01' 
  },
  { 
    id: 3, 
    name: 'Item 3', 
    createdAt: '2020-01-01', 
    updatedAt: '2020-01-01' 
  }
]

这是我的表格的简化版本:

<template>
  <v-card>
    <v-card-title>
      <span class="text-h5">Edit Item</span>
    </v-card-title>
    <v-card-text>
      <v-container>
        <v-row>
          <v-col>
            <v-combobox
              v-model="item"
              :items="items"
              item-text="name"
              item-value="id"
              label="ComboBox"
              return-object
            ></v-combobox>
          </v-col>
        </v-row>
      </v-container>
    </v-card-text>
  </v-card>
</template>
<script>
export default {
  name: 'TestForm',
  data() {
    return {
      item: null,
      items: [
        {
          id: 1,
          name: 'Item 1',
          createdAt: '2020-01-01',
          updatedAt: '2020-01-01',
        },
        {
          id: 2,
          name: 'Item 2',
          createdAt: '2020-01-01',
          updatedAt: '2020-01-01',
        },
        {
          id: 3,
          name: 'Item 3',
          createdAt: '2020-01-01',
          updatedAt: '2020-01-01',
        },
      ],
    }
  },
}
</script>

在我将表单发送到 API 的方法中,我可以检查每个组合框的值并在需要时转换为一个对象,但我想知道是否可以在组合框组件中处理它.

似乎ComboBox在添加新项时不能直接return一个对象。但是,看起来您可以利用组件的 input 事件来 运行 一个转换为对象的方法。

下面是我如何调用组件的 input 事件的方法:

<v-combobox
  v-model="item"
  :items="items"
  item-text="name"
  item-value="id"
  label="ComboBox"
  return-object
  @input="makeObject"
></v-combobox>

@input="makeObject"监听组件的input事件并调用makeObject方法并传递当前选中的item

然后我能够编写一个方法来检查组合框值是否为字符串。如果是,则将其转换为对象

<script>
export default {
  name: "TestForm",
  data() {
    return {
      item: null
      // Remaining code omitted for readability
    }
  }
  methods: {
    makeObject(val) {
      if (typeof val === "string") {
        this.item = {
          name: val,
        };
      }
    },
  },
};
</script>

这是我的完整组件:

<template>
  <v-card>
    <v-card-title>
      <span class="text-h5">Edit Item</span>
    </v-card-title>
    <v-card-text>
      <v-container>
        <v-row>
          <v-col>
            <v-combobox
              v-model="item"
              :items="items"
              item-text="name"
              item-value="id"
              label="ComboBox"
              return-object
              @input="makeObject"
            ></v-combobox>
          </v-col>
        </v-row>
      </v-container>
    </v-card-text>
    <v-card-text>
      {{ item }}
    </v-card-text>
  </v-card>
</template>
<script>
export default {
  name: "TestForm",
  data() {
    return {
      item: null,
      items: [
        {
          id: 1,
          name: "Item 1",
          createdAt: "2020-01-01",
          updatedAt: "2020-01-01",
        },
        {
          id: 2,
          name: "Item 2",
          createdAt: "2020-01-01",
          updatedAt: "2020-01-01",
        },
        {
          id: 3,
          name: "Item 3",
          createdAt: "2020-01-01",
          updatedAt: "2020-01-01",
        },
      ],
    };
  },
  methods: {
    makeObject(val) {
      if (typeof val === "string") {
        this.item = {
          name: val,
        };
      }
    },
  },
};
</script>

sandbox link

VComboBox 似乎不支持这一点,但您可以通过使用 component's change-event 来解决它,如果需要的话添加一个带有条目的新对象:

  1. v-combobox.

    上添加一个 change 事件处理程序(例如,命名为 onChange
  2. onChange()中,通过name查找items中的条目(即对应于VComboBoxitem-text支柱).

  3. 如果找到,将 item 设置为现有项目。

  4. 否则,使用该条目创建一个新对象,并将 item 设置为新创建的对象。请注意,新对象的 id(即对应于 VComboBoxitem-value 属性)必须是唯一的,VComboBox 才能创建和跟踪它。

<v-combobox
  v-model="item"
  @change="onChange" 1️⃣
/>
let nextId = 1

export default {
  ⋮
  methods: {
    addItem(name) {
      const newItem = {
        id: nextId++,
        name,
      }
      this.items.push(newItem)
      return newItem
    },
    1️⃣
    onChange(entry) {
      if (typeof entry === 'string' && entry.trim()) {
        2️⃣
        const item = this.items.find(item => item.name === entry)
        if (item) {
          3️⃣
          this.item = item
        } else {
          4️⃣
          this.item = this.addItem(entry)
        }
      }
    },
  },
}

demo

我发现接受的答案很有用,但这里有一个类似的解决方案,允许使用芯片的多个 selection 组合框。我用它来实现用户 select 多个“标签”的方法:

例如。 Stackblitz runnable example

<v-combobox
 v-model="selectedItems"
 :items="items"
 chips
 clearable
 multiple
 @change="OnChange"
 item-text="TagName"
 item-value="TagId"
 label="Categories"
 solo
 prepend-icon="mdi-tag-multiple"
 return-object>
    <template v-slot:selection="{ attrs, item, select, selected }">
        <v-chip
            v-bind="attrs"
            :input-value="selected"
            color="primary"
            close
            @click="select"
            @click:close="RemoveTag(item)"
        >
          <span>{{ item.TagName }}</span>
        </v-chip>
    </template>
</v-combobox>

这是处理这些标签的 Add/Removal 的代码:

private OnChange(tags: any) {
    let tagsArray = tags as [];

    tagsArray.forEach(function(tag: any) {
      // New tags are added as type "string" so we need to convert this to a Tag
      // Iterate through the selected Tags and for each "string" found convert it to a Tag
      if (typeof tag === 'string' && tag.trim()) {
        const item = this.selectedItems.find(item => item === tag);
        
        if (item) {
          let newTag = new Tag(0, tag);
          
          // Remove the string based tag
          const index = this.selectedItems.indexOf(tag, 0);
          if (index > -1) {
            this.selectedItems.splice(index, 1);
          }
          
          // Add a new tag object instead
          this.selectedItems.push(newTag);
          
          return;
        }
      }
    }.bind(this))
}
  
private RemoveTag(tagToRemove: Tag) {
    var indexOfItemToRemove = this.selectedItems.findIndex(x => x.TagName == tagToRemove.TagName);
    this.selectedItems.splice(indexOfItemToRemove, 1);
}