Skip to content

vue3+elementplus的下拉弹出层自定义数据示例代码(含阻止关闭弹窗示例代码)

仅仅表格展示

html
<template>
  <el-main>
    <el-alert
      title="表格选择器演示 —— 数据写死,无请求"
      type="success"
      style="margin-bottom: 20px"
    />

    <!-- ====== 单选 ====== -->
    <el-card shadow="never" header="单选">
      <el-select
        ref="singleSelect"
        v-model="singleKey"
        placeholder="请选择(单选)"
        clearable
        :filter-method="filterMethod"
        style="width: 260px"
        @visible-change="visibleChange"
      >
        <template #empty>
          <div class="table-box">
            <el-table
              ref="singleTable"
              :data="tableData"
              height="245"
              highlight-current-row
              @row-click="rowClick"
            >
              <el-table-column type="index" width="45" />
              <el-table-column prop="id" label="ID" width="180" />
              <el-table-column prop="user" label="姓名" />
            </el-table>

            <div class="pagination-box">
              <el-pagination
                small
                background
                layout="prev, pager, next"
                :total="rawData.length"
                :page-size="pageSize"
                v-model:current-page="currentPage"
                @current-change="reload"
              />
            </div>
          </div>
        </template>
      </el-select>
    </el-card>

    <div style="height: 15px" />

    <!-- ====== 多选 ====== -->
    <el-card shadow="never" header="多选">
      <el-select
        ref="multiSelect"
        v-model="multiKeys"
        placeholder="请选择(多选)"
        multiple
        clearable
        collapse-tags
        collapse-tags-tooltip
        filterable
        :filter-method="filterMethod"
        style="width: 400px"
        @visible-change="visibleChange"
        @remove-tag="removeTag"
        @clear="clearMulti"
      >
        <template #empty>
          <div class="table-box">
            <el-table
              ref="multiTable"
              :data="tableData"
              height="245"
              @select="select"
              @select-all="selectAll"
            >
              <el-table-column type="selection" width="45" />
              <el-table-column prop="id" label="ID" width="180" />
              <el-table-column prop="user" label="姓名" width="100" />
              <el-table-column prop="cip" label="最后请求IP" width="150" />
              <el-table-column prop="time" label="注册时间" />
            </el-table>

            <div class="pagination-box">
              <el-pagination
                small
                background
                layout="prev, pager, next"
                :total="rawData.length"
                :page-size="pageSize"
                v-model:current-page="currentPage"
                @current-change="reload"
              />
            </div>
          </div>
        </template>
      </el-select>
    </el-card>
  </el-main>
</template>

<script setup>
import { nextTick, ref, watch } from 'vue'

/* ------------------ 写死数据 ------------------ */
const rawData = ref([
  { id: '410000199512025445', user: '魏磊', sex: '1', time: '2023-01-15', cip: '192.168.1.101' },
  { id: '520000198407304275', user: '史平', sex: '1', time: '2023-02-20', cip: '192.168.1.102' },
  { id: '330000199003034567', user: '王丽', sex: '2', time: '2023-03-10', cip: '192.168.1.103' },
  { id: '110000198801012233', user: '李强', sex: '1', time: '2023-04-05', cip: '192.168.1.104' },
  { id: '440000199212126666', user: '赵敏', sex: '2', time: '2023-05-18', cip: '192.168.1.105' },
  { id: '310000198505059999', user: '张三', sex: '1', time: '2023-06-22', cip: '192.168.1.106' },
  { id: '220000199111114444', user: '李四', sex: '1', time: '2023-07-30', cip: '192.168.1.107' },
  { id: '150000198707077777', user: '周五', sex: '2', time: '2023-08-11', cip: '192.168.1.108' },
  { id: '640000199404048888', user: '吴六', sex: '1', time: '2023-09-03', cip: '192.168.1.109' },
  { id: '510000198909093333', user: '郑七', sex: '2', time: '2023-10-14', cip: '192.168.1.110' }
])

