<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>邀请码管理系统</title>
<!-- Tailwind CSS v3 -->
<script src="https://cdn.tailwindcss.com"></script>
<!-- Font Awesome -->
<link href="https://cdn.jsdelivr.net/npm/[email protected]/css/font-awesome.min.css" rel="stylesheet">
<!-- 统一的 Tailwind 配置 -->
<script>
tailwind.config = {
theme: {
extend: {
colors: {
primary: '#3b82f6',
secondary: '#64748b',
success: '#10b981',
warning: '#f59e0b',
danger: '#ef4444',
light: '#f3f4f6',
dark: '#1f2937'
},
fontFamily: {
sans: ['Inter', 'system-ui', 'sans-serif'],
},
boxShadow: {
card: '0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06)',
modal: '0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05)',
}
}
}
}
</script>
<style type="text/tailwindcss">
@layer utilities {
.content-auto {
content-visibility: auto;
}
.transition-all-300 {
transition: all 300ms ease-in-out;
}
.glass-effect {
background: rgba(255, 255, 255, 0.7);
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
}
}
</style>
</head>
<body class="bg-gray-50 min-h-screen">
<div class="container mx-auto px-4 py-8">
<!-- 页面标题 -->
<header class="mb-8">
<h1 class="text-3xl font-bold text-gray-800">邀请码管理系统</h1>
<p class="text-gray-600 mt-2">管理医生邀请码的生成、使用和失效情况</p>
</header>
<!-- 筛选区域 -->
<section class="bg-white rounded-lg shadow-card p-6 mb-6">
<h2 class="text-xl font-semibold text-gray-800 mb-4">筛选条件</h2>
<div class="grid grid-cols-1 md:grid-cols-5 gap-6">
<!-- 医生姓名筛选 -->
<div>
<label for="doctor-name" class="block text-sm font-medium text-gray-700 mb-1">医生姓名</label>
<input type="text" id="doctor-name" class="w-full px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent transition-all-300" placeholder="请输入医生姓名">
</div>
<!-- 医院名称筛选 -->
<div>
<label for="hospital-name" class="block text-sm font-medium text-gray-700 mb-1">医院名称</label>
<input type="text" id="hospital-name" class="w-full px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent transition-all-300" placeholder="请输入医院名称">
</div>
<!-- 已生成总数 -->
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">已生成总数</label>
<div id="total-generated" class="w-full px-4 py-2 bg-gray-50 border border-gray-300 rounded-md text-gray-800 font-medium">0</div>
</div>
<!-- 未使用总数 -->
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">未使用总数</label>
<div id="total-unused" class="w-full px-4 py-2 bg-gray-50 border border-gray-300 rounded-md text-blue-600 font-medium">0</div>
</div>
<!-- 新增邀请码按钮 -->
<div class="flex items-end">
<button id="main-add-code-btn" class="w-full px-4 py-2 bg-primary text-white rounded-md hover:bg-blue-600 transition-all-300">
<i class="fa fa-plus mr-2"></i>新增邀请码
</button>
</div>
</div>
<!-- 筛选按钮 -->
<div class="mt-6 flex justify-end">
<button id="reset-filter" class="px-6 py-2 bg-gray-200 text-gray-700 rounded-md hover:bg-gray-300 transition-all-300 mr-3">
<i class="fa fa-refresh mr-2"></i>重置
</button>
<button id="apply-filter" class="px-6 py-2 bg-primary text-white rounded-md hover:bg-blue-600 transition-all-300">
<i class="fa fa-search mr-2"></i>筛选
</button>
</div>
</section>
<!-- 医生列表区域 -->
<section class="bg-white rounded-lg shadow-card overflow-hidden">
<div class="p-6 border-b border-gray-200">
<h2 class="text-xl font-semibold text-gray-800">医生邀请码列表</h2>
</div>
<!-- 列表头部 -->
<div class="grid grid-cols-12 bg-gray-50 px-6 py-3 text-sm font-medium text-gray-500 border-b border-gray-200">
<div class="col-span-2">医生姓名</div>
<div class="col-span-2">医院名称</div>
<div class="col-span-1 text-center">未使用</div>
<div class="col-span-1 text-center">已使用</div>
<div class="col-span-1 text-center">已失效</div>
<div class="col-span-2 text-center">统计(已生成/总上限)</div>
<div class="col-span-2 text-center">操作</div>
</div>
<!-- 列表内容 -->
<div id="doctor-list" class="divide-y divide-gray-200">
<!-- 列表项将通过JavaScript动态生成 -->
</div>
<!-- 分页区域 -->
<div class="px-6 py-4 flex items-center justify-between border-t border-gray-200">
<div class="text-sm text-gray-700">
显示 <span id="start-item">1</span> 到 <span id="end-item">10</span> 条,共 <span id="total-items">0</span> 条
</div>
<div class="flex space-x-2">
<button id="prev-page" class="px-3 py-1 border border-gray-300 rounded-md text-sm disabled:opacity-50 disabled:cursor-not-allowed">
<i class="fa fa-chevron-left"></i>
</button>
<button id="next-page" class="px-3 py-1 border border-gray-300 rounded-md text-sm disabled:opacity-50 disabled:cursor-not-allowed">
<i class="fa fa-chevron-right"></i>
</button>
</div>
</div>
</section>
</div>
<!-- 明细浮窗 -->
<div id="detail-modal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 hidden">
<div class="bg-white rounded-lg shadow-modal w-full max-w-2xl max-h-[90vh] overflow-hidden flex flex-col">
<!-- 浮窗头部 -->
<div class="px-6 py-4 border-b border-gray-200 flex justify-between items-center">
<h3 class="text-xl font-semibold text-gray-800">邀请码明细</h3>
<button id="close-modal" class="text-gray-500 hover:text-gray-700">
<i class="fa fa-times text-xl"></i>
</button>
</div>
<!-- 浮窗内容 -->
<div class="px-6 py-4 overflow-y-auto flex-grow">
<div class="mb-4">
<span class="text-gray-600">医生姓名:</span>
<span id="detail-doctor-name" class="font-medium text-gray-800"></span>
</div>
<!-- 明细筛选 -->
<div class="mb-4 grid grid-cols-12 gap-4">
<div class="col-span-4">
<input type="text" id="detail-code-filter" class="w-full px-3 py-2 text-sm border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent" placeholder="筛选邀请码">
</div>
<div class="col-span-3">
<select id="detail-status-filter" class="w-full px-3 py-2 text-sm border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent">
<option value="">全部状态</option>
<option value="unused">未使用</option>
<option value="used">已使用</option>
<option value="expired">已失效</option>
</select>
</div>
<div class="col-span-5 flex gap-2">
<input type="datetime-local" id="detail-date-from" class="w-1/2 px-3 py-2 text-sm border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent" placeholder="开始日期">
<span class="flex items-center text-gray-500">至</span>
<input type="datetime-local" id="detail-date-to" class="w-1/2 px-3 py-2 text-sm border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent" placeholder="结束日期">
</div>
</div>
<!-- 邀请码列表 -->
<div class="grid grid-cols-12 bg-gray-50 px-4 py-2 text-sm font-medium text-gray-500 rounded-t-md">
<div class="col-span-5">邀请码</div>
<div class="col-span-3">状态</div>
<div class="col-span-4">失效日期</div>
</div>
<div id="code-detail-list" class="divide-y divide-gray-200 bg-white rounded-b-md">
<!-- 邀请码明细将通过JavaScript动态生成 -->
</div>
</div>
<!-- 浮窗底部 -->
<div class="px-6 py-4 border-t border-gray-200 flex justify-end">
<button id="close-detail-modal" class="px-6 py-2 bg-gray-200 text-gray-700 rounded-md hover:bg-gray-300 transition-all-300">
关闭
</button>
</div>
</div>
</div>
<!-- 新增邀请码浮窗 -->
<div id="add-code-modal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 hidden">
<div class="bg-white rounded-lg shadow-modal w-full max-w-md">
<!-- 浮窗头部 -->
<div class="px-6 py-4 border-b border-gray-200 flex justify-between items-center">
<h3 class="text-xl font-semibold text-gray-800">新增邀请码</h3>
<button id="close-add-modal" class="text-gray-500 hover:text-gray-700">
<i class="fa fa-times text-xl"></i>
</button>
</div>
<!-- 浮窗内容 -->
<div class="px-6 py-6">
<div class="mb-6">
<label for="doctor-select" class="block text-sm font-medium text-gray-700 mb-1">医生姓名</label>
<select id="doctor-select" class="w-full px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent transition-all-300">
<!-- 医生选项将通过JavaScript动态生成 -->
</select>
</div>
<div class="mb-6">
<label for="code-count" class="block text-sm font-medium text-gray-700 mb-1">生成数量</label>
<input type="number" id="code-count" class="w-full px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent transition-all-300" min="1" max="100" value="10">
</div>
<div class="mb-6">
<label for="expire-time" class="block text-sm font-medium text-gray-700 mb-1">失效时间</label>
<input type="datetime-local" id="expire-time" class="w-full px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent transition-all-300">
</div>
</div>
<!-- 浮窗底部 -->
<div class="px-6 pb-6 pt-2 flex justify-end">
<button id="cancel-add-code" class="px-6 py-2 bg-gray-200 text-gray-700 rounded-md hover:bg-gray-300 transition-all-300 mr-3">
取消
</button>
<button id="confirm-add-code" class="px-6 py-2 bg-primary text-white rounded-md hover:bg-blue-600 transition-all-300">
确认生成
</button>
</div>
</div>
</div>
<!-- 提示消息 -->
<div id="toast" class="fixed top-4 right-4 bg-white rounded-lg shadow-card px-6 py-3 flex items-center z-50 transform translate-x-full transition-all duration-300">
<div id="toast-icon" class="mr-3 text-xl"></div>
<div id="toast-message" class="text-gray-800"></div>
</div>
<script>
// 模拟数据
const mockDoctors = [
{
id: 1,
name: "张医生",
hospital: "北京协和医院",
codes: [
{ code: "ABC123", status: "unused", expireTime: "2026-12-31T23:59:59" },
{ code: "DEF456", status: "used", expireTime: "2026-12-31T23:59:59" },
{ code: "GHI789", status: "expired", expireTime: "2025-12-31T23:59:59" },
{ code: "JKL012", status: "unused", expireTime: "2026-12-31T23:59:59" },
{ code: "MNO345", status: "unused", expireTime: "2026-12-31T23:59:59" }
]
},
{
id: 2,
name: "李医生",
hospital: "上海瑞金医院",
codes: [
{ code: "PQR678", status: "used", expireTime: "2026-12-31T23:59:59" },
{ code: "STU901", status: "used", expireTime: "2026-12-31T23:59:59" },
{ code: "VWX234", status: "unused", expireTime: "2026-12-31T23:59:59" }
]
},
{
id: 3,
name: "王医生",
hospital: "广州中山医院",
codes: [
{ code: "YZA567", status: "expired", expireTime: "2025-12-31T23:59:59" },
{ code: "BCD890", status: "unused", expireTime: "2026-12-31T23:59:59" },
{ code: "EFG123", status: "unused", expireTime: "2026-12-31T23:59:59" },
{ code: "HIJ456", status: "unused", expireTime: "2026-12-31T23:59:59" }
]
},
{
id: 4,
name: "赵医生",
hospital: "北京协和医院",
codes: [
{ code: "KLM789", status: "used", expireTime: "2026-12-31T23:59:59" },
{ code: "NOP012", status: "unused", expireTime: "2026-12-31T23:59:59" }
]
},
{
id: 5,
name: "刘医生",
hospital: "上海瑞金医院",
codes: [
{ code: "QRS345", status: "unused", expireTime: "2026-12-31T23:59:59" },
{ code: "TUV678", status: "unused", expireTime: "2026-12-31T23:59:59" },
{ code: "WXY901", status: "expired", expireTime: "2025-12-31T23:59:59" },
{ code: "ZAB234", status: "used", expireTime: "2026-12-31T23:59:59" },
{ code: "CDE567", status: "unused", expireTime: "2026-12-31T23:59:59" },
{ code: "FGH890", status: "unused", expireTime: "2026-12-31T23:59:59" }
]
}
];
// 全局变量
let currentPage = 1;
const itemsPerPage = 10;
let filteredDoctors = [...mockDoctors];
let currentDoctorId = null;
// DOM 加载完成后执行
document.addEventListener('DOMContentLoaded', function() {
// 初始化页面
renderDoctorList();
setupEventListeners();
updateStatistics();
// 设置默认失效时间为30天后
const defaultExpireTime = new Date();
defaultExpireTime.setDate(defaultExpireTime.getDate() + 30);
document.getElementById('expire-time').value = formatDateTime(defaultExpireTime);
});
// 设置事件监听器
function setupEventListeners() {
// 筛选按钮
document.getElementById('apply-filter').addEventListener('click', applyFilter);
document.getElementById('reset-filter').addEventListener('click', resetFilter);
// 分页按钮
document.getElementById('prev-page').addEventListener('click', goToPrevPage);
document.getElementById('next-page').addEventListener('click', goToNextPage);
// 明细浮窗
document.getElementById('close-modal').addEventListener('click', closeDetailModal);
document.getElementById('close-detail-modal').addEventListener('click', closeDetailModal);
// 新增邀请码浮窗
document.getElementById('main-add-code-btn').addEventListener('click', openAddCodeModal);
document.getElementById('close-add-modal').addEventListener('click', closeAddCodeModal);
document.getElementById('cancel-add-code').addEventListener('click', closeAddCodeModal);
document.getElementById('confirm-add-code').addEventListener('click', confirmAddCode);
// 明细筛选
document.getElementById('detail-code-filter').addEventListener('input', filterDetailList);
document.getElementById('detail-status-filter').addEventListener('change', filterDetailList);
document.getElementById('detail-date-from').addEventListener('change', filterDetailList);
document.getElementById('detail-date-to').addEventListener('change', filterDetailList);
// 点击浮窗外区域关闭浮窗
document.getElementById('detail-modal').addEventListener('click', function(e) {
if (e.target === this) closeDetailModal();
});
document.getElementById('add-code-modal').addEventListener('click', function(e) {
if (e.target === this) closeAddCodeModal();
});
}
// 渲染医生列表
function renderDoctorList() {
const doctorList = document.getElementById('doctor-list');
doctorList.innerHTML = '';
// 计算分页
const startIndex = (currentPage - 1) * itemsPerPage;
const endIndex = Math.min(startIndex + itemsPerPage, filteredDoctors.length);
const pageDoctors = filteredDoctors.slice(startIndex, endIndex);
// 更新分页信息
document.getElementById('start-item').textContent = filteredDoctors.length > 0 ? startIndex + 1 : 0;
document.getElementById('end-item').textContent = endIndex;
document.getElementById('total-items').textContent = filteredDoctors.length;
// 更新分页按钮状态
document.getElementById('prev-page').disabled = currentPage === 1;
document.getElementById('next-page').disabled = endIndex >= filteredDoctors.length;
// 渲染列表项
if (pageDoctors.length === 0) {
const emptyItem = document.createElement('div');
emptyItem.className = 'px-6 py-10 text-center text-gray-500';
emptyItem.textContent = '暂无数据';
doctorList.appendChild(emptyItem);
} else {
pageDoctors.forEach(doctor => {
const item = document.createElement('div');
item.className = 'grid grid-cols-12 px-6 py-4 hover:bg-gray-50 transition-all-300';
// 计算邀请码使用情况
const unused = doctor.codes.filter(code => code.status === 'unused').length;
const used = doctor.codes.filter(code => code.status === 'used').length;
const expired = doctor.codes.filter(code => code.status === 'expired').length;
const generated = unused + used; // 已生成 = 未使用 + 已使用
// 设置总上限
const totalLimit = 100;
item.innerHTML = `
<div class="col-span-2 font-medium text-gray-800">${doctor.name}</div>
<div class="col-span-2 text-gray-600">${doctor.hospital}</div>
<div class="col-span-1 text-center font-medium text-blue-600">${unused}</div>
<div class="col-span-1 text-center font-medium text-green-600">${used}</div>
<div class="col-span-1 text-center font-medium text-red-600">${expired}</div>
<div class="col-span-2 text-center">
<span class="px-3 py-1 bg-gray-100 text-gray-800 rounded-full text-sm">${Math.min(generated, totalLimit)}/${totalLimit}</span>
</div>
<div class="col-span-2 text-center">
<button class="view-detail px-3 py-1 bg-primary text-white rounded-md hover:bg-blue-600 transition-all-300" data-doctor-id="${doctor.id}">
明细
</button>
</div>
`;
doctorList.appendChild(item);
});
// 添加明细按钮点击事件
document.querySelectorAll('.view-detail').forEach(button => {
button.addEventListener('click', function() {
const doctorId = parseInt(this.getAttribute('data-doctor-id'));
openDetailModal(doctorId);
});
});
}
}
// 应用筛选
function applyFilter() {
const doctorName = document.getElementById('doctor-name').value.trim().toLowerCase();
const hospitalName = document.getElementById('hospital-name').value.trim().toLowerCase();
filteredDoctors = mockDoctors.filter(doctor => {
// 筛选医生姓名
const nameMatch = doctorName === '' || doctor.name.toLowerCase().includes(doctorName);
// 筛选医院名称
const hospitalMatch = hospitalName === '' || doctor.hospital.toLowerCase().includes(hospitalName);
return nameMatch && hospitalMatch;
});
currentPage = 1;
renderDoctorList();
updateStatistics();
}
// 重置筛选
function resetFilter() {
document.getElementById('doctor-name').value = '';
document.getElementById('hospital-name').value = '';
filteredDoctors = [...mockDoctors];
currentPage = 1;
renderDoctorList();
updateStatistics();
}
// 上一页
function goToPrevPage() {
if (currentPage > 1) {
currentPage--;
renderDoctorList();
}
}
// 下一页
function goToNextPage() {
const maxPage = Math.ceil(filteredDoctors.length / itemsPerPage);
if (currentPage < maxPage) {
currentPage++;
renderDoctorList();
}
}
// 打开明细浮窗
function openDetailModal(doctorId) {
const doctor = mockDoctors.find(d => d.id === doctorId);
if (!doctor) return;
currentDoctorId = doctorId;
document.getElementById('detail-doctor-name').textContent = doctor.name;
// 重置筛选条件
document.getElementById('detail-code-filter').value = '';
document.getElementById('detail-status-filter').value = '';
document.getElementById('detail-date-from').value = '';
document.getElementById('detail-date-to').value = '';
// 渲染明细列表
renderDetailList(doctor.codes);
document.getElementById('detail-modal').classList.remove('hidden');
}
// 关闭明细浮窗
function closeDetailModal() {
document.getElementById('detail-modal').classList.add('hidden');
currentDoctorId = null;
}
// 打开新增邀请码浮窗
function openAddCodeModal() {
// 填充医生选择下拉框
populateDoctorSelect();
document.getElementById('add-code-modal').classList.remove('hidden');
}
// 关闭新增邀请码浮窗
function closeAddCodeModal() {
document.getElementById('add-code-modal').classList.add('hidden');
}
// 更新统计信息
function updateStatistics() {
let totalGenerated = 0;
let totalUnused = 0;
mockDoctors.forEach(doctor => {
const unused = doctor.codes.filter(code => code.status === 'unused').length;
const used = doctor.codes.filter(code => code.status === 'used').length;
totalGenerated += unused + used;
totalUnused += unused;
});
document.getElementById('total-generated').textContent = totalGenerated;
document.getElementById('total-unused').textContent = totalUnused;
}
// 填充医生选择下拉框
function populateDoctorSelect() {
const select = document.getElementById('doctor-select');
select.innerHTML = '';
mockDoctors.forEach(doctor => {
const option = document.createElement('option');
option.value = doctor.id;
option.textContent = `${doctor.name} - ${doctor.hospital}`;
select.appendChild(option);
});
// 如果当前有选中的医生,设置为默认选中
if (currentDoctorId) {
select.value = currentDoctorId;
}
}
// 筛选明细列表
function filterDetailList() {
const codeFilter = document.getElementById('detail-code-filter').value.trim().toUpperCase();
const statusFilter = document.getElementById('detail-status-filter').value;
const dateFrom = document.getElementById('detail-date-from').value;
const dateTo = document.getElementById('detail-date-to').value;
const doctor = mockDoctors.find(d => d.id === currentDoctorId);
if (!doctor) return;
const filteredCodes = doctor.codes.filter(code => {
// 筛选邀请码
const codeMatch = codeFilter === '' || code.code.includes(codeFilter);
// 筛选状态
const statusMatch = statusFilter === '' || code.status === statusFilter;
// 筛选日期范围
let dateMatch = true;
if (dateFrom) {
const codeDate = new Date(code.expireTime);
const fromDate = new Date(dateFrom);
dateMatch = dateMatch && codeDate >= fromDate;
}
if (dateTo) {
const codeDate = new Date(code.expireTime);
const toDate = new Date(dateTo);
dateMatch = dateMatch && codeDate <= toDate;
}
return codeMatch && statusMatch && dateMatch;
});
renderDetailList(filteredCodes);
}
// 渲染明细列表
function renderDetailList(codes) {
const codeList = document.getElementById('code-detail-list');
codeList.innerHTML = '';
if (codes.length === 0) {
const emptyItem = document.createElement('div');
emptyItem.className = 'px-4 py-6 text-center text-gray-500';
emptyItem.textContent = '暂无邀请码';
codeList.appendChild(emptyItem);
} else {
codes.forEach(code => {
const item = document.createElement('div');
item.className = 'grid grid-cols-12 px-4 py-3 hover:bg-gray-50 transition-all-300';
// 格式化失效时间(仅显示日期)
const expireTime = new Date(code.expireTime);
const formattedTime = expireTime.toLocaleDateString('zh-CN', {
year: 'numeric',
month: '2-digit',
day: '2-digit'
});
// 根据状态设置样式
let statusText = '';
let statusClass = '';
switch (code.status) {
case 'unused':
statusText = '未使用';
statusClass = 'bg-blue-100 text-blue-800';
break;
case 'used':
statusText = '已使用';
statusClass = 'bg-green-100 text-green-800';
break;
case 'expired':
statusText = '已失效';
statusClass = 'bg-red-100 text-red-800';
break;
}
item.innerHTML = `
<div class="col-span-5 font-mono text-gray-800">${code.code}</div>
<div class="col-span-3">
<span class="px-2 py-1 ${statusClass} rounded-full text-xs">${statusText}</span>
</div>
<div class="col-span-4 text-gray-600">${formattedTime}</div>
`;
codeList.appendChild(item);
});
}
}
// 确认新增邀请码
function confirmAddCode() {
const doctorId = parseInt(document.getElementById('doctor-select').value);
const count = parseInt(document.getElementById('code-count').value);
const expireTime = document.getElementById('expire-time').value;
// 获取当前医生信息
const doctor = mockDoctors.find(d => d.id === doctorId);
if (!doctor) {
showToast('error', '未找到医生信息');
return;
}
// 设置总上限
const totalLimit = 100;
// 验证输入
if (isNaN(count) || count < 1) {
showToast('error', '请输入有效的生成数量(至少1个)');
return;
}
// 检查是否超过总上限
if (doctor.codes.length + count > totalLimit) {
showToast('error', `生成数量超过总上限,最多还可生成 ${totalLimit - doctor.codes.length} 个邀请码`);
return;
}
if (!expireTime) {
showToast('error', '请选择失效时间');
return;
}
// 生成随机邀请码
for (let i = 0; i < count; i++) {
const newCode = generateRandomCode();
doctor.codes.push({
code: newCode,
status: 'unused',
expireTime: new Date(expireTime).toISOString().slice(0, 19).replace('T', ' ')
});
}
// 关闭浮窗
closeAddCodeModal();
// 刷新主列表和统计信息
applyFilter();
showToast('success', `成功生成 ${count} 个邀请码`);
}
// 生成随机邀请码
function generateRandomCode() {
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
let code = '';
for (let i = 0; i < 6; i++) {
code += chars.charAt(Math.floor(Math.random() * chars.length));
}
return code;
}
// 格式化日期时间
function formatDateTime(date) {
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
const hours = String(date.getHours()).padStart(2, '0');
const minutes = String(date.getMinutes()).padStart(2, '0');
return `${year}-${month}-${day}T${hours}:${minutes}`;
}
// 显示提示消息
function showToast(type, message) {
const toast = document.getElementById('toast');
const toastIcon = document.getElementById('toast-icon');
const toastMessage = document.getElementById('toast-message');
// 设置图标和颜色
if (type === 'success') {
toastIcon.className = 'mr-3 text-xl text-green-500';
toastIcon.innerHTML = '<i class="fa fa-check-circle"></i>';
toast.classList.add('border-l-4', 'border-green-500');
} else if (type === 'error') {
toastIcon.className = 'mr-3 text-xl text-red-500';
toastIcon.innerHTML = '<i class="fa fa-exclamation-circle"></i>';
toast.classList.add('border-l-4', 'border-red-500');
} else if (type === 'warning') {
toastIcon.className = 'mr-3 text-xl text-yellow-500';
toastIcon.innerHTML = '<i class="fa fa-exclamation-triangle"></i>';
toast.classList.add('border-l-4', 'border-yellow-500');
}
// 设置消息
toastMessage.textContent = message;
// 显示提示
toast.classList.remove('translate-x-full');
// 3秒后隐藏
setTimeout(() => {
toast.classList.add('translate-x-full');
// 重置样式
setTimeout(() => {
toast.classList.remove('border-l-4', 'border-green-500', 'border-red-500', 'border-yellow-500');
}, 300);
}, 3000);
}
</script>
</body>
</html>
index.html
md
README.md
index.html