Skip to content

Commit fbc3419

Browse files
committed
security: 猫咪位置模糊化 + 领养申请记录PIN保护
安全修复: 1. 位置模糊化 — DEFAULT_CATS 位置从具体地点改为大区域(校园东区/教学区等),防止虐猫者精准定位 2. 领养隐私保护 — 申请记录默认只显示统计摘要(总数+各类状态计数),管理员PIN验证后可查看详情 3. 管理员PIN系统 — 4位密码 0426,sessionStorage 存储验证状态,关闭浏览器自动退出 4. 最近动态脱敏 — 领养相关动态不再暴露申请人姓名(显示为 X**) 5. 新增/修改内容:PIN弹窗UI、renderAdoptAppsPublicView()、updateAdminUI()、toggleAdminMode() 等
1 parent 0312318 commit fbc3419

1 file changed

Lines changed: 232 additions & 15 deletions

File tree

demo/index.html

Lines changed: 232 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -364,6 +364,72 @@
364364
max-width: 540px;
365365
}
366366

367+
/* 管理员验证 */
368+
.app-summary {
369+
text-align: center;
370+
padding: 30px 20px;
371+
background: #f8f9fa;
372+
border-radius: 15px;
373+
color: #888;
374+
line-height: 1.8;
375+
}
376+
377+
.app-summary .lock-icon {
378+
font-size: 2.5em;
379+
margin-bottom: 10px;
380+
}
381+
382+
.pin-modal {
383+
position: fixed;
384+
top: 0; left: 0; width: 100%; height: 100%;
385+
background: rgba(0,0,0,0.5);
386+
display: flex;
387+
align-items: center;
388+
justify-content: center;
389+
z-index: 2000;
390+
}
391+
392+
.pin-modal.hidden { display: none; }
393+
394+
.pin-box {
395+
background: white;
396+
border-radius: 16px;
397+
padding: 30px;
398+
width: 90%;
399+
max-width: 360px;
400+
text-align: center;
401+
}
402+
403+
.pin-box h3 { margin: 0 0 8px; font-size: 1.2em; }
404+
405+
.pin-box .pin-subtitle { color: #999; font-size: 0.85em; margin-bottom: 20px; }
406+
407+
.pin-input-row { display: flex; gap: 8px; justify-content: center; margin-bottom: 15px; }
408+
409+
.pin-digit {
410+
width: 44px; height: 52px;
411+
border: 2px solid #ddd;
412+
border-radius: 10px;
413+
text-align: center;
414+
font-size: 1.3em;
415+
font-weight: 700;
416+
outline: none;
417+
}
418+
419+
.pin-digit:focus { border-color: #667eea; box-shadow: 0 0 0 2px rgba(102,126,234,0.2); }
420+
421+
.pin-error { color: #d63031; font-size: 0.82em; margin-bottom: 10px; min-height: 20px; }
422+
423+
.admin-badge {
424+
display: inline-block;
425+
padding: 3px 10px;
426+
border-radius: 12px;
427+
font-size: 0.75em;
428+
font-weight: 600;
429+
background: #d4edda;
430+
color: #155724;
431+
}
432+
367433
margin-bottom: 30px;
368434
flex-wrap: wrap;
369435
}
@@ -1164,14 +1230,26 @@ <h3 style="margin: 0 0 16px; color: #333; font-size: 1.15em;">🐱 可领养猫
11641230
<p>目前没有可领养的猫咪,请先添加</p>
11651231
</div>
11661232

1233+
<!-- 领养申请记录(管理员验证后可见详情) -->
11671234
<div style="margin: 30px 0 16px; display:flex; align-items:center; justify-content:space-between;">
11681235
<h3 style="color: #333; font-size: 1.15em;">📋 领养申请记录</h3>
1169-
<button class="btn btn-primary" onclick="openAdoptModal()" style="padding: 10px 22px; font-size: 0.9em;">➕ 新增申请</button>
1236+
<div style="display:flex; gap:8px; align-items:center;">
1237+
<span id="admin-status" style="font-size:0.8em; color:#999;"></span>
1238+
<button id="admin-toggle-btn" class="btn btn-secondary" onclick="toggleAdminMode()" style="padding: 8px 16px; font-size: 0.85em;">🔒 管理员验证</button>
1239+
</div>
11701240
</div>
1171-
<div id="adopt-app-list" class="app-list"></div>
1172-
<div id="adopt-app-empty" class="app-empty" style="display:none;">
1173-
<div class="icon">📋</div>
1174-
<p>暂无领养申请记录</p>
1241+
<!-- 未验证:只显示汇总 -->
1242+
<div id="adopt-app-public" class="app-summary"></div>
1243+
<!-- 已验证:显示完整详情 -->
1244+
<div id="adopt-app-admin" style="display:none;">
1245+
<div style="display:flex; justify-content:flex-end; margin-bottom:10px;">
1246+
<button class="btn btn-primary" onclick="openAdoptModal()" style="padding: 8px 18px; font-size: 0.85em;">➕ 新增申请</button>
1247+
</div>
1248+
<div id="adopt-app-list" class="app-list"></div>
1249+
<div id="adopt-app-empty" class="app-empty" style="display:none;">
1250+
<div class="icon">📋</div>
1251+
<p>暂无领养申请记录</p>
1252+
</div>
11751253
</div>
11761254
</section>
11771255
</div>
@@ -1250,6 +1328,22 @@ <h3 style="color: #333; font-size: 1.15em;">📋 领养申请记录</h3>
12501328
</div>
12511329
</div>
12521330

1331+
<!-- 管理员 PIN 验证弹窗 -->
1332+
<div id="pin-modal" class="pin-modal hidden">
1333+
<div class="pin-box">
1334+
<h3>🔒 管理员验证</h3>
1335+
<p class="pin-subtitle">请输入4位管理密码以查看领养申请详情</p>
1336+
<div class="pin-input-row">
1337+
<input type="password" class="pin-digit" maxlength="1" id="p1" oninput="pinNext(this, 'p2')" autocomplete="off">
1338+
<input type="password" class="pin-digit" maxlength="1" id="p2" oninput="pinNext(this, 'p3')" autocomplete="off">
1339+
<input type="password" class="pin-digit" maxlength="1" id="p3" oninput="pinNext(this, 'p4')" autocomplete="off">
1340+
<input type="password" class="pin-digit" maxlength="1" id="p4" oninput="verifyPin()" autocomplete="off">
1341+
</div>
1342+
<div class="pin-error" id="pin-error"></div>
1343+
<button class="btn btn-secondary" onclick="closePinModal()" style="width:100%; padding:10px;">取消</button>
1344+
</div>
1345+
</div>
1346+
12531347
<!-- Toast 提示 -->
12541348
<div id="toast" class="toast"></div>
12551349

@@ -1272,13 +1366,14 @@ <h3 style="color: #333; font-size: 1.15em;">📋 领养申请记录</h3>
12721366

12731367
const CAT_EMOJIS = ['🐱', '😺', '😸', '🙀', '😻'];
12741368

1369+
// ⚠️ 安全:位置仅保留大区域,不暴露具体活动地点(防虐猫)
12751370
const DEFAULT_CATS = [
1276-
{ id: 1, name: '大黄', neutered: true, personality: '亲人可摸', location: '图书馆后门', color: '橘色/黄色', isDefault: true },
1277-
{ id: 2, name: '小白', neutered: false, personality: '胆小怕人', location: '食堂门口', color: '白色', isDefault: true },
1278-
{ id: 3, name: '花花', neutered: true, personality: '可摸不可抱', location: '教学楼A座', color: '三花', isDefault: true },
1279-
{ id: 4, name: '黑豹', neutered: null, personality: '不亲人', location: '操场看台', color: '黑色', isDefault: true },
1371+
{ id: 1, name: '大黄', neutered: true, personality: '亲人可摸', location: '校园东区', color: '橘色/黄色', isDefault: true },
1372+
{ id: 2, name: '小白', neutered: false, personality: '胆小怕人', location: '校园西区', color: '白色', isDefault: true },
1373+
{ id: 3, name: '花花', neutered: true, personality: '可摸不可抱', location: '教学区', color: '三花', isDefault: true },
1374+
{ id: 4, name: '黑豹', neutered: null, personality: '不亲人', location: '运动区', color: '黑色', isDefault: true },
12801375
{ id: 5, name: '咪咪', neutered: true, personality: '非常亲人', location: '宿舍区', color: '狸花(条纹)', isDefault: true },
1281-
{ id: 6, name: '蓝蓝', neutered: false, personality: '亲人', location: '实验楼后', color: '蓝色', isDefault: true }
1376+
{ id: 6, name: '蓝蓝', neutered: false, personality: '亲人', location: '教学区', color: '蓝色', isDefault: true }
12821377
];
12831378

12841379
const DEFAULT_VOLUNTEERS = [
@@ -1347,6 +1442,123 @@ <h3 style="color: #333; font-size: 1.15em;">📋 领养申请记录</h3>
13471442
localStorage.setItem('campuscat_adopt_apps', JSON.stringify(list));
13481443
}
13491444

1445+
// ====================================================
1446+
// 领养对接 — 管理员 PIN 验证
1447+
// ⚠️ 安全:领养申请记录仅管理员可见,防止偷猫风险
1448+
// ====================================================
1449+
const ADMIN_PIN = '0426';
1450+
1451+
function isAdminVerified() {
1452+
return sessionStorage.getItem('campuscat_admin') === '1';
1453+
}
1454+
1455+
function setAdminVerified(val) {
1456+
if (val) {
1457+
sessionStorage.setItem('campuscat_admin', '1');
1458+
} else {
1459+
sessionStorage.removeItem('campuscat_admin');
1460+
}
1461+
}
1462+
1463+
function renderAdoptAppsPublicView() {
1464+
const apps = loadAdoptApps();
1465+
const div = document.getElementById('adopt-app-public');
1466+
if (!div) return;
1467+
1468+
const pending = apps.filter(a => a.status === 'pending').length;
1469+
const approved = apps.filter(a => a.status === 'approved').length;
1470+
const adopted = apps.filter(a => a.status === 'adopted').length;
1471+
1472+
div.innerHTML = `<div class="lock-icon">🔒</div>
1473+
<p>共有 <b>${apps.length}</b> 条领养申请记录</p>
1474+
<p style="font-size:0.85em;">待审核 <b>${pending}</b> · 已通过 <b>${approved}</b> · 已领养 <b>${adopted}</b></p>
1475+
<p style="font-size:0.82em; color:#bbb; margin-top:8px;">申请人信息仅管理员可见</p>`;
1476+
}
1477+
1478+
function updateAdminUI() {
1479+
const verified = isAdminVerified();
1480+
document.getElementById('adopt-app-public').style.display = verified ? 'none' : 'block';
1481+
document.getElementById('adopt-app-admin').style.display = verified ? 'block' : 'none';
1482+
const statusEl = document.getElementById('admin-status');
1483+
const toggleBtn = document.getElementById('admin-toggle-btn');
1484+
if (verified) {
1485+
statusEl.innerHTML = '<span class="admin-badge">✅ 已验证</span>';
1486+
toggleBtn.textContent = '🔓 退出管理';
1487+
toggleBtn.className = 'btn btn-secondary';
1488+
} else {
1489+
statusEl.innerHTML = '';
1490+
toggleBtn.textContent = '🔒 管理员验证';
1491+
toggleBtn.className = 'btn btn-secondary';
1492+
}
1493+
}
1494+
1495+
function toggleAdminMode() {
1496+
if (isAdminVerified()) {
1497+
setAdminVerified(false);
1498+
updateAdminUI();
1499+
renderAdoptAppsPublicView();
1500+
showToast('已退出管理员模式');
1501+
} else {
1502+
openPinModal();
1503+
}
1504+
}
1505+
1506+
function openPinModal() {
1507+
document.getElementById('pin-modal').classList.remove('hidden');
1508+
document.getElementById('pin-error').textContent = '';
1509+
document.getElementById('p1').value = '';
1510+
document.getElementById('p2').value = '';
1511+
document.getElementById('p3').value = '';
1512+
document.getElementById('p4').value = '';
1513+
setTimeout(() => document.getElementById('p1').focus(), 100);
1514+
}
1515+
1516+
function closePinModal() {
1517+
document.getElementById('pin-modal').classList.add('hidden');
1518+
}
1519+
1520+
function pinNext(current, nextId) {
1521+
if (current.value.length === 1) {
1522+
const next = document.getElementById(nextId);
1523+
if (next) next.focus();
1524+
}
1525+
}
1526+
1527+
function verifyPin() {
1528+
const p1 = document.getElementById('p1').value;
1529+
const p2 = document.getElementById('p2').value;
1530+
const p3 = document.getElementById('p3').value;
1531+
const p4 = document.getElementById('p4').value;
1532+
const pin = p1 + p2 + p3 + p4;
1533+
1534+
if (pin.length < 4) return;
1535+
1536+
if (pin === ADMIN_PIN) {
1537+
setAdminVerified(true);
1538+
closePinModal();
1539+
updateAdminUI();
1540+
renderAdoptApps();
1541+
showToast('✅ 验证成功,管理员模式已开启');
1542+
} else {
1543+
document.getElementById('pin-error').textContent = '❌ 密码错误,请重试';
1544+
document.getElementById('p1').value = '';
1545+
document.getElementById('p2').value = '';
1546+
document.getElementById('p3').value = '';
1547+
document.getElementById('p4').value = '';
1548+
setTimeout(() => document.getElementById('p1').focus(), 100);
1549+
}
1550+
}
1551+
1552+
// 点击遮罩关闭 PIN 弹窗
1553+
document.addEventListener('DOMContentLoaded', function() {
1554+
const pinModal = document.getElementById('pin-modal');
1555+
if (pinModal) {
1556+
pinModal.addEventListener('click', function(e) {
1557+
if (e.target === pinModal) closePinModal();
1558+
});
1559+
}
1560+
});
1561+
13501562
// ====================================================
13511563
// 领养对接渲染
13521564
// ====================================================
@@ -1451,15 +1663,17 @@ <h3 style="color: #333; font-size: 1.15em;">📋 领养申请记录</h3>
14511663
}
14521664
}
14531665

