大数据模型练习1

html 复制代码
<template>
  <el-dialog
    v-model="visible"
    title="搜索项配置"
    width="900px"
    class="custom-transfer-dialog"
    @open="initData"
    align-center
  >
    <div class="transfer-container">
      <div class="transfer-panel left-panel">
        <div class="panel-header">
          <div class="header-left-group">
            <el-checkbox
              v-model="isAllLeftChecked"
              :indeterminate="isLeftIndeterminate"
              @change="handleLeftAllChange"
            >
              全部待选项
            </el-checkbox>
          </div>
          <span class="count">{{ checkedLeft.length }}/{{ leftListFiltered.length }}</span>
        </div>

        <div class="panel-body">
          <div class="search-bar-body">
            <el-input
              v-model="leftSearchKeyword"
              placeholder="请输入关键词"
              :prefix-icon="Search"
              clearable
            />
          </div>

          <div class="list-container">
            <el-checkbox-group v-model="checkedLeft">
              <div v-for="item in leftListFiltered" :key="item.key" class="list-item">
                <el-checkbox :label="item.key">
                  {{ item.label }}
                </el-checkbox>
              </div>
              <div v-if="leftListFiltered.length === 0" class="empty-text">无匹配数据</div>
            </el-checkbox-group>
          </div>
        </div>
      </div>

      <div class="transfer-buttons">
        <el-button
          type="primary"
          :disabled="checkedLeft.length === 0"
          class="operate-btn"
          @click="moveToRight"
        >
          <el-icon><ArrowRight /></el-icon>
        </el-button>

        <el-button
          type="info"
          plain
          :disabled="checkedRight.length === 0"
          class="operate-btn"
          @click="moveToLeft"
        >
          <el-icon><ArrowLeft /></el-icon>
        </el-button>
      </div>

      <div class="transfer-panel right-panel">
        <div class="panel-header right-header-layout">
          <div class="header-check-area">
            <el-checkbox
              v-model="isAllRightChecked"
              :indeterminate="isRightIndeterminate"
              @change="handleRightAllChange"
            >
              已选搜索项
            </el-checkbox>
            <span class="count">已选 {{ checkedRight.length }}/{{ rightListFiltered.length }}</span>
          </div>

          <div class="header-search-area">
            <el-input
              v-model="rightSearchKeyword"
              placeholder="请输入字段名称"
              :prefix-icon="Search"
              size="small"
              clearable
              class="header-input"
            />
          </div>
        </div>

        <div class="panel-body">
          <div class="list-container full-height">
            <el-checkbox-group v-model="checkedRight">
              <div v-for="item in rightListFiltered" :key="item.key" class="list-item">
                <el-checkbox :label="item.key">
                  {{ item.label }}
                </el-checkbox>
              </div>
              <div v-if="rightListFiltered.length === 0" class="empty-text">无数据</div>
            </el-checkbox-group>
          </div>
        </div>
      </div>
    </div>

    <template #footer>
      <span class="dialog-footer">
        <el-button @click="visible = false">取消</el-button>
        <el-button type="primary" @click="handleConfirm">确定</el-button>
      </span>
    </template>
  </el-dialog>
</template>

<script setup>
import { computed, ref, watch } from 'vue'
import { useSearchStore } from '../../stores/searchStore'
import { Search, ArrowRight, ArrowLeft } from '@element-plus/icons-vue'

const props = defineProps(['modelValue'])
const emit = defineEmits(['update:modelValue'])
const searchStore = useSearchStore()

// === 状态定义 ===
const visible = computed({
  get: () => props.modelValue,
  set: (val) => emit('update:modelValue', val),
})

const currentTargetKeys = ref([]) // 右侧实际包含的 Key
const checkedLeft = ref([]) // 左侧当前勾选的 Key
const checkedRight = ref([]) // 右侧当前勾选的 Key

const leftSearchKeyword = ref('')
const rightSearchKeyword = ref('')

// 全选状态
const isAllLeftChecked = ref(false)
const isLeftIndeterminate = ref(false)
const isAllRightChecked = ref(false)
const isRightIndeterminate = ref(false)

// === 数据源计算 ===

// 1. 左侧列表:所有字段 减去 已在右侧的字段
const leftList = computed(() => {
  return searchStore.allFields.filter((f) => !currentTargetKeys.value.includes(f.key))
})
const leftListFiltered = computed(() => {
  if (!leftSearchKeyword.value) return leftList.value
  return leftList.value.filter((item) =>
    item.label.toLowerCase().includes(leftSearchKeyword.value.toLowerCase()),
  )
})

// 2. 右侧列表:根据 currentTargetKeys 获取完整对象
const rightList = computed(() => {
  return currentTargetKeys.value
    .map((key) => searchStore.allFields.find((f) => f.key === key))
    .filter(Boolean)
})
const rightListFiltered = computed(() => {
  if (!rightSearchKeyword.value) return rightList.value
  return rightList.value.filter((item) =>
    item.label.toLowerCase().includes(rightSearchKeyword.value.toLowerCase()),
  )
})

// === 监听与逻辑 ===

// 初始化
const initData = () => {
  currentTargetKeys.value = [...searchStore.activeFieldKeys]
  checkedLeft.value = []
  checkedRight.value = []
  leftSearchKeyword.value = ''
  rightSearchKeyword.value = ''
  updateLeftCheckState()
  updateRightCheckState()
}

