<!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>