1454-
// 如果审核通过,发动态
1666+
// 如果审核通过,发动态(不暴露申请人姓名)
14551667
if (newStatus === 'approved' && oldStatus === 'pending') {
1456-
addActivity(`🏠 领养申请通过:「${app.applicantName}」申请领养「${app.catName}」已通过审核`);
1668+
const maskedName = app.applicantName ? app.applicantName.charAt(0) + '**' : '申请人';
1669+
addActivity(`🏠 领养申请通过:${maskedName} 申请领养「${app.catName}」已通过审核`);
14571670
} else if (newStatus === 'adopted' && oldStatus !== 'adopted') {
1458-
addActivity(`🎉 领养成功!「${app.catName}」已找到新家(申请人:${app.applicantName}`);
1671+
addActivity(`🎉 领养成功!「${app.catName}」已找到新家`);
14591672
}
14601673

14611674
renderAdoptStats();
14621675
renderAdoptCats();
1676+
renderAdoptAppsPublicView();
14631677
renderAdoptApps();
14641678
showToast(`✅ 状态已更新为「${ADOPT_STATUS_MAP[newStatus].label}」`);
14651679
}
@@ -1514,10 +1728,11 @@ <h3 style="color: #333; font-size: 1.15em;">📋 领养申请记录</h3>
15141728
apps.push(app);
15151729
saveAdoptApps(apps);
15161730

