Vue3 + Element Plus 人员列表搜索功能实现

设计思路

使用Element Plus的el-table组件展示人员数据

在姓名表头添加搜索图标按钮

点击按钮弹出搜索对话框

在对话框中输入姓名进行搜索

实现搜索功能并高亮匹配项

下面是完整的实现代码:

复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>人员管理系统 - Vue3 + Element Plus</title>
  <!-- 引入Element Plus样式 -->
  <link rel="stylesheet" href="https://unpkg.com/element-plus/dist/index.css">
  <!-- 引入Element Plus图标 -->
  <link rel="stylesheet" href="https://unpkg.com/@element-plus/icons-vue/dist/index.css">
  <style>
    * {
      margin: 0;
      padding: 0;
      box-sizing: border-box;
    }
    body {
      font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
      background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
      min-height: 100vh;
      padding: 20px;
      color: #333;
    }
    .container {
      max-width: 1200px;
      margin: 0 auto;
      padding: 20px;
    }
    header {
      text-align: center;
      margin-bottom: 30px;
      padding: 20px;
      background: linear-gradient(90deg, #1e3c72 0%, #2a5298 100%);
      border-radius: 12px;
      color: white;
      box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
    }
    h1 {
      font-size: 2.5rem;
      margin-bottom: 10px;
    }
    .subtitle {
      font-size: 1.1rem;
      opacity: 0.9;
    }
    .card {
      background: white;
      border-radius: 12px;
      box-shadow: 0 6px 30px rgba(0, 0, 0, 0.1);
      padding: 25px;
      margin-bottom: 30px;
      transition: transform 0.3s ease, box-shadow 0.3s ease;
    }
    .card:hover {
      transform: translateY(-5px);
      box-shadow: 0 12px 40px rgba(0, 0, 0, 0.15);
    }
    .search-container {
      display: flex;
      justify-content: flex-end;
      margin-bottom: 20px;
    }
    .search-btn {
      display: flex;
      align-items: center;
      background: linear-gradient(90deg, #1e3c72 0%, #2a5298 100%);
      color: white;
      border: none;
      padding: 10px 20px;
      border-radius: 8px;
      cursor: pointer;
      transition: all 0.3s ease;
      box-shadow: 0 4px 15px rgba(30, 60, 114, 0.3);
    }
    .search-btn:hover {
      transform: translateY(-2px);
      box-shadow: 0 6px 20px rgba(30, 60, 114, 0.4);
    }
    .search-btn i {
      margin-right: 8px;
      font-size: 18px;
    }
    .highlight {
      background-color: #ffeb3b;
      font-weight: bold;
      padding: 0 2px;
      border-radius: 3px;
    }
    .stats {
      display: flex;
      justify-content: space-between;
      margin-top: 20px;
      background: #f8f9fc;
      padding: 15px;
      border-radius: 8px;
      font-size: 0.95rem;
    }
    .stat-item {
      text-align: center;
      padding: 0 15px;
    }
    .stat-value {
      font-size: 1.4rem;
      font-weight: bold;
      color: #1e3c72;
    }
    .stat-label {
      color: #6c757d;
      font-size: 0.9rem;
    }
    .footer {
      text-align: center;
      margin-top: 30px;
      color: #6c757d;
      font-size: 0.9rem;
    }
    @media (max-width: 768px) {
      .stats {
        flex-direction: column;
      }
      .stat-item {
        margin-bottom: 15px;
      }
    }
  </style>
</head>
<body>
  <div id="app">
    <div class="container">
      <header>
        <h1>人员管理系统</h1>
        <p class="subtitle">使用 Vue3 和 Element Plus 构建</p>
      </header>
      
      <main>
        <div class="card">
          <div class="search-container">
            <button class="search-btn" @click="openSearchDialog">
              <i class="el-icon-search"></i> 搜索人员
            </button>
          </div>
          
          <el-table :data="filteredPersonnel" style="width: 100%" border stripe>
            <el-table-column prop="id" label="ID" width="80" sortable></el-table-column>
            <el-table-column label="姓名" sortable>
              <template #header>
                <span>姓名</span>
                <el-button type="text" @click="openSearchDialog">
                  <i class="el-icon-search" style="margin-left: 8px; font-size: 16px;"></i>
                </el-button>
              </template>
              <template #default="scope">
                <span v-html="highlightSearchTerm(scope.row.name)"></span>
              </template>
            </el-table-column>
            <el-table-column prop="position" label="职位" width="180"></el-table-column>
            <el-table-column prop="department" label="部门" width="150"></el-table-column>
            <el-table-column prop="email" label="邮箱" width="220"></el-table-column>
            <el-table-column prop="phone" label="电话" width="150"></el-table-column>
            <el-table-column label="状态" width="120">
              <template #default="scope">
                <el-tag :type="scope.row.status === '在职' ? 'success' : 'danger'">
                  {{ scope.row.status }}
                </el-tag>
              </template>
            </el-table-column>
          </el-table>
          
          <div class="stats">
            <div class="stat-item">
              <div class="stat-value">{{ totalPersonnel }}</div>
              <div class="stat-label">总人数</div>
            </div>
            <div class="stat-item">
              <div class="stat-value">{{ activePersonnel }}</div>
              <div class="stat-label">在职人员</div>
            </div>
            <div class="stat-item">
              <div class="stat-value">{{ filteredCount }}</div>
              <div class="stat-label">搜索结果</div>
            </div>
          </div>
        </div>
      </main>
      
      <!-- 搜索对话框 -->
      <el-dialog v-model="searchDialogVisible" title="搜索人员" width="500px">
        <el-input
          v-model="searchTerm"
          placeholder="请输入人员姓名"
          clearable
          @keyup.enter="applySearch"
        >
          <template #prefix>
            <i class="el-icon-search"></i>
          </template>
        </el-input>
        
        <div style="margin-top: 20px;">
          <el-button type="primary" @click="applySearch" style="width: 100%;">
            <i class="el-icon-search"></i> 搜索
          </el-button>
          <el-button @click="clearSearch" style="width: 100%; margin-top: 10px;">
            <i class="el-icon-refresh"></i> 重置搜索
          </el-button>
        </div>
        
        <template #footer>
          <div style="text-align: center; margin-top: 10px;">
            <el-button type="text" @click="searchDialogVisible = false">关闭</el-button>
          </div>
        </template>
      </el-dialog>
      
      <footer class="footer">
        <p>© 2023 人员管理系统 | 使用 Vue3 和 Element Plus 构建</p>
      </footer>
    </div>
  </div>

  <!-- 引入Vue 3 -->
  <script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
  <!-- 引入Element Plus -->
  <script src="https://unpkg.com/element-plus/dist/index.full.js"></script>
  <!-- 引入Element Plus图标 -->
  <script src="https://unpkg.com/@element-plus/icons-vue"></script>

  <script>
    const { createApp, ref, computed } = Vue;
    
    const app = createApp({
      setup() {
        // 搜索对话框可见性
        const searchDialogVisible = ref(false);
        // 搜索关键词
        const searchTerm = ref('');
        
        // 打开搜索对话框
        const openSearchDialog = () => {
          searchDialogVisible.value = true;
        };
        
        // 应用搜索
        const applySearch = () => {
          searchDialogVisible.value = false;
        };
        
        // 清除搜索
        const clearSearch = () => {
          searchTerm.value = '';
          applySearch();
        };
        
        // 模拟人员数据
        const personnelData = ref([
          { id: 1, name: '张明', position: '前端工程师', department: '技术部', email: 'zhangming@example.com', phone: '13800138001', status: '在职' },
          { id: 2, name: '李华', position: '后端工程师', department: '技术部', email: 'lihua@example.com', phone: '13800138002', status: '在职' },
          { id: 3, name: '王芳', position: 'UI设计师', department: '设计部', email: 'wangfang@example.com', phone: '13800138003', status: '在职' },
          { id: 4, name: '刘伟', position: '产品经理', department: '产品部', email: 'liuwei@example.com', phone: '13800138004', status: '在职' },
          { id: 5, name: '陈静', position: '测试工程师', department: '质量保障部', email: 'chenjing@example.com', phone: '13800138005', status: '在职' },
          { id: 6, name: '赵阳', position: '运维工程师', department: '技术部', email: 'zhaoyang@example.com', phone: '13800138006', status: '离职' },
          { id: 7, name: '钱浩', position: '项目经理', department: '项目管理部', email: 'qianhao@example.com', phone: '13800138007', status: '在职' },
          { id: 8, name: '孙琳', position: '数据分析师', department: '数据分析部', email: 'sunlin@example.com', phone: '13800138008', status: '在职' },
          { id: 9, name: '周涛', position: '移动开发工程师', department: '技术部', email: 'zhoutao@example.com', phone: '13800138009', status: '在职' },
          { id: 10, name: '吴敏', position: 'HR专员', department: '人力资源部', email: 'wumin@example.com', phone: '13800138010', status: '在职' }
        ]);
        
        // 过滤后的人员数据
        const filteredPersonnel = computed(() => {
          if (!searchTerm.value) {
            return personnelData.value;
          }
          
          const term = searchTerm.value.toLowerCase();
          return personnelData.value.filter(person => 
            person.name.toLowerCase().includes(term)
          );
        });
        
        // 高亮搜索关键词
        const highlightSearchTerm = (name) => {
          if (!searchTerm.value) return name;
          
          const term = searchTerm.value;
          const regex = new RegExp(term, 'gi');
          return name.replace(regex, match => 
            `<span class="highlight">${match}</span>`
          );
        };
        
        // 统计信息
        const totalPersonnel = computed(() => personnelData.value.length);
        const activePersonnel = computed(() => 
          personnelData.value.filter(p => p.status === '在职').length
        );
        const filteredCount = computed(() => filteredPersonnel.value.length);
        
        return {
          searchDialogVisible,
          searchTerm,
          openSearchDialog,
          applySearch,
          clearSearch,
          filteredPersonnel,
          highlightSearchTerm,
          totalPersonnel,
          activePersonnel,
          filteredCount
        };
      }
    });
    
    // 注册Element Plus
    app.use(ElementPlus);
    
    // 注册图标组件
    const ElIcon = ElementPlus.ElIcon;
    app.component('el-icon', ElIcon);
    
    app.mount('#app');
  </script>
</body>
</html>