/* ------------------ 分页 / 搜索 ------------------ */
const pageSize = 5
const currentPage = ref(1)
const keyword = ref('')
const formData = ref({ sex: '', date: '' })

const tableData = ref([])
const reload = () => {
  let list = rawData.value.filter(item => {
    const kw = keyword.value
    if (kw && !item.user.includes(kw) && !item.id.includes(kw)) return false
    if (formData.value.sex && item.sex !== formData.value.sex) return false
    if (formData.value.date && item.time !== formData.value.date) return false
    return true
  })
  const start = (currentPage.value - 1) * pageSize
  const end = start + pageSize
  tableData.value = list.slice(start, end)
  nextTick(() => syncChecked())
}

const filterMethod = val => {
  keyword.value = val || ''
  currentPage.value = 1
  reload()
}
const formSubmit = () => {
  currentPage.value = 1
  reload()
}

/* ------------------ 单选 ------------------ */
const singleKey = ref('')
const singleSel = ref({})
const singleSelect = ref(null)

const rowClick = row => {
  singleKey.value = row.id
  singleSel.value = row
  nextTick(() => {
    singleSelect.value.selectedLabel = row.user
    console.log(singleSelect.value,'singleSelect.value')
    singleSelect.value.blur()
  })
}

/* ------------------ 多选 ------------------ */
const multiKeys = ref([])
const multiSel = ref([])
const multiTable = ref(null)

const select = (rows, row) => {
  const checked = rows.includes(row)
  if (checked) {
    if (!multiKeys.value.includes(row.id)) {
      multiKeys.value.push(row.id)
      multiSel.value.push(row)
    }
  } else {
    multiKeys.value = multiKeys.value.filter(k => k !== row.id)
    multiSel.value = multiSel.value.filter(r => r.id !== row.id)
  }
}

const selectAll = rows => {
  const checked = rows.length > 0
  if (checked) {
    rows.forEach(r => {
      if (!multiKeys.value.includes(r.id)) {
        multiKeys.value.push(r.id)
        multiSel.value.push(r)
      }
    })
  } else {
    const curPageIds = tableData.value.map(i => i.id)
    multiKeys.value = multiKeys.value.filter(k => !curPageIds.includes(k))
    multiSel.value = multiSel.value.filter(r => !curPageIds.includes(r.id))
  }
}

const removeTag = tag => {
  multiKeys.value = multiKeys.value.filter(k => k !== tag.id)
  multiSel.value = multiSel.value.filter(r => r.id !== tag.id)
  const row = tableData.value.find(r => r.id === tag.id)
  if (row) multiTable.value.toggleRowSelection(row, false)
}

const clearMulti = () => {
  multiKeys.value = []
  multiSel.value = []
}

const syncChecked = () => {
  if (!multiTable.value) return
  multiTable.value.clearSelection()
  multiSel.value.forEach(r => {
    const r2 = tableData.value.find(i => i.id === r.id)
    if (r2) multiTable.value.toggleRowSelection(r2, true)
  })
}

/* 统一 visibleChange */
const visibleChange = val => {
  if (val) reload()
  else {
    nextTick(() => {
      if (singleSel.value.user) singleSelect.value.selectedLabel = singleSel.value.user
    })
  }
}

/* 初始值 */
multiKeys.value = ['410000199512025445', '520000198407304275']
multiSel.value = [
  { id: '410000199512025445', user: '魏磊' },
  { id: '520000198407304275', user: '史平' }
]
singleKey.value = '520000198407304275'
singleSel.value = { id: '520000198407304275', user: '史平' }

watch(currentPage, reload)
</script>

<style scoped>
.table-box {
  padding: 12px;
  width: 700px;
}
.pagination-box {
  padding-top: 12px;
  text-align: right;
}
</style>

带搜索项和内部下拉选项筛选(单选)

