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

基于JavaScript和Alpine JS适配多实例多选输入组件

适配Alpine JS多选组件支持多实例不同选项

问题背景

作为Django开发者,JS基础薄弱且不了解Alpine JS,找到的多选组件只能单实例使用,表单中3个多选输入框需要各自独立的选项,但当前复用同一组选项。

修改方案

1. 重构Alpine JS组件函数

让dropdown函数支持传入目标select元素的ID,实现每个组件实例加载对应独立选项:

function dropdown(selectId) {
  return {
    options: [],
    selected: [],
    show: false,
    open() { this.show = true },
    close() { this.show = false },
    isOpen() { return this.show === true },
    select(index, event) {
      if (!this.options[index].selected) {
        this.options[index].selected = true;
        this.options[index].element = event.target;
        this.selected.push(index);
      } else {
        this.selected.splice(this.selected.lastIndexOf(index), 1);
        this.options[index].selected = false
      }
    },
    remove(index, option) {
      this.options[option].selected = false;
      this.selected.splice(index, 1);
    },
    loadOptions() {
      const options = document.getElementById(selectId).options;
      for (let i = 0; i < options.length; i++) {
        this.options.push({
          value: options[i].value,
          text: options[i].innerText,
          selected: options[i].getAttribute('selected') != null
        });
      }
    },
    selectedValues() {
      return this.selected.map((option) => {
        return this.options[option].value;
      })
    }
  }
}

2. 修改HTML结构,创建多实例

每个多选组件对应独立的隐藏<select>和唯一ID,调用dropdown时传入对应select的ID,同时修改隐藏input的name属性避免表单提交冲突:

<!-- 第一个多选组件:科目 -->
<section class="w-full mb-4">
  <select x-cloak id="select-subjects" style="display:none" class="text-gray-400">
    <option value="1">数学</option>
    <option value="2">语文</option>
    <option value="3">英语</option>
  </select>

  <label class="w-full text-sm">
    <span class="text-gray-700 dark:text-gray-400">科目</span>
    <div x-data="dropdown('select-subjects')" x-init="loadOptions()" class="h-fit w-full flex flex-col items-center mx-auto">
      <input name="subjects" type="hidden" x-bind:value="selectedValues()">
      <div class="inline-block relative w-full">
        <div class="flex flex-col items-center relative">
          <div x-on:click="open" class="w-full svelte-1l8159u">
            <div class="mt-1 p-2 flex rounded border dark:border-gray-600 dark:bg-gray-700 focus:border-purple-400 focus:outline-none focus:shadow-outline-purple dark:text-gray-400 dark:focus:shadow-outline-gray svelte-1l8159u">
              <div class="flex flex-auto flex-wrap grow gap-1">
                <template x-for="(option,index) in selected" :key="options[option].value">
                  <div
                    class="flex justify-center gap-1 items-center font-medium pl-1 pr-0.5 rounded text-purple-700 bg-purple-100 border border-purple-300">
                    <div class="text-xs font-normal leading-none max-w-full flex-initial" x-text="options[option].text"></div>
                    <div class="flex flex-auto flex-row-reverse">
                      <div x-on:click="remove(index,option)">
                        <svg xmlns="http://www.w3.org/2000/svg" role="button" viewBox="0 0 20 20" fill="currentColor" class="size-4 fill-current">
                          <path d="M6.28 5.22a.75.75 0 0 0-1.06 1.06L8.94 10l-3.72 3.72a.75.75 0 1 0 1.06 1.06L10 11.06l3.72 3.72a.75.75 0 1 0 1.06-1.06L11.06 10l3.72-3.72a.75.75 0 0 0-1.06-1.06L10 8.94 6.28 5.22Z" />
                        </svg>
                      </div>
                    </div>
                  </div>
                </template>
                <div x-show="selected.length == 0" class="flex-1">
                  <input placeholder="选择科目..."
                    class="bg-transparent p-0 border-none appearance-none outline-none h-full w-full text-gray-400 text-sm"
                    x-bind:value="selectedValues()">
                </div>
              </div>
              <div
                class="w-fit pl-2 border-l flex items-center border-gray-600 svelte-1l8159u">
                <button type="button" x-show="isOpen() === true" x-on:click="open"
                  class="cursor-pointer outline-none focus:outline-none">
                  <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="size-5 cursor-pointer outline-none focus:outline-none">
                    <path fill-rule="evenodd" d="M5.22 8.22a.75.75 0 0 1 1.06 0L10 11.94l3.72-3.72a.75.75 0 1 1 1.06 1.06l-4.25 4.25a.75.75 0 0 1-1.06 0L5.22 9.28a.75.75 0 0 1 0-1.06Z" clip-rule="evenodd" />
                  </svg>
                </button>
                <button type="button" x-show="isOpen() === false" @click="close"
                  class="cursor-pointer outline-none focus:outline-none">
                  <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="size-5 cursor-pointer outline-none focus:outline-none">
                    <path fill-rule="evenodd" d="M9.47 6.47a.75.75 0 0 1 1.06 0l4.25 4.25a.75.75 0 1 1-1.06 1.06L10 8.06l-3.72 3.72a.75.75 0 0 1-1.06-1.06l4.25-4.25Z" clip-rule="evenodd" />
                  </svg>  
                </button>
              </div>
            </div>
          </div>
          <div class="w-full px-4">
            <div x-show.transition.origin.top="isOpen()"
              class="absolute shadow top-100 text-gray-400 bg-gray-700 z-40 w-full left-0 rounded max-h-select overflow-y-auto svelte-5uyqqj"
              x-on:click.away="close">
              <div class="flex flex-col w-full">
                <template x-for="(option,index) in options" :key="option.value">
                  <div>
                    <div class="cursor-pointer w-full border-gray-600 rounded-b border-b hover:bg-purple-700"
                      @click="select(index,$event)">
                      <div x-bind:class="option.selected ? 'border-purple-600' : ''"
                        class="flex w-full items-center p-2 pl-2 hover:text-gray-100 relative">
                        <div class="w-full items-center flex">
                          <div class="mx-2 leading-6" x-text="option.text"></div>
                        </div>
                      </div>
                    </div>
                  </div>
                </template>
              </div>
            </div>
          </div>
        </div>
      </div>
    </div>
  </label>
