列表页
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>优化客户管理系统</title> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> <style> * { margin: 0; padding: 0; box-sizing: border-box; font-family: 'Segoe UI', 'Microsoft YaHei', sans-serif; } :root { --primary: #4361ee; --primary-light: #eef2ff; --secondary: #6c757d; --success: #10b981; --danger: #ef476f; --light: #f8f9fa; --dark: #212529; --border: #dee2e6; --border-radius: 8px; --shadow: 0 4px 12px rgba(0, 0, 0, 0.08); } body { background: linear-gradient(135deg, #f0f5ff 0%, #e6edff 100%); color: var(--dark); padding: 20px; min-height: 100vh; display: flex; justify-content: center; align-items: center; } .container { width: 100%; max-width: 1200px; background: white; border-radius: var(--border-radius); box-shadow: var(--shadow); overflow: hidden; } /* 头部 */ .header { background: linear-gradient(120deg, #3a0ca3, var(--primary)); color: white; padding: 20px; text-align: center; } .header h1 { font-size: 2.2rem; margin-bottom: 10px; display: flex; align-items: center; justify-content: center; gap: 12px; } .header p { opacity: 0.9; max-width: 600px; margin: 0 auto; } /* 控制栏 */ .control-bar { display: flex; flex-wrap: wrap; padding: 15px; background: var(--light); border-bottom: 1px solid var(--border); gap: 12px; align-items: center; } .search-box { flex: 1; min-width: 250px; position: relative; } .search-input { width: 100%; padding: 10px 15px 10px 40px; border: 1px solid var(--border); border-radius: var(--border-radius); font-size: 0.95rem; transition: all 0.3s; } .search-input:focus { outline: none; border-color: var(--primary); box-shadow: 0 0 0 3px rgba(67, 97, 238, 0.15); } .search-icon { position: absolute; left: 15px; top: 50%; transform: translateY(-50%); color: var(--secondary); } .action-btn { padding: 10px 16px; background: white; border: 1px solid var(--border); border-radius: var(--border-radius); cursor: pointer; display: flex; align-items: center; gap: 8px; transition: all 0.2s ease; font-weight: 500; font-size: 0.95rem; } .action-btn:hover { background: var(--primary-light); border-color: var(--primary); transform: translateY(-2px); box-shadow: 0 4px 8px rgba(0, 0, 0, 0.05); } .btn-primary { background: var(--primary); color: white; border-color: var(--primary); } .btn-primary:hover { background: #3a56d4; } .btn-danger { background: var(--danger); color: white; border-color: var(--danger); } /* 表格区域 */ .table-container { overflow-x: auto; border-bottom: 1px solid var(--border); } .data-table { width: 100%; border-collapse: collapse; min-width: 800px; } .data-table th { background: var(--light); text-align: left; padding: 14px 16px; font-weight: 600; font-size: 0.95rem; border-bottom: 2px solid var(--border); cursor: pointer; position: relative; transition: background 0.2s; } .data-table th:hover { background: #e9ecef; } .data-table th.sorted-asc::after { content: "▲"; font-size: 0.8rem; margin-left: 5px; color: var(--primary); } .data-table th.sorted-desc::after { content: "▼"; font-size: 0.8rem; margin-left: 5px; color: var(--primary); } .data-table td { padding: 12px 16px; border-bottom: 1px solid var(--border); font-size: 0.95rem; } .data-table tr:hover td { background: var(--primary-light); } .status { padding: 4px 12px; border-radius: 12px; font-size: 0.85rem; display: inline-block; font-weight: 500; } .status-active { background: rgba(16, 185, 129, 0.15); color: #0e9f6e; } .status-inactive { background: rgba(239, 71, 111, 0.15); color: #e53e3e; } .action-cell { display: flex; gap: 8px; } .action-icon { width: 32px; height: 32px; border-radius: 50%; display: flex; align-items: center; justify-content: center; cursor: pointer; transition: all 0.2s ease; } .action-icon:hover { transform: scale(1.1); box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1); } .edit-icon { background: rgba(67, 97, 238, 0.1); color: var(--primary); } .delete-icon { background: rgba(239, 71, 111, 0.1); color: var(--danger); } .checkbox-cell { width: 40px; text-align: center; } /* 分页区域 */ .pagination { display: flex; justify-content: space-between; align-items: center; padding: 16px; background: var(--light); flex-wrap: wrap; gap: 16px; } .pagination-info { color: var(--secondary); font-size: 0.95rem; display: flex; align-items: center; gap: 8px; } .pagination-controls { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; } .page-btn { min-width: 36px; height: 36px; display: flex; align-items: center; justify-content: center; border-radius: var(--border-radius); background: white; border: 1px solid var(--border); cursor: pointer; transition: all 0.2s ease; font-size: 0.9rem; padding: 0 10px; } .page-btn:hover, .page-btn.active { background: var(--primary); color: white; border-color: var(--primary); } .page-input { width: 60px; padding: 8px 10px; border: 1px solid var(--border); border-radius: var(--border-radius); text-align: center; font-size: 0.95rem; } .goto-btn { padding: 8px 14px; background: white; border: 1px solid var(--border); border-radius: var(--border-radius); cursor: pointer; transition: all 0.2s ease; font-weight: 500; } .goto-btn:hover { background: var(--primary-light); border-color: var(--primary); } /* 模态框 */ .modal-overlay { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0, 0, 0, 0.5); display: flex; align-items: center; justify-content: center; z-index: 100; opacity: 0; visibility: hidden; transition: all 0.3s ease; } .modal-overlay.show { opacity: 1; visibility: visible; } .modal { background: white; border-radius: var(--border-radius); box-shadow: 0 20px 40px rgba(0, 0, 0, 0.2); width: 100%; max-width: 500px; transform: translateY(-20px); transition: transform 0.3s ease; } .modal-overlay.show .modal { transform: translateY(0); } .modal-header { padding: 18px 20px; border-bottom: 1px solid var(--border); display: flex; align-items: center; justify-content: space-between; background: linear-gradient(to right, #f8f9fa, #e9ecef); } .modal-header h3 { font-size: 1.3rem; color: var(--primary); display: flex; align-items: center; gap: 10px; } .modal-body { padding: 20px; } .form-group { margin-bottom: 18px; } .form-group label { display: block; margin-bottom: 8px; font-weight: 500; color: var(--secondary); font-size: 0.95rem; display: flex; align-items: center; gap: 6px; } .form-control { width: 100%; padding: 11px 14px; border: 1px solid var(--border); border-radius: var(--border-radius); font-size: 0.95rem; transition: all 0.2s; } .form-control:focus { outline: none; border-color: var(--primary); box-shadow: 0 0 0 3px rgba(67, 97, 238, 0.15); } .modal-footer { padding: 16px 20px; border-top: 1px solid var(--border); display: flex; justify-content: flex-end; gap: 12px; background: #f8f9fa; } /* 空状态 */ .empty-state { text-align: center; padding: 40px 20px; color: var(--secondary); } .empty-state i { font-size: 3rem; margin-bottom: 15px; color: #ced4da; opacity: 0.7; } /* 响应式设计 */ @media (max-width: 768px) { .control-bar { flex-direction: column; align-items: stretch; } .action-bar { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; } .pagination { flex-direction: column; align-items: stretch; gap: 12px; } .pagination-controls { justify-content: center; } .header h1 { font-size: 1.8rem; } } @media (max-width: 480px) { .action-bar { grid-template-columns: 1fr; } .page-btn { padding: 0 8px; min-width: 32px; height: 32px; } } </style> </head> <body> <div class="container"> <!-- 头部 --> <div class="header"> <h1><i class="fas fa-users"></i> 客户管理系统</h1> <p>高效管理客户信息,支持搜索、排序、分页和批量操作</p> </div> <!-- 控制栏 --> <div class="control-bar"> <div class="search-box"> <i class="fas fa-search search-icon"></i> <input type="text" id="searchInput" class="search-input" placeholder="输入关键词搜索..."> </div> <div class="action-bar"> <button class="action-btn btn-primary" id="addCustomerBtn"> <i class="fas fa-plus"></i> 新增客户 </button> <button class="action-btn btn-danger" id="batchDeleteBtn"> <i class="fas fa-trash-alt"></i> 批量删除 </button> <button class="action-btn" id="refreshBtn"> <i class="fas fa-sync-alt"></i> 刷新数据 </button> </div> </div> <!-- 表格区域 --> <div class="table-container"> <table class="data-table"> <thead> <tr> <th class="checkbox-cell"><input type="checkbox" id="selectAll"></th> <th data-sort="name">客户姓名</th> <th data-sort="phone">联系电话</th> <th data-sort="email">电子邮箱</th> <th data-sort="company">公司名称</th> <th data-sort="status">状态</th> <th>操作</th> </tr> </thead> <tbody id="customerTableBody"> <!-- 客户数据将通过JavaScript动态生成 --> </tbody> </table> </div> <!-- 分页区域 --> <div class="pagination"> <div class="pagination-info"> <i class="fas fa-info-circle"></i> <span id="paginationInfo">显示 0 条记录</span> </div> <div class="pagination-controls"> <button class="page-btn" id="firstPageBtn" title="第一页"><i class="fas fa-angle-double-left"></i></button> <button class="page-btn" id="prevPageBtn" title="上一页"><i class="fas fa-angle-left"></i></button> <input type="number" id="pageInput" class="page-input" min="1" value="1"> <span>/</span> <span id="totalPages">1</span> <button class="page-btn" id="nextPageBtn" title="下一页"><i class="fas fa-angle-right"></i></button> <button class="page-btn" id="lastPageBtn" title="最后一页"><i class="fas fa-angle-double-right"></i></button> <button class="goto-btn" id="gotoPageBtn">跳转</button> </div> </div> </div> <!-- 新增/编辑客户模态框 --> <div class="modal-overlay" id="customerModal"> <div class="modal"> <div class="modal-header"> <h3><i class="fas fa-user-edit"></i> <span id="modalTitle">新增客户</span></h3> <button class="action-icon edit-icon" id="closeModal"> <i class="fas fa-times"></i> </button> </div> <div class="modal-body"> <div class="form-group"> <label for="customerName"><i class="fas fa-user"></i> 客户姓名 *</label> <input type="text" id="customerName" class="form-control" placeholder="请输入客户姓名"> </div> <div class="form-group"> <label for="customerPhone"><i class="fas fa-phone"></i> 联系电话 *</label> <input type="text" id="customerPhone" class="form-control" placeholder="请输入联系电话"> </div> <div class="form-group"> <label for="customerEmail"><i class="fas fa-envelope"></i> 电子邮箱 *</label> <input type="email" id="customerEmail" class="form-control" placeholder="请输入电子邮箱"> </div> <div class="form-group"> <label for="customerCompany"><i class="fas fa-building"></i> 公司名称</label> <input type="text" id="customerCompany" class="form-control" placeholder="请输入公司名称"> </div> <div class="form-group"> <label for="customerStatus"><i class="fas fa-chart-line"></i> 客户状态</label> <select id="customerStatus" class="form-control"> <option value="active">活跃客户</option> <option value="inactive">非活跃客户</option> </select> </div> </div> <div class="modal-footer"> <button class="action-btn" id="cancelBtn">取消</button> <button class="action-btn btn-primary" id="saveCustomerBtn">保存</button> </div> </div> </div> <script> // 模拟客户数据 const generateCustomers = () => { const names = ['张伟', '王芳', '李明', '刘洋', '陈静', '赵强', '杨艳', '周杰', '黄丽', '吴刚']; const companies = ['科技有限公司', '贸易公司', '集团股份', '电子商务', '咨询公司', '制造企业', '设计工作室']; const domains = ['gmail.com', 'qq.com', '163.com', 'hotmail.com', 'outlook.com']; const customers = []; for (let i = 1; i <= 98; i++) { const firstName = names[Math.floor(Math.random() * names.length)]; const lastName = names[Math.floor(Math.random() * names.length)]; const company = `${lastName}${companies[Math.floor(Math.random() * companies.length)]}`; const domain = domains[Math.floor(Math.random() * domains.length)]; customers.push({ id: i, name: `${firstName}${lastName}`, phone: `13${Math.floor(Math.random() * 100000000).toString().padStart(8, '0')}`, email: `${firstName.toLowerCase()}${lastName.toLowerCase()}@${domain}`, company: company, status: Math.random() > 0.3 ? 'active' : 'inactive', createDate: `2023-${Math.floor(Math.random() * 12 + 1).toString().padStart(2, '0')}-${Math.floor(Math.random() * 28 + 1).toString().padStart(2, '0')}` }); } return customers; }; // 初始化数据 let allCustomers = generateCustomers(); let filteredCustomers = [...allCustomers]; let currentPage = 1; const pageSize = 10; let searchTerm = ''; let editingCustomerId = null; let sortField = null; let sortDirection = 'asc'; // 'asc' or 'desc' // DOM元素 const customerTableBody = document.getElementById('customerTableBody'); const paginationInfo = document.getElementById('paginationInfo'); const totalPagesSpan = document.getElementById('totalPages'); const selectAllCheckbox = document.getElementById('selectAll'); // 渲染客户表格 const renderCustomerTable = () => { // 排序处理 if (sortField) { filteredCustomers.sort((a, b) => { let valA = a[sortField]; let valB = b[sortField]; if (sortField === 'status') { valA = a.status === 'active' ? 1 : 0; valB = b.status === 'active' ? 1 : 0; } if (valA < valB) return sortDirection === 'asc' ? -1 : 1; if (valA > valB) return sortDirection === 'asc' ? 1 : -1; return 0; }); } const startIndex = (currentPage - 1) * pageSize; const endIndex = Math.min(startIndex + pageSize, filteredCustomers.length); const currentPageCustomers = filteredCustomers.slice(startIndex, endIndex); customerTableBody.innerHTML = ''; if (currentPageCustomers.length === 0) { customerTableBody.innerHTML = ` <tr> <td colspan="7"> <div class="empty-state"> <i class="fas fa-inbox"></i> <h3>未找到客户记录</h3> <p>请尝试修改搜索条件或添加新客户</p> </div> </td> </tr> `; return; } currentPageCustomers.forEach(customer => { const tr = document.createElement('tr'); tr.dataset.id = customer.id; // 高亮显示搜索关键词 const highlightText = (text) => { if (!searchTerm) return text; const regex = new RegExp(searchTerm, 'gi'); return text.replace(regex, match => `<mark>${match}</mark>`); }; tr.innerHTML = ` <td class="checkbox-cell"><input type="checkbox" class="row-checkbox" data-id="${customer.id}"></td> <td>${highlightText(customer.name)}</td> <td>${highlightText(customer.phone)}</td> <td>${highlightText(customer.email)}</td> <td>${customer.company}</td> <td><span class="status ${customer.status === 'active' ? 'status-active' : 'status-inactive'}">${customer.status === 'active' ? '活跃' : '非活跃'}</span></td> <td class="action-cell"> <div class="action-icon edit-icon" data-id="${customer.id}" title="编辑"> <i class="fas fa-edit"></i> </div> <div class="action-icon delete-icon" data-id="${customer.id}" title="删除"> <i class="fas fa-trash-alt"></i> </div> </td> `; customerTableBody.appendChild(tr); }); // 更新分页信息 updatePaginationInfo(); }; // 更新分页信息 const updatePaginationInfo = () => { const totalPages = Math.ceil(filteredCustomers.length / pageSize); totalPagesSpan.textContent = totalPages; const start = (currentPage - 1) * pageSize + 1; const end = Math.min(currentPage * pageSize, filteredCustomers.length); paginationInfo.textContent = `显示 ${start} 到 ${end} 条,共 ${filteredCustomers.length} 条记录`; // 更新排序指示器 document.querySelectorAll('[data-sort]').forEach(th => { th.classList.remove('sorted-asc', 'sorted-desc'); if (th.dataset.sort === sortField) { th.classList.add(sortDirection === 'asc' ? 'sorted-asc' : 'sorted-desc'); } }); }; // 搜索功能 const searchCustomers = () => { searchTerm = document.getElementById('searchInput').value.toLowerCase(); filteredCustomers = allCustomers.filter(customer => { const nameMatch = customer.name.toLowerCase().includes(searchTerm); const phoneMatch = customer.phone.toLowerCase().includes(searchTerm); const emailMatch = customer.email.toLowerCase().includes(searchTerm); const companyMatch = customer.company.toLowerCase().includes(searchTerm); return nameMatch || phoneMatch || emailMatch || companyMatch; }); currentPage = 1; document.getElementById('pageInput').value = 1; renderCustomerTable(); }; // 排序功能 const sortCustomers = (field) => { if (sortField === field) { // 切换排序方向 sortDirection = sortDirection === 'asc' ? 'desc' : 'asc'; } else { // 新字段排序,默认升序 sortField = field; sortDirection = 'asc'; } renderCustomerTable(); }; // 打开新增客户模态框 const openAddCustomerModal = () => { editingCustomerId = null; modalTitle.textContent = '新增客户'; document.getElementById('customerName').value = ''; document.getElementById('customerPhone').value = ''; document.getElementById('customerEmail').value = ''; document.getElementById('customerCompany').value = ''; document.getElementById('customerStatus').value = 'active'; document.getElementById('customerModal').classList.add('show'); }; // 打开编辑客户模态框 const openEditCustomerModal = (id) => { const customer = allCustomers.find(c => c.id === id); if (!customer) return; editingCustomerId = id; modalTitle.textContent = '编辑客户'; document.getElementById('customerName').value = customer.name; document.getElementById('customerPhone').value = customer.phone; document.getElementById('customerEmail').value = customer.email; document.getElementById('customerCompany').value = customer.company; document.getElementById('customerStatus').value = customer.status; document.getElementById('customerModal').classList.add('show'); }; // 保存客户 const saveCustomer = () => { const name = document.getElementById('customerName').value.trim(); const phone = document.getElementById('customerPhone').value.trim(); const email = document.getElementById('customerEmail').value.trim(); const company = document.getElementById('customerCompany').value.trim(); const status = document.getElementById('customerStatus').value; if (!name || !phone || !email) { alert('请填写必填字段:客户姓名、联系电话和电子邮箱'); return; } // 检查邮箱是否已存在 const emailExists = allCustomers.some(c => c.email.toLowerCase() === email.toLowerCase() && (!editingCustomerId || c.id !== editingCustomerId) ); if (emailExists) { alert('该电子邮箱已被使用,请使用其他邮箱'); return; } if (editingCustomerId) { // 更新现有客户 const index = allCustomers.findIndex(c => c.id === editingCustomerId); if (index !== -1) { allCustomers[index] = { ...allCustomers[index], name, phone, email, company, status }; } } else { // 新增客户 const newId = Math.max(...allCustomers.map(c => c.id)) + 1; const newCustomer = { id: newId, name, phone, email, company, status, createDate: new Date().toISOString().split('T')[0] }; allCustomers.unshift(newCustomer); } document.getElementById('customerModal').classList.remove('show'); searchCustomers(); }; // 删除客户 const deleteCustomer = (id) => { if (confirm('确定要删除此客户吗?此操作不可恢复。')) { allCustomers = allCustomers.filter(customer => customer.id !== id); searchCustomers(); } }; // 批量删除客户 const deleteSelectedCustomers = () => { const selectedCheckboxes = document.querySelectorAll('.row-checkbox:checked'); if (selectedCheckboxes.length === 0) { alert('请至少选择一个客户进行删除'); return; } if (confirm(`确定要删除选中的 ${selectedCheckboxes.length} 个客户吗?此操作不可恢复。`)) { const selectedIds = Array.from(selectedCheckboxes).map(checkbox => parseInt(checkbox.dataset.id)); allCustomers = allCustomers.filter(customer => !selectedIds.includes(customer.id)); searchCustomers(); selectAllCheckbox.checked = false; } }; // 全选/取消全选 const toggleSelectAll = () => { const rowCheckboxes = document.querySelectorAll('.row-checkbox'); rowCheckboxes.forEach(checkbox => { checkbox.checked = selectAllCheckbox.checked; }); }; // 跳转到指定页面 const goToPage = (page) => { const totalPages = Math.ceil(filteredCustomers.length / pageSize); if (page < 1) page = 1; if (page > totalPages) page = totalPages; currentPage = page; document.getElementById('pageInput').value = page; renderCustomerTable(); }; // 初始化事件监听 document.getElementById('searchInput').addEventListener('input', searchCustomers); document.getElementById('addCustomerBtn').addEventListener('click', openAddCustomerModal); document.getElementById('batchDeleteBtn').addEventListener('click', deleteSelectedCustomers); document.getElementById('refreshBtn').addEventListener('click', () => { document.getElementById('searchInput').value = ''; searchTerm = ''; currentPage = 1; document.getElementById('pageInput').value = 1; filteredCustomers = [...allCustomers]; renderCustomerTable(); }); document.getElementById('closeModal').addEventListener('click', () => { document.getElementById('customerModal').classList.remove('show'); }); document.getElementById('cancelBtn').addEventListener('click', () => { document.getElementById('customerModal').classList.remove('show'); }); document.getElementById('saveCustomerBtn').addEventListener('click', saveCustomer); selectAllCheckbox.addEventListener('change', toggleSelectAll); // 分页控制 document.getElementById('firstPageBtn').addEventListener('click', () => goToPage(1)); document.getElementById('prevPageBtn').addEventListener('click', () => goToPage(currentPage - 1)); document.getElementById('nextPageBtn').addEventListener('click', () => goToPage(currentPage + 1)); document.getElementById('lastPageBtn').addEventListener('click', () => { const totalPages = Math.ceil(filteredCustomers.length / pageSize); goToPage(totalPages); }); document.getElementById('gotoPageBtn').addEventListener('click', () => { const page = parseInt(document.getElementById('pageInput').value); if (!isNaN(page)) { goToPage(page); } }); document.getElementById('pageInput').addEventListener('keypress', (e) => { if (e.key === 'Enter') { const page = parseInt(document.getElementById('pageInput').value); if (!isNaN(page)) { goToPage(page); } } }); // 表头排序 document.querySelectorAll('[data-sort]').forEach(th => { th.addEventListener('click', () => { sortCustomers(th.dataset.sort); }); }); // 事件委托处理编辑和删除操作 document.addEventListener('click', (e) => { if (e.target.closest('.edit-icon')) { const customerId = parseInt(e.target.closest('.edit-icon').dataset.id); openEditCustomerModal(customerId); } if (e.target.closest('.delete-icon')) { const customerId = parseInt(e.target.closest('.delete-icon').dataset.id); deleteCustomer(customerId); } }); // 初始化页面 document.addEventListener('DOMContentLoaded', () => { renderCustomerTable(); // 模拟一些搜索关键词 document.getElementById('searchInput').value = '张'; searchCustomers(); }); </script> </body> </html>