<!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 rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: 'Segoe UI', system-ui, -apple-system, sans-serif;
}
body {
min-height: 100vh;
background: linear-gradient(135deg, #1a202c, #2d3748);
color: #e2e8f0;
padding: 20px;
display: flex;
justify-content: center;
align-items: flex-start;
}
.container {
width: 100%;
max-width: 1200px;
display: grid;
grid-template-columns: 280px 1fr;
gap: 30px;
}
header {
grid-column: 1 / -1;
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
}
.brand {
display: flex;
align-items: center;
gap: 12px;
}
.brand i {
font-size: 1.8rem;
color: #63b3ed;
}
.brand h1 {
font-weight: 600;
font-size: 1.5rem;
letter-spacing: -0.5px;
}
.controls {
display: flex;
gap: 15px;
}
.btn {
background: rgba(74, 85, 104, 0.6);
backdrop-filter: blur(10px);
padding: 8px 16px;
border-radius: 8px;
color: #e2e8f0;
display: flex;
align-items: center;
gap: 8px;
cursor: pointer;
transition: all 0.2s ease;
border: none;
font-weight: 500;
}
.btn:hover {
background: rgba(90, 103, 128, 0.8);
transform: translateY(-2px);
}
.btn-primary {
background: rgba(56, 178, 172, 0.8);
}
.btn-primary:hover {
background: rgba(56, 178, 172, 1);
}
/* 通知中心样式 */
.notification-center {
background: rgba(30, 41, 59, 0.6);
backdrop-filter: blur(20px);
border-radius: 16px;
padding: 20px;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
border: 1px solid rgba(255, 255, 255, 0.08);
display: flex;
flex-direction: column;
overflow: hidden;
}
.notification-center-header {
margin-bottom: 20px;
display: flex;
justify-content: space-between;
align-items: center;
}
.section-title {
font-size: 1.4rem;
font-weight: 600;
}
.notification-list {
display: flex;
flex-direction: column;
gap: 16px;
max-height: 75vh;
overflow-y: auto;
padding: 5px;
scrollbar-width: thin;
scrollbar-color: rgba(113, 128, 150, 0.5) transparent;
}
.notification-list::-webkit-scrollbar {
width: 8px;
}
.notification-list::-webkit-scrollbar-thumb {
background: rgba(113, 128, 150, 0.5);
border-radius: 4px;
}
.notification-list::-webkit-scrollbar-thumb:hover {
background: rgba(113, 128, 150, 0.7);
}
.notification {
background: rgba(26, 32, 44, 0.8);
border-radius: 12px;
padding: 18px;
cursor: pointer;
transition: all 0.2s ease;
border: 1px solid rgba(74, 85, 104, 0.4);
position: relative;
overflow: hidden;
}
.notification::before {
content: '';
position: absolute;
top: 0;
left: 0;
width: 3px;
height: 100%;
background: #63b3ed;
border-radius: 3px 0 0 3px;
}
.notification:hover {
transform: translateY(-3px);
box-shadow: 0 6px 16px rgba(0, 0, 0, 0.2);
border-color: rgba(99, 179, 237, 0.4);
}
.notification-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 10px;
}
.notification-icon {
width: 40px;
height: 40px;
background: rgba(56, 178, 172, 0.15);
border-radius: 10px;
display: flex;
align-items: center;
justify-content: center;
color: #38b2ac;
}
.notification-title {
font-weight: 600;
font-size: 1.1rem;
margin-bottom: 5px;
}
.notification-time {
color: #a0aec0;
font-size: 0.85rem;
}
.notification-content {
color: #cbd5e0;
line-height: 1.5;
font-size: 0.95rem;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
}
.notification-unread::after {
content: '';
position: absolute;
top: 18px;
right: 18px;
width: 8px;
height: 8px;
border-radius: 50%;
background: #63b3ed;
animation: pulse 1.5s infinite;
}
@keyframes pulse {
0% { opacity: 0.8; }
50% { opacity: 0.3; }
100% { opacity: 0.8; }
}
.notification-type {
display: inline-block;
padding: 3px 8px;
font-size: 0.8rem;
border-radius: 6px;
background: rgba(99, 179, 237, 0.15);
color: #90cdf4;
margin-top: 10px;
}
/* 详情弹窗 */
.notification-detail {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: rgba(30, 41, 59, 0.95);
backdrop-filter: blur(25px);
width: 500px;
max-width: 90%;
border-radius: 16px;
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.4);
z-index: 100;
overflow: hidden;
border: 1px solid rgba(255, 255, 255, 0.1);
display: none;
opacity: 0;
transition: opacity 0.3s ease;
}
.notification-detail.draggable .detail-header {
cursor: move;
}
.notification-detail.dragging .detail-header {
cursor: grabbing;
}
.detail-header {
padding: 20px;
background: rgba(23, 32, 49, 0.8);
border-bottom: 1px solid rgba(74, 85, 104, 0.4);
user-select: none;
}
.detail-title {
font-size: 1.3rem;
font-weight: 600;
display: flex;
align-items: center;
gap: 10px;
}
.detail-title i {
color: #63b3ed;
}
.detail-content {
padding: 25px;
max-height: 50vh;
overflow-y: auto;
scrollbar-width: thin;
scrollbar-color: rgba(113, 128, 150, 0.5) transparent;
}
.detail-content::-webkit-scrollbar {
width: 8px;
}
.detail-content::-webkit-scrollbar-thumb {
background: rgba(113, 128, 150, 0.5);
border-radius: 4px;
}
.detail-content p {
line-height: 1.7;
margin-bottom: 15px;
color: #e2e8f0;
}
.detail-footer {
padding: 15px 25px;
display: flex;
justify-content: space-between;
align-items: center;
background: rgba(23, 32, 49, 0.8);
border-top: 1px solid rgba(74, 85, 104, 0.4);
}
.detail-time {
font-size: 0.9rem;
color: #a0aec0;
}
.close-btn {
background: rgba(74, 85, 104, 0.4);
padding: 8px 16px;
border-radius: 8px;
color: #e2e8f0;
border: none;
cursor: pointer;
transition: all 0.2s ease;
}
.close-btn:hover {
background: rgba(90, 103, 128, 0.6);
}
.sent-by {
display: flex;
align-items: center;
gap: 10px;
margin: 20px 0;
background: rgba(26, 32, 44, 0.6);
padding: 12px 15px;
border-radius: 10px;
}
.avatar {
width: 40px;
height: 40px;
border-radius: 50%;
background: linear-gradient(45deg, #38b2ac, #4299e1);
display: flex;
align-items: center;
justify-content: center;
color: white;
font-weight: 600;
}
.overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.5);
backdrop-filter: blur(5px);
z-index: 99;
display: none;
}
.empty-message {
text-align: center;
color: #a0aec0;
padding: 40px 20px;
font-size: 0.95rem;
}
.feedback {
position: fixed;
top: 20px;
left: 50%;
transform: translateX(-50%);
padding: 12px 24px;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
z-index: 1000;
animation: fadeInOut 2.5s forwards;
}
@keyframes fadeInOut {
0% { opacity: 0; transform: translateX(-50%) translateY(-20px); }
15% { opacity: 1; transform: translateX(-50%) translateY(0); }
85% { opacity: 1; transform: translateX(-50%) translateY(0); }
100% { opacity: 0; transform: translateX(-50%) translateY(-20px); }
}
@media (max-width: 768px) {
.container {
grid-template-columns: 1fr;
}
.notification-center {
padding: 15px;
}
.controls {
flex-direction: column;
}
}
</style>
</head>
<body>
<div class="container">
<header>
<div class="brand">
<i class="fas fa-bell"></i>
<h1>系统通知中心</h1>
</div>
<div class="controls">
<button class="btn" id="mark-read">
<i class="fas fa-check-circle"></i> 全部已读
</button>
<button class="btn btn-primary" id="new-notification">
<i class="fas fa-plus"></i> 添加通知
</button>
</div>
</header>
<section class="notification-center">
<div class="notification-center-header">
<h2 class="section-title">未读通知</h2>
<span class="notification-count">3 条未读</span>
</div>
<div class="notification-list" id="unread-list">
</div>
</section>
<section class="notification-center">
<div class="notification-center-header">
<h2 class="section-title">历史通知</h2>
</div>
<div class="notification-list" id="history-list">
<!-- 历史通知将通过JS动态生成 -->
</div>
</section>
</div>
<!-- 通知详情弹窗 -->
<div class="notification-detail" id="detail-window">
<div class="detail-header">
<h3 class="detail-title">
<i class="fas fa-info-circle"></i>
<span id="detail-title">通知标题</span>
</h3>
</div>
<div class="detail-content">
<div class="sent-by">
<div class="avatar">JS</div>
<div>
<div>发送者:系统通知服务</div>
<div class="detail-time" id="detail-send-time">2023年10月15日 14:30</div>
</div>
</div>
<p id="detail-message">通知消息内容...</p>
<p>这是通知的详细内容。当您收到重要系统更新、安全提醒或需要关注的任务时,我们会在这里通知您。</p>
<p>您可以看到现在窗口有边缘检测功能:当靠近浏览器窗口边缘时,窗口会自动调整位置保持在视口中。</p>
<p>调整浏览器窗口大小会实时更新位置,确保窗口始终可见。</p>
</div>
<div class="detail-footer">
<div class="detail-time">ID: NS-2023-0012</div>
<button class="close-btn" id="close-detail">关闭</button>
</div>
</div>
<div class="overlay" id="overlay"></div>
<script>
// 模拟通知数据
const notifications = [
{
id: 1,
title: "系统更新可用",
content: "新版本的系统更新已经可用,请安装以获取最新功能和安全更新。安装过程需要约10分钟,请确保您的设备已接通电源。",
time: "2 分钟前",
datetime: "2023-10-25T11:25:15",
icon: "fas fa-sync-alt",
color: "#63b3ed",
type: "更新",
unread: true,
category: "unread",
sender: "系统更新服务",
details: "更新版本号:v2.4.3。包括性能改进和安全补丁,修复了上一版本中的内存泄漏问题,建议所有用户尽快更新。"
},
{
id: 2,
title: "安全扫描完成",
content: "系统安全扫描已完成,未检测到威胁。所有核心系统文件均通过验证,系统状态良好。",
time: "1 小时前",
datetime: "2023-10-25T10:20:45",
icon: "fas fa-shield-alt",
color: "#48bb78",
type: "安全",
unread: true,
category: "unread",
sender: "安全防护中心",
details: "本次扫描范围包括系统文件、第三方应用程序和注册表项。未发现恶意软件或可疑活动。扫描耗时12分45秒,覆盖了系统关键区域。"
},
{
id: 3,
title: "存储空间不足",
content: "系统盘仅剩 5.2GB 可用空间,请清理一些文件以释放空间。长期低存储空间可能导致系统性能下降。",
time: "3 小时前",
datetime: "2023-10-25T08:45:22",
icon: "fas fa-hdd",
color: "#ed64a6",
type: "警告",
unread: true,
category: "unread",
sender: "存储管理系统",
details: "您的系统分区(C:)剩余空间不足总容量的10%。大文件:下载文件夹(1.8GB), 应用程序缓存(1.2GB)。建议使用磁盘清理工具或删除不再需要的文件。"
},
{
id: 4,
title: "备份已完成",
content: "系统自动备份已完成。耗时 25 分钟,所有用户文件和系统设置都已安全存储在备份设备中。",
time: "昨天",
datetime: "2023-10-24T17:30:10",
icon: "fas fa-save",
color: "#9f7aea",
type: "任务",
unread: false,
category: "history",
sender: "备份服务",
details: "本次备份成功归档了256个文件和7个系统配置项。总备份大小: 3.7GB。使用增量备份技术节省了35%的存储空间。备份位置:D:\\Backup\\System\\2023-10-24。"
},
{
id: 5,
title: "登录通知",
content: "您的帐户在 2023年10月24日 13:45 从新的设备登录。如非本人操作,请立即更改密码。",
time: "2023年10月24日",
datetime: "2023-10-24T13:45:20",
icon: "fas fa-user-check",
color: "#f6ad55",
type: "安全",
unread: false,
category: "history",
sender: "账户安全服务",
details: "登录设备型号:Samsung SM-G998U (Android 13)。位置:上海市。如果这并非您本人的设备,建议您检查账户安全设置并更新密码。"
}
];
// 初始化
document.addEventListener('DOMContentLoaded', () => {
renderNotifications();
setupEventListeners();
// 添加窗口大小变化监听
window.addEventListener('resize', handleWindowResize);
});
// 当前弹窗位置状态
let currentPosition = {
x: 0,
y: 0,
visible: false
};
// 渲染通知列表
function renderNotifications() {
const unreadList = document.getElementById('unread-list');
const historyList = document.getElementById('history-list');
// 过滤通知
const unreadNotifications = notifications.filter(n => n.category === 'unread');
const historyNotifications = notifications.filter(n => n.category === 'history');
// 更新未读计数
const unreadCount = unreadNotifications.filter(n => n.unread).length;
document.querySelector('.notification-count').textContent = `${unreadCount} 条未读`;
// 清空列表并添加内容
renderList(unreadList, unreadNotifications, '无未读通知');
renderList(historyList, historyNotifications, '无历史通知');
}
function renderList(listElement, notifications, emptyMessage) {
// 清空列表
listElement.innerHTML = '';
if (notifications.length === 0) {
// 添加空状态提示
const emptyMsg = document.createElement('div');
emptyMsg.className = 'empty-message';
emptyMsg.textContent = emptyMessage;
listElement.appendChild(emptyMsg);
return;
}
// 添加通知到列表
notifications.forEach(notification => {
const element = createNotificationElement(notification);
listElement.appendChild(element);
});
}
// 创建单个通知DOM元素
function createNotificationElement(notification) {
const element = document.createElement('div');
element.className = `notification ${notification.unread ? 'notification-unread' : ''}`;
element.dataset.id = notification.id;
element.innerHTML = `
<div class="notification-header">
<div>
<div class="notification-title">${notification.title}</div>
<div class="notification-time">${notification.time}</div>
</div>
<div class="notification-icon" style="background: rgba(${hexToRgb(notification.color)}, 0.15); color: ${notification.color}">
<i class="${notification.icon}"></i>
</div>
</div>
<div class="notification-content">
${notification.content}
</div>
<div class="notification-type">${notification.type}</div>
`;
return element;
}
// HEX颜色转RGB
function hexToRgb(hex) {
const r = parseInt(hex.slice(1, 3), 16);
const g = parseInt(hex.slice(3, 5), 16);
const b = parseInt(hex.slice(5, 7), 16);
return `${r}, ${g}, ${b}`;
}
// 设置事件监听
function setupEventListeners() {
// 点击通知显示详情
document.addEventListener('click', (e) => {
const notification = e.target.closest('.notification');
if (notification) {
const id = parseInt(notification.dataset.id);
const notificationData = notifications.find(n => n.id === id);
if (notificationData) {
showNotificationDetail(notificationData);
// 标记为已读
if (notificationData.unread) {
notificationData.unread = false;
notification.classList.remove('notification-unread');
renderNotifications();
}
}
}
});
// 关闭详情窗口
document.getElementById('close-detail').addEventListener('click', closeDetailWindow);
// 点击遮罩关闭窗口
document.getElementById('overlay').addEventListener('click', closeDetailWindow);
// 添加新通知
document.getElementById('new-notification').addEventListener('click', addNewNotification);
// 全部已读
document.getElementById('mark-read').addEventListener('click', markAllAsRead);
// 设置拖拽功能
setupDraggable();
// 按ESC关闭详情窗口
document.addEventListener('keydown', (e) => {
if (e.key === 'Escape') {
closeDetailWindow();
}
});
}
// 添加新通知
function addNewNotification() {
const titles = [
'系统优化完成',
'新应用可用',
'账户安全提示',
'性能报告就绪',
'连接新设备'
];
const contents = [
'系统已完成后台优化,您的设备运行更快更流畅。',
'您订阅的应用程序现已可供下载安装。',
'检测到异常登录活动,请确认是否为本人操作。',
'月度系统性能报告已生成,请查看详细信息。',
'发现一个新设备连接到您的网络,请确认身份。'
];
const icons = [
'fas fa-rocket',
'fas fa-download',
'fas fa-exclamation-triangle',
'fas fa-chart-line',
'fas fa-wifi'
];
const colors = [
'#63b3ed',
'#9f7aea',
'#f6ad55',
'#48bb78',
'#ed64a6'
];
const randomIndex = Math.floor(Math.random() * titles.length);
const newNotification = {
id: Date.now(), // 使用时间戳作为唯一ID
title: titles[randomIndex],
content: contents[randomIndex],
time: "刚刚",
datetime: new Date().toISOString(),
icon: icons[randomIndex],
color: colors[randomIndex],
type: ["信息", "提示", "警告"][Math.floor(Math.random() * 3)],
unread: true,
category: "unread",
sender: "系统通知服务",
details: '这是系统自动生成的通知内容。您可以在此通知详情中查看更具体的信息。此功能用于测试系统通知系统的各项功能。'
};
notifications.unshift(newNotification);
renderNotifications();
// 显示操作反馈
showFeedback(`已成功添加新通知: ${titles[randomIndex]}`, '#38b2ac');
}
// 全部标记为已读
function markAllAsRead() {
notifications.forEach(n => {
if (n.category === 'unread') {
n.unread = false;
}
});
renderNotifications();
// 显示操作反馈
showFeedback('所有通知已标记为已读', '#48bb78');
}
function showFeedback(message, color) {
// 移除已有的反馈
const existingFeedback = document.querySelector('.feedback');
if (existingFeedback) {
existingFeedback.remove();
}
const feedback = document.createElement('div');
feedback.textContent = message;
feedback.className = 'feedback';
feedback.style.background = color;
document.body.appendChild(feedback);
setTimeout(() => {
if (document.body.contains(feedback)) {
document.body.removeChild(feedback);
}
}, 2500);
}
// 显示通知详情
function showNotificationDetail(notification) {
const detailWindow = document.getElementById('detail-window');
const overlay = document.getElementById('overlay');
const title = document.getElementById('detail-title');
const sendTime = document.getElementById('detail-send-time');
const message = document.getElementById('detail-message');
title.textContent = notification.title;
// 格式化日期
const date = new Date(notification.datetime);
const formattedDate = date.toLocaleDateString('zh-CN', {
year: 'numeric',
month: 'long',
day: 'numeric',
hour: '2-digit',
minute: '2-digit'
});
sendTime.textContent = formattedDate;
message.textContent = notification.content;
// 更新详情内容
const detailContent = document.querySelector('.detail-content');
if (notification.details) {
const contentParagraphs = detailContent.querySelectorAll('p:nth-child(n+2)');
if (contentParagraphs[1]) {
contentParagraphs[1].textContent = notification.details;
}
}
// 更新发送者
if (notification.sender) {
const senderElement = document.querySelector('.sent-by div > div:first-child');
if (senderElement) {
senderElement.textContent = `发送者: ${notification.sender}`;
}
}
// 显示窗口并居中
detailWindow.style.display = 'block';
overlay.style.display = 'block';
// 重置窗口位置状态
currentPosition.visible = true;
// 居中显示
positionCenter(detailWindow);
// 动画效果
setTimeout(() => {
detailWindow.style.opacity = 1;
overlay.style.opacity = 1;
}, 10);
}
// 居中定位
function positionCenter(element) {
const elementRect = element.getBoundingClientRect();
const x = Math.max(20, Math.min(window.innerWidth - elementRect.width - 20, (window.innerWidth - elementRect.width) / 2));
const y = Math.max(20, Math.min(window.innerHeight - elementRect.height - 20, (window.innerHeight - elementRect.height) / 2));
element.style.left = `${x}px`;
element.style.top = `${y}px`;
element.style.transform = 'none';
// 保存当前窗口位置
currentPosition = {
x: x,
y: y,
visible: true
};
}
// 窗口大小变化处理
function handleWindowResize() {
if (currentPosition.visible) {
const detailWindow = document.getElementById('detail-window');
if (detailWindow.style.display === 'block') {
// 重新调整位置
adjustWindowPosition();
}
}
}
// 调整窗口位置以确保在视口内
function adjustWindowPosition() {
const detailWindow = document.getElementById('detail-window');
const elementRect = detailWindow.getBoundingClientRect();
const viewportWidth = window.innerWidth;
const viewportHeight = window.innerHeight;
// 边界安全距离(10px)
const margin = 10;
// 计算新位置,确保在边界内
let newX = currentPosition.x;
let newY = currentPosition.y;
// 右侧边界检查
if (currentPosition.x + elementRect.width > viewportWidth - margin) {
newX = viewportWidth - elementRect.width - margin;
}
// 左侧边界检查
if (currentPosition.x < margin) {
newX = margin;
}
// 底部边界检查
if (currentPosition.y + elementRect.height > viewportHeight - margin) {
newY = viewportHeight - elementRect.height - margin;
}
// 顶部边界检查
if (currentPosition.y < margin) {
newY = margin;
}
// 应用新位置
detailWindow.style.left = `${newX}px`;
detailWindow.style.top = `${newY}px`;
detailWindow.style.transform = 'none';
// 更新当前位置
currentPosition.x = newX;
currentPosition.y = newY;
}
// 关闭详情窗口
function closeDetailWindow() {
const detailWindow = document.getElementById('detail-window');
const overlay = document.getElementById('overlay');
detailWindow.style.opacity = 0;
overlay.style.opacity = 0;
setTimeout(() => {
detailWindow.style.display = 'none';
overlay.style.display = 'none';
// 更新状态
currentPosition.visible = false;
}, 300);
}
// 设置窗口拖拽功能
function setupDraggable() {
const detailWindow = document.getElementById('detail-window');
const header = document.querySelector('.detail-header');
let isDragging = false;
let currentX, currentY;
let initialX, initialY;
let xOffset = 0, yOffset = 0;
// 开始拖拽
header.addEventListener('mousedown', (e) => {
if (e.target.closest('.detail-header')) {
detailWindow.classList.add('draggable');
isDragging = true;
// 获取初始鼠标位置
initialX = e.clientX - currentPosition.x;
initialY = e.clientY - currentPosition.y;
// 应用拖拽样式
detailWindow.style.transition = 'none';
detailWindow.classList.add('dragging');
e.preventDefault();
}
});
// 拖拽中
document.addEventListener('mousemove', (e) => {
if (isDragging) {
// 计算新位置
currentX = e.clientX - initialX;
currentY = e.clientY - initialY;
// 边界检查 (带有10px安全距离)
const windowRect = detailWindow.getBoundingClientRect();
const viewportWidth = window.innerWidth;
const viewportHeight = window.innerHeight;
// 确保窗口不会移出屏幕 (带有安全距离)
if (currentX < 10) currentX = 10;
if (currentY < 10) currentY = 10;
if (currentX > viewportWidth - windowRect.width - 10)
currentX = viewportWidth - windowRect.width - 10;
if (currentY > viewportHeight - windowRect.height - 20)
currentY = viewportHeight - windowRect.height - 20;
// 更新偏移量
xOffset = currentX;
yOffset = currentY;
// 保存当前位置
currentPosition.x = currentX;
currentPosition.y = currentY;
// 应用新位置
detailWindow.style.left = `${currentX}px`;
detailWindow.style.top = `${currentY}px`;
detailWindow.style.transform = 'none';
}
});
// 停止拖拽
document.addEventListener('mouseup', () => {
if (isDragging) {
isDragging = false;
// 移除拖拽样式
detailWindow.classList.remove('draggable');
detailWindow.classList.remove('dragging');
// 恢复动画效果
detailWindow.style.transition = '';
}
});
}
</script>
</body>
</html>
index.html