</section>

<!-- 第二个多选组件:年级 -->
<section class="w-full mb-4">
  <select x-cloak id="select-grades" style="display:none" class="text-gray-400">
    <option value="1">初一</option>
    <option value="2">初二</option>
    <option value="3">初三</option>
  </select>

  <label class="w-full text-sm">
    <span class="text-gray-700 dark:text-gray-400">年级</span>
    <div x-data="dropdown('select-grades')" x-init="loadOptions()" class="h-fit w-full flex flex-col items-center mx-auto">
      <input name="grades" type="hidden" x-bind:value="selectedValues()">
      <!-- 复制第一个组件的内部交互结构即可 -->
      <div class="inline-block relative w-full">
        <!-- 此处省略重复的内部结构代码,与第一个组件完全一致 -->
      </div>
    </div>
  </label>
</section>

<!-- 第三个多选组件:班级 -->
<section class="w-full">
  <select x-cloak id="select-classes" style="display:none" class="text-gray-400">
    <option value="1">1班</option>
    <option value="2">2班</option>
    <option value="3">3班</option>
  </select>

  <label class="w-full text-sm">
    <span class="text-gray-700 dark:text-gray-400">班级</span>
    <div x-data="dropdown('select-classes')" x-init="loadOptions()" class="h-fit w-full flex flex-col items-center mx-auto">
      <input name="classes" type="hidden" x-bind:value="selectedValues()">
      <!-- 复制第一个组件的内部交互结构即可 -->
      <div class="inline-block relative w-full">
        <!-- 此处省略重复的内部结构代码,与第一个组件完全一致 -->
      </div>
    </div>
  </label>
</section>

3. 关键修改点说明

  • 组件参数化:通过给dropdown函数传入selectId参数,让每个实例加载对应ID的隐藏select选项,实现选项隔离。
  • 唯一标识:每个隐藏select使用唯一ID,每个隐藏input使用唯一name,避免DOM元素冲突和表单提交数据混乱。
  • 模板复用:组件内部的交互逻辑和UI结构完全复用,仅需修改外部的select选项和绑定参数即可快速创建新实例。

注意事项

  • 确保每个隐藏select的ID唯一,且与dropdown()调用时传入的参数完全匹配。
  • Django后端获取多选值时,可使用request.POST.getlist('subjects')这类方法(替换为对应input的name值),获取该组件的所有选中值数组。

内容的提问来源于stack exchange,提问作者Kdisell Q

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.17 12:25:58