1517-
addActivity(`🏠 ${name} 提交了领养${catName}」的申请,等待审核`);
1731+
addActivity(`🏠 有新同学提交了领养${catName}」的申请,等待审核`);
15181732

15191733
closeAdoptModal();
15201734
renderAdoptStats();
1735+
renderAdoptAppsPublicView();
15211736
renderAdoptApps();
15221737
showToast(`✅ 领养申请已提交!审核结果将通知 ${contact}`, 'success');
15231738
}
@@ -2005,7 +2220,7 @@ <h3 style="margin-bottom: 20px; color: #333;">识别结果</h3>
20052220
if (sectionId === 'cats') renderCats();
20062221
if (sectionId === 'volunteers') renderVolunteers();
20072222
if (sectionId === 'finance') { renderFinanceSummary(); renderFinanceChart(); renderFinanceRecords(); }
2008-
if (sectionId === 'adoption') { renderAdoptStats(); renderAdoptCats(); renderAdoptApps(); }
2223+
if (sectionId === 'adoption') { renderAdoptStats(); renderAdoptCats(); renderAdoptAppsPublicView(); renderAdoptApps(); updateAdminUI(); }
20092224
}
20102225

20112226
// ====================================================
@@ -2250,7 +2465,9 @@ <h3 style="margin-bottom: 20px; color: #333;">识别结果</h3>
22502465
renderVolunteers();
22512466
renderAdoptStats();
22522467
renderAdoptCats();
2468+
renderAdoptAppsPublicView();
22532469
renderAdoptApps();
2470+
updateAdminUI();
22542471
renderFinanceSummary();
22552472
renderFinanceChart();
22562473
renderFinanceRecords();

0 commit comments

Comments
 (0)