|
@@ -132,12 +132,14 @@
|
|
|
background-color: rgba(0,0,0,0.4);
|
|
|
}
|
|
|
.login-content {
|
|
|
- background-color: #fefefe;
|
|
|
+ background-color: rgba(255, 255, 255, 0.8);
|
|
|
+ backdrop-filter: blur(10px);
|
|
|
margin: 15% auto;
|
|
|
padding: 20px;
|
|
|
border: 1px solid #888;
|
|
|
width: 30%;
|
|
|
border-radius: 8px;
|
|
|
+ box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
|
|
}
|
|
|
.user-management {
|
|
|
position: absolute;
|
|
@@ -213,7 +215,7 @@
|
|
|
<label for="currentPassword">当前密码</label>
|
|
|
<input type="password" id="currentPassword" name="currentPassword">
|
|
|
<label for="newPassword">新密码</label>
|
|
|
- <span class="password-hint" id="passwordHint">密码必须包含至少一个字母和一个数字,长度在8到16个字符之间</span>
|
|
|
+ <span class="password-hint" id="passwordHint">密码必须包含至少一个字母、一个数字和一个特殊字符,长度在8到16个字符之间</span>
|
|
|
<input type="password" id="newPassword" name="newPassword" oninput="checkPasswordStrength()">
|
|
|
<span id="passwordStrength" style="color: red;"></span>
|
|
|
<button type="button" onclick="changePassword()">修改密码</button>
|
|
@@ -248,6 +250,44 @@
|
|
|
return menuItems;
|
|
|
}
|
|
|
|
|
|
+ function setupDeleteButtons() {
|
|
|
+ const deleteButtons = document.querySelectorAll('.delete-btn');
|
|
|
+ deleteButtons.forEach((button, index) => {
|
|
|
+ button.addEventListener('click', () => {
|
|
|
+ const row = button.closest('tr');
|
|
|
+ const index = row.getAttribute('data-index');
|
|
|
+ console.log(`Deleting menu item at index: ${index}`); // 添加日志输出
|
|
|
+ deleteMenuItem(index);
|
|
|
+ });
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ function renderMenuItems() {
|
|
|
+ const tbody = document.getElementById('menuTableBody');
|
|
|
+ tbody.innerHTML = '';
|
|
|
+ menuItems.forEach((item, index) => {
|
|
|
+ const row = `
|
|
|
+ <tr data-index="${index}">
|
|
|
+ <td><input type="text" class="menu-text" value="${item.text}" disabled></td>
|
|
|
+ <td><input type="url" class="menu-link" value="${item.link || ''}" disabled></td>
|
|
|
+ <td>
|
|
|
+ <select class="menu-newtab" disabled>
|
|
|
+ <option value="false" ${item.newTab ? '' : 'selected'}>否</option>
|
|
|
+ <option value="true" ${item.newTab ? 'selected' : ''}>是</option>
|
|
|
+ </select>
|
|
|
+ </td>
|
|
|
+ <td>
|
|
|
+ <button type="button" class="action-btn edit-btn">编辑</button>
|
|
|
+ <button type="button" class="action-btn delete-btn">删除</button>
|
|
|
+ </td>
|
|
|
+ </tr>
|
|
|
+ `;
|
|
|
+ tbody.innerHTML += row;
|
|
|
+ });
|
|
|
+ setupEditButtons();
|
|
|
+ setupDeleteButtons();
|
|
|
+ }
|
|
|
+
|
|
|
function setMenuItems(items) {
|
|
|
menuItems = items;
|
|
|
renderMenuItems();
|
|
@@ -284,45 +324,8 @@
|
|
|
});
|
|
|
});
|
|
|
}
|
|
|
-
|
|
|
- function renderMenuItems() {
|
|
|
- const tbody = document.getElementById('menuTableBody');
|
|
|
- tbody.innerHTML = '';
|
|
|
- menuItems.forEach((item, index) => {
|
|
|
- const row = `
|
|
|
- <tr data-index="${index}">
|
|
|
- <td><input type="text" class="menu-text" value="${item.text}" disabled></td>
|
|
|
- <td><input type="url" class="menu-link" value="${item.link || ''}" disabled></td>
|
|
|
- <td>
|
|
|
- <select class="menu-newtab" disabled>
|
|
|
- <option value="false" ${item.newTab ? '' : 'selected'}>否</option>
|
|
|
- <option value="true" ${item.newTab ? 'selected' : ''}>是</option>
|
|
|
- </select>
|
|
|
- </td>
|
|
|
- <td>
|
|
|
- <button type="button" class="action-btn edit-btn">编辑</button>
|
|
|
- <button type="button" class="action-btn delete-btn">删除</button>
|
|
|
- </td>
|
|
|
- </tr>
|
|
|
- `;
|
|
|
- tbody.innerHTML += row;
|
|
|
- });
|
|
|
- setupEditButtons();
|
|
|
- setupDeleteButtons();
|
|
|
- }
|
|
|
|
|
|
|
|
|
- function setupDeleteButtons() {
|
|
|
- const deleteButtons = document.querySelectorAll('.delete-btn');
|
|
|
- deleteButtons.forEach((button, index) => {
|
|
|
- button.addEventListener('click', () => {
|
|
|
- const row = button.closest('tr');
|
|
|
- const index = row.getAttribute('data-index');
|
|
|
- deleteMenuItem(index);
|
|
|
- });
|
|
|
- });
|
|
|
- }
|
|
|
-
|
|
|
function showNewMenuItemRow() {
|
|
|
const tbody = document.getElementById('menuTableBody');
|
|
|
const newRow = `
|
|
@@ -406,13 +409,54 @@
|
|
|
});
|
|
|
setupAdEditButtons();
|
|
|
setupAdDeleteButtons();
|
|
|
- }
|
|
|
+ }
|
|
|
+
|
|
|
+ function setupAdEditButtons() {
|
|
|
+ const editButtons = document.querySelectorAll('.edit-btn');
|
|
|
+ editButtons.forEach((button, index) => {
|
|
|
+ button.addEventListener('click', () => {
|
|
|
+ const row = button.closest('tr');
|
|
|
+ const urlInput = row.querySelector('.ad-url');
|
|
|
+ const linkInput = row.querySelector('.ad-link');
|
|
|
+
|
|
|
+ if (urlInput.disabled) {
|
|
|
+ urlInput.disabled = false;
|
|
|
+ linkInput.disabled = false;
|
|
|
+ button.textContent = '保存';
|
|
|
+ editingIndex = row.getAttribute('data-index');
|
|
|
+ } else {
|
|
|
+ const url = urlInput.value || '';
|
|
|
+ const link = linkInput.value || '';
|
|
|
+
|
|
|
+ adImages[editingIndex] = { url, link };
|
|
|
+ renderAdItems(); // 重新渲染广告项
|
|
|
+ saveAd(editingIndex, { url, link });
|
|
|
+ editingIndex = -1;
|
|
|
+ }
|
|
|
+ });
|
|
|
+ });
|
|
|
+ }
|
|
|
|
|
|
+ function setupAdDeleteButtons() {
|
|
|
+ const deleteButtons = document.querySelectorAll('.delete-btn');
|
|
|
+ deleteButtons.forEach((button, index) => {
|
|
|
+ button.addEventListener('click', () => {
|
|
|
+ const row = button.closest('tr');
|
|
|
+ const index = row.getAttribute('data-index');
|
|
|
+ deleteAd(index);
|
|
|
+ });
|
|
|
+ });
|
|
|
+ }
|
|
|
|
|
|
function saveNewAd() {
|
|
|
const url = document.getElementById('newAdUrl').value || '';
|
|
|
const link = document.getElementById('newAdLink').value || '';
|
|
|
|
|
|
+ if (!url) {
|
|
|
+ alert('广告URL为必填项');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
const newAd = { url, link };
|
|
|
adImages.push(newAd);
|
|
|
renderAdItems(); // 先更新页面
|
|
@@ -447,43 +491,6 @@
|
|
|
setupAdDeleteButtons();
|
|
|
}
|
|
|
|
|
|
- function setupAdEditButtons() {
|
|
|
- const editButtons = document.querySelectorAll('.edit-btn');
|
|
|
- editButtons.forEach((button, index) => {
|
|
|
- button.addEventListener('click', () => {
|
|
|
- const row = button.closest('tr');
|
|
|
- const urlInput = row.querySelector('.ad-url');
|
|
|
- const linkInput = row.querySelector('.ad-link');
|
|
|
-
|
|
|
- if (urlInput.disabled) {
|
|
|
- urlInput.disabled = false;
|
|
|
- linkInput.disabled = false;
|
|
|
- button.textContent = '保存';
|
|
|
- editingIndex = row.getAttribute('data-index');
|
|
|
- } else {
|
|
|
- const url = urlInput.value || '';
|
|
|
- const link = linkInput.value || '';
|
|
|
-
|
|
|
- adImages[editingIndex] = { url, link };
|
|
|
- renderAdItems(); // 重新渲染广告项
|
|
|
- saveAd(editingIndex, { url, link });
|
|
|
- editingIndex = -1;
|
|
|
- }
|
|
|
- });
|
|
|
- });
|
|
|
- }
|
|
|
-
|
|
|
- function setupAdDeleteButtons() {
|
|
|
- const deleteButtons = document.querySelectorAll('.delete-btn');
|
|
|
- deleteButtons.forEach((button, index) => {
|
|
|
- button.addEventListener('click', () => {
|
|
|
- const row = button.closest('tr');
|
|
|
- const index = row.getAttribute('data-index');
|
|
|
- deleteAd(index);
|
|
|
- });
|
|
|
- });
|
|
|
- }
|
|
|
-
|
|
|
async function saveLogo() {
|
|
|
const logoUrl = document.getElementById('logoUrl').value;
|
|
|
if (!logoUrl) {
|
|
@@ -519,9 +526,23 @@
|
|
|
}
|
|
|
|
|
|
async function deleteMenuItem(index) {
|
|
|
- menuItems.splice(index, 1);
|
|
|
- renderMenuItems(); // 先更新页面
|
|
|
- await saveConfig({ menuItems: menuItems });
|
|
|
+ try {
|
|
|
+ const response = await fetch('/api/config', {
|
|
|
+ method: 'POST',
|
|
|
+ headers: { 'Content-Type': 'application/json' },
|
|
|
+ body: JSON.stringify({ menuItems: menuItems.filter((_, i) => i !== parseInt(index)) })
|
|
|
+ });
|
|
|
+ if (response.ok) {
|
|
|
+ menuItems.splice(index, 1);
|
|
|
+ renderMenuItems(); // 先更新页面
|
|
|
+ await loadConfig(); // 重新加载配置
|
|
|
+ } else {
|
|
|
+ alert('删除菜单项失败');
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error('删除菜单项失败: ' + error.message);
|
|
|
+ alert('删除菜单项失败: ' + error.message);
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
async function saveAd(index, ad) {
|
|
@@ -531,9 +552,23 @@
|
|
|
}
|
|
|
|
|
|
async function deleteAd(index) {
|
|
|
- adImages.splice(index, 1);
|
|
|
- renderAdItems(); // 先更新页面
|
|
|
- await saveConfig({ adImages: adImages });
|
|
|
+ try {
|
|
|
+ const response = await fetch('/api/config', {
|
|
|
+ method: 'POST',
|
|
|
+ headers: { 'Content-Type': 'application/json' },
|
|
|
+ body: JSON.stringify({ adImages: adImages.filter((_, i) => i !== parseInt(index)) })
|
|
|
+ });
|
|
|
+ if (response.ok) {
|
|
|
+ adImages.splice(index, 1);
|
|
|
+ renderAdItems(); // 先更新页面
|
|
|
+ await loadConfig(); // 重新加载配置
|
|
|
+ } else {
|
|
|
+ alert('删除广告项失败');
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error('删除广告项失败: ' + error.message);
|
|
|
+ alert('删除广告项失败: ' + error.message);
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
async function saveConfig(partialConfig) {
|
|
@@ -550,7 +585,7 @@
|
|
|
console.error('保存失败: ' + error.message);
|
|
|
throw error;
|
|
|
}
|
|
|
- }
|
|
|
+ }
|
|
|
|
|
|
async function loadConfig() {
|
|
|
try {
|
|
@@ -594,12 +629,14 @@
|
|
|
async function changePassword() {
|
|
|
const currentPassword = document.getElementById('currentPassword').value;
|
|
|
const newPassword = document.getElementById('newPassword').value;
|
|
|
+ const passwordRegex = /^(?=.*[A-Za-z])(?=.*\d)(?=.*[@$!%*#?&])[A-Za-z\d@$!%*#?&]{8,16}$/;
|
|
|
+
|
|
|
if (!currentPassword || !newPassword) {
|
|
|
alert('请填写当前密码和新密码');
|
|
|
return;
|
|
|
}
|
|
|
- if (!/^(?=.*[A-Za-z])(?=.*\d)[A-Za-z\d]{8,16}$/.test(newPassword)) {
|
|
|
- alert('密码必须包含至少一个字母和一个数字,长度在8到16个字符之间');
|
|
|
+ if (!passwordRegex.test(newPassword)) {
|
|
|
+ alert('密码必须包含至少一个字母、一个数字和一个特殊字符,长度在8到16个字符之间');
|
|
|
return;
|
|
|
}
|
|
|
try {
|
|
@@ -619,9 +656,12 @@
|
|
|
}
|
|
|
|
|
|
function checkPasswordStrength() {
|
|
|
- const newPassword = document.getElementById('newPassword');
|
|
|
+ const newPassword = document.getElementById('newPassword').value;
|
|
|
const passwordHint = document.getElementById('passwordHint');
|
|
|
- if (!/^(?=.*[A-Za-z])(?=.*\d)[A-Za-z\d]{8,16}$/.test(newPassword.value)) {
|
|
|
+
|
|
|
+ const passwordRegex = /^(?=.*[A-Za-z])(?=.*\d)(?=.*[@$!%*#?&])[A-Za-z\d@$!%*#?&]{8,16}$/;
|
|
|
+
|
|
|
+ if (!passwordRegex.test(newPassword)) {
|
|
|
passwordHint.style.display = 'block';
|
|
|
} else {
|
|
|
passwordHint.style.display = 'none';
|