Files
showpic/public/admin.html
T

441 lines
23 KiB
HTML
Raw Normal View History

2026-07-16 22:51:02 +08:00
<!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 href="/vendor/bootstrap/bootstrap.min.css" rel="stylesheet">
<link href="/vendor/cropperjs/cropper.min.css" rel="stylesheet">
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
2026-07-16 22:58:46 +08:00
body { font-family: -apple-system, BlinkMacSystemFont, 'PingFang SC', sans-serif; background: #f5f5f5; }
.header { background: #fff; padding: 15px 30px; border-bottom: 1px solid #e8e8e8; display: flex; justify-content: space-between; align-items: center; }
.header h1 { font-size: 20px; font-weight: 500; }
.header nav a { margin-left: 20px; text-decoration: none; color: #666; font-size: 14px; }
.header nav a:hover { color: #333; }
.container { max-width: 1200px; margin: 20px auto; padding: 0 20px; }
.card { background: #fff; border-radius: 8px; padding: 20px; margin-bottom: 20px; box-shadow: 0 1px 3px rgba(0,0,0,0.08); }
.card h2 { font-size: 16px; margin-bottom: 15px; padding-bottom: 10px; border-bottom: 1px solid #eee; }
2026-07-16 22:51:02 +08:00
.top-bar { display: flex; justify-content: space-between; align-items: center; margin-bottom: 30px; }
.feature-status { display: flex; align-items: center; gap: 15px; }
.feature-status span { font-size: 16px; font-weight: 500; }
2026-07-16 22:58:46 +08:00
2026-07-16 22:51:02 +08:00
.toggle { position: relative; display: inline-block; width: 50px; height: 26px; }
.toggle input { opacity: 0; width: 0; height: 0; }
.slider { position: absolute; cursor: pointer; top: 0; left: 0; right: 0; bottom: 0; background: #ccc; transition: .3s; border-radius: 26px; }
.slider:before { position: absolute; content: ""; height: 22px; width: 22px; left: 2px; bottom: 2px; background: #fff; transition: .3s; border-radius: 50%; }
input:checked + .slider { background: #1890ff; }
input:checked + .slider:before { transform: translateX(24px); }
2026-07-16 22:58:46 +08:00
.group { margin-bottom: 30px; border: 1px solid #e8e8e8; border-radius: 8px; padding: 20px; background: #fff; }
.group.playing { border-color: #1890ff; box-shadow: 0 0 0 2px rgba(24,144,255,0.1); }
.group-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 15px; }
.group-info { display: flex; align-items: center; gap: 15px; flex-wrap: wrap; }
.group-name { font-size: 16px; font-weight: 500; }
.group-time { font-size: 13px; color: #666; }
.group-badge { font-size: 11px; padding: 2px 8px; border-radius: 10px; background: #e6f7ff; color: #1890ff; }
.group-actions { display: flex; gap: 15px; }
.group-actions a { color: #1890ff; text-decoration: none; font-size: 13px; cursor: pointer; }
.group-actions a:hover { text-decoration: underline; }
2026-07-16 22:51:02 +08:00
.group-actions a.delete { color: #ff4d4f; }
2026-07-16 22:58:46 +08:00
2026-07-16 22:51:02 +08:00
.image-list { display: flex; flex-wrap: wrap; gap: 15px; }
2026-07-16 22:58:46 +08:00
.image-item { position: relative; width: 140px; height: 140px; background: #e8e8e8; cursor: pointer; border-radius: 4px; overflow: hidden; }
2026-07-16 22:51:02 +08:00
.image-item img { width: 100%; height: 100%; object-fit: cover; display: block; }
2026-07-16 22:58:46 +08:00
.image-item.disabled::after { content: ''; position: absolute; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0,0,0,0.5); }
.delete-btn { position: absolute; top: -6px; right: -6px; width: 22px; height: 22px; background: #ff4d4f; color: #fff; border: 2px solid #fff; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 13px; cursor: pointer; z-index: 10; }
.action-menu { display: none; position: absolute; top: -6px; right: 22px; background: #fff; border: 1px solid #e8e8e8; box-shadow: 0 2px 8px rgba(0,0,0,0.12); z-index: 100; min-width: 70px; }
.action-menu.show { display: block; }
.action-menu a { display: block; padding: 8px 16px; text-decoration: none; font-size: 13px; text-align: center; border-bottom: 1px solid #e8e8e8; cursor: pointer; }
.action-menu a:last-child { border-bottom: none; }
2026-07-16 22:51:02 +08:00
.action-menu a.enable { color: #52c41a; }
.action-menu a.disable { color: #1890ff; }
.action-menu a.delete { color: #ff4d4f; }
2026-07-16 22:58:46 +08:00
2026-07-16 22:51:02 +08:00
.time-input { display: flex; align-items: center; gap: 10px; justify-content: center; }
2026-07-16 22:58:46 +08:00
.time-input input { width: 60px; height: 36px; border: 1px solid #d9d9d9; text-align: center; font-size: 14px; border-radius: 4px; }
2026-07-16 22:51:02 +08:00
</style>
</head>
<body>
2026-07-16 22:58:46 +08:00
<div class="header">
<h1>后台管理</h1>
<nav>
<a href="/">首页</a>
<a href="/admin">管理</a>
<a href="#" id="changePasswordBtn">修改密码</a>
<a href="#" id="logout">退出登录</a>
</nav>
</div>
2026-07-16 22:51:02 +08:00
<div class="container">
<div class="top-bar">
<div class="feature-status">
<span>功能状态</span>
<label class="toggle">
<input type="checkbox" id="featureToggle">
<span class="slider"></span>
</label>
2026-07-16 22:58:46 +08:00
<span id="featureStatus" style="color:#999;font-size:13px;">已关闭</span>
2026-07-16 22:51:02 +08:00
</div>
<div class="d-flex gap-2">
2026-07-16 22:58:46 +08:00
<button class="btn btn-outline-secondary btn-sm" id="resetBtn" style="display:none;">重置轮播</button>
<button class="btn btn-dark btn-sm" id="addGroupBtn">添加分组</button>
2026-07-16 22:51:02 +08:00
</div>
</div>
2026-07-16 22:58:46 +08:00
2026-07-16 22:51:02 +08:00
<div id="groupsContainer"></div>
</div>
2026-07-16 22:58:46 +08:00
<!-- 删除分组 -->
2026-07-16 22:51:02 +08:00
<div class="modal fade" id="deleteGroupModal" tabindex="-1">
2026-07-16 22:58:46 +08:00
<div class="modal-dialog modal-dialog-centered modal-sm">
<div class="modal-content"><div class="modal-body text-center py-4">
<h6 class="mb-4">确认删除该分组?</h6>
<div class="d-flex justify-content-center gap-3">
<button class="btn btn-outline-secondary btn-sm px-3" data-bs-dismiss="modal">取消</button>
<button class="btn btn-danger btn-sm px-3" id="confirmDeleteGroup">确认删除</button>
2026-07-16 22:51:02 +08:00
</div>
2026-07-16 22:58:46 +08:00
</div></div>
2026-07-16 22:51:02 +08:00
</div>
</div>
2026-07-16 22:58:46 +08:00
<!-- 设置时间 -->
2026-07-16 22:51:02 +08:00
<div class="modal fade" id="setTimeModal" tabindex="-1">
2026-07-16 22:58:46 +08:00
<div class="modal-dialog modal-dialog-centered modal-sm">
<div class="modal-content"><div class="modal-body text-center py-4">
<h6 class="mb-3">设置时间</h6>
<div class="time-input mb-3">
<input type="number" id="timeMinutes" min="0" value="0">
<span></span>
<input type="number" id="timeSeconds" min="0" value="30">
<span></span>
2026-07-16 22:51:02 +08:00
</div>
2026-07-16 22:58:46 +08:00
<div class="d-flex justify-content-center gap-3">
<button class="btn btn-outline-secondary btn-sm px-3" data-bs-dismiss="modal">取消</button>
<button class="btn btn-dark btn-sm px-3" id="confirmSetTime">保存</button>
</div>
</div></div>
2026-07-16 22:51:02 +08:00
</div>
</div>
2026-07-16 22:58:46 +08:00
<!-- 删除图片 -->
2026-07-16 22:51:02 +08:00
<div class="modal fade" id="deleteImageModal" tabindex="-1">
2026-07-16 22:58:46 +08:00
<div class="modal-dialog modal-dialog-centered modal-sm">
<div class="modal-content"><div class="modal-body text-center py-4">
<h6 class="mb-4">确认删除该图片?</h6>
<div class="d-flex justify-content-center gap-3">
<button class="btn btn-outline-secondary btn-sm px-3" data-bs-dismiss="modal">取消</button>
<button class="btn btn-danger btn-sm px-3" id="confirmDeleteImage">确认删除</button>
2026-07-16 22:51:02 +08:00
</div>
2026-07-16 22:58:46 +08:00
</div></div>
2026-07-16 22:51:02 +08:00
</div>
</div>
2026-07-16 22:58:46 +08:00
<!-- 裁剪 -->
2026-07-16 22:51:02 +08:00
<div class="modal fade" id="cropModal" tabindex="-1">
<div class="modal-dialog modal-lg">
<div class="modal-content">
2026-07-16 22:58:46 +08:00
<div class="modal-header py-2"><h6 class="modal-title">裁剪图片</h6><button type="button" class="btn-close" data-bs-dismiss="modal"></button></div>
<div class="modal-body"><div style="max-height:400px;overflow:hidden;"><img id="cropImage" style="max-width:100%;"></div></div>
<div class="modal-footer py-2"><button class="btn btn-outline-secondary btn-sm" data-bs-dismiss="modal">取消</button><button class="btn btn-primary btn-sm" id="confirmCrop">确认上传</button></div>
</div>
</div>
</div>
<!-- 修改密码 -->
<div class="modal fade" id="changePasswordModal" tabindex="-1">
<div class="modal-dialog modal-dialog-centered modal-sm">
<div class="modal-content"><div class="modal-body py-4">
<h6 class="mb-4 text-center">修改密码</h6>
<div class="mb-3">
<label class="form-label" style="font-size:13px;">旧密码</label>
<input type="password" class="form-control form-control-sm" id="oldPassword">
2026-07-16 22:51:02 +08:00
</div>
2026-07-16 22:58:46 +08:00
<div class="mb-3">
<label class="form-label" style="font-size:13px;">新密码</label>
<input type="password" class="form-control form-control-sm" id="newPassword">
2026-07-16 22:51:02 +08:00
</div>
2026-07-16 22:58:46 +08:00
<div class="mb-3">
<label class="form-label" style="font-size:13px;">确认新密码</label>
<input type="password" class="form-control form-control-sm" id="confirmPassword">
2026-07-16 22:51:02 +08:00
</div>
2026-07-16 22:58:46 +08:00
<div id="passwordError" class="text-danger mb-3" style="display:none;font-size:12px;"></div>
<div class="d-flex justify-content-center gap-3">
<button class="btn btn-outline-secondary btn-sm px-3" data-bs-dismiss="modal">取消</button>
<button class="btn btn-dark btn-sm px-3" id="confirmChangePassword">确认修改</button>
</div>
</div></div>
2026-07-16 22:51:02 +08:00
</div>
</div>
<script src="/vendor/bootstrap/bootstrap.bundle.min.js"></script>
<script src="/vendor/cropperjs/cropper.min.js"></script>
<script>
2026-07-16 22:58:46 +08:00
var cropper = null, selectedFile = null, currentGroupId = null;
var deleteGroupId = null, deleteImageId = null, setTimeGroupId = null;
var groupsData = [], playingGroupId = null;
var deleteGroupModal, setTimeModal, deleteImageModal, cropModal, changePasswordModal;
var token = localStorage.getItem('token');
2026-07-16 22:51:02 +08:00
if (!token) window.location.href = '/login';
2026-07-16 22:58:46 +08:00
var headers = { 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + token };
// WebSocket 连接到前台端口
var ws;
function connectWS() {
var protocol = location.protocol === 'https:' ? 'wss:' : 'ws:';
var host = location.hostname;
ws = new WebSocket(protocol + '//' + host + ':80');
ws.onmessage = function(e) {
try {
var data = JSON.parse(e.data);
if (data.type === 'state') {
playingGroupId = data.playing ? data.group_id : null;
highlightPlaying();
}
} catch(err) {}
};
ws.onclose = function() { setTimeout(connectWS, 3000); };
}
function highlightPlaying() {
document.querySelectorAll('.group').forEach(function(el) {
var gid = parseInt(el.getAttribute('data-id'));
if (gid === playingGroupId) {
el.classList.add('playing');
var badge = el.querySelector('.group-badge');
if (badge) badge.style.display = '';
} else {
el.classList.remove('playing');
var badge = el.querySelector('.group-badge');
if (badge) badge.style.display = 'none';
}
});
}
2026-07-16 22:51:02 +08:00
document.addEventListener('DOMContentLoaded', function() {
deleteGroupModal = new bootstrap.Modal(document.getElementById('deleteGroupModal'));
setTimeModal = new bootstrap.Modal(document.getElementById('setTimeModal'));
deleteImageModal = new bootstrap.Modal(document.getElementById('deleteImageModal'));
cropModal = new bootstrap.Modal(document.getElementById('cropModal'));
2026-07-16 22:58:46 +08:00
changePasswordModal = new bootstrap.Modal(document.getElementById('changePasswordModal'));
2026-07-16 22:51:02 +08:00
loadConfig();
loadGroups();
2026-07-16 22:58:46 +08:00
connectWS();
2026-07-16 22:51:02 +08:00
document.getElementById('featureToggle').onchange = toggleFeature;
document.getElementById('addGroupBtn').onclick = addGroup;
2026-07-16 22:58:46 +08:00
document.getElementById('resetBtn').onclick = resetCarousel;
2026-07-16 22:51:02 +08:00
document.getElementById('confirmDeleteGroup').onclick = doDeleteGroup;
document.getElementById('confirmSetTime').onclick = doSetTime;
document.getElementById('confirmDeleteImage').onclick = doDeleteImage;
document.getElementById('confirmCrop').onclick = doCrop;
2026-07-16 22:58:46 +08:00
document.getElementById('changePasswordBtn').onclick = function(e) { e.preventDefault(); changePasswordModal.show(); };
document.getElementById('confirmChangePassword').onclick = doChangePassword;
document.getElementById('logout').onclick = function() { localStorage.removeItem('token'); window.location.href = '/login'; };
2026-07-16 22:51:02 +08:00
document.addEventListener('click', function(e) {
if (!e.target.closest('.image-item')) {
2026-07-16 22:58:46 +08:00
document.querySelectorAll('.action-menu').forEach(function(m) { m.classList.remove('show'); });
2026-07-16 22:51:02 +08:00
}
});
});
2026-07-16 22:58:46 +08:00
2026-07-16 22:51:02 +08:00
async function loadConfig() {
2026-07-16 22:58:46 +08:00
var res = await fetch('/api/config');
var config = await res.json();
var enabled = config.feature_enabled === 'true';
document.getElementById('featureToggle').checked = enabled;
document.getElementById('featureStatus').textContent = enabled ? '已开启' : '已关闭';
document.getElementById('resetBtn').style.display = enabled ? '' : 'none';
2026-07-16 22:51:02 +08:00
}
2026-07-16 22:58:46 +08:00
2026-07-16 22:51:02 +08:00
async function toggleFeature() {
2026-07-16 22:58:46 +08:00
var enabled = document.getElementById('featureToggle').checked;
await fetch('/api/config', { method: 'PUT', headers: headers, body: JSON.stringify({ key: 'feature_enabled', value: enabled.toString() }) });
document.getElementById('featureStatus').textContent = enabled ? '已开启' : '已关闭';
document.getElementById('resetBtn').style.display = enabled ? '' : 'none';
2026-07-16 22:51:02 +08:00
}
2026-07-16 22:58:46 +08:00
async function resetCarousel() {
await fetch('/api/carousel/reset', { method: 'POST', headers: headers });
}
2026-07-16 22:51:02 +08:00
async function loadGroups() {
2026-07-16 22:58:46 +08:00
var res = await fetch('/api/groups');
2026-07-16 22:51:02 +08:00
groupsData = await res.json();
renderGroups();
}
2026-07-16 22:58:46 +08:00
2026-07-16 22:51:02 +08:00
function renderGroups() {
2026-07-16 22:58:46 +08:00
var container = document.getElementById('groupsContainer');
container.innerHTML = groupsData.map(function(g) {
var min = Math.floor(g.display_seconds / 60);
var sec = g.display_seconds % 60;
var timeStr = min + '分' + sec + '秒';
var isPlaying = g.id === playingGroupId;
return '<div class="group' + (isPlaying ? ' playing' : '') + '" data-id="' + g.id + '">' +
2026-07-16 22:51:02 +08:00
'<div class="group-header">' +
'<div class="group-info">' +
'<span class="group-name">' + g.name + '</span>' +
2026-07-16 22:58:46 +08:00
'<span class="group-time">展示时间:' + timeStr + '</span>' +
'<span class="group-badge" style="display:' + (isPlaying ? '' : 'none') + ';">播放中</span>' +
2026-07-16 22:51:02 +08:00
'</div>' +
'<div class="group-actions">' +
'<a onclick="addImage(' + g.id + ')">添加图片</a>' +
2026-07-16 22:58:46 +08:00
'<a onclick="setTime(' + g.id + ',' + g.display_seconds + ')">设置时间</a>' +
2026-07-16 22:51:02 +08:00
'<a class="delete" onclick="deleteGroup(' + g.id + ')">删除</a>' +
'</div>' +
'</div>' +
'<div class="image-list" id="images-' + g.id + '"></div>' +
'</div>';
}).join('');
2026-07-16 22:58:46 +08:00
groupsData.forEach(function(g) { loadImages(g.id); });
2026-07-16 22:51:02 +08:00
}
2026-07-16 22:58:46 +08:00
async function loadImages(gid) {
var res = await fetch('/api/images?group_id=' + gid);
var images = await res.json();
var container = document.getElementById('images-' + gid);
container.innerHTML = images.map(function(img) {
var cls = img.enabled ? '' : ' disabled';
var toggleText = img.enabled ? '下架' : '上架';
var toggleClass = img.enabled ? 'disable' : 'enable';
return '<div class="image-item' + cls + '">' +
2026-07-16 22:51:02 +08:00
'<img src="/uploads/' + img.filename + '">' +
'<div class="delete-btn" onclick="toggleActionMenu(this)">-</div>' +
'<div class="action-menu">' +
2026-07-16 22:58:46 +08:00
'<a class="' + toggleClass + '" onclick="toggleImage(' + img.id + ',' + (img.enabled ? 0 : 1) + ')">' + toggleText + '</a>' +
2026-07-16 22:51:02 +08:00
'<a class="delete" onclick="deleteImage(' + img.id + ')">删除</a>' +
'</div>' +
'</div>';
}).join('');
}
2026-07-16 22:58:46 +08:00
2026-07-16 22:51:02 +08:00
async function addGroup() {
2026-07-16 22:58:46 +08:00
var res = await fetch('/api/groups', { method: 'POST', headers: headers, body: JSON.stringify({ name: '分组' + (groupsData.length + 1), display_seconds: 30 }) });
2026-07-16 22:51:02 +08:00
if (res.ok) loadGroups();
}
2026-07-16 22:58:46 +08:00
function deleteGroup(id) { deleteGroupId = id; deleteGroupModal.show(); }
2026-07-16 22:51:02 +08:00
async function doDeleteGroup() {
2026-07-16 22:58:46 +08:00
await fetch('/api/groups/' + deleteGroupId, { method: 'DELETE', headers: { 'Authorization': 'Bearer ' + token } });
deleteGroupModal.hide(); loadGroups();
2026-07-16 22:51:02 +08:00
}
2026-07-16 22:58:46 +08:00
function setTime(gid, sec) {
setTimeGroupId = gid;
document.getElementById('timeMinutes').value = Math.floor(sec / 60);
document.getElementById('timeSeconds').value = sec % 60;
2026-07-16 22:51:02 +08:00
setTimeModal.show();
}
async function doSetTime() {
2026-07-16 22:58:46 +08:00
var total = (parseInt(document.getElementById('timeMinutes').value) || 0) * 60 + (parseInt(document.getElementById('timeSeconds').value) || 0);
await fetch('/api/groups/' + setTimeGroupId + '/settime', { method: 'PUT', headers: headers, body: JSON.stringify({ display_seconds: total }) });
setTimeModal.hide(); loadGroups();
2026-07-16 22:51:02 +08:00
}
2026-07-16 22:58:46 +08:00
function addImage(gid) {
currentGroupId = gid;
var input = document.createElement('input');
input.type = 'file'; input.accept = 'image/*';
2026-07-16 22:51:02 +08:00
input.onchange = function(e) {
2026-07-16 22:58:46 +08:00
var file = e.target.files[0];
2026-07-16 22:51:02 +08:00
if (!file) return;
selectedFile = file;
2026-07-16 22:58:46 +08:00
var reader = new FileReader();
2026-07-16 22:51:02 +08:00
reader.onload = function(e) {
document.getElementById('cropImage').src = e.target.result;
cropModal.show();
2026-07-16 22:58:46 +08:00
setTimeout(function() {
2026-07-16 22:51:02 +08:00
if (cropper) cropper.destroy();
2026-07-16 22:58:46 +08:00
cropper = new Cropper(document.getElementById('cropImage'), { aspectRatio: 1, viewMode: 1, autoCropArea: 1 });
2026-07-16 22:51:02 +08:00
}, 300);
};
reader.readAsDataURL(file);
};
input.click();
}
2026-07-16 22:58:46 +08:00
2026-07-16 22:51:02 +08:00
async function doCrop() {
if (!cropper || !selectedFile) return;
2026-07-16 22:58:46 +08:00
var canvas = cropper.getCroppedCanvas({ width: 280, height: 280 });
var blob = await new Promise(function(r) { canvas.toBlob(r, 'image/jpeg', 0.7); });
var formData = new FormData();
2026-07-16 22:51:02 +08:00
formData.append('image', blob, selectedFile.name);
formData.append('group_id', currentGroupId);
2026-07-16 22:58:46 +08:00
var res = await fetch('/api/upload', { method: 'POST', headers: { 'Authorization': 'Bearer ' + token }, body: formData });
if (res.ok) { cropModal.hide(); loadImages(currentGroupId); }
2026-07-16 22:51:02 +08:00
}
2026-07-16 22:58:46 +08:00
2026-07-16 22:51:02 +08:00
function toggleActionMenu(btn) {
event.stopPropagation();
2026-07-16 22:58:46 +08:00
var menu = btn.nextElementSibling;
var wasShow = menu.classList.contains('show');
document.querySelectorAll('.action-menu').forEach(function(m) { m.classList.remove('show'); });
2026-07-16 22:51:02 +08:00
if (!wasShow) menu.classList.add('show');
}
2026-07-16 22:58:46 +08:00
2026-07-16 22:51:02 +08:00
async function toggleImage(id, enable) {
event.stopPropagation();
2026-07-16 22:58:46 +08:00
var endpoint = enable ? 'enable' : 'disable';
await fetch('/api/images/' + id + '/' + endpoint, { method: 'PUT', headers: { 'Authorization': 'Bearer ' + token } });
document.querySelectorAll('.action-menu').forEach(function(m) { m.classList.remove('show'); });
2026-07-16 22:51:02 +08:00
loadGroups();
}
2026-07-16 22:58:46 +08:00
2026-07-16 22:51:02 +08:00
function deleteImage(id) {
event.stopPropagation();
deleteImageId = id;
2026-07-16 22:58:46 +08:00
document.querySelectorAll('.action-menu').forEach(function(m) { m.classList.remove('show'); });
2026-07-16 22:51:02 +08:00
deleteImageModal.show();
}
async function doDeleteImage() {
2026-07-16 22:58:46 +08:00
await fetch('/api/images/' + deleteImageId, { method: 'DELETE', headers: { 'Authorization': 'Bearer ' + token } });
deleteImageModal.hide(); loadGroups();
}
async function doChangePassword() {
var oldPwd = document.getElementById('oldPassword').value;
var newPwd = document.getElementById('newPassword').value;
var confirmPwd = document.getElementById('confirmPassword').value;
var errorEl = document.getElementById('passwordError');
errorEl.style.display = 'none';
if (!oldPwd || !newPwd || !confirmPwd) {
errorEl.textContent = '请填写所有字段';
errorEl.style.display = 'block';
return;
}
if (newPwd !== confirmPwd) {
errorEl.textContent = '两次输入的新密码不一致';
errorEl.style.display = 'block';
return;
}
var res = await fetch('/api/change-password', {
method: 'PUT',
headers: headers,
body: JSON.stringify({ oldPassword: oldPwd, newPassword: newPwd })
2026-07-16 22:51:02 +08:00
});
2026-07-16 22:58:46 +08:00
var data = await res.json();
if (res.ok) {
changePasswordModal.hide();
document.getElementById('oldPassword').value = '';
document.getElementById('newPassword').value = '';
document.getElementById('confirmPassword').value = '';
alert('密码修改成功');
} else {
errorEl.textContent = data.error || '修改失败';
errorEl.style.display = 'block';
}
2026-07-16 22:51:02 +08:00
}
</script>
</body>
2026-07-16 22:58:46 +08:00
</html>