894 lines
41 KiB
HTML
894 lines
41 KiB
HTML
<!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 href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
||
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.0/font/bootstrap-icons.css" rel="stylesheet">
|
||
<style>
|
||
body {
|
||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||
min-height: 100vh;
|
||
padding: 20px;
|
||
}
|
||
.container-main {
|
||
background: white;
|
||
border-radius: 15px;
|
||
box-shadow: 0 10px 40px rgba(0,0,0,0.2);
|
||
padding: 30px;
|
||
}
|
||
.header {
|
||
text-align: center;
|
||
margin-bottom: 30px;
|
||
padding-bottom: 20px;
|
||
border-bottom: 2px solid #eee;
|
||
}
|
||
.stats-bar {
|
||
display: flex;
|
||
justify-content: center;
|
||
gap: 30px;
|
||
margin-bottom: 20px;
|
||
flex-wrap: wrap;
|
||
}
|
||
.stat-item {
|
||
text-align: center;
|
||
padding: 15px 25px;
|
||
background: #f8f9fa;
|
||
border-radius: 10px;
|
||
}
|
||
.stat-number {
|
||
font-size: 28px;
|
||
font-weight: bold;
|
||
color: #667eea;
|
||
}
|
||
.search-bar {
|
||
margin-bottom: 20px;
|
||
}
|
||
.filter-bar {
|
||
margin-bottom: 20px;
|
||
display: flex;
|
||
gap: 8px;
|
||
flex-wrap: wrap;
|
||
}
|
||
.filter-btn {
|
||
padding: 6px 16px;
|
||
border-radius: 20px;
|
||
font-size: 13px;
|
||
}
|
||
.contact-table {
|
||
font-size: 14px;
|
||
}
|
||
.contact-table th {
|
||
background: #f8f9fa;
|
||
font-weight: 600;
|
||
white-space: nowrap;
|
||
}
|
||
.contact-name {
|
||
cursor: pointer;
|
||
color: #0d6efd;
|
||
font-weight: 500;
|
||
}
|
||
.contact-name:hover {
|
||
text-decoration: underline;
|
||
}
|
||
.category-badge {
|
||
cursor: pointer;
|
||
padding: 2px 8px;
|
||
border-radius: 12px;
|
||
font-size: 11px;
|
||
display: inline-block;
|
||
background: #e9ecef;
|
||
color: #6c757d;
|
||
border: 1px solid transparent;
|
||
transition: all 0.15s;
|
||
margin: 2px;
|
||
}
|
||
.category-badge:hover {
|
||
transform: scale(1.05);
|
||
}
|
||
.category-badge.active {
|
||
color: #fff;
|
||
font-weight: 500;
|
||
}
|
||
.category-badge.同事.active { background: #0dcaf0; border-color: #0dcaf0; }
|
||
.category-badge.好友.active { background: #198754; border-color: #198754; }
|
||
.category-badge.同学.active { background: #17a2b8; border-color: #17a2b8; }
|
||
.category-badge.老师.active { background: #ffc107; border-color: #ffc107; color: #212529; }
|
||
.category-badge.亲戚.active { background: #dc3545; border-color: #dc3545; }
|
||
.category-badge.客户.active { background: #6f42c1; border-color: #6f42c1; }
|
||
.category-badge.供应商.active { background: #20c997; border-color: #20c997; }
|
||
.category-badge.其他.active { background: #fd7e14; border-color: #fd7e14; }
|
||
.category-tags {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 2px;
|
||
}
|
||
.send-badge {
|
||
cursor: pointer;
|
||
padding: 4px 12px;
|
||
border-radius: 15px;
|
||
font-size: 12px;
|
||
display: inline-block;
|
||
transition: all 0.15s;
|
||
border: 2px solid #dee2e6;
|
||
background: #fff;
|
||
color: #6c757d;
|
||
}
|
||
.send-badge:hover {
|
||
transform: scale(1.05);
|
||
}
|
||
.send-badge.active {
|
||
background: #198754;
|
||
border-color: #198754;
|
||
color: #fff;
|
||
font-weight: 500;
|
||
}
|
||
.blessing-text {
|
||
cursor: pointer;
|
||
max-width: 400px;
|
||
color: #198754;
|
||
white-space: pre-wrap;
|
||
word-break: break-word;
|
||
font-size: 12px;
|
||
line-height: 1.5;
|
||
}
|
||
.blessing-text:hover {
|
||
text-decoration: underline;
|
||
background: #f8f9fa;
|
||
}
|
||
.pagination-container {
|
||
margin-top: 20px;
|
||
justify-content: center;
|
||
}
|
||
.loading {
|
||
text-align: center;
|
||
padding: 40px;
|
||
color: #666;
|
||
}
|
||
.toast-container {
|
||
position: fixed;
|
||
top: 20px;
|
||
right: 20px;
|
||
z-index: 9999;
|
||
}
|
||
/* Modal styles */
|
||
.modal-body input, .modal-body textarea, .modal-body select {
|
||
width: 100%;
|
||
padding: 10px;
|
||
border: 1px solid #ddd;
|
||
border-radius: 5px;
|
||
font-size: 14px;
|
||
}
|
||
.modal-body textarea {
|
||
resize: vertical;
|
||
min-height: 80px;
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div class="container-main">
|
||
<div class="header">
|
||
<h1><i class="bi bi-wechat"></i> 微信联系人祝福管理</h1>
|
||
<p class="subtitle">点击姓名/分类/祝福语可编辑,勾选发送选择框</p>
|
||
</div>
|
||
|
||
<!-- 统计栏 -->
|
||
<div class="stats-bar">
|
||
<div class="stat-item">
|
||
<div class="stat-number" id="totalCount">0</div>
|
||
<div>总联系人</div>
|
||
</div>
|
||
<div class="stat-item">
|
||
<div class="stat-number" id="selectedCount">0</div>
|
||
<div>已选择</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 新增联系人表单 -->
|
||
<div class="add-contact-form mb-4">
|
||
<div class="card">
|
||
<div class="card-header d-flex justify-content-between align-items-center">
|
||
<span><i class="bi bi-person-plus"></i> 新增联系人</span>
|
||
<button class="btn btn-sm btn-outline-secondary" type="button" data-bs-toggle="collapse" data-bs-target="#addContactCollapse">
|
||
<i class="bi bi-chevron-down"></i>
|
||
</button>
|
||
</div>
|
||
<div class="collapse show" id="addContactCollapse">
|
||
<div class="card-body">
|
||
<div class="row g-2 align-items-end">
|
||
<div class="col-md-4">
|
||
<label class="form-label">姓名 <span class="text-danger">*</span></label>
|
||
<input type="text" class="form-control" id="newNameInput" placeholder="联系人姓名">
|
||
</div>
|
||
<div class="col-md-4">
|
||
<label class="form-label">搜索姓名</label>
|
||
<input type="text" class="form-control" id="newSearchNameInput" placeholder="用于搜索的简化姓名(可选)">
|
||
</div>
|
||
<div class="col-md-2">
|
||
<label class="form-label">分类</label>
|
||
<select class="form-select" id="newCategorySelect">
|
||
<option value="">未分类</option>
|
||
<option value="同事">同事</option>
|
||
<option value="好友">好友</option>
|
||
<option value="同学">同学</option>
|
||
<option value="老师">老师</option>
|
||
<option value="亲戚">亲戚</option>
|
||
<option value="客户">客户</option>
|
||
<option value="供应商">供应商</option>
|
||
<option value="其他">其他</option>
|
||
</select>
|
||
</div>
|
||
<div class="col-md-2">
|
||
<button class="btn btn-primary w-100" onclick="addContact()">
|
||
<i class="bi bi-plus-lg"></i> 添加
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 搜索栏 -->
|
||
<div class="search-bar">
|
||
<div class="input-group">
|
||
<span class="input-group-text"><i class="bi bi-search"></i></span>
|
||
<input type="text" class="form-control" id="searchInput" placeholder="搜索联系人...">
|
||
<button class="btn btn-primary" onclick="loadContacts()">搜索</button>
|
||
<select class="form-select" style="max-width: 120px;" id="pageSizeSelect" onchange="changePageSize()">
|
||
<option value="20">20条/页</option>
|
||
<option value="50">50条/页</option>
|
||
<option value="100" selected>100条/页</option>
|
||
</select>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 分类筛选 -->
|
||
<div class="filter-bar">
|
||
<button class="btn btn-outline-primary filter-btn active" data-category="" onclick="setFilter('')">全部</button>
|
||
<button class="btn btn-outline-primary filter-btn" data-category="同事" onclick="setFilter('同事')">同事</button>
|
||
<button class="btn btn-outline-primary filter-btn" data-category="好友" onclick="setFilter('好友')">好友</button>
|
||
<button class="btn btn-outline-primary filter-btn" data-category="同学" onclick="setFilter('同学')">同学</button>
|
||
<button class="btn btn-outline-primary filter-btn" data-category="老师" onclick="setFilter('老师')">老师</button>
|
||
<button class="btn btn-outline-primary filter-btn" data-category="亲戚" onclick="setFilter('亲戚')">亲戚</button>
|
||
<button class="btn btn-outline-primary filter-btn" data-category="客户" onclick="setFilter('客户')">客户</button>
|
||
<button class="btn btn-outline-primary filter-btn" data-category="供应商" onclick="setFilter('供应商')">供应商</button>
|
||
<button class="btn btn-outline-primary filter-btn" data-category="其他" onclick="setFilter('其他')">其他</button>
|
||
<button class="btn btn-danger ms-3" id="batchDeleteBtn" onclick="batchDelete()" style="display:none;">
|
||
<i class="bi bi-trash"></i> 批量删除 (<span id="selectedCount2">0</span>)
|
||
</button>
|
||
</div>
|
||
|
||
<!-- 表格 -->
|
||
<div class="table-responsive">
|
||
<table class="table table-hover contact-table">
|
||
<thead>
|
||
<tr>
|
||
<th style="width: 50px;"><input type="checkbox" id="selectAll" onchange="toggleSelectAll()"></th>
|
||
<th style="width: 50px;">ID</th>
|
||
<th style="min-width: 150px;">姓名</th>
|
||
<th style="min-width: 120px;">搜索姓名</th>
|
||
<th style="min-width: 100px;">分类</th>
|
||
<th style="min-width: 100px;">自定义内容</th>
|
||
<th>祝福语</th>
|
||
<th style="width: 60px;">发送</th>
|
||
<th style="width: 50px;">操作</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody id="contactTable">
|
||
<tr>
|
||
<td colspan="9" class="loading">加载中...</td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
|
||
<!-- 分页 -->
|
||
<nav>
|
||
<ul class="pagination pagination-container" id="pagination"></ul>
|
||
</nav>
|
||
</div>
|
||
|
||
<!-- 编辑姓名 Modal -->
|
||
<div class="modal fade" id="nameModal" tabindex="-1">
|
||
<div class="modal-dialog">
|
||
<div class="modal-content">
|
||
<div class="modal-header">
|
||
<h5 class="modal-title">编辑姓名</h5>
|
||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||
</div>
|
||
<div class="modal-body">
|
||
<input type="hidden" id="editNameId">
|
||
<div class="mb-3">
|
||
<label class="form-label">姓名</label>
|
||
<input type="text" id="editNameInput">
|
||
</div>
|
||
<div class="mb-3">
|
||
<label class="form-label">搜索姓名</label>
|
||
<input type="text" id="editSearchNameInput" placeholder="用于搜索的简化姓名">
|
||
</div>
|
||
</div>
|
||
<div class="modal-footer">
|
||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">取消</button>
|
||
<button type="button" class="btn btn-primary" onclick="saveName()">保存</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 编辑祝福语 Modal -->
|
||
<div class="modal fade" id="blessingModal" tabindex="-1">
|
||
<div class="modal-dialog modal-lg">
|
||
<div class="modal-content">
|
||
<div class="modal-header">
|
||
<h5 class="modal-title">编辑祝福语</h5>
|
||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||
</div>
|
||
<div class="modal-body">
|
||
<input type="hidden" id="editBlessingId">
|
||
<div class="mb-3">
|
||
<label class="form-label">祝福语(点击编辑框自动生成个性化内容)</label>
|
||
<textarea id="editBlessingInput" rows="10" style="resize: vertical;"></textarea>
|
||
</div>
|
||
</div>
|
||
<div class="modal-footer">
|
||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">取消</button>
|
||
<button type="button" class="btn btn-primary" onclick="saveBlessing()">保存</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 编辑自定义内容 Modal -->
|
||
<div class="modal fade" id="customContentModal" tabindex="-1">
|
||
<div class="modal-dialog">
|
||
<div class="modal-content">
|
||
<div class="modal-header">
|
||
<h5 class="modal-title">编辑自定义内容</h5>
|
||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||
</div>
|
||
<div class="modal-body">
|
||
<input type="hidden" id="editCustomContentId">
|
||
<div class="mb-3">
|
||
<label class="form-label">自定义内容</label>
|
||
<input type="text" id="editCustomContentInput" placeholder="输入自定义内容,默认为分类值">
|
||
</div>
|
||
</div>
|
||
<div class="modal-footer">
|
||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">取消</button>
|
||
<button type="button" class="btn btn-primary" onclick="saveCustomContent()">保存</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Toast 提示 -->
|
||
<div class="toast-container">
|
||
<div class="toast" id="toast" role="alert" aria-live="assertive" aria-atomic="true">
|
||
<div class="toast-body" id="toastBody"></div>
|
||
</div>
|
||
</div>
|
||
|
||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
||
<script>
|
||
const API_BASE = '/api';
|
||
let currentPage = 1;
|
||
let pageSize = 100;
|
||
let currentFilter = '';
|
||
let contacts = [];
|
||
|
||
// 页面加载
|
||
document.addEventListener('DOMContentLoaded', function() {
|
||
loadContacts();
|
||
updateStats();
|
||
|
||
// 绑定祝福语点击事件(使用事件委托)
|
||
document.getElementById('contactTable').addEventListener('click', function(e) {
|
||
// 祝福语点击
|
||
if (e.target.classList.contains('blessing-text')) {
|
||
const id = parseInt(e.target.dataset.id);
|
||
const blessing = e.target.dataset.blessing;
|
||
const searchName = e.target.dataset.searchName;
|
||
const category = e.target.dataset.category;
|
||
openBlessingModal(id, blessing, searchName, category);
|
||
}
|
||
// 自定义内容点击
|
||
if (e.target.classList.contains('contact-name') && e.target.dataset.customContent !== undefined) {
|
||
const id = parseInt(e.target.dataset.id);
|
||
const customContent = e.target.dataset.customContent;
|
||
openCustomContentModal(id, customContent);
|
||
}
|
||
});
|
||
});
|
||
|
||
// 搜索回车事件
|
||
document.getElementById('searchInput').addEventListener('keypress', function(e) {
|
||
if (e.key === 'Enter') {
|
||
loadContacts(1);
|
||
}
|
||
});
|
||
|
||
let totalPages = 1;
|
||
let totalCount = 0;
|
||
|
||
// 加载联系人
|
||
async function loadContacts(page = 1) {
|
||
currentPage = page;
|
||
const search = document.getElementById('searchInput').value;
|
||
|
||
let url = `${API_BASE}/contacts?page=${page}&page_size=${pageSize}`;
|
||
if (currentFilter) url += `&category=${encodeURIComponent(currentFilter)}`;
|
||
if (search) url += `&search=${encodeURIComponent(search)}`;
|
||
|
||
try {
|
||
const response = await fetch(url);
|
||
const data = await response.json();
|
||
contacts = data.contacts;
|
||
totalPages = data.total_pages;
|
||
totalCount = data.total;
|
||
renderTable();
|
||
renderPagination();
|
||
} catch (error) {
|
||
console.error('加载失败:', error);
|
||
showToast('加载失败,请检查后端服务是否运行', 'danger');
|
||
}
|
||
}
|
||
|
||
// 渲染表格
|
||
function renderTable() {
|
||
const tbody = document.getElementById('contactTable');
|
||
const allCategories = ['同事', '好友', '同学', '老师', '亲戚', '客户', '供应商', '其他'];
|
||
|
||
if (contacts.length === 0) {
|
||
tbody.innerHTML = '<tr><td colspan="9" class="text-center py-4">暂无数据</td></tr>';
|
||
updateBatchDeleteBtn();
|
||
return;
|
||
}
|
||
|
||
tbody.innerHTML = contacts.map(c => {
|
||
// 解析当前分类(支持逗号分隔的多标签)
|
||
const currentCats = (c.category || '').split(',').map(s => s.trim()).filter(s => s);
|
||
|
||
// 生成分类标签
|
||
const catTags = allCategories.map(cat => {
|
||
const isActive = currentCats.includes(cat);
|
||
return `<span class="category-badge ${cat} ${isActive ? 'active' : ''}"
|
||
onclick="toggleCategory(${c.id}, '${cat}')">${cat}</span>`;
|
||
}).join('');
|
||
|
||
// 自定义内容:如果没有则默认显示分类
|
||
const customContent = c.custom_content || c.category || '';
|
||
|
||
// 发送状态
|
||
const isSelected = c.selected;
|
||
const sendBadge = `<span class="send-badge ${isSelected ? 'active' : ''}"
|
||
onclick="toggleSend(${c.id})">${isSelected ? '✓ 发送' : '发送'}</span>`;
|
||
|
||
return `
|
||
<tr>
|
||
<td><input type="checkbox" class="contact-checkbox" data-id="${c.id}" ${c.selected ? 'checked' : ''} onchange="toggleSelect(${c.id}); updateBatchDeleteBtn()"></td>
|
||
<td>${c.id}</td>
|
||
<td><span class="contact-name" onclick="openNameModal(${c.id}, '${escapeHtml(c.name)}', '${escapeHtml(c.search_name || '')}')">${escapeHtml(c.name)}</span></td>
|
||
<td><span class="contact-name" onclick="openNameModal(${c.id}, '${escapeHtml(c.name)}', '${escapeHtml(c.search_name || '')}')">${escapeHtml(c.search_name || '')}</span></td>
|
||
<td><div class="category-tags">${catTags}</div></td>
|
||
<td><span class="contact-name" data-id="${c.id}" data-custom-content="${escapeHtml(customContent)}" title="点击编辑">${escapeHtml(customContent)}</span></td>
|
||
<td><span class="blessing-text" data-id="${c.id}" data-blessing="${escapeHtml(c.blessing)}" data-search-name="${escapeHtml(c.search_name || '')}" data-category="${escapeHtml(c.category || '')}" title="点击编辑祝福语">${escapeHtml(c.blessing)}</span></td>
|
||
<td>${sendBadge}</td>
|
||
<td>
|
||
<button class="btn btn-sm btn-danger" onclick="deleteContact(${c.id})"><i class="bi bi-trash"></i></button>
|
||
</td>
|
||
</tr>
|
||
`;
|
||
}).join('');
|
||
|
||
updateBatchDeleteBtn();
|
||
}
|
||
|
||
// 渲染分页
|
||
function renderPagination() {
|
||
const pagination = document.getElementById('pagination');
|
||
|
||
if (totalPages <= 1) {
|
||
pagination.innerHTML = '';
|
||
return;
|
||
}
|
||
|
||
let html = `
|
||
<li class="page-item ${currentPage <= 1 ? 'disabled' : ''}">
|
||
<a class="page-link" href="javascript:void(0)" onclick="loadContacts(${currentPage - 1})">上一页</a>
|
||
</li>
|
||
`;
|
||
|
||
// 显示页码
|
||
for (let i = Math.max(1, currentPage - 2); i <= Math.min(totalPages, currentPage + 2); i++) {
|
||
html += `
|
||
<li class="page-item ${i === currentPage ? 'active' : ''}">
|
||
<a class="page-link" href="javascript:void(0)" onclick="loadContacts(${i})">${i}</a>
|
||
</li>
|
||
`;
|
||
}
|
||
|
||
html += `
|
||
<li class="page-item ${currentPage >= totalPages ? 'disabled' : ''}">
|
||
<a class="page-link" href="javascript:void(0)" onclick="loadContacts(${currentPage + 1})">下一页</a>
|
||
</li>
|
||
`;
|
||
|
||
pagination.innerHTML = html;
|
||
}
|
||
|
||
// 设置筛选
|
||
function setFilter(category) {
|
||
currentFilter = category;
|
||
document.querySelectorAll('.filter-btn').forEach(btn => {
|
||
btn.classList.remove('active');
|
||
if (btn.dataset.category === category) {
|
||
btn.classList.add('active');
|
||
}
|
||
});
|
||
loadContacts(1);
|
||
}
|
||
|
||
// 改变每页显示数量
|
||
function changePageSize() {
|
||
pageSize = parseInt(document.getElementById('pageSizeSelect').value);
|
||
loadContacts(1);
|
||
}
|
||
|
||
// 新增联系人
|
||
async function addContact() {
|
||
const name = document.getElementById('newNameInput').value.trim();
|
||
const searchName = document.getElementById('newSearchNameInput').value.trim();
|
||
const category = document.getElementById('newCategorySelect').value;
|
||
|
||
if (!name) {
|
||
showToast('请输入联系人姓名', 'warning');
|
||
document.getElementById('newNameInput').focus();
|
||
return;
|
||
}
|
||
|
||
try {
|
||
const response = await fetch(`${API_BASE}/contacts`, {
|
||
method: 'POST',
|
||
headers: {'Content-Type': 'application/json'},
|
||
body: JSON.stringify({
|
||
name: name,
|
||
search_name: searchName || name,
|
||
category: category,
|
||
blessing: '马年新春快乐!愿您在新的一年里,事业腾飞,马到成功!',
|
||
selected: false
|
||
})
|
||
});
|
||
|
||
if (response.ok) {
|
||
// 清空表单
|
||
document.getElementById('newNameInput').value = '';
|
||
document.getElementById('newSearchNameInput').value = '';
|
||
document.getElementById('newCategorySelect').value = '';
|
||
|
||
showToast('添加成功', 'success');
|
||
loadContacts(1);
|
||
updateStats();
|
||
} else {
|
||
showToast('添加失败', 'danger');
|
||
}
|
||
} catch (error) {
|
||
showToast('添加失败,请检查后端服务', 'danger');
|
||
}
|
||
}
|
||
|
||
// 回车快捷添加
|
||
document.getElementById('newNameInput').addEventListener('keypress', function(e) {
|
||
if (e.key === 'Enter') {
|
||
addContact();
|
||
}
|
||
});
|
||
document.getElementById('newSearchNameInput').addEventListener('keypress', function(e) {
|
||
if (e.key === 'Enter') {
|
||
addContact();
|
||
}
|
||
});
|
||
|
||
// 更新统计
|
||
async function updateStats() {
|
||
try {
|
||
const response = await fetch(`${API_BASE}/stats`);
|
||
const stats = await response.json();
|
||
document.getElementById('totalCount').textContent = stats.total;
|
||
document.getElementById('selectedCount').textContent = stats.selected;
|
||
} catch (error) {
|
||
console.error('统计加载失败:', error);
|
||
}
|
||
}
|
||
|
||
// 全选/取消全选
|
||
function toggleSelectAll() {
|
||
const selectAll = document.getElementById('selectAll').checked;
|
||
document.querySelectorAll('.contact-checkbox').forEach(cb => {
|
||
cb.checked = selectAll;
|
||
const id = parseInt(cb.dataset.id);
|
||
updateSelect(id, selectAll);
|
||
});
|
||
updateStats();
|
||
updateBatchDeleteBtn();
|
||
}
|
||
|
||
// 单个选择
|
||
async function toggleSelect(id) {
|
||
const checkbox = document.querySelector(`.contact-checkbox[data-id="${id}"]`);
|
||
await updateSelect(id, checkbox.checked);
|
||
updateStats();
|
||
}
|
||
|
||
// 更新选择状态
|
||
async function updateSelect(id, selected) {
|
||
try {
|
||
await fetch(`${API_BASE}/contacts/${id}`, {
|
||
method: 'PUT',
|
||
headers: {'Content-Type': 'application/json'},
|
||
body: JSON.stringify({selected: selected})
|
||
});
|
||
} catch (error) {
|
||
console.error('更新失败:', error);
|
||
}
|
||
}
|
||
|
||
// 打开编辑姓名Modal
|
||
function openNameModal(id, name, searchName) {
|
||
document.getElementById('editNameId').value = id;
|
||
document.getElementById('editNameInput').value = name;
|
||
document.getElementById('editSearchNameInput').value = searchName || '';
|
||
new bootstrap.Modal(document.getElementById('nameModal')).show();
|
||
}
|
||
|
||
// 保存姓名
|
||
async function saveName() {
|
||
const id = document.getElementById('editNameId').value;
|
||
const name = document.getElementById('editNameInput').value.trim();
|
||
const searchName = document.getElementById('editSearchNameInput').value.trim();
|
||
|
||
if (!name) {
|
||
showToast('姓名不能为空', 'warning');
|
||
return;
|
||
}
|
||
|
||
try {
|
||
await fetch(`${API_BASE}/contacts/${id}`, {
|
||
method: 'PUT',
|
||
headers: {'Content-Type': 'application/json'},
|
||
body: JSON.stringify({name: name, search_name: searchName})
|
||
});
|
||
bootstrap.Modal.getInstance(document.getElementById('nameModal')).hide();
|
||
showToast('保存成功', 'success');
|
||
loadContacts(currentPage);
|
||
} catch (error) {
|
||
showToast('保存失败', 'danger');
|
||
}
|
||
}
|
||
|
||
// 快速切换分类标签
|
||
async function toggleCategory(id, category) {
|
||
// 找到当前联系人
|
||
const contact = contacts.find(c => c.id === id);
|
||
if (!contact) return;
|
||
|
||
// 解析当前分类
|
||
let currentCats = (contact.category || '').split(',').map(s => s.trim()).filter(s => s);
|
||
|
||
// 切换分类
|
||
const index = currentCats.indexOf(category);
|
||
if (index > -1) {
|
||
currentCats.splice(index, 1); // 移除
|
||
} else {
|
||
currentCats.push(category); // 添加
|
||
}
|
||
|
||
const newCategory = currentCats.join(',');
|
||
|
||
try {
|
||
await fetch(`${API_BASE}/contacts/${id}`, {
|
||
method: 'PUT',
|
||
headers: {'Content-Type': 'application/json'},
|
||
body: JSON.stringify({category: newCategory})
|
||
});
|
||
// 更新本地数据
|
||
contact.category = newCategory;
|
||
// 重新渲染表格
|
||
renderTable();
|
||
showToast('分类已更新', 'success');
|
||
updateStats();
|
||
} catch (error) {
|
||
showToast('更新失败', 'danger');
|
||
}
|
||
}
|
||
|
||
// 快速切换发送状态
|
||
async function toggleSend(id) {
|
||
const contact = contacts.find(c => c.id === id);
|
||
if (!contact) return;
|
||
|
||
const newSelected = !contact.selected;
|
||
|
||
try {
|
||
await fetch(`${API_BASE}/contacts/${id}`, {
|
||
method: 'PUT',
|
||
headers: {'Content-Type': 'application/json'},
|
||
body: JSON.stringify({selected: newSelected})
|
||
});
|
||
contact.selected = newSelected;
|
||
renderTable();
|
||
showToast(newSelected ? '已标记发送' : '已取消发送', 'success');
|
||
updateStats();
|
||
} catch (error) {
|
||
showToast('更新失败', 'danger');
|
||
}
|
||
}
|
||
|
||
// 打开编辑祝福语Modal
|
||
function openBlessingModal(id, blessing, searchName, category) {
|
||
document.getElementById('editBlessingId').value = id;
|
||
// 如果祝福语为空或为默认值,生成个性化祝福语
|
||
const defaultBlessing = '马年新春快乐!愿您在新的一年里,事业腾飞,马到成功!';
|
||
if (!blessing || blessing === defaultBlessing) {
|
||
blessing = generateBlessing(searchName || '', category || '');
|
||
}
|
||
document.getElementById('editBlessingInput').value = blessing;
|
||
new bootstrap.Modal(document.getElementById('blessingModal')).show();
|
||
}
|
||
|
||
// 生成个性化祝福语
|
||
function generateBlessing(searchName, category) {
|
||
const sender = '夏骥';
|
||
const name = searchName || '朋友';
|
||
|
||
// 根据分类生成不同风格的祝福语
|
||
const blessings = {
|
||
'同学': `${sender}祝你${name}:\n\n时光荏苒,岁月如梭,转眼间我们已经毕业多年。回首那些年在校园里一起度过的青春岁月,依然历历在目。课堂上一起认真听讲,课间一起嬉戏打闹,运动会上为班级加油呐喊,考试前互相鼓励打气……那些纯真美好的时光,是我人生中最珍贵的记忆。\n\n如今我们各自在不同的城市打拼,虽然联系不如从前频繁,但那份同窗情谊始终深藏心底。新的一年,愿你工作顺心,生活美满,身体健康,万事如意!无论身在何方,都不要忘记曾经的我们,期待有机会再相聚,共叙同窗情!`,
|
||
|
||
'亲戚': `${sender}敬祝${name}:\n\n岁月匆匆,又是一年新春到。在这个阖家团圆的美好时刻,我怀着最诚挚的心情,向您送上最真挚的祝福。\n\n感谢您一直以来对我们大家庭的关爱和照顾,您的言传身教让我们受益匪浅。您是我们家族的长辈,更是我们学习的榜样。新的一年,衷心祝愿您身体安康,笑口常开,福寿绵长!愿您的生活如春日暖阳般温馨美好,愿您的每一天都充满欢声笑语。晚辈在这里给您拜年了,祝您新年快乐,万事胜意!`,
|
||
|
||
'老师': `${sender}敬祝${name}老师:\n\n春风化雨,桃李芬芳。在成长的路上,是您用知识的甘露浇灌我们,用智慧的光芒照亮我们前行的道路。您的谆谆教诲,至今仍在耳边回响;您的悉心栽培,让我们受益终生。\n\n新的一年到来之际,学生在此向您致以最崇高的敬意和最真挚的祝福。愿您桃李满天下,春晖遍四方!祝您身体健康,工作顺利,阖家幸福!感谢您曾经的付出与教导,无论我们走得多远,都不会忘记您的恩情。新年快乐,万事如意!`,
|
||
|
||
'好友': `${sender}祝你${name}:\n\n朋友,我们相识多年,一起经历过无数欢笑与泪水。感谢你一直以来的陪伴和支持,在我困难时给予鼓励,在我成功时分享喜悦。这份真挚的友谊,是我人生中最宝贵的财富。\n\n新的一年,愿我们的友谊地久天长!愿你心想事成,前程似锦,每一天都充满阳光和希望!无论生活带给我们什么,我们都将携手同行,共同面对。期待新的一年,我们能有更多相聚的时光,一起创造更多美好的回忆。新年快乐,我的挚友!`,
|
||
|
||
'同事': `${sender}祝你${name}:\n\n工作中我们是并肩作战的战友,生活中我们是相互扶持的朋友。感谢你在工作中的支持与配合,让我们的团队更加团结高效。新的一年,愿你事业蒸蒸日上,工作顺心如意!\n\n愿我们在新的一年里继续携手共进,一起迎接新的挑战,创造更多的辉煌!也希望我们在忙碌的工作之余,能够多一些轻松愉快的时光。祝你在新的一年里,所有的努力都能得到回报,所有的付出都能收获满满。新年快乐,工作顺利!`,
|
||
|
||
'客户': `${sender}诚挚祝福${name}:\n\n感谢您一直以来的信任与支持,是您的认可让我们不断进步,是您的建议让我们日趋完善。在过去的日子里,我们携手合作,共同成长,这份合作关系是我们最珍视的财富。\n\n新的一年,我们将继续以最优质的服务回报您的信任,期待我们能有更多的合作机会,共创美好未来!祝您事业兴旺,财源广进,身体健康,阖家幸福!感谢您选择我们,我们将不负所托,为您提供最好的服务。新年快乐,万事如意!`,
|
||
|
||
'供应商': `${sender}诚挚祝福${name}:\n\n感谢贵公司一直以来的优质服务与鼎力支持,是你们的可靠保障让我们的业务得以顺利运转。在过去合作的岁月里,我们建立了深厚的信任与友谊。\n\n新的一年,期待我们继续深化合作,互利共赢,共同发展!祝贵公司生意兴隆,业绩长虹!祝您工作顺利,身体健康,阖家幸福!感谢您和贵公司的付出与支持,愿我们在新的一年里携手共进,再创佳绩!新年快乐!`,
|
||
|
||
'其他': `${sender}祝你${name}:\n\n新的一年已经到来,在这个充满希望和机遇的时刻,我向你送上最真挚的祝福。愿你在新的一年里,心想事成,万事如意!\n\n愿你的每一天都充满阳光,每一刻都充满欢笑。无论生活中遇到什么困难和挑战,都希望你能保持乐观积极的心态,勇敢前行。新的一年,新的开始,愿你能实现自己的梦想,收获满满的幸福和成功!新年快乐,前程似锦!`
|
||
};
|
||
|
||
return blessings[category] || blessings['其他'];
|
||
}
|
||
|
||
// 保存祝福语
|
||
async function saveBlessing() {
|
||
const id = document.getElementById('editBlessingId').value;
|
||
const blessing = document.getElementById('editBlessingInput').value.trim();
|
||
|
||
if (!blessing) {
|
||
showToast('祝福语不能为空', 'warning');
|
||
return;
|
||
}
|
||
|
||
try {
|
||
await fetch(`${API_BASE}/contacts/${id}`, {
|
||
method: 'PUT',
|
||
headers: {'Content-Type': 'application/json'},
|
||
body: JSON.stringify({blessing: blessing})
|
||
});
|
||
bootstrap.Modal.getInstance(document.getElementById('blessingModal')).hide();
|
||
showToast('保存成功', 'success');
|
||
loadContacts(currentPage);
|
||
} catch (error) {
|
||
showToast('保存失败', 'danger');
|
||
}
|
||
}
|
||
|
||
// 打开编辑自定义内容Modal
|
||
function openCustomContentModal(id, customContent) {
|
||
document.getElementById('editCustomContentId').value = id;
|
||
document.getElementById('editCustomContentInput').value = customContent;
|
||
new bootstrap.Modal(document.getElementById('customContentModal')).show();
|
||
}
|
||
|
||
// 保存自定义内容
|
||
async function saveCustomContent() {
|
||
const id = document.getElementById('editCustomContentId').value;
|
||
const customContent = document.getElementById('editCustomContentInput').value.trim();
|
||
|
||
try {
|
||
await fetch(`${API_BASE}/contacts/${id}`, {
|
||
method: 'PUT',
|
||
headers: {'Content-Type': 'application/json'},
|
||
body: JSON.stringify({custom_content: customContent})
|
||
});
|
||
bootstrap.Modal.getInstance(document.getElementById('customContentModal')).hide();
|
||
showToast('保存成功', 'success');
|
||
loadContacts(currentPage);
|
||
} catch (error) {
|
||
showToast('保存失败', 'danger');
|
||
}
|
||
}
|
||
|
||
// 删除联系人
|
||
async function deleteContact(id) {
|
||
if (!confirm('确定要删除这个联系人吗?')) return;
|
||
|
||
try {
|
||
await fetch(`${API_BASE}/contacts/${id}`, {method: 'DELETE'});
|
||
showToast('删除成功', 'success');
|
||
loadContacts(currentPage);
|
||
updateStats();
|
||
} catch (error) {
|
||
showToast('删除失败', 'danger');
|
||
}
|
||
}
|
||
|
||
// 批量删除
|
||
async function batchDelete() {
|
||
const checkedIds = [];
|
||
document.querySelectorAll('.contact-checkbox:checked').forEach(cb => {
|
||
checkedIds.push(parseInt(cb.dataset.id));
|
||
});
|
||
|
||
if (checkedIds.length === 0) {
|
||
showToast('请先选择要删除的联系人', 'warning');
|
||
return;
|
||
}
|
||
|
||
if (!confirm(`确定要删除选中的 ${checkedIds.length} 个联系人吗?`)) return;
|
||
|
||
try {
|
||
let success = 0;
|
||
for (const id of checkedIds) {
|
||
await fetch(`${API_BASE}/contacts/${id}`, {method: 'DELETE'});
|
||
success++;
|
||
}
|
||
showToast(`成功删除 ${success} 个联系人`, 'success');
|
||
loadContacts(currentPage);
|
||
updateStats();
|
||
updateBatchDeleteBtn();
|
||
} catch (error) {
|
||
showToast('删除失败', 'danger');
|
||
}
|
||
}
|
||
|
||
// 更新批量删除按钮状态
|
||
function updateBatchDeleteBtn() {
|
||
const checkedCount = document.querySelectorAll('.contact-checkbox:checked').length;
|
||
const btn = document.getElementById('batchDeleteBtn');
|
||
const countSpan = document.getElementById('selectedCount2');
|
||
|
||
if (checkedCount > 0) {
|
||
btn.style.display = 'inline-block';
|
||
countSpan.textContent = checkedCount;
|
||
} else {
|
||
btn.style.display = 'none';
|
||
}
|
||
}
|
||
|
||
// 显示Toast提示
|
||
function showToast(message, type = 'info') {
|
||
const toast = document.getElementById('toast');
|
||
const toastBody = document.getElementById('toastBody');
|
||
toastBody.textContent = message;
|
||
toast.className = `toast show bg-${type} text-white`;
|
||
setTimeout(() => {
|
||
toast.className = 'toast';
|
||
}, 3000);
|
||
}
|
||
|
||
// HTML转义
|
||
function escapeHtml(text) {
|
||
const div = document.createElement('div');
|
||
div.textContent = text;
|
||
return div.innerHTML;
|
||
}
|
||
</script>
|
||
</body>
|
||
</html>
|