// 左侧 Checkbox 状态逻辑
watch(checkedLeft, () => updateLeftCheckState())
const updateLeftCheckState = () => {
  const checkedCount = checkedLeft.value.length
  const totalCount = leftListFiltered.value.length
  isAllLeftChecked.value = totalCount > 0 && checkedCount === totalCount
  isLeftIndeterminate.value = checkedCount > 0 && checkedCount < totalCount
}
const handleLeftAllChange = (val) => {
  checkedLeft.value = val ? leftListFiltered.value.map((i) => i.key) : []
  isLeftIndeterminate.value = false
}

// 右侧 Checkbox 状态逻辑 (新增)
watch(checkedRight, () => updateRightCheckState())
const updateRightCheckState = () => {
  const checkedCount = checkedRight.value.length
  const totalCount = rightListFiltered.value.length
  isAllRightChecked.value = totalCount > 0 && checkedCount === totalCount
  isRightIndeterminate.value = checkedCount > 0 && checkedCount < totalCount
}
const handleRightAllChange = (val) => {
  checkedRight.value = val ? rightListFiltered.value.map((i) => i.key) : []
  isRightIndeterminate.value = false
}

// 移动逻辑
const moveToRight = () => {
  currentTargetKeys.value.push(...checkedLeft.value)
  checkedLeft.value = []
  // 清空左侧搜索,体验更好,也可不保留
  // leftSearchKeyword.value = ''
}

const moveToLeft = () => {
  currentTargetKeys.value = currentTargetKeys.value.filter(
    (key) => !checkedRight.value.includes(key),
  )
  checkedRight.value = []
}

const handleConfirm = () => {
  searchStore.updateActiveFields(currentTargetKeys.value)
  visible.value = false
}
</script>

<style scoped>
.transfer-container {
  display: flex;
  justify-content: space-between;
  align-items: center;
  height: 500px;
}

/* 左右面板基础 */
.transfer-panel {
  width: 45%;
  height: 100%;
  border: 1px solid #dcdfe6;
  border-radius: 4px;
  display: flex;
  flex-direction: column;
  background: #fff;
  overflow: hidden;
}

/* 标题栏通用 */
.panel-header {
  height: 48px; /* 稍微加高一点以容纳搜索框 */
  background: #f5f7fa;
  border-bottom: 1px solid #dcdfe6;
  display: flex;
  align-items: center;
  padding: 0 12px;
  font-size: 14px;
  color: #606266;
}

/* 左侧标题布局 */
.panel-header {
  justify-content: space-between;
}

/* 右侧标题布局:Flex 布局让搜索框靠右 */
.right-header-layout {
  justify-content: space-between;
  padding-right: 8px; /* 右侧稍微留点空隙 */
}

.header-check-area {
  display: flex;
  align-items: center;
  gap: 12px; /* Checkbox和计数之间的间距 */
}

/* 搜索框区域 */
.header-search-area {
  width: 140px; /* 限制头部搜索框宽度 */
}

.count {
  font-size: 12px;
  color: #909399;
}

/* 面板内容区 */
.panel-body {
  flex: 1;
  display: flex;
  flex-direction: column;
  padding: 8px;
  overflow: hidden;
}

/* 左侧内部搜索框 */
.search-bar-body {
  margin-bottom: 8px;
}

.list-container {
  flex: 1;
  overflow-y: auto;
  border: 1px solid #ebeef5;
  border-radius: 2px;
}

.list-container.full-height {
  /* 右侧没有内部搜索框,列表直接占满 */
  margin-top: 0;
}

.list-item {
  padding: 6px 10px;
  cursor: pointer;
}
.list-item:hover {
  background-color: #f5f7fa;
}

/* 按钮区 */
.transfer-buttons {
  display: flex;
  flex-direction: column;
  gap: 12px;
}

.operate-btn {
  margin-left: 0 !important;
  width: 40px;
  height: 40px;
  display: flex;
  justify-content: center;
  align-items: center;
  border-radius: 4px;
}

.empty-text {
  text-align: center;
  color: #909399;
  margin-top: 20px;
  font-size: 13px;
}

/* 深度样式 */
:deep(.el-checkbox__label) {
  padding-left: 8px;
  color: #333;
  font-weight: normal;
}

/* 调整头部输入框样式 */
:deep(.header-input .el-input__wrapper) {
  background-color: #fff;
  box-shadow: 0 0 0 1px #dcdfe6 inset;
}
</style>
相关推荐
EveryPossible4 小时前
大数据模型练习2
大数据
会员源码网4 小时前
Elasticsearch从零启动指南:安装、配置、启停与排坑全解析
大数据·elasticsearch·搜索引擎
才盛智能科技4 小时前
元K:自助KTV行业AI生态领航者
大数据·人工智能·物联网·自助ktv系统·才盛云自助ktv系统
xuefuhe4 小时前
postgresql xmin xmax cmin cmax ctid
大数据·数据库
昨夜见军贴06164 小时前
合规性管理的现代化实践:IACheck的AI审核如何系统提升生产型检测报告的合规水平
大数据·运维·人工智能
wuxi_joe5 小时前
一家研发制造企业的“软件进化史”
大数据·数据库·制造
Lansonli5 小时前
大数据Spark(七十九):Action行动算子countByKey和countByValue使用案例
大数据·分布式·spark
发哥来了5 小时前
主流AI视频生成模型商用化能力评测:三大核心维度对比分析
大数据·人工智能·音视频
Hello.Reader5 小时前
Flink CLI 从提交作业到 Savepoint/Checkpoint、再到 YARN/K8S 与 PyFlink
大数据·flink·kubernetes