增加用户名水印,避免信息泄露

This commit is contained in:
zzh 2025-11-22 21:06:20 +08:00
parent 9ac36d7a41
commit 4a7376c318
3 changed files with 113 additions and 0 deletions

View File

@ -589,3 +589,30 @@ input[type="date"]::-webkit-calendar-picker-indicator:hover{
font-size: 14px;
}
}
/* Watermark styles */
.watermark {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none;
z-index: 9999;
overflow: hidden;
}
.watermark-text {
position: absolute;
font-size: 16px;
color: var(--text);
opacity: 0.08;
transform: rotate(-45deg);
white-space: nowrap;
user-select: none;
font-weight: 500;
}
[data-theme="light"] .watermark-text {
opacity: 0.06;
}

View File

@ -21,6 +21,58 @@
} else if (avatarImg) {
avatarImg.src = './assets/user-avatar.svg';
}
// 更新水印
if (user && user.username) {
createWatermark(user.username);
} else {
removeWatermark();
}
}
// 创建水印
function createWatermark(username) {
// 检查水印是否启用
const watermarkEnabled = localStorage.getItem('watermarkEnabled');
if (watermarkEnabled === 'false') {
return;
}
removeWatermark();
const watermarkDiv = document.createElement('div');
watermarkDiv.id = 'watermark-container';
watermarkDiv.className = 'watermark';
const now = new Date();
const dateStr = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-${String(now.getDate()).padStart(2, '0')} ${String(now.getHours()).padStart(2, '0')}:${String(now.getMinutes()).padStart(2, '0')}`;
const watermarkText = `${username} ${dateStr}`;
// 计算水印平铺
const spacing = 300;
const rows = Math.ceil(window.innerHeight / spacing) + 2;
const cols = Math.ceil(window.innerWidth / spacing) + 2;
for (let i = 0; i < rows; i++) {
for (let j = 0; j < cols; j++) {
const span = document.createElement('span');
span.className = 'watermark-text';
span.textContent = watermarkText;
span.style.left = `${j * spacing - 100}px`;
span.style.top = `${i * spacing - 100}px`;
watermarkDiv.appendChild(span);
}
}
document.body.appendChild(watermarkDiv);
}
// 移除水印
function removeWatermark() {
const existing = document.getElementById('watermark-container');
if (existing) {
existing.remove();
}
}
Router.onBeforeEach(async (path) => {
@ -84,6 +136,7 @@
API.logout()
.then(() => {
currentUser = null;
removeWatermark();
updateUserDisplay(null);
userDropdown.style.display = 'none';
location.hash = '#/login';
@ -130,4 +183,14 @@
// 暴露更新用户显示的函数,供设置页面使用
window.updateUserDisplay = updateUserDisplay;
// 暴露水印控制函数
window.toggleWatermark = function(enabled) {
localStorage.setItem('watermarkEnabled', enabled ? 'true' : 'false');
if (enabled && currentUser && currentUser.username) {
createWatermark(currentUser.username);
} else {
removeWatermark();
}
};
})();

View File

@ -64,6 +64,19 @@ Router.register('/settings', async () => {
<div class="actions"><button id="change-btn" class="btn">修改密码</button></div>
</div>
<div style="margin-bottom:24px;padding-bottom:24px;border-bottom:1px solid var(--border)">
<div style="font-weight:500;margin-bottom:12px">水印设置</div>
<div style="display:flex;align-items:center;gap:12px;padding:12px;background:var(--surface);border:1px solid var(--border);border-radius:8px">
<label style="flex:1;cursor:pointer;display:flex;align-items:center;gap:8px">
<input type="checkbox" id="watermark-toggle" ${localStorage.getItem('watermarkEnabled') !== 'false' ? 'checked' : ''} style="width:18px;height:18px;cursor:pointer" />
<span style="font-size:14px">启用页面水印显示用户名和时间戳</span>
</label>
</div>
<div style="margin-top:8px;font-size:12px;color:var(--text-2)">
水印用于防止数据泄露和责任追溯建议保持启用状态
</div>
</div>
<div style="font-weight:500;margin-bottom:12px">超级管理员工具</div>
<div style="margin-top:12px"></div>
<div class="grid cols-2" style="margin-top:8px">
@ -288,6 +301,16 @@ Router.register('/settings', async () => {
change.disabled = false;
}
});
// 水印开关
const watermarkToggle = document.getElementById('watermark-toggle');
watermarkToggle?.addEventListener('change', (e) => {
const enabled = e.target.checked;
if (window.toggleWatermark) {
window.toggleWatermark(enabled);
API.toast(enabled ? '水印已启用' : '水印已关闭');
}
});
document.querySelectorAll('button[data-clear]')?.forEach(btn => {
btn.addEventListener('click', async () => {
const mod = btn.getAttribute('data-clear');