html
<!-- ProductSelect.vue -->
<template>
  <!-- 外层仅做占位,真实内容在 #empty 插槽里 -->
  <el-select
    ref="singleSelect"
    v-model="singleKey"
    placeholder="请选择产品(单选)"
    clearable
    style="width: 260px"
  >
    <template #empty>
      <div class="table-box">
        <!-- 搜索区 -->
        <div class="sc-table-select__header">
          <el-select
            v-model="firstValue"
            placeholder="主分类"
            size="small"
            clearable
            style="width: 160px; margin-right: 12px"
            @change="onFirstPick"
            :teleported="false"
          >
            <el-option
              v-for="item in productCategories"
              :key="item.name"
              :label="item.name"
              :value="item.name"
            />
          </el-select>

          <el-select
            v-model="secondValue"
            placeholder="子项"
            size="small"
            clearable
            style="width: 160px; margin-right: 12px"
            :teleported="false"
          >
            <el-option
              v-for="child in secondOptions"
              :key="child.name"
              :label="child.name"
              :value="child.name"
            />
          </el-select>

          <el-input
            v-model="keyword"
            size="small"
            clearable
            style="width: 180px; margin-right: 12px"
            placeholder="ID 或 产品名称"
          />

          <el-button size="small" type="primary" @click="doSearch">查询</el-button>
          <el-button size="small" @click="doReset">重置</el-button>
        </div>

        <!-- 表格 -->
        <el-table
          ref="singleTable"
          :data="tableData"
          height="245"
          highlight-current-row
          @row-click="rowClick"
        >
          <el-table-column type="index" width="45" />
          <el-table-column prop="id" label="ID" width="160" />
          <el-table-column prop="main" label="主分类" width="120" />
          <el-table-column prop="sub" label="次分类" width="120" />
          <el-table-column prop="name" label="产品名称" />
        </el-table>

        <!-- 分页 -->
        <div class="pagination-box">
          <el-pagination
            small
            background
            layout="prev, pager, next"
            :total="filterTotal"
            :page-size="pageSize"
            v-model:current-page="currentPage"
            @current-change="reload"
          />
        </div>
      </div>
    </template>
  </el-select>
</template>

<script setup>
import { ref, reactive, computed, nextTick } from 'vue'

/* ------------------ 1. 原始数据 ------------------ */
const productCategories = reactive([
  { name: '积分礼品', children: [{ name: '赠品' }, { name: '杯' }, { name: '返利商城' }] },
  { name: '试用装及广促品', children: [{ name: '广促品' }, { name: '试用装' }] },
  { name: '礼包', children: [{ name: '礼包' }] }
])

