这是一个完整的前端CRUD管理系统,使用 IndexedDB(浏览器内置数据库)实现真实的数据持久化存储,刷新页面数据不会丢失。

(图片来源网络,侵删)
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">数据管理中心</title>
<link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;600;700&family=Noto+Sans+SC:wght@300;400;500;700&display=swap" rel="stylesheet">
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.0/css/all.min.css" rel="stylesheet">
<style>
:root {
--bg: #0c0f14;
--bg-secondary: #141820;
--card: #1a1f2b;
--card-hover: #1f2535;
--border: #2a3040;
--fg: #e8ecf4;
--fg-muted: #7a849a;
--accent: #00d4aa;
--accent-dim: rgba(0, 212, 170, 0.12);
--accent-glow: rgba(0, 212, 170, 0.3);
--danger: #ff4d6a;
--danger-dim: rgba(255, 77, 106, 0.12);
--warning: #ffb347;
--info: #4dc9f6;
--radius: 12px;
--radius-sm: 8px;
--shadow: 0 4px 24px rgba(0,0,0,0.3);
--transition: 0.25s cubic-bezier(0.4, 0, 0.2, 1);
}
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: 'Noto Sans SC', sans-serif;
background: var(--bg);
color: var(--fg);
min-height: 100vh;
overflow-x: hidden;
}
/* ===== 背景装饰 ===== */
.bg-decoration {
position: fixed;
inset: 0;
pointer-events: none;
z-index: 0;
overflow: hidden;
}
.bg-blob {
position: absolute;
border-radius: 50%;
filter: blur(120px);
opacity: 0.15;
animation: blobFloat 20s ease-in-out infinite;
}
.bg-blob:nth-child(1) {
width: 600px; height: 600px;
background: var(--accent);
top: -200px; right: -100px;
animation-delay: 0s;
}
.bg-blob:nth-child(2) {
width: 400px; height: 400px;
background: #6366f1;
bottom: -100px; left: -50px;
animation-delay: -7s;
}
.bg-blob:nth-child(3) {
width: 300px; height: 300px;
background: var(--warning);
top: 50%; left: 50%;
animation-delay: -14s;
opacity: 0.08;
}
@keyframes blobFloat {
0%, 100% { transform: translate(0, 0) scale(1); }
33% { transform: translate(30px, -40px) scale(1.05); }
66% { transform: translate(-20px, 30px) scale(0.95); }
}
/* ===== 网格线背景 ===== */
.bg-grid {
position: fixed;
inset: 0;
pointer-events: none;
z-index: 0;
background-image:
linear-gradient(rgba(42, 48, 64, 0.3) 1px, transparent 1px),
linear-gradient(90deg, rgba(42, 48, 64, 0.3) 1px, transparent 1px);
background-size: 60px 60px;
mask-image: radial-gradient(ellipse at center, black 30%, transparent 70%);
}
/* ===== 主容器 ===== */
.app-container {
position: relative;
z-index: 1;
max-width: 1200px;
margin: 0 auto;
padding: 32px 24px;
}
/* ===== 顶部标题 ===== */
.app-header {
text-align: center;
margin-bottom: 40px;
}
.app-header h1 {
font-family: 'Space Grotesk', sans-serif;
font-size: clamp(2rem, 5vw, 3rem);
font-weight: 700;
letter-spacing: -1px;
background: linear-gradient(135deg, var(--fg) 0%, var(--accent) 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
margin-bottom: 8px;
}
.app-header p {
color: var(--fg-muted);
font-size: 0.95rem;
font-weight: 300;
}
.db-indicator {
display: inline-flex;
align-items: center;
gap: 6px;
margin-top: 12px;
padding: 6px 14px;
background: var(--accent-dim);
border: 1px solid rgba(0,212,170,0.2);
border-radius: 20px;
font-size: 0.78rem;
color: var(--accent);
font-weight: 500;
}
.db-indicator .dot {
width: 7px; height: 7px;
background: var(--accent);
border-radius: 50%;
animation: pulse 2s ease-in-out infinite;
}
@keyframes pulse {
0%, 100% { opacity: 1; transform: scale(1); }
50% { opacity: 0.5; transform: scale(0.8); }
}
/* ===== 统计卡片 ===== */
.stats-row {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 16px;
margin-bottom: 32px;
}
.stat-card {
background: var(--card);
border: 1px solid var(--border);
border-radius: var(--radius);
padding: 20px 24px;
display: flex;
align-items: center;
gap: 16px;
transition: var(--transition);
}
.stat-card:hover {
border-color: var(--accent);
transform: translateY(-2px);
box-shadow: 0 8px 32px rgba(0,212,170,0.08);
}
.stat-icon {
width: 48px; height: 48px;
border-radius: 12px;
display: flex;
align-items: center;
justify-content: center;
font-size: 1.2rem;
flex-shrink: 0;
}
.stat-icon.total { background: var(--accent-dim); color: var(--accent); }
.stat-icon.active { background: rgba(77,201,246,0.12); color: var(--info); }
.stat-icon.pending { background: rgba(255,179,71,0.12); color: var(--warning); }
.stat-icon.inactive { background: var(--danger-dim); color: var(--danger); }
.stat-info .stat-label {
font-size: 0.78rem;
color: var(--fg-muted);
text-transform: uppercase;
letter-spacing: 0.5px;
margin-bottom: 4px;
}
.stat-info .stat-value {
font-family: 'Space Grotesk', sans-serif;
font-size: 1.6rem;
font-weight: 700;
}
/* ===== 工具栏 ===== */
.toolbar {
display: flex;
flex-wrap: wrap;
gap: 12px;
align-items: center;
margin-bottom: 24px;
}
.search-box {
flex: 1;
min-width: 240px;
position: relative;
}
.search-box i {
position: absolute;
left: 14px;
top: 50%;
transform: translateY(-50%);
color: var(--fg-muted);
font-size: 0.9rem;
}
.search-box input {
width: 100%;
padding: 12px 14px 12px 42px;
background: var(--card);
border: 1px solid var(--border);
border-radius: var(--radius-sm);
color: var(--fg);
font-size: 0.9rem;
font-family: inherit;
outline: none;
transition: var(--transition);
}
.search-box input::placeholder { color: var(--fg-muted); }
.search-box input:focus {
border-color: var(--accent);
box-shadow: 0 0 0 3px var(--accent-dim);
}
.filter-select {
padding: 12px 36px 12px 14px;
background: var(--card);
border: 1px solid var(--border);
border-radius: var(--radius-sm);
color: var(--fg);
font-size: 0.9rem;
font-family: inherit;
outline: none;
cursor: pointer;
appearance: none;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' fill='%237a849a' viewBox='0 0 16 16'%3E%3Cpath d='M8 11L3 6h10z'/%3E%3C/svg%3E");
background-repeat: no-repeat;
background-position: right 12px center;
transition: var(--transition);
}
.filter-select:focus {
border-color: var(--accent);
box-shadow: 0 0 0 3px var(--accent-dim);
}
.btn {
display: inline-flex;
align-items: center;
gap: 8px;
padding: 12px 22px;
border: none;
border-radius: var(--radius-sm);
font-size: 0.9rem;
font-family: inherit;
font-weight: 500;
cursor: pointer;
transition: var(--transition);
white-space: nowrap;
}
.btn:active { transform: scale(0.97); }
.btn-primary {
background: var(--accent);
color: #0c0f14;
}
.btn-primary:hover {
background: #00eabb;
box-shadow: 0 4px 20px var(--accent-glow);
}
.btn-ghost {
background: transparent;
color: var(--fg-muted);
border: 1px solid var(--border);
}
.btn-ghost:hover {
color: var(--fg);
border-color: var(--fg-muted);
background: var(--card);
}
.btn-danger {
background: var(--danger-dim);
color: var(--danger);
border: 1px solid rgba(255,77,106,0.2);
}
.btn-danger:hover {
background: rgba(255,77,106,0.2);
}
.btn-sm {
padding: 8px 14px;
font-size: 0.82rem;
}
/* ===== 数据表格 ===== */
.table-wrapper {
background: var(--card);
border: 1px solid var(--border);
border-radius: var(--radius);
overflow: hidden;
box-shadow: var(--shadow);
}
.table-scroll {
overflow-x: auto;
}
table {
width: 100%;
border-collapse: collapse;
min-width: 700px;
}
thead {
background: var(--bg-secondary);
}
th {
padding: 14px 20px;
text-align: left;
font-size: 0.78rem;
font-weight: 600;
color: var(--fg-muted);
text-transform: uppercase;
letter-spacing: 0.8px;
border-bottom: 1px solid var(--border);
white-space: nowrap;
cursor: pointer;
user-select: none;
transition: var(--transition);
}
th:hover { color: var(--accent); }
th .sort-icon { margin-left: 4px; font-size: 0.7rem; }
td {
padding: 16px 20px;
font-size: 0.9rem;
border-bottom: 1px solid rgba(42,48,64,0.5);
vertical-align: middle;
}
tr { transition: var(--transition); }
tbody tr:hover { background: var(--card-hover); }
tbody tr:last-child td { border-bottom: none; }
/* 用户头像 */
.user-cell {
display: flex;
align-items: center;
gap: 12px;
}
.avatar {
width: 38px; height: 38px;
border-radius: 10px;
display: flex;
align-items: center;
justify-content: center;
font-weight: 700;
font-size: 0.85rem;
flex-shrink: 0;
color: #fff;
}
.user-name { font-weight: 500; }
.user-email { font-size: 0.78rem; color: var(--fg-muted); margin-top: 2px; }
/* 状态标签 */
.status-badge {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 5px 12px;
border-radius: 20px;
font-size: 0.78rem;
font-weight: 500;
}
.status-badge .status-dot {
width: 6px; height: 6px;
border-radius: 50%;
}
.status-active { background: rgba(0,212,170,0.12); color: var(--accent); }
.status-active .status-dot { background: var(--accent); }
.status-pending { background: rgba(255,179,71,0.12); color: var(--warning); }
.status-pending .status-dot { background: var(--warning); }
.status-inactive { background: var(--danger-dim); color: var(--danger); }
.status-inactive .status-dot { background: var(--danger); }
/* 操作按钮 */
.action-btns {
display: flex;
gap: 6px;
}
.action-btn {
width: 34px; height: 34px;
border: 1px solid var(--border);
border-radius: var(--radius-sm);
background: transparent;
color: var(--fg-muted);
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
font-size: 0.82rem;
transition: var(--transition);
}
.action-btn:hover { color: var(--fg); border-color: var(--fg-muted); background: var(--bg-secondary); }
.action-btn.edit:hover { color: var(--accent); border-color: var(--accent); background: var(--accent-dim); }
.action-btn.delete:hover { color: var(--danger); border-color: var(--danger); background: var(--danger-dim); }
/* 空状态 */
.empty-state {
text-align: center;
padding: 60px 20px;
}
.empty-state i {
font-size: 3rem;
color: var(--border);
margin-bottom: 16px;
}
.empty-state h3 {
color: var(--fg-muted);
font-weight: 500;
margin-bottom: 8px;
}
.empty-state p {
color: var(--fg-muted);
font-size: 0.85rem;
opacity: 0.7;
}
/* 分页 */
.table-footer {
display: flex;
justify-content: space-between;
align-items: center;
padding: 14px 20px;
border-top: 1px solid var(--border);
font-size: 0.82rem;
color: var(--fg-muted);
}
.pagination {
display: flex;
gap: 4px;
}
.page-btn {
width: 34px; height: 34px;
border: 1px solid var(--border);
border-radius: var(--radius-sm);
background: transparent;
color: var(--fg-muted);
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
font-size: 0.82rem;
font-family: 'Space Grotesk', sans-serif;
transition: var(--transition);
}
.page-btn:hover { border-color: var(--fg-muted); color: var(--fg); }
.page-btn.active {
background: var(--accent);
border-color: var(--accent);
color: #0c0f14;
font-weight: 600;
}
.page-btn:disabled { opacity: 0.3; cursor: not-allowed; }
/* ===== 模态框 ===== */
.modal-overlay {
position: fixed;
inset: 0;
background: rgba(0,0,0,0.6);
backdrop-filter: blur(8px);
z-index: 1000;
display: flex;
align-items: center;
justify-content: center;
padding: 20px;
opacity: 0;
visibility: hidden;
transition: var(--transition);
}
.modal-overlay.active {
opacity: 1;
visibility: visible;
}
.modal {
background: var(--card);
border: 1px solid var(--border);
border-radius: 16px;
width: 100%;
max-width: 520px;
box-shadow: 0 24px 80px rgba(0,0,0,0.5);
transform: translateY(20px) scale(0.97);
transition: var(--transition);
}
.modal-overlay.active .modal {
transform: translateY(0) scale(1);
}
.modal-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 24px 28px 0;
}
.modal-header h2 {
font-family: 'Space Grotesk', sans-serif;
font-size: 1.3rem;
font-weight: 700;
}
.modal-close {
width: 36px; height: 36px;
border: 1px solid var(--border);
border-radius: 10px;
background: transparent;
color: var(--fg-muted);
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
font-size: 1rem;
transition: var(--transition);
}
.modal-close:hover { background: var(--danger-dim); color: var(--danger); border-color: var(--danger); }
.modal-body {
padding: 24px 28px;
}
.form-group {
margin-bottom: 18px;
}
.form-group label {
display: block;
font-size: 0.82rem;
font-weight: 500;
color: var(--fg-muted);
margin-bottom: 8px;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.form-group input,
.form-group select {
width: 100%;
padding: 12px 14px;
background: var(--bg-secondary);
border: 1px solid var(--border);
border-radius: var(--radius-sm);
color: var(--fg);
font-size: 0.9rem;
font-family: inherit;
outline: none;
transition: var(--transition);
}
.form-group input:focus,
.form-group select:focus {
border-color: var(--accent);
box-shadow: 0 0 0 3px var(--accent-dim);
}
.form-group .error-msg {
font-size: 0.78rem;
color: var(--danger);
margin-top: 6px;
display: none;
}
.form-group.has-error input { border-color: var(--danger); }
.form-group.has-error .error-msg { display: block; }
.form-row {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 14px;
}
.modal-footer {
display: flex;
justify-content: flex-end;
gap: 10px;
padding: 0 28px 24px;
}
/* ===== 确认对话框 ===== */
.confirm-modal .modal {
max-width: 400px;
text-align: center;
}
.confirm-icon {
width: 64px; height: 64px;
border-radius: 50%;
background: var(--danger-dim);
display: flex;
align-items: center;
justify-content: center;
margin: 0 auto 16px;
font-size: 1.5rem;
color: var(--danger);
}
.confirm-text {
color: var(--fg-muted);
font-size: 0.9rem;
margin-top: 8px;
line-height: 1.6;
}
.confirm-name {
color: var(--fg);
font-weight: 600;
}
.confirm-actions {
display: flex;
gap: 10px;
justify-content: center;
margin-top: 24px;
padding: 0 28px 28px;
}
/* ===== Toast 通知 ===== */
.toast-container {
position: fixed;
top: 24px;
right: 24px;
z-index: 2000;
display: flex;
flex-direction: column;
gap: 10px;
}
.toast {
display: flex;
align-items: center;
gap: 12px;
padding: 14px 20px;
background: var(--card);
border: 1px solid var(--border);
border-radius: var(--radius-sm);
box-shadow: 0 8px 32px rgba(0,0,0,0.4);
font-size: 0.88rem;
min-width: 280px;
animation: toastIn 0.35s ease-out;
transition: var(--transition);
}
.toast.removing {
animation: toastOut 0.3s ease-in forwards;
}
.toast-icon {
width: 28px; height: 28px;
border-radius: 8px;
display: flex;
align-items: center;
justify-content: center;
font-size: 0.8rem;
flex-shrink: 0;
}
.toast.success .toast-icon { background: var(--accent-dim); color: var(--accent); }
.toast.success { border-left: 3px solid var(--accent); }
.toast.error .toast-icon { background: var(--danger-dim); color: var(--danger); }
.toast.error { border-left: 3px solid var(--danger); }
.toast.info .toast-icon { background: rgba(77,201,246,0.12); color: var(--info); }
.toast.info { border-left: 3px solid var(--info); }
@keyframes toastIn {
from { opacity: 0; transform: translateX(40px); }
to { opacity: 1; transform: translateX(0); }
}
@keyframes toastOut {
from { opacity: 1; transform: translateX(0); }
to { opacity: 0; transform: translateX(40px); }
}
/* ===== 行动画 ===== */
@keyframes rowIn {
from { opacity: 0; transform: translateY(10px); }
to { opacity: 1; transform: translateY(0); }
}
.row-animate {
animation: rowIn 0.3s ease-out;
}
/* ===== 选中行 ===== */
.row-selected {
background: var(--accent-dim) !important;
}
/* ===== 响应式 ===== */
@media (max-width: 640px) {
.app-container { padding: 20px 14px; }
.stats-row { grid-template-columns: 1fr 1fr; }
.toolbar { flex-direction: column; }
.search-box { min-width: 100%; }
.form-row { grid-template-columns: 1fr; }
.modal-body, .modal-header, .modal-footer { padding-left: 20px; padding-right: 20px; }
.confirm-actions { padding-left: 20px; padding-right: 20px; }
}
/* ===== 滚动条 ===== */
::-webkit-scrollbar { width: 6px; height: 6px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb { background: var(--border); border-radius: 3px; }
::-webkit-scrollbar-thumb:hover { background: var(--fg-muted); }
/* ===== prefers-reduced-motion ===== */
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after {
animation-duration: 0.01ms !important;
transition-duration: 0.01ms !important;
}
}
</style>
</head>
<body>
<!-- 背景装饰 -->
<div class="bg-decoration">
<div class="bg-blob"></div>
<div class="bg-blob"></div>
<div class="bg-blob"></div>
</div>
<div class="bg-grid"></div>
<!-- Toast 容器 -->
<div class="toast-container" id="toastContainer"></div>
<!-- 主容器 -->
<div class="app-container">
<!-- 头部 -->
<header class="app-header">
<h1>DataHub</h1>
<p>基于 IndexedDB 的前端数据管理中心</p>
<div class="db-indicator">
<span class="dot"></span>
<span id="dbStatus">正在连接数据库...</span>
</div>
</header>
<!-- 统计卡片 -->
<section class="stats-row" id="statsRow">
<div class="stat-card">
<div class="stat-icon total"><i class="fas fa-database"></i></div>
<div class="stat-info">
<div class="stat-label">总记录</div>
<div class="stat-value" id="statTotal">0</div>
</div>
</div>
<div class="stat-card">
<div class="stat-icon active"><i class="fas fa-check-circle"></i></div>
<div class="stat-info">
<div class="stat-label">活跃</div>
<div class="stat-value" id="statActive">0</div>
</div>
</div>
<div class="stat-card">
<div class="stat-icon pending"><i class="fas fa-clock"></i></div>
<div class="stat-info">
<div class="stat-label">待审核</div>
<div class="stat-value" id="statPending">0</div>
</div>
</div>
<div class="stat-card">
<div class="stat-icon inactive"><i class="fas fa-times-circle"></i></div>
<div class="stat-info">
<div class="stat-label">已停用</div>
<div class="stat-value" id="statInactive">0</div>
</div>
</div>
</section>
<!-- 工具栏 -->
<section class="toolbar">
<div class="search-box">
<i class="fas fa-search"></i>
<input type="text" id="searchInput" placeholder="搜索姓名、邮箱或部门..." aria-label="搜索">
</div>
<select class="filter-select" id="statusFilter" aria-label="状态筛选">
<option value="all">全部状态</option>
<option value="active">活跃</option>
<option value="pending">待审核</option>
<option value="inactive">已停用</option>
</select>
<select class="filter-select" id="deptFilter" aria-label="部门筛选">
<option value="all">全部部门</option>
</select>
<button class="btn btn-ghost" id="btnExport" aria-label="导出数据">
<i class="fas fa-download"></i> 导出
</button>
<button class="btn btn-primary" id="btnAdd" aria-label="新增记录">
<i class="fas fa-plus"></i> 新增记录
</button>
</section>
<!-- 数据表格 -->
<section class="table-wrapper">
<div class="table-scroll">
<table>
<thead>
<tr>
<th data-sort="id">ID <span class="sort-icon"><i class="fas fa-sort"></i></span></th>
<th data-sort="name">姓名 <span class="sort-icon"><i class="fas fa-sort"></i></span></th>
<th data-sort="email">邮箱</th>
<th data-sort="department">部门 <span class="sort-icon"><i class="fas fa-sort"></i></span></th>
<th data-sort="role">职位</th>
<th data-sort="status">状态 <span class="sort-icon"><i class="fas fa-sort"></i></span></th>
<th data-sort="createdAt">创建时间 <span class="sort-icon"><i class="fas fa-sort"></i></span></th>
<th>操作</th>
</tr>
</thead>
<tbody id="tableBody"></tbody>
</table>
</div>
<div id="emptyState" class="empty-state" style="display:none;">
<i class="fas fa-inbox"></i>
<h3>暂无数据</h3>
<p>点击「新增记录」按钮添加第一条数据</p>
</div>
<div class="table-footer">
<span id="tableInfo">显示 0 条记录</span>
<div class="pagination" id="pagination"></div>
</div>
</section>
</div>
<!-- 新增/编辑模态框 -->
<div class="modal-overlay" id="formModal">
<div class="modal" role="dialog" aria-labelledby="formModalTitle">
<div class="modal-header">
<h2 id="formModalTitle">新增记录</h2>
<button class="modal-close" id="formModalClose" aria-label="关闭"><i class="fas fa-times"></i></button>
</div>
<div class="modal-body">
<form id="recordForm" novalidate>
<input type="hidden" id="formId">
<div class="form-row">
<div class="form-group">
<label for="formName">姓名 *</label>
<input type="text" id="formName" placeholder="请输入姓名" required>
<div class="error-msg">请输入姓名</div>
</div>
<div class="form-group">
<label for="formEmail">邮箱 *</label>
<input type="email" id="formEmail" placeholder="example@mail.com" required>
<div class="error-msg">请输入有效邮箱</div>
</div>
</div>
<div class="form-row">
<div class="form-group">
<label for="formDept">部门 *</label>
<select id="formDept" required>
<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 class="error-msg">请选择部门</div>
</div>
<div class="form-group">
<label for="formRole">职位 *</label>
<input type="text" id="formRole" placeholder="请输入职位" required>
<div class="error-msg">请输入职位</div>
</div>
</div>
<div class="form-group">
<label for="formStatus">状态</label>
<select id="formStatus">
<option value="active">活跃</option>
<option value="pending">待审核</option>
<option value="inactive">已停用</option>
</select>
</div>
</form>
</div>
<div class="modal-footer">
<button class="btn btn-ghost" id="formCancel">取消</button>
<button class="btn btn-primary" id="formSubmit"><i class="fas fa-check"></i> 保存</button>
</div>
</div>
</div>
<!-- 删除确认模态框 -->
<div class="modal-overlay confirm-modal" id="confirmModal">
<div class="modal" role="alertdialog" aria-labelledby="confirmTitle">
<div class="modal-body" style="padding-top:32px;">
<div class="confirm-icon"><i class="fas fa-trash-alt"></i></div>
<h2 id="confirmTitle" style="font-family:'Space Grotesk',sans-serif;font-size:1.2rem;font-weight:700;">确认删除</h2>
<p class="confirm-text">确定要删除 <span class="confirm-name" id="confirmName"></span> 的记录吗?此操作不可撤销。</p>
</div>
<div class="confirm-actions">
<button class="btn btn-ghost" id="confirmCancel">取消</button>
<button class="btn btn-danger" id="confirmDelete"><i class="fas fa-trash-alt"></i> 确认删除</button>
</div>
</div>
</div>
<!-- 详情模态框 -->
<div class="modal-overlay" id="detailModal">
<div class="modal" role="dialog" aria-labelledby="detailTitle">
<div class="modal-header">
<h2 id="detailTitle">记录详情</h2>
<button class="modal-close" id="detailModalClose" aria-label="关闭"><i class="fas fa-times"></i></button>
</div>
<div class="modal-body" id="detailBody"></div>
<div class="modal-footer">
<button class="btn btn-ghost" id="detailClose">关闭</button>
</div>
</div>
</div>
<script>
// ===========================
// IndexedDB 数据库操作层
// ===========================
const DB_NAME = 'DataHubDB';
const DB_VERSION = 1;
const STORE_NAME = 'records';
let db = null;
// 头像颜色池
const avatarColors = [
'#00d4aa', '#6366f1', '#f59e0b', '#ef4444',
'#8b5cf6', '#ec4899', '#14b8a6', '#f97316',
'#06b6
(图片来源网络,侵删)
