
本系统的数据全部自动存储在浏览器自带的 localStorage (本地存储) 里
对应代码就是这两处:
-
保存: localStorage.setItem('customerData', 数据)
-
读取: JSON.parse(localStorage.getItem('customerData'))
javascript
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>意向客户跟进系统</title>
<script src="https://cdn.jsdelivr.net/npm/xlsx@0.18.5/dist/xlsx.full.min.js"></script>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; font-family: "Microsoft YaHei", sans-serif; }
body { background-color: #f5f7fa; color: #333; padding: 20px; transition: background-color 0.3s, color 0.3s; }
.container { max-width: 1320px; margin: 0 auto; background: white; padding: 20px; border-radius: 8px; box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1); transition: background-color 0.3s, box-shadow 0.3s; }
header { text-align: center; margin-bottom: 30px; }
h1 { color: #2c3e50; transition: color 0.3s; }
.operation-bar { display: flex; justify-content: space-between; margin-bottom: 20px; flex-wrap: wrap; gap: 10px; align-items: center; }
.filter-group { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }
.search-box input { padding: 7px 12px; border: 1px solid #ccc; border-radius: 4px; width: 220px; height: 34px; outline: none; transition: border-color 0.3s; }
.search-box input:focus { border-color: #4e73df; }
.btn { padding: 7px 12px; border: none; border-radius: 4px; cursor: pointer; color: white; transition: background-color 0.3s; font-size: 14px; height: 34px; }
.btn-primary { background-color: #4e73df; }
.btn-success { background-color: #1cc88a; }
.btn-danger { background-color: #e74a3b; }
.btn-warning { background-color: #f6c23e; color: #fff; }
.btn:disabled { background-color: #ccc; cursor: not-allowed; }
.btn:hover:not(:disabled) { opacity: 0.85; }
/* 统一下拉框样式 */
.page-size-select {
padding: 7px 10px;
border: 1px solid #ccc;
border-radius: 4px;
font-size: 14px;
width: 110px;
height: 34px;
background: white;
cursor: pointer;
outline: none;
transition: border-color 0.3s;
}
.page-size-select:focus { border-color: #4e73df; }
/* 自定义页码跳转下拉 */
.page-jump-wrap {
position: relative;
display: inline-block;
}
.page-jump-btn {
padding: 5px 12px;
border: 1px solid #ccc;
border-radius: 4px;
background: white;
cursor: pointer;
font-size: 14px;
height: 34px;
min-width: 90px;
text-align: center;
transition: border-color 0.3s;
color: #333;
}
.page-jump-btn:hover { border-color: #4e73df; }
.page-jump-dropdown {
position: absolute;
bottom: calc(100% + 4px);
left: 0;
background: white;
border: 1px solid #ccc;
border-radius: 4px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
max-height: 300px; /* 固定显示10个选项高度 */
overflow-y: auto;
z-index: 200;
display: none;
min-width: 100%;
}
.page-jump-dropdown.show { display: block; }
.page-jump-option {
padding: 7px 12px;
cursor: pointer;
font-size: 14px;
border-bottom: 1px solid #f0f0f0;
text-align: center;
transition: background-color 0.2s;
}
.page-jump-option:hover { background-color: #f5f7fa; }
.page-jump-option.active { background-color: #4e73df; color: white; }
.page-jump-option:last-child { border-bottom: none; }
/* 滚动条美化 */
.page-jump-dropdown::-webkit-scrollbar { width: 6px; }
.page-jump-dropdown::-webkit-scrollbar-thumb {
background: #c1c1c1;
border-radius: 3px;
}
.page-jump-dropdown::-webkit-scrollbar-track { background: #f5f5f5; }
table { width: 100%; border-collapse: collapse; margin-top: 20px; table-layout: fixed; }
th, td { padding: 10px 12px; text-align: left; border-bottom: 1px solid #e0e0e0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; transition: background-color 0.3s, border-color 0.3s; }
th { background-color: #f8f9fc; color: #6e707e; font-weight: bold; transition: background-color 0.3s, color 0.3s; }
tr:hover { background-color: #f5f7fa; transition: background-color 0.2s; }
.today-tag { display: inline-block; padding: 2px 6px; background-color: #1cc88a; color: white; border-radius: 3px; font-size: 12px; margin-left: 6px; }
.empty-tip { text-align: center; color: #999; padding: 40px 0; white-space: normal; }
input[type="checkbox"] { cursor: pointer; width: 16px; height: 16px; }
/* 意向级别标签样式 */
.intention-tag {
display: inline-block;
padding: 2px 8px;
border-radius: 3px;
font-size: 12px;
font-weight: bold;
color: white;
}
.level-a { background-color: #e74a3b; }
.level-b { background-color: #f6c23e; }
.level-c { background-color: #1cc88a; }
/* 固定列宽 */
th:nth-child(1), td:nth-child(1) { width: 50px; text-align: center; }
th:nth-child(2), td:nth-child(2) { width: 70px; text-align: center; }
th:nth-child(3), td:nth-child(3) { width: 120px; }
th:nth-child(4), td:nth-child(4) { width: 100px; }
th:nth-child(5), td:nth-child(5) { width: 100px; }
th:nth-child(6), td:nth-child(6) { width: 80px; }
th:nth-child(7), td:nth-child(7) { width: 130px; }
th:nth-child(8), td:nth-child(8) { width: 100px; text-align: center; }
th:nth-child(9), td:nth-child(9) { width: 80px; text-align: center; }
th:nth-child(10), td:nth-child(10) { width: 200px; white-space: normal; }
/* 分页控件样式 */
.pagination {
display: flex;
justify-content: center;
align-items: center;
margin-top: 20px;
gap: 8px;
flex-wrap: wrap;
}
.pagination button {
padding: 6px 14px;
border: 1px solid #4e73df;
background-color: white;
color: #4e73df;
border-radius: 4px;
cursor: pointer;
transition: 0.3s;
height: 32px;
}
.pagination button:hover:not(:disabled) {
background-color: #4e73df;
color: white;
}
.pagination button:disabled {
border-color: #ccc;
color: #ccc;
cursor: not-allowed;
}
.pagination .page-info {
color: #666;
font-size: 14px;
margin: 0 10px;
}
.modal { display: none; position: fixed; top: 0; left: 0; width: 100%; height: 100%; background-color: rgba(0, 0, 0, 0.5); justify-content: center; align-items: center; z-index: 1000; }
.modal-content { background-color: white; padding: 24px; border-radius: 8px; width: 100%; max-width: 500px; box-shadow: 0 4px 20px rgba(0, 0, 0, 0.2); transition: background-color 0.3s; }
.detail-content { max-width: 550px; }
.form-group { margin-bottom: 15px; }
.form-group label { display: block; margin-bottom: 5px; font-weight: bold; color: #5a5c69; transition: color 0.3s; }
input, textarea, select { width: 100%; padding: 8px 12px; border: 1px solid #ccc; border-radius: 4px; transition: background-color 0.3s, border-color 0.3s, color 0.3s; }
textarea { resize: vertical; min-height: 100px; }
.action-buttons { display: flex; justify-content: flex-end; gap: 10px; margin-top: 20px; }
.modal-content h2 { color: #2c3e50; margin-bottom: 20px; transition: color 0.3s; }
/* 单选组横向排列样式 */
.radio-group {
display: flex;
gap: 24px;
align-items: center;
flex-wrap: wrap;
}
.radio-group label {
display: inline;
font-weight: normal;
margin-bottom: 0;
margin-left: 4px;
cursor: pointer;
color: #333;
}
.radio-group input[type="radio"] {
width: auto;
margin: 0;
vertical-align: middle;
cursor: pointer;
}
/* 详情弹窗样式 */
.detail-item {
margin-bottom: 14px;
line-height: 1.6;
}
.detail-item .label {
display: inline-block;
font-weight: bold;
color: #5a5c69;
min-width: 90px;
transition: color 0.3s;
}
.detail-notes .notes-content {
margin: 6px 0 0 0;
padding: 10px;
background-color: #f8f9fc;
border-radius: 4px;
max-height: 220px;
overflow-y: auto;
white-space: pre-wrap;
word-break: break-all;
line-height: 1.6;
transition: background-color 0.3s;
}
/* ========== 主题样式 ========== */
/* 浅蓝主题 */
body.theme-blue { background-color: #e6f2ff; }
body.theme-blue th { background-color: #eaf4ff; color: #2c5aa0; }
body.theme-blue .container { box-shadow: 0 2px 12px 0 rgba(0, 80, 180, 0.08); }
body.theme-blue tr:hover { background-color: #f0f7ff; }
/* 浅紫主题 */
body.theme-purple { background-color: #f3e8ff; }
body.theme-purple th { background-color: #f5efff; color: #6b46c1; }
body.theme-purple tr:hover { background-color: #faf5ff; }
/* 浅绿主题 */
body.theme-green { background-color: #e8f8f0; }
body.theme-green th { background-color: #e6f7ee; color: #2f855a; }
body.theme-green tr:hover { background-color: #f0faf5; }
/* 深黑主题 */
body.theme-dark { background-color: #121217; color: #e4e4eb; }
body.theme-dark .container { background: #1e1e2a; box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.4); }
body.theme-dark h1,
body.theme-dark .modal-content h2 { color: #e4e4eb; }
body.theme-dark th { background-color: #2a2a3a; color: #c9c9d6; border-bottom-color: #3a3a4a; }
body.theme-dark td { border-bottom-color: #2d2d3d; }
body.theme-dark tr:hover { background-color: #272736; }
body.theme-dark input,
body.theme-dark select,
body.theme-dark textarea,
body.theme-dark .page-size-select,
body.theme-dark .page-jump-btn {
background-color: #2a2a3a;
border-color: #3a3a4a;
color: #e4e4eb;
}
body.theme-dark .form-group label,
body.theme-dark .detail-item .label { color: #c9c9d6; }
body.theme-dark .radio-group label { color: #e4e4eb; }
body.theme-dark .modal-content { background-color: #1e1e2a; }
body.theme-dark .detail-notes .notes-content { background-color: #2a2a3a; }
body.theme-dark .empty-tip { color: #888; }
body.theme-dark .pagination button { background-color: #2a2a3a; border-color: #3a3a4a; color: #e4e4eb; }
body.theme-dark .pagination button:hover:not(:disabled) { background-color: #4e73df; border-color: #4e73df; color: white; }
body.theme-dark .page-info { color: #aaa; }
body.theme-dark .page-jump-dropdown {
background-color: #1e1e2a;
border-color: #3a3a4a;
}
body.theme-dark .page-jump-option { border-bottom-color: #2d2d3d; }
body.theme-dark .page-jump-option:hover { background-color: #272736; }
body.theme-dark .page-jump-dropdown::-webkit-scrollbar-track { background: #2a2a3a; }
body.theme-dark .page-jump-dropdown::-webkit-scrollbar-thumb { background: #4a4a5a; }
</style>
</head>
<body class="theme-classic">
<div class="container">
<header>
<h1>意向客户跟进系统</h1>
</header>
<div class="operation-bar">
<div class="filter-group">
<button class="btn btn-primary" onclick="openAddModal()">添加客户</button>
<button class="btn btn-danger" id="batchDeleteBtn" onclick="batchDelete()" disabled>批量删除</button>
<button class="btn btn-warning" onclick="toggleTodayFilter()" id="todayFilterBtn">今日回访</button>
<button class="btn btn-success" onclick="exportData()">导出Excel</button>
<input type="file" id="fileInput" accept=".xlsx,.xls" style="display: none;" onchange="importData(event)">
<button class="btn btn-danger" onclick="document.getElementById('fileInput').click()">导入Excel</button>
</div>
<div class="filter-group">
<div class="search-box">
<input type="text" id="searchInput" placeholder="搜索姓名/手机号/备注..." oninput="handleSearch()">
</div>
<select id="regionFilter" class="page-size-select" onchange="handleRegionFilter()">
<option value="all">全部地区</option>
</select>
<select id="intentionFilter" class="page-size-select" onchange="handleIntentionFilter()">
<option value="all">全部级别</option>
<option value="A">A级意向</option>
<option value="B">B级意向</option>
<option value="C">C级意向</option>
</select>
<select id="themeSelect" class="page-size-select" onchange="changeTheme(this.value)">
<option value="classic">经典主题</option>
<option value="blue">浅蓝主题</option>
<option value="purple">浅紫主题</option>
<option value="green">浅绿主题</option>
<option value="dark">深黑主题</option>
</select>
</div>
</div>
<table id="customerTable">
<thead>
<tr>
<th><input type="checkbox" id="selectAll" onchange="handleSelectAll()"></th>
<th>序号</th>
<th>回访日期</th>
<th>地区</th>
<th>学生姓名</th>
<th>联系人</th>
<th>手机号</th>
<th>意向级别</th>
<th>跟进次数</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<!-- 数据将由 JS 动态填充 -->
</tbody>
</table>
<!-- 分页控件 -->
<div class="pagination" id="pagination"></div>
<!-- 添加/编辑客户模态框 -->
<div id="customerModal" class="modal">
<div class="modal-content">
<h2 id="modalTitle">添加客户</h2>
<form id="customerForm" onsubmit="return saveCustomer()">
<input type="hidden" id="editId">
<div class="form-group">
<label for="date">录入日期:</label>
<input type="date" id="date" required>
</div>
<div class="form-group">
<label for="followUpDate">回访日期:</label>
<input type="date" id="followUpDate">
</div>
<div class="form-group">
<label for="region">地区:</label>
<input type="text" id="region" placeholder="请输入地区" required>
</div>
<div class="form-group">
<label for="name">学生姓名:</label>
<input type="text" id="name" placeholder="请输入学生姓名" required>
</div>
<div class="form-group">
<label>联系人:</label>
<div class="radio-group">
<div>
<input type="radio" id="genderMale" name="gender" value="男" required>
<label for="genderMale">爸爸</label>
</div>
<div>
<input type="radio" id="genderFemale" name="gender" value="女">
<label for="genderFemale">妈妈</label>
</div>
</div>
</div>
<div class="form-group">
<label for="phone">手机号:</label>
<input type="tel" id="phone" placeholder="请输入11位手机号" maxlength="11" pattern="[0-9]{11}">
</div>
<div class="form-group">
<label>意向级别:</label>
<div class="radio-group">
<div>
<input type="radio" id="intentionA" name="intentionLevel" value="A" required>
<label for="intentionA">A</label>
</div>
<div>
<input type="radio" id="intentionB" name="intentionLevel" value="B">
<label for="intentionB">B</label>
</div>
<div>
<input type="radio" id="intentionC" name="intentionLevel" value="C">
<label for="intentionC">C</label>
</div>
</div>
</div>
<div class="form-group">
<label for="notes">备注:</label>
<textarea id="notes" placeholder="请输入跟进备注"></textarea>
</div>
<div class="form-group">
<label for="followUpCount">跟进次数:</label>
<input type="number" id="followUpCount" min="0" value="0" required>
</div>
<div class="action-buttons">
<button type="button" class="btn btn-danger" onclick="closeModal()">取消</button>
<button type="submit" class="btn btn-primary">保存</button>
</div>
</form>
</div>
</div>
<!-- 客户详情模态框 -->
<div id="detailModal" class="modal">
<div class="modal-content detail-content">
<h2>客户详情</h2>
<div class="detail-item">
<span class="label">录入序号:</span>
<span id="detailSerial"></span>
</div>
<div class="detail-item">
<span class="label">录入日期:</span>
<span id="detailDate"></span>
</div>
<div class="detail-item">
<span class="label">回访日期:</span>
<span id="detailFollowUpDate"></span>
</div>
<div class="detail-item">
<span class="label">地区:</span>
<span id="detailRegion"></span>
</div>
<div class="detail-item">
<span class="label">学生姓名:</span>
<span id="detailName"></span>
</div>
<div class="detail-item">
<span class="label">联系人:</span>
<span id="detailContact"></span>
</div>
<div class="detail-item">
<span class="label">手机号:</span>
<span id="detailPhone"></span>
</div>
<div class="detail-item">
<span class="label">意向级别:</span>
<span id="detailIntention"></span>
</div>
<div class="detail-item detail-notes">
<span class="label">备注:</span>
<p class="notes-content" id="detailNotes"></p>
</div>
<div class="detail-item">
<span class="label">跟进次数:</span>
<span id="detailFollowUpCount"></span>
</div>
<div class="action-buttons">
<button type="button" class="btn btn-primary" onclick="closeDetailModal()">关闭</button>
</div>
</div>
</div>
</div>
<script>
// 1. 数据初始化与兼容处理
let customers = JSON.parse(localStorage.getItem('customerData')) || [];
// 兼容旧数据:自动补全意向级别和录入序号
(function initData() {
let maxSerial = 0;
customers = customers.map((item, index) => {
if (!item.intentionLevel) item.intentionLevel = 'A';
if (!item.serialNumber) item.serialNumber = index + 1;
if (item.serialNumber > maxSerial) maxSerial = item.serialNumber;
return item;
});
window.nextSerialNumber = maxSerial + 1;
})();
// 主题初始化
const savedTheme = localStorage.getItem('customerTheme') || 'classic';
document.body.className = 'theme-' + savedTheme;
let editingId = null;
let currentPage = 1;
let pageSize = 10;
let searchKeyword = '';
let isTodayFilter = false;
let selectedIds = [];
let intentionFilter = 'all';
let regionFilter = 'all';
window.onload = function() {
document.getElementById('themeSelect').value = savedTheme;
updateRegionFilterOptions();
renderTable();
};
// 切换主题
function changeTheme(theme) {
document.body.className = 'theme-' + theme;
localStorage.setItem('customerTheme', theme);
}
// 获取今日日期字符串
function getTodayString() {
const today = new Date();
const year = today.getFullYear();
const month = String(today.getMonth() + 1).padStart(2, '0');
const day = String(today.getDate()).padStart(2, '0');
return `${year}-${month}-${day}`;
}
// 切换今日回访筛选
function toggleTodayFilter() {
isTodayFilter = !isTodayFilter;
document.getElementById('todayFilterBtn').textContent = isTodayFilter ? '显示全部' : '今日回访';
currentPage = 1;
selectedIds = [];
closePageJumpDropdown();
renderTable();
}
// 意向级别筛选处理
function handleIntentionFilter() {
intentionFilter = document.getElementById('intentionFilter').value;
currentPage = 1;
selectedIds = [];
closePageJumpDropdown();
renderTable();
}
// 地区筛选处理
function handleRegionFilter() {
regionFilter = document.getElementById('regionFilter').value;
currentPage = 1;
selectedIds = [];
closePageJumpDropdown();
renderTable();
}
// 更新地区下拉选项
function updateRegionFilterOptions() {
const regionSelect = document.getElementById('regionFilter');
const currentSelected = regionSelect.value;
const uniqueRegions = [...new Set(
customers
.map(c => c.region?.trim())
.filter(region => region)
)].sort((a, b) => a.localeCompare(b, 'zh-CN'));
let options = '<option value="all">全部地区</option>';
uniqueRegions.forEach(region => {
options += `<option value="${region}">${region}</option>`;
});
regionSelect.innerHTML = options;
if (uniqueRegions.includes(currentSelected)) {
regionSelect.value = currentSelected;
} else {
regionSelect.value = 'all';
regionFilter = 'all';
}
}
// 切换页码跳转下拉
function togglePageJumpDropdown(event) {
event.stopPropagation();
const dropdown = document.getElementById('pageJumpDropdown');
dropdown.classList.toggle('show');
// 展开时自动滚动到当前选中项
if (dropdown.classList.contains('show')) {
const activeOption = dropdown.querySelector('.page-jump-option.active');
if (activeOption) {
activeOption.scrollIntoView({ block: 'center' });
}
}
}
// 关闭页码跳转下拉
function closePageJumpDropdown() {
const dropdown = document.getElementById('pageJumpDropdown');
if (dropdown) dropdown.classList.remove('show');
}
// 全选/取消全选当前页
function handleSelectAll() {
const isChecked = document.getElementById('selectAll').checked;
const filteredData = getFilteredData();
const sortedData = getSortedData(filteredData);
const startIndex = (currentPage - 1) * pageSize;
const endIndex = Math.min(startIndex + pageSize, sortedData.length);
const currentPageIds = sortedData.slice(startIndex, endIndex).map(c => c.id);
if (isChecked) {
currentPageIds.forEach(id => {
if (!selectedIds.includes(id)) selectedIds.push(id);
});
} else {
selectedIds = selectedIds.filter(id => !currentPageIds.includes(id));
}
updateBatchBtnState();
renderTable();
}
// 单行选中/取消
function handleRowSelect(id) {
if (selectedIds.includes(id)) {
selectedIds = selectedIds.filter(item => item !== id);
} else {
selectedIds.push(id);
}
updateBatchBtnState();
updateSelectAllState();
}
// 更新全选框状态
function updateSelectAllState() {
const filteredData = getFilteredData();
const sortedData = getSortedData(filteredData);
const startIndex = (currentPage - 1) * pageSize;
const endIndex = Math.min(startIndex + pageSize, sortedData.length);
const currentPageIds = sortedData.slice(startIndex, endIndex).map(c => c.id);
const allChecked = currentPageIds.length > 0 && currentPageIds.every(id => selectedIds.includes(id));
document.getElementById('selectAll').checked = allChecked;
}
// 更新批量删除按钮状态
function updateBatchBtnState() {
document.getElementById('batchDeleteBtn').disabled = selectedIds.length === 0;
}
// 批量删除
function batchDelete() {
if (selectedIds.length === 0) {
alert('请先选择要删除的客户');
return;
}
if (confirm(`确定要删除选中的 ${selectedIds.length} 条客户信息吗?此操作不可恢复。`)) {
customers = customers.filter(c => !selectedIds.includes(c.id));
selectedIds = [];
const totalPages = getTotalPages();
if (currentPage > totalPages) currentPage = totalPages;
updateRegionFilterOptions();
saveData();
renderTable();
}
}
// 全字段模糊搜索 + 多条件筛选
function getFilteredData() {
let result = [...customers];
if (isTodayFilter) {
const today = getTodayString();
result = result.filter(c => c.followUpDate === today);
}
if (intentionFilter !== 'all') {
result = result.filter(c => c.intentionLevel === intentionFilter);
}
if (regionFilter !== 'all') {
result = result.filter(c => c.region === regionFilter);
}
if (searchKeyword.trim()) {
const keyword = searchKeyword.trim().toLowerCase();
result = result.filter(c => {
const name = String(c.name || '').toLowerCase();
const phone = String(c.phone || '').toLowerCase();
const region = String(c.region || '').toLowerCase();
const notes = String(c.notes || '').toLowerCase();
const contact = c.gender === '男' ? '爸爸' : '妈妈';
const intention = String(c.intentionLevel || '').toLowerCase();
const serial = String(c.serialNumber || '').toLowerCase();
return name.includes(keyword)
|| phone.includes(keyword)
|| region.includes(keyword)
|| notes.includes(keyword)
|| contact.includes(keyword)
|| intention.includes(keyword)
|| serial.includes(keyword);
});
}
return result;
}
// 固定排序规则:按录入序号降序
function getSortedData(data) {
return [...data].sort((a, b) => b.serialNumber - a.serialNumber);
}
// 搜索输入处理
function handleSearch() {
searchKeyword = document.getElementById('searchInput').value;
currentPage = 1;
selectedIds = [];
closePageJumpDropdown();
renderTable();
}
// 计算总页数
function getTotalPages() {
const filtered = getFilteredData();
return Math.ceil(filtered.length / pageSize) || 1;
}
// 渲染客户表格
function renderTable() {
const tbody = document.getElementById('customerTable').getElementsByTagName('tbody')[0];
tbody.innerHTML = '';
const filteredData = getFilteredData();
const sortedData = getSortedData(filteredData);
const today = getTodayString();
const totalPages = getTotalPages();
if (currentPage > totalPages) currentPage = totalPages;
if (currentPage < 1) currentPage = 1;
const startIndex = (currentPage - 1) * pageSize;
const endIndex = Math.min(startIndex + pageSize, sortedData.length);
const pageData = sortedData.slice(startIndex, endIndex);
if (pageData.length === 0) {
const row = tbody.insertRow();
const cell = row.insertCell(0);
cell.colSpan = 10;
cell.className = 'empty-tip';
if (isTodayFilter && searchKeyword.trim()) {
cell.textContent = '今日回访中未找到匹配结果';
} else if (isTodayFilter) {
cell.textContent = '今日暂无待回访客户';
} else if (searchKeyword.trim()) {
cell.textContent = '未找到匹配的客户信息';
} else {
cell.textContent = '暂无数据,请添加客户';
}
renderPagination();
updateSelectAllState();
updateBatchBtnState();
return;
}
pageData.forEach(customer => {
const row = tbody.insertRow();
const checkboxCell = row.insertCell(0);
checkboxCell.innerHTML = `<input type="checkbox" class="row-checkbox"
${selectedIds.includes(customer.id) ? 'checked' : ''}
onchange="handleRowSelect(${customer.id})">`;
row.insertCell(1).textContent = customer.serialNumber;
const followUpCell = row.insertCell(2);
const dateText = customer.followUpDate || '-';
if (customer.followUpDate === today) {
followUpCell.innerHTML = dateText + '<span class="today-tag">今日</span>';
} else {
followUpCell.textContent = dateText;
}
row.insertCell(3).textContent = customer.region;
row.insertCell(4).textContent = customer.name;
row.insertCell(5).textContent = customer.gender === '男' ? '爸爸' : '妈妈';
row.insertCell(6).textContent = customer.phone || '-';
const intentionCell = row.insertCell(7);
const level = customer.intentionLevel || 'A';
const levelClass = level === 'A' ? 'level-a' : level === 'B' ? 'level-b' : 'level-c';
intentionCell.innerHTML = `<span class="intention-tag ${levelClass}">${level}</span>`;
row.insertCell(8).textContent = customer.followUpCount;
row.insertCell(9).innerHTML = `
<div class="actions" style="display: flex; gap: 6px; flex-wrap: wrap;">
<button class="btn btn-success" onclick="openDetailModal(${customer.id})">详情</button>
<button class="btn btn-primary" onclick="openEditModal(${customer.id})">编辑</button>
<button class="btn btn-danger" onclick="deleteCustomer(${customer.id})">删除</button>
</div>
`;
});
renderPagination();
updateSelectAllState();
updateBatchBtnState();
}
// 打开详情弹窗
function openDetailModal(id) {
const customer = customers.find(c => c.id === id);
if (!customer) return;
document.getElementById('detailSerial').textContent = customer.serialNumber;
document.getElementById('detailDate').textContent = customer.date;
document.getElementById('detailFollowUpDate').textContent = customer.followUpDate || '未设置';
document.getElementById('detailRegion').textContent = customer.region;
document.getElementById('detailName').textContent = customer.name;
document.getElementById('detailContact').textContent = customer.gender === '男' ? '爸爸' : '妈妈';
document.getElementById('detailPhone').textContent = customer.phone || '-';
document.getElementById('detailIntention').textContent = customer.intentionLevel || 'A';
document.getElementById('detailNotes').textContent = customer.notes || '无';
document.getElementById('detailFollowUpCount').textContent = customer.followUpCount + ' 次';
document.getElementById('detailModal').style.display = 'flex';
}
function closeDetailModal() {
document.getElementById('detailModal').style.display = 'none';
}
// 渲染分页控件
function renderPagination() {
const totalPages = getTotalPages();
const filteredData = getFilteredData();
const pagination = document.getElementById('pagination');
let html = '';
html += '<button onclick="goToPage(' + (currentPage - 1) + ')" ' + (currentPage === 1 ? 'disabled' : '') + '>上一页</button>';
html += '<span class="page-info">第 ' + currentPage + ' 页 / 共 ' + totalPages + ' 页(共 ' + filteredData.length + ' 条)</span>';
html += '<button onclick="goToPage(' + (currentPage + 1) + ')" ' + (currentPage === totalPages ? 'disabled' : '') + '>下一页</button>';
// 自定义页码跳转下拉(固定显示10个选项,超出滚动)
html += '<span class="page-info">跳转:</span>';
html += '<div class="page-jump-wrap">';
html += '<button type="button" class="page-jump-btn" onclick="togglePageJumpDropdown(event)">第' + currentPage + '页</button>';
html += '<div class="page-jump-dropdown" id="pageJumpDropdown">';
for (let i = 1; i <= totalPages; i++) {
html += '<div class="page-jump-option ' + (i === currentPage ? 'active' : '') + '" onclick="goToPage(' + i + ')">第' + i + '页</div>';
}
html += '</div>';
html += '</div>';
html += '<span class="page-info">每页:</span>';
html += '<select class="page-size-select" onchange="changePageSize(parseInt(this.value))">';
[5, 10, 20, 50].forEach(size => {
html += '<option value="' + size + '"' + (size === pageSize ? ' selected' : '') + '>' + size + '条</option>';
});
html += '</select>';
pagination.innerHTML = html;
}
function goToPage(page) {
const totalPages = getTotalPages();
if (page < 1 || page > totalPages) return;
currentPage = page;
closePageJumpDropdown();
renderTable();
}
function changePageSize(newSize) {
pageSize = newSize;
currentPage = 1;
closePageJumpDropdown();
renderTable();
}
// 打开添加模态框
function openAddModal() {
document.getElementById('modalTitle').textContent = '添加客户';
document.getElementById('customerForm').reset();
document.getElementById('date').value = getTodayString();
document.getElementById('intentionA').checked = true;
editingId = null;
document.getElementById('customerModal').style.display = 'flex';
}
// 打开编辑模态框
function openEditModal(id) {
const customer = customers.find(c => c.id === id);
if (customer) {
document.getElementById('modalTitle').textContent = '编辑客户';
document.getElementById('editId').value = customer.id;
document.getElementById('date').value = customer.date;
document.getElementById('followUpDate').value = customer.followUpDate || '';
document.getElementById('region').value = customer.region;
document.getElementById('name').value = customer.name;
document.getElementById('gender' + (customer.gender === '男' ? 'Male' : 'Female')).checked = true;
document.getElementById('phone').value = customer.phone || '';
const level = customer.intentionLevel || 'A';
document.getElementById('intention' + level).checked = true;
document.getElementById('notes').value = customer.notes || '';
document.getElementById('followUpCount').value = customer.followUpCount;
editingId = id;
document.getElementById('customerModal').style.display = 'flex';
}
}
function closeModal() {
document.getElementById('customerModal').style.display = 'none';
}
// 保存客户
function saveCustomer() {
const id = editingId || new Date().getTime();
const date = document.getElementById('date').value;
const followUpDate = document.getElementById('followUpDate').value || '';
const region = document.getElementById('region').value.trim();
const name = document.getElementById('name').value;
const gender = document.querySelector('input[name="gender"]:checked').value;
const phone = document.getElementById('phone').value.trim();
const intentionLevel = document.querySelector('input[name="intentionLevel"]:checked').value;
const notes = document.getElementById('notes').value;
const followUpCount = parseInt(document.getElementById('followUpCount').value) || 0;
let customer;
if (editingId) {
const oldCustomer = customers.find(c => c.id === editingId);
customer = {
...oldCustomer,
date, followUpDate, region, name, gender,
phone, intentionLevel, notes, followUpCount
};
const index = customers.findIndex(c => c.id === editingId);
if (index !== -1) customers[index] = customer;
} else {
const serialNumber = window.nextSerialNumber;
window.nextSerialNumber++;
customer = {
id, serialNumber, date, followUpDate, region, name,
gender, phone, intentionLevel, notes, followUpCount
};
customers.push(customer);
}
updateRegionFilterOptions();
saveData();
renderTable();
closeModal();
return false;
}
// 删除单个客户
function deleteCustomer(id) {
if (confirm('确定要删除这条客户信息吗?')) {
customers = customers.filter(customer => customer.id !== id);
selectedIds = selectedIds.filter(item => item !== id);
const totalPages = getTotalPages();
if (currentPage > totalPages) currentPage = totalPages;
updateRegionFilterOptions();
saveData();
renderTable();
}
}
// 导出Excel
function exportData() {
if (customers.length === 0) {
alert('暂无数据可导出!');
return;
}
const exportList = [...customers].sort((a, b) => a.serialNumber - b.serialNumber);
const exportData = exportList.map(c => ({
'录入序号': c.serialNumber,
'录入日期': c.date,
'回访日期': c.followUpDate || '',
'地区': c.region,
'学生姓名': c.name,
'联系人': c.gender === '男' ? '爸爸' : '妈妈',
'手机号': c.phone || '',
'意向级别': c.intentionLevel || 'A',
'备注': c.notes || '',
'跟进次数': c.followUpCount
}));
const ws = XLSX.utils.json_to_sheet(exportData);
ws['!cols'] = [
{ wch: 10 }, { wch: 14 }, { wch: 14 }, { wch: 12 }, { wch: 12 },
{ wch: 10 }, { wch: 15 }, { wch: 12 }, { wch: 25 }, { wch: 10 }
];
const wb = XLSX.utils.book_new();
XLSX.utils.book_append_sheet(wb, ws, '客户数据');
XLSX.writeFile(wb, 'customer_data.xlsx');
alert('数据已导出为 customer_data.xlsx');
}
// 导入Excel
function importData(event) {
const file = event.target.files[0];
if (!file) return;
if (!confirm('导入后会覆盖掉以前的全部数据,您确认继续吗?')) {
event.target.value = '';
return;
}
const reader = new FileReader();
reader.onload = function(e) {
try {
const data = e.target.result;
const wb = XLSX.read(data, { type: 'array' });
const ws = wb.Sheets[wb.SheetNames[0]];
const jsonData = XLSX.utils.sheet_to_json(ws);
let maxSerial = 0;
customers = jsonData.map((row, index) => {
let serialNumber = row['录入序号'] ? Number(row['录入序号']) : index + 1;
if (serialNumber > maxSerial) maxSerial = serialNumber;
return {
id: row.id || new Date().getTime() + index,
serialNumber: serialNumber,
date: row['录入日期'] || row['日期'] || '',
followUpDate: row['回访日期'] || '',
region: row['地区'] || '',
name: row['学生姓名'] || '',
gender: row['联系人'] === '爸爸' ? '男' : (row['联系人'] === '妈妈' ? '女' : '男'),
phone: row['手机号'] || '',
intentionLevel: row['意向级别'] || 'A',
notes: row['备注'] || '',
followUpCount: parseInt(row['跟进次数']) || 0
};
});
window.nextSerialNumber = maxSerial + 1;
currentPage = 1;
selectedIds = [];
document.getElementById('searchInput').value = '';
searchKeyword = '';
isTodayFilter = false;
document.getElementById('todayFilterBtn').textContent = '今日回访';
intentionFilter = 'all';
document.getElementById('intentionFilter').value = 'all';
regionFilter = 'all';
updateRegionFilterOptions();
saveData();
renderTable();
alert('数据导入成功!共导入 ' + customers.length + ' 条记录。');
} catch (err) {
alert('解析失败:请确保Excel文件内容格式正确。');
console.error(err);
}
};
reader.readAsArrayBuffer(file);
event.target.value = '';
}
// 保存到 localStorage
function saveData() {
localStorage.setItem('customerData', JSON.stringify(customers));
}
// 点击页面其他区域关闭弹窗和下拉
window.onclick = function(event) {
if (event.target.id === 'customerModal') closeModal();
if (event.target.id === 'detailModal') closeDetailModal();
// 关闭页码跳转下拉
if (!event.target.closest('.page-jump-wrap')) {
closePageJumpDropdown();
}
}
</script>
</body>
</html>