// 4 列完整数据
const allList = reactive([
  { id: 'P001', main: '积分礼品', sub: '返利商城', name: '10元京东卡' },
  { id: 'P002', main: '礼包', sub: '礼包', name: '新春福袋' },
  { id: 'P003', main: '试用装及广促品', sub: '试用装', name: '小棕瓶7ml' },
  { id: 'P004', main: '积分礼品', sub: '杯', name: '星巴克联名杯' },
  { id: 'P005', main: '积分礼品', sub: '赠品', name: '定制马克杯' },
  { id: 'P006', main: '试用装及广促品', sub: '广促品', name: '品牌手提袋' },
  { id: 'P007', main: '礼包', sub: '礼包', name: '中秋礼包' },
  { id: 'P008', main: '积分礼品', sub: '返利商城', name: '50元天猫卡' },
  { id: 'P009', main: '试用装及广促品', sub: '试用装', name: '粉底液5ml' },
  { id: 'P010', main: '积分礼品', sub: '杯', name: '保温杯套装' },
  { id: 'P011', main: '礼包', sub: '礼包', name: '双11狂欢礼包' },
  { id: 'P012', main: '积分礼品', sub: '赠品', name: '数据线' },
  { id: 'P013', main: '试用装及广促品', sub: '广促品', name: '广告扇' },
  { id: 'P014', main: '积分礼品', sub: '返利商城', name: '100元苏宁卡' },
  { id: 'P015', main: '礼包', sub: '礼包', name: '圣诞礼包' },
  { id: 'P016', main: '试用装及广促品', sub: '试用装', name: '面膜单片' },
  { id: 'P017', main: '积分礼品', sub: '杯', name: '陶瓷杯' },
  { id: 'P018', main: '积分礼品', sub: '赠品', name: '鼠标垫' },
  { id: 'P019', main: '试用装及广促品', sub: '广促品', name: '宣传册' },
  { id: 'P020', main: '礼包', sub: '礼包', name: '会员专属礼包' },
  { id: 'P021', main: '积分礼品', sub: '返利商城', name: '20元话费' },
  { id: 'P022', main: '试用装及广促品', sub: '试用装', name: '洗发水50ml' },
  { id: 'P023', main: '积分礼品', sub: '杯', name: '玻璃杯' },
  { id: 'P024', main: '礼包', sub: '礼包', name: '新人礼包' },
  { id: 'P025', main: '积分礼品', sub: '赠品', name: '钥匙扣' },
  { id: 'P026', main: '试用装及广促品', sub: '广促品', name: '海报' },
  { id: 'P027', main: '积分礼品', sub: '返利商城', name: '30元饿了么红包' },
  { id: 'P028', main: '礼包', sub: '礼包', name: '端午礼包' },
  { id: 'P029', main: '试用装及广促品', sub: '试用装', name: '口红小样' },
  { id: 'P030', main: '积分礼品', sub: '杯', name: '塑料杯' },
  { id: 'P031', main: '积分礼品', sub: '赠品', name: '支架' },
  { id: 'P032', main: '试用装及广促品', sub: '广促品', name: '易拉宝' },
  { id: 'P033', main: '礼包', sub: '礼包', name: '情侣礼包' },
  { id: 'P034', main: '积分礼品', sub: '返利商城', name: '500元携程卡' },
  { id: 'P035', main: '试用装及广促品', sub: '试用装', name: '护肤乳15ml' },
  { id: 'P036', main: '积分礼品', sub: '杯', name: '不锈钢杯' },
  { id: 'P037', main: '礼包', sub: '礼包', name: '生日礼包' },
  { id: 'P038', main: '积分礼品', sub: '赠品', name: '卡包' },
  { id: 'P039', main: '试用装及广促品', sub: '广促品', name: '展架' },
  { id: 'P040', main: '积分礼品', sub: '返利商城', name: '5元支付宝红包' },
  { id: 'P041', main: '礼包', sub: '礼包', name: '开学礼包' },
  { id: 'P042', main: '试用装及广促品', sub: '试用装', name: '香水2ml' },
  { id: 'P043', main: '积分礼品', sub: '杯', name: '保温杯' },
  { id: 'P044', main: '积分礼品', sub: '赠品', name: '绕线器' },
  { id: 'P045', main: '试用装及广促品', sub: '广促品', name: '桌牌' },
  { id: 'P046', main: '礼包', sub: '礼包', name: '旅行礼包' },
  { id: 'P047', main: '积分礼品', sub: '返利商城', name: '200元加油卡' },
  { id: 'P048', main: '试用装及广促品', sub: '试用装', name: '洁面乳20ml' },
  { id: 'P049', main: '积分礼品', sub: '杯', name: '磨砂杯' },
  { id: 'P050', main: '礼包', sub: '礼包', name: '养生礼包' },
  { id: 'P051', main: '积分礼品', sub: '赠品', name: '指甲刀' },
  { id: 'P052', main: '试用装及广促品', sub: '广促品', name: '门型展架' },
  { id: 'P053', main: '积分礼品', sub: '返利商城', name: '15元美团红包' },
  { id: 'P054', main: '礼包', sub: '礼包', name: '夏日礼包' },
  { id: 'P055', main: '试用装及广促品', sub: '试用装', name: '防晒5ml' },
  { id: 'P056', main: '积分礼品', sub: '杯', name: '折叠杯' },
  { id: 'P057', main: '积分礼品', sub: '赠品', name: '开瓶器' },
  { id: 'P058', main: '试用装及广促品', sub: '广促品', name: '旗帜' },
  { id: 'P059', main: '礼包', sub: '礼包', name: '儿童礼包' },
  { id: 'P060', main: '积分礼品', sub: '返利商城', name: '80元滴滴券' },
  { id: 'P061', main: '试用装及广促品', sub: '试用装', name: '精华3ml' },
  { id: 'P062', main: '积分礼品', sub: '杯', name: '咖啡杯' },
  { id: 'P063', main: '礼包', sub: '礼包', name: '健身礼包' },
  { id: 'P064', main: '积分礼品', sub: '赠品', name: '购物袋' },
  { id: 'P065', main: '试用装及广促品', sub: '广促品', name: '横幅' },
  { id: 'P066', main: '积分礼品', sub: '返利商城', name: '99元网易严选卡' },
  { id: 'P067', main: '礼包', sub: '礼包', name: '厨房礼包' },
  { id: 'P068', main: '试用装及广促品', sub: '试用装', name: '眼霜3g' },
  { id: 'P069', main: '积分礼品', sub: '杯', name: '运动水杯' },
  { id: 'P070', main: '积分礼品', sub: '赠品', name: '冰箱贴' },
  { id: 'P071', main: '试用装及广促品', sub: '广促品', name: '灯箱' },
  { id: 'P072', main: '礼包', sub: '礼包', name: '宠物礼包' },
  { id: 'P073', main: '积分礼品', sub: '返利商城', name: '66元喜茶券' },
  { id: 'P074', main: '试用装及广促品', sub: '试用装', name: 'BB霜8ml' },
  { id: 'P075', main: '积分礼品', sub: '杯', name: '啤酒杯' },
  { id: 'P076', main: '礼包', sub: '礼包', name: '女神礼包' },
  { id: 'P077', main: '积分礼品', sub: '赠品', name: '手机支架' },
  { id: 'P078', main: '试用装及广促品', sub: '广促品', name: '吊旗' },
  { id: 'P079', main: '积分礼品', sub: '返利商城', name: '150元盒马卡' },
  { id: 'P080', main: '礼包', sub: '礼包', name: '电竞礼包' },
  { id: 'P081', main: '试用装及广促品', sub: '试用装', name: '卸妆水30ml' },
  { id: 'P082', main: '积分礼品', sub: '杯', name: '威士忌杯' },
  { id: 'P083', main: '积分礼品', sub: '赠品', name: '魔方' },
  { id: 'P084', main: '试用装及广促品', sub: '广促品', name: '手举牌' },
  { id: 'P085', main: '礼包', sub: '礼包', name: '程序员礼包' },
  { id: 'P086', main: '积分礼品', sub: '返利商城', name: '88元星巴克卡' },
  { id: 'P087', main: '试用装及广促品', sub: '试用装', name: '护发素20ml' },
  { id: 'P088', main: '积分礼品', sub: '杯', name: '搪瓷杯' },
  { id: 'P089', main: '礼包', sub: '礼包', name: '美白礼包' },
  { id: 'P090', main: '积分礼品', sub: '赠品', name: '挖耳勺' },
  { id: 'P091', main: '试用装及广促品', sub: '广促品', name: '背景板' },
  { id: 'P092', main: '积分礼品', sub: '返利商城', name: '300元叮咚卡' },
  { id: 'P093', main: '礼包', sub: '礼包', name: '懒人礼包' },
  { id: 'P094', main: '试用装及广促品', sub: '试用装', name: '精油1ml' },
  { id: 'P095', main: '积分礼品', sub: '杯', name: '果汁杯' },
  { id: 'P096', main: '积分礼品', sub: '赠品', name: '开快递刀' },
  { id: 'P097', main: '试用装及广促品', sub: '广促品', name: '注水旗' },
  { id: 'P098', main: '礼包', sub: '礼包', name: '露营礼包' },
  { id: 'P099', main: '积分礼品', sub: '返利商城', name: '66元瑞幸券' },
  { id: 'P100', main: '试用装及广促品', sub: '试用装', name: '素颜霜10ml' },
  { id: 'P101', main: '积分礼品', sub: '杯', name: '保温杯Pro' },
  { id: 'P102', main: '礼包', sub: '礼包', name: '火锅礼包' },
  { id: 'P103', main: '积分礼品', sub: '赠品', name: '鞋拔子' },
  { id: 'P104', main: '试用装及广促品', sub: '广促品', name: 'X展架' },
  { id: 'P105', main: '积分礼品', sub: '返利商城', name: '20元腾讯视频月卡' },
  { id: 'P106', main: '礼包', sub: '礼包', name: '学霸礼包' },
  { id: 'P107', main: '试用装及广促品', sub: '试用装', name: '隔离霜8ml' },
  { id: 'P108', main: '积分礼品', sub: '杯', name: '啤酒杯套装' },
  { id: 'P109', main: '积分礼品', sub: '赠品', name: '牙签筒' },
  { id: 'P110', main: '试用装及广促品', sub: '广促品', name: '门贴' },
  { id: 'P111', main: '礼包', sub: '礼包', name: '单身礼包' },
  { id: 'P112', main: '积分礼品', sub: '返利商城', name: '99元得到课程券' },
  { id: 'P113', main: '试用装及广促品', sub: '试用装', name: '唇釉1.5ml' },
  { id: 'P114', main: '积分礼品', sub: '杯', name: '牛奶杯' },
  { id: 'P115', main: '礼包', sub: '礼包', name: '减脂礼包' },
  { id: 'P116', main: '积分礼品', sub: '赠品', name: '指甲锉' },
  { id: 'P117', main: '试用装及广促品', sub: '广促品', name: '拉网展架' },
  { id: 'P118', main: '积分礼品', sub: '返利商城', name: '166元猫眼电影券' },
  { id: 'P119', main: '礼包', sub: '礼包', name: '熬夜党礼包' },
  { id: 'P120', main: '试用装及广促品', sub: '试用装', name: '睫毛膏4ml' }
])

/* ------------------ 2. 搜索 / 分页 ------------------ */
const pageSize = 5
const currentPage = ref(1)
const firstValue = ref('')
const secondValue = ref('')
const keyword = ref('')

const secondOptions = computed(() => {
  if (!firstValue.value) return []
  const hit = productCategories.find(i => i.name === firstValue.value)
  return hit ? hit.children : []
})

// 过滤结果
const filterList = computed(() => {
  let arr = allList
  if (firstValue.value) arr = arr.filter(v => v.main === firstValue.value)
  if (secondValue.value) arr = arr.filter(v => v.sub === secondValue.value)
  if (keyword.value) {
    const k = keyword.value.trim().toLowerCase()
    arr = arr.filter(v => v.id.toLowerCase().includes(k) || v.name.toLowerCase().includes(k))
  }
  return arr
})

const filterTotal = computed(() => filterList.value.length)

const tableData = ref([])
const reload = () => {
  const start = (currentPage.value - 1) * pageSize
  const end = start + pageSize
  tableData.value = filterList.value.slice(start, end)
}
reload()

const doSearch = () => {
  currentPage.value = 1
  reload()
}

const doReset = () => {
  firstValue.value = ''
  secondValue.value = ''
  keyword.value = ''
  currentPage.value = 1
  reload()
}

const onFirstPick = () => {
  secondValue.value = ''
  nextTick(() => doSearch())
}

/* ------------------ 3. 单选回填 ------------------ */
const singleKey = ref('')
const singleSelect = ref(null)
const rowClick = row => {
  singleKey.value = row.id
  singleSelect.value?.blur()
}
</script>

<style scoped>
.table-box {
  padding: 12px;
  width: 700px;
}
.pagination-box {
  padding-top: 12px;
  text-align: right;
}
.sc-table-select__header {
  display: flex;
  align-items: center;
  padding: 6px 0;
}
</style>