Files
weixin-holiday-message/static/contacts_manager.html

876 lines
40 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!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('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" onclick="openCustomContentModal(${c.id}, '${escapeHtml(customContent)}')">${escapeHtml(customContent)}</span></td>
<td><span class="blessing-text" onclick="openBlessingModal(${c.id}, '${escapeHtml(c.blessing)}', '${escapeHtml(c.search_name || '')}', '${escapeHtml(c.category || '')}')" title="${escapeHtml(c.blessing)}">${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>