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

演示效果

实现分项多选功能,原始数据格式如下(来自 国内直连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>
相关推荐
麻花201313 分钟前
WPF里面的C1FlexGrid表格控件添加RadioButton单选
java·服务器·前端
理想不理想v27 分钟前
【经典】webpack和vite的区别?
java·前端·javascript·vue.js·面试
羊子雄起38 分钟前
CKEditor前端样式和编辑器的样式不一致的问题
前端·编辑器
聊无生1 小时前
JavaSrcipt 函数高级
开发语言·前端·javascript
xiyusec1 小时前
HTML基础
前端·html
好开心332 小时前
javaScript交互案例2
开发语言·前端·javascript·html·ecmascript·交互
xChive2 小时前
优化表单交互:在 el-select 组件中嵌入表格显示选项
前端·vue.js·交互·element-plus
tian-ming2 小时前
(十八)JavaWeb后端开发案例——会话/yml/过滤器/拦截器
java·开发语言·前端
_jacobfu2 小时前
mac2024 安装node和vue
前端·javascript·vue.js
Ztiddler2 小时前
【npm设置代理-解决npm网络连接error network失败问题】
前端·后端·npm·node.js·vue