前端实现选项多选效果(三层结构)

演示效果

实现分项多选功能,原始数据格式如下(来自 国内直连GPT/Claude ):

javascript 复制代码
originalData: [
        {
          devName: "21321",
          devNo: "23892819302",
          segments: ["锅 炉标", "拆卸 汽缸 清洗", "油压 清洗"]
        },
        {
          devName: "23211321",
          devNo: "432423892819302",
          segments: ["1锅 2炉标", "2拆卸 2汽缸 清洗", "油压 清洗"]
        }
      ],

实现代码(基于vue2),如果想实现Vue3或者React或者svelete等其他框架

可以使用 国内直连GPT/Claude 让AI直接转换即可,正确率99%

下面直接上代码:

javascript 复制代码
<template>
  <div class="container">
    <div v-for="(item, index) in processedData" :key="index" class="device-item">
      <h3>设备名称: {{ item.devName }}</h3>
      <p>设备编号: {{ item.devNo }}</p>
      <div class="segments-container">
        <div 
          v-for="(segment, segIndex) in item.processedSegments" 
          :key="segIndex" 
          :class="[
            'segment-group',
            {
              'active-segment': isActiveSegment(index, segIndex)
            }
          ]"
        >
          <!-- 显示原始分段作为标题 -->
          <div class="segment-title">{{ item.segments[segIndex] }}</div>
          <!-- 显示拆分后的词语 -->
          <div class="segment-words">
            <span
              v-for="(word, wordIndex) in segment"
              :key="wordIndex"
              :class="['word', { 'selected': isWordSelected(index, segIndex, wordIndex) }]"
              @click="toggleWordSelection(index, segIndex, wordIndex, word)"
            >
              {{ word }}
            </span>
          </div>
        </div>
      </div>
    </div>
  </div>
</template>

<script>
export default {
  name: 'SegmentsSelector',
  data() {
    return {
      originalData: [
        {
          devName: "21321",
          devNo: "23892819302",
          segments: ["锅 炉标", "拆卸 汽缸 清洗", "油压 清洗"]
        },
        {
          devName: "23211321",
          devNo: "432423892819302",
          segments: ["1锅 2炉标", "2拆卸 2汽缸 清洗", "油压 清洗"]
        }
      ],
      currentDeviceIndex: null, // 当前操作的设备索引
      currentSegmentIndex: null, // 当前操作的分组索引
      selectedWords: {},
    }
  },
  computed: {
    processedData() {
      return this.originalData.map(item => ({
        ...item,
        processedSegments: item.segments.map(segment => segment.split(' '))
      }));
    }
  },
  methods: {
    // 判断当前分组是否激活
    isActiveSegment(deviceIndex, segmentIndex) {
      return this.currentDeviceIndex === deviceIndex && 
             this.currentSegmentIndex === segmentIndex;
    },

    initDeviceSelection(deviceIndex) {
      if (!this.selectedWords[deviceIndex]) {
        this.$set(this.selectedWords, deviceIndex, {});
      }
    },

    initSegmentSelection(deviceIndex, segmentIndex) {
      if (!this.selectedWords[deviceIndex][segmentIndex]) {
        this.$set(this.selectedWords[deviceIndex], segmentIndex, []);
      }
    },

    isWordSelected(deviceIndex, segmentIndex, wordIndex) {
      if (!this.selectedWords[deviceIndex] || 
          !this.selectedWords[deviceIndex][segmentIndex]) {
        return false;
      }
      
      return this.selectedWords[deviceIndex][segmentIndex].some(
        selection => selection.wordIndex === wordIndex
      );
    },

    toggleWordSelection(deviceIndex, segmentIndex, wordIndex, word) {
      // 如果切换到新设备,清空所有选择
      if (this.currentDeviceIndex !== deviceIndex) {
        this.selectedWords = {};
        this.currentDeviceIndex = deviceIndex;
        this.currentSegmentIndex = segmentIndex;
      }
      // 如果在同一设备内切换到新分组,清空当前设备的其他分组选择
      else if (this.currentSegmentIndex !== segmentIndex) {
        this.$set(this.selectedWords, deviceIndex, {});
        this.currentSegmentIndex = segmentIndex;
      }

      // 初始化数据结构
      this.initDeviceSelection(deviceIndex);
      this.initSegmentSelection(deviceIndex, segmentIndex);

      const segmentSelections = this.selectedWords[deviceIndex][segmentIndex];
      const selectionIndex = segmentSelections.findIndex(
        selection => selection.wordIndex === wordIndex
      );

      if (selectionIndex === -1) {
        // 添加选中
        segmentSelections.push({
          wordIndex,
          word
        });
      } else {
        // 取消选中
        segmentSelections.splice(selectionIndex, 1);
      }

      // 触发选择变化事件
      this.emitSelectionChange();
    },

    getCurrentSelections() {
      if (this.currentDeviceIndex !== null && this.currentSegmentIndex !== null) {
        return {
          deviceIndex: this.currentDeviceIndex,
          segmentIndex: this.currentSegmentIndex,
          selections: this.selectedWords[this.currentDeviceIndex]?.[this.currentSegmentIndex] || []
        };
      }
      return null;
    },

    emitSelectionChange() {
      const currentSelections = this.getCurrentSelections();
      if (currentSelections) {
        this.$emit('selection-change', {
          deviceIndex: currentSelections.deviceIndex,
          segmentIndex: currentSelections.segmentIndex,
          selections: currentSelections.selections,
          deviceName: this.originalData[currentSelections.deviceIndex].devName,
          segmentOriginal: this.originalData[currentSelections.deviceIndex].segments[currentSelections.segmentIndex]
        });
      }
    }
  },
  watch: {
    selectedWords: {
      handler(newVal) {
        console.log('选择状态更新:', newVal);
      },
      deep: true
    }
  }
}
</script>

<style scoped>
.container {
  padding: 20px;
}

.device-item {
  margin-bottom: 20px;
  padding: 15px;
  border: 1px solid #eee;
  border-radius: 4px;
}

.segments-container {
  margin-top: 10px;
}

.segment-group {
  margin-bottom: 15px;
  padding: 12px;
  border: 2px solid transparent;
  border-radius: 4px;
  background-color: #fafafa;
  transition: all 0.3s ease;
}

/* 当前激活的分组样式 */
.segment-group.active-segment {
  border-color: #409EFF;
  background-color: #fff;
  box-shadow: 0 2px 6px rgba(0, 0, 0, 0.05);
}

.segment-title {
  font-weight: bold;
  margin-bottom: 8px;
  color: #666;
  padding-bottom: 5px;
  border-bottom: 1px solid #eee;
}

.segment-words {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
}

.word {
  padding: 5px 10px;
  border: 1px solid #ddd;
  border-radius: 4px;
  cursor: pointer;
  transition: all 0.3s;
  background-color: white;
}

.word:hover {
  background-color: #f5f5f5;
}

.word.selected {
  background-color: #409EFF;
  color: white;
  border-color: #409EFF;
}

/* 激活分组内的词语hover效果增强 */
.active-segment .word:hover {
  background-color: #f0f7ff;
  border-color: #409EFF;
}
</style>
相关推荐
学无止境鸭13 分钟前
vue读取本地excel文件并渲染到列表页面
前端·javascript·vue.js
不cong明的亚子23 分钟前
在vue中,完成@wangeditor/editor组件的大数据量加载,解决卡顿
前端·vue.js
原机小子24 分钟前
Spring Boot编程训练系统:前端与后端集成
前端·spring boot·后端
小满zs28 分钟前
React第十五章(useEffect)
前端·react.js
爱米的前端小笔记28 分钟前
前端学习八股资料CSS(一)
前端·css·经验分享·学习·职场和发展
丁总学Java1 小时前
npm list -g --depth=0(用来列出全局安装的所有 npm 软件包而不显示它们的依赖项)
前端·npm·node.js
丁总学Java1 小时前
前端开发中常用的包管理器(npm、yarn、pnpm、bower、parcel)
前端·npm·node.js
提笔惊蚂蚁1 小时前
java-web-苍穹外卖-day1:软件开发步骤简化版+后端环境搭建
java·开发语言·前端·程序人生
customer081 小时前
【开源免费】基于SpringBoot+Vue.JS美发门店管理系统(JAVA毕业设计)
java·vue.js·spring boot·后端·spring cloud·kafka·开源
灌江口话事人2 小时前
html(超文本标记语言)
前端·html