大数据模型练习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>
相关推荐
玄微云22 分钟前
2026年通用软件难适配,垂直店务系统反而更省心
大数据·云计算·软件需求
Elastic 中国社区官方博客1 小时前
Elastic 为什么捐赠其 OpenTelemetry PHP 发行版
大数据·开发语言·elasticsearch·搜索引擎·信息可视化·全文检索·php
方向研究2 小时前
ABS生产
大数据
TDengine (老段)2 小时前
TDengine 视图功能使用
大数据·数据库·servlet·时序数据库·tdengine·涛思数据
TDengine (老段)2 小时前
TDengine IDMP 运维指南 —— 部署架构
大数据·运维·数据库·架构·时序数据库·tdengine·涛思数据
utmhikari2 小时前
【测试人生】变更规则校验Agent研发的一些思路
大数据·人工智能·llm·agent·变更风险·openclaw
AC赳赳老秦2 小时前
DeepSeek优化多智能体指令:避免协同冲突,提升自动化流程稳定性
android·大数据·运维·人工智能·自然语言处理·自动化·deepseek
成长之路5143 小时前
【数据集】A股上市公司数字投资数据集-含代码(2000-2024年)
大数据
jkyy20144 小时前
破局家电同质化:智能冰箱+主动健康,解锁家庭健康新赛道
大数据·人工智能·健康医疗
weiyvyy4 小时前
信息化系统建设规划篇——蓝图设计与路径规划
大数据·信息可视化·信息化系统·企业信息化核心业务模块·信息化建设