// API Base URL const API_BASE = '/api'; // State Management const state = { currentPage: 'partner-list', isAuthenticated: false, currentUser: null, partners: [] }; // DOM Elements const loginPage = document.getElementById('loginPage'); const adminDashboard = document.getElementById('adminDashboard'); const loginForm = document.getElementById('loginForm'); const loginError = document.getElementById('loginError'); const logoutBtn = document.getElementById('logoutBtn'); const navLinks = document.querySelectorAll('.nav-link'); const pageTitle = document.getElementById('pageTitle'); const currentUserSpan = document.getElementById('currentUser'); const partnerModal = document.getElementById('partnerModal'); const partnerForm = document.getElementById('partnerForm'); const toast = document.getElementById('toast'); // Initialize App document.addEventListener('DOMContentLoaded', () => { checkAuthStatus(); setupEventListeners(); }); // Check Authentication Status async function checkAuthStatus() { try { const response = await fetch(`${API_BASE}/auth/status`, { credentials: 'include' }); const data = await response.json(); if (data.authenticated) { state.isAuthenticated = true; state.currentUser = data.user; showDashboard(); } else { showLogin(); } } catch (error) { console.error('Auth check failed:', error); showLogin(); } } // Show Login Page function showLogin() { loginPage.style.display = 'flex'; adminDashboard.style.display = 'none'; } // Show Dashboard function showDashboard() { loginPage.style.display = 'none'; adminDashboard.style.display = 'flex'; currentUserSpan.textContent = state.currentUser.username; navigateToPage('partner-list'); } // Setup Event Listeners function setupEventListeners() { // Login Form loginForm.addEventListener('submit', handleLogin); // Logout Button logoutBtn.addEventListener('click', handleLogout); // Navigation Links navLinks.forEach(link => { link.addEventListener('click', (e) => { e.preventDefault(); const page = link.getAttribute('data-page'); navigateToPage(page); }); }); // Partner Modal document.querySelectorAll('.modal-close').forEach(btn => { btn.addEventListener('click', () => { partnerModal.classList.remove('show'); }); }); // Click outside modal to close partnerModal.addEventListener('click', (e) => { if (e.target === partnerModal) { partnerModal.classList.remove('show'); } }); // Partner Form Submit partnerForm.addEventListener('submit', handlePartnerSubmit); // Add Partner Button (from nav link) const addPartnerLink = document.querySelector('[data-page="add-partner"]'); addPartnerLink.addEventListener('click', (e) => { e.preventDefault(); openPartnerModal(); }); // Storage Configuration Form const storageConfigForm = document.getElementById('storageConfigForm'); if (storageConfigForm) { storageConfigForm.addEventListener('submit', handleStorageConfigSubmit); } // Create Directory Button const createDirBtn = document.getElementById('createDirBtn'); if (createDirBtn) { createDirBtn.addEventListener('click', handleCreateDirectory); } // VOD Config Form const vodConfigForm = document.getElementById('vodConfigForm'); if (vodConfigForm) { vodConfigForm.addEventListener('submit', handleVodConfigSubmit); } // Add Video Form const addVideoForm = document.getElementById('addVideoForm'); if (addVideoForm) { addVideoForm.addEventListener('submit', handleVideoUpload); } // Video Edit Form const videoEditForm = document.getElementById('videoEditForm'); if (videoEditForm) { videoEditForm.addEventListener('submit', handleVideoEditSubmit); } // Cover Preview const videoCover = document.getElementById('videoCover'); if (videoCover) { videoCover.addEventListener('change', handleCoverPreview); } // Change Password Form const changePasswordForm = document.getElementById('changePasswordForm'); if (changePasswordForm) { changePasswordForm.addEventListener('submit', handleChangePassword); } } // Handle Login async function handleLogin(e) { e.preventDefault(); loginError.style.display = 'none'; const formData = new FormData(loginForm); const data = { username: formData.get('username'), password: formData.get('password') }; try { const response = await fetch(`${API_BASE}/auth/login`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, credentials: 'include', body: JSON.stringify(data) }); const result = await response.json(); if (result.success) { state.isAuthenticated = true; state.currentUser = result.user; showDashboard(); } else { loginError.textContent = result.message; loginError.style.display = 'block'; } } catch (error) { console.error('Login error:', error); loginError.textContent = 'Login failed. Please try again.'; loginError.style.display = 'block'; } } // Handle Logout async function handleLogout() { try { await fetch(`${API_BASE}/auth/logout`, { method: 'POST', credentials: 'include' }); state.isAuthenticated = false; state.currentUser = null; showLogin(); showToast('Logged out successfully', 'success'); } catch (error) { console.error('Logout error:', error); showToast('Logout failed', 'error'); } } // Navigate to Page function navigateToPage(page) { state.currentPage = page; // Update active nav link navLinks.forEach(link => { if (link.getAttribute('data-page') === page) { link.classList.add('active'); } else { link.classList.remove('active'); } }); // Hide all pages document.querySelectorAll('.page-content').forEach(p => { p.style.display = 'none'; }); // Show selected page switch (page) { case 'storage-config': pageTitle.textContent = '存储配置'; document.getElementById('storageConfigPage').style.display = 'block'; loadStorageConfig(); break; case 'add-partner': openPartnerModal(); break; case 'partner-list': pageTitle.textContent = '合作伙伴列表'; document.getElementById('partnerListPage').style.display = 'block'; loadPartners(); break; case 'video-list': pageTitle.textContent = '媒体列表'; document.getElementById('videoListPage').style.display = 'block'; loadVideos(); break; case 'video-config': pageTitle.textContent = '媒体配置'; document.getElementById('videoConfigPage').style.display = 'block'; loadVodConfig(); break; case 'add-video': pageTitle.textContent = '新增媒体'; document.getElementById('addVideoPage').style.display = 'block'; resetVideoUploadForm(); break; case 'system-settings': pageTitle.textContent = '系统设置'; document.getElementById('systemSettingsPage').style.display = 'block'; loadSystemSettings(); break; case 'change-password': pageTitle.textContent = '修改密码'; document.getElementById('changePasswordPage').style.display = 'block'; document.getElementById('changePasswordForm').reset(); break; } } // Load Storage Configuration async function loadStorageConfig() { try { const response = await fetch(`${API_BASE}/config/storage`, { credentials: 'include' }); const result = await response.json(); if (result.success) { const { config, dirStatus } = result.data; document.getElementById('uploadDir').value = config.logo_upload_dir || ''; document.getElementById('maxSize').value = config.logo_max_size || ''; document.getElementById('allowedExt').value = config.allowed_extensions || ''; // Update directory status const existsIndicator = document.getElementById('dirExists'); const writableIndicator = document.getElementById('dirWritable'); if (dirStatus.exists) { existsIndicator.classList.add('success'); existsIndicator.classList.remove('error'); } else { existsIndicator.classList.add('error'); existsIndicator.classList.remove('success'); } if (dirStatus.writable) { writableIndicator.classList.add('success'); writableIndicator.classList.remove('error'); } else { writableIndicator.classList.add('error'); writableIndicator.classList.remove('success'); } } } catch (error) { console.error('Load storage config error:', error); showToast('Failed to load storage configuration', 'error'); } } // Handle Storage Config Submit async function handleStorageConfigSubmit(e) { e.preventDefault(); const formData = new FormData(e.target); const data = { logo_upload_dir: formData.get('logo_upload_dir'), logo_max_size: formData.get('logo_max_size'), allowed_extensions: formData.get('allowed_extensions') }; try { const response = await fetch(`${API_BASE}/config/storage`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, credentials: 'include', body: JSON.stringify(data) }); const result = await response.json(); if (result.success) { showToast('Storage configuration updated successfully', 'success'); loadStorageConfig(); } else { showToast(result.message || 'Failed to update configuration', 'error'); } } catch (error) { console.error('Update storage config error:', error); showToast('Failed to update configuration', 'error'); } } // Handle Create Directory async function handleCreateDirectory() { const dirPath = document.getElementById('uploadDir').value; if (!dirPath) { showToast('Please enter a directory path', 'warning'); return; } try { const response = await fetch(`${API_BASE}/config/storage/create-dir`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, credentials: 'include', body: JSON.stringify({ dirPath }) }); const result = await response.json(); if (result.success) { showToast('Directory created successfully', 'success'); loadStorageConfig(); } else { showToast(result.message || 'Failed to create directory', 'error'); } } catch (error) { console.error('Create directory error:', error); showToast('Failed to create directory', 'error'); } } // Load Partners async function loadPartners() { try { const response = await fetch(`${API_BASE}/partners`, { credentials: 'include' }); const result = await response.json(); if (result.success) { state.partners = result.data; renderPartners(result.data); } } catch (error) { console.error('Load partners error:', error); showToast('Failed to load partners', 'error'); } } // Render Partners Table function renderPartners(partners) { const tbody = document.getElementById('partnersTableBody'); if (partners.length === 0) { tbody.innerHTML = '暂无合作伙伴'; return; } tbody.innerHTML = partners.map(partner => ` ${partner.id} ${partner.name} ${partner.name} ${partner.url || '-'} ${partner.sort_order} ${partner.status === 1 ? '启用' : '禁用'}
`).join(''); } // Open Partner Modal function openPartnerModal(partnerId = null) { const modal = document.getElementById('partnerModal'); const modalTitle = document.getElementById('modalTitle'); const form = document.getElementById('partnerForm'); form.reset(); document.getElementById('partnerId').value = ''; document.getElementById('currentLogoPreview').style.display = 'none'; if (partnerId) { modalTitle.textContent = '编辑合作伙伴'; loadPartnerData(partnerId); } else { modalTitle.textContent = '新增合作伙伴'; document.getElementById('partnerLogo').required = true; } modal.classList.add('show'); } // Load Partner Data for Editing async function loadPartnerData(partnerId) { try { const response = await fetch(`${API_BASE}/partners/${partnerId}`, { credentials: 'include' }); const result = await response.json(); if (result.success) { const partner = result.data; document.getElementById('partnerId').value = partner.id; document.getElementById('partnerName').value = partner.name; document.getElementById('partnerUrl').value = partner.url || ''; document.getElementById('partnerStatus').value = partner.status; document.getElementById('partnerSortOrder').value = partner.sort_order; // Show current logo document.getElementById('currentLogo').src = '/' + partner.logo; document.getElementById('currentLogoPreview').style.display = 'block'; document.getElementById('partnerLogo').required = false; } } catch (error) { console.error('Load partner error:', error); showToast('Failed to load partner data', 'error'); } } // Handle Partner Form Submit async function handlePartnerSubmit(e) { e.preventDefault(); const formData = new FormData(partnerForm); const partnerId = formData.get('partnerId'); const url = partnerId ? `${API_BASE}/partners/${partnerId}` : `${API_BASE}/partners`; const method = partnerId ? 'PUT' : 'POST'; try { const response = await fetch(url, { method: method, credentials: 'include', body: formData }); const result = await response.json(); if (result.success) { showToast(result.message, 'success'); partnerModal.classList.remove('show'); loadPartners(); } else { showToast(result.message || 'Operation failed', 'error'); } } catch (error) { console.error('Partner submit error:', error); showToast('Operation failed', 'error'); } } // Edit Partner (Global function for onclick) window.editPartner = function(partnerId) { openPartnerModal(partnerId); }; // Delete Partner (Global function for onclick) window.deletePartner = async function(partnerId) { if (!confirm('Are you sure you want to delete this partner?')) { return; } try { const response = await fetch(`${API_BASE}/partners/${partnerId}`, { method: 'DELETE', credentials: 'include' }); const result = await response.json(); if (result.success) { showToast('Partner deleted successfully', 'success'); loadPartners(); } else { showToast(result.message || 'Failed to delete partner', 'error'); } } catch (error) { console.error('Delete partner error:', error); showToast('Failed to delete partner', 'error'); } }; // Load System Settings async function loadSystemSettings() { try { const response = await fetch(`${API_BASE}/config/settings`, { credentials: 'include' }); const result = await response.json(); if (result.success) { renderSystemSettings(result.data); } } catch (error) { console.error('Load system settings error:', error); showToast('Failed to load system settings', 'error'); } } // Render System Settings function renderSystemSettings(settings) { const container = document.getElementById('settingsTableContainer'); if (settings.length === 0) { container.innerHTML = '

No settings found

'; return; } const html = `
${settings.map(setting => ` `).join('')}
Setting Key Value Description
${setting.setting_key} ${setting.setting_value} ${setting.description || '-'}
`; container.innerHTML = html; } // Show Toast Notification function showToast(message, type = 'success') { toast.textContent = message; toast.className = `toast ${type} show`; setTimeout(() => { toast.classList.remove('show'); }, 3000); } // ==================== 视频管理功能 ==================== // 加载视频列表 async function loadVideos() { try { const response = await fetch(`${API_BASE}/videos`, { credentials: 'include' }); const result = await response.json(); if (result.success) { renderVideos(result.data); } } catch (error) { console.error('Load videos error:', error); showToast('Failed to load videos', 'error'); } } // 渲染视频列表 function renderVideos(videos) { const tbody = document.getElementById('videosTableBody'); if (videos.length === 0) { tbody.innerHTML = '暂无视频'; return; } tbody.innerHTML = videos.map(video => { const duration = video.duration ? formatDuration(video.duration) : '-'; const uploadStatusText = getUploadStatusText(video.upload_status); const statusBadge = video.status === 1 ? 'badge-success' : video.status === 2 ? 'badge-warning' : 'badge-danger'; const statusText = video.status === 1 ? '启用' : video.status === 2 ? '处理中' : '禁用'; // 判断封面URL是完整URL还是相对路径 const coverSrc = video.cover_url ? (video.cover_url.startsWith('http') ? video.cover_url : `/${video.cover_url}`) : ''; return ` ${video.id} ${coverSrc ? `${video.title_cn}` : '-'} ${video.title_cn} ${video.title_en} ${duration} ${uploadStatusText} ${statusText}
`; }).join(''); } // 格式化时长 function formatDuration(seconds) { const hours = Math.floor(seconds / 3600); const minutes = Math.floor((seconds % 3600) / 60); const secs = seconds % 60; if (hours > 0) { return `${hours}:${String(minutes).padStart(2, '0')}:${String(secs).padStart(2, '0')}`; } return `${minutes}:${String(secs).padStart(2, '0')}`; } // 获取上传状态文本 function getUploadStatusText(status) { const statusMap = { 'pending': '待上传', 'uploading': '上传中', 'success': '成功', 'failed': '失败' }; return statusMap[status] || status; } // 获取上传状态徽章样式 function getUploadStatusBadge(status) { const badgeMap = { 'pending': 'badge-secondary', 'uploading': 'badge-warning', 'success': 'badge-success', 'failed': 'badge-danger' }; return badgeMap[status] || 'badge-secondary'; } // 编辑视频 window.editVideo = async function(videoId) { try { const response = await fetch(`${API_BASE}/videos/${videoId}`, { credentials: 'include' }); const result = await response.json(); if (result.success) { const video = result.data; document.getElementById('editVideoId').value = video.id; document.getElementById('editTitleCn').value = video.title_cn; document.getElementById('editTitleEn').value = video.title_en; document.getElementById('editDescCn').value = video.desc_cn || ''; document.getElementById('editDescEn').value = video.desc_en || ''; document.getElementById('editStatus').value = video.status; document.getElementById('editSortOrder').value = video.sort_order; document.getElementById('videoModal').classList.add('show'); } } catch (error) { console.error('Load video error:', error); showToast('Failed to load video data', 'error'); } }; // 处理视频编辑表单提交 async function handleVideoEditSubmit(e) { e.preventDefault(); const videoId = document.getElementById('editVideoId').value; const formData = new FormData(); formData.append('title_cn', document.getElementById('editTitleCn').value); formData.append('title_en', document.getElementById('editTitleEn').value); formData.append('desc_cn', document.getElementById('editDescCn').value); formData.append('desc_en', document.getElementById('editDescEn').value); formData.append('status', document.getElementById('editStatus').value); formData.append('sort_order', document.getElementById('editSortOrder').value); try { const response = await fetch(`${API_BASE}/videos/${videoId}`, { method: 'PUT', credentials: 'include', body: formData }); const result = await response.json(); if (result.success) { showToast('Video updated successfully', 'success'); document.getElementById('videoModal').classList.remove('show'); loadVideos(); } else { showToast(result.message || 'Failed to update video', 'error'); } } catch (error) { console.error('Update video error:', error); showToast('Failed to update video', 'error'); } } // 删除视频 window.deleteVideo = async function(videoId) { if (!confirm('确定要删除这个视频吗?')) { return; } try { const response = await fetch(`${API_BASE}/videos/${videoId}`, { method: 'DELETE', credentials: 'include' }); const result = await response.json(); if (result.success) { showToast('Video deleted successfully', 'success'); loadVideos(); } else { showToast(result.message || 'Failed to delete video', 'error'); } } catch (error) { console.error('Delete video error:', error); showToast('Failed to delete video', 'error'); } }; // ==================== VOD配置 ==================== // 加载VOD配置 async function loadVodConfig() { try { const response = await fetch(`${API_BASE}/videos/vod-config`, { credentials: 'include' }); const result = await response.json(); if (result.success) { const config = result.data; document.getElementById('secretId').value = config.secret_id || ''; document.getElementById('secretKey').value = config.secret_key || ''; document.getElementById('vodRegion').value = config.region || 'ap-guangzhou'; document.getElementById('subAppId').value = config.sub_app_id || ''; document.getElementById('vodProcedure').value = config.procedure || ''; document.getElementById('storageRegion').value = config.storage_region || ''; // COS配置 document.getElementById('cosBucket').value = config.cos_bucket || ''; document.getElementById('cosRegion').value = config.cos_region || 'ap-guangzhou'; document.getElementById('cosPath').value = config.cos_path || 'video-covers/'; // 防盗链配置 document.getElementById('signKey').value = config.sign_key || ''; } } catch (error) { console.error('Load VOD config error:', error); showToast('加载VOD配置失败', 'error'); } } // 处理VOD配置提交 async function handleVodConfigSubmit(e) { e.preventDefault(); const formData = new FormData(e.target); const data = { secret_id: formData.get('secret_id'), secret_key: formData.get('secret_key'), region: formData.get('region'), sub_app_id: formData.get('sub_app_id'), procedure: formData.get('procedure'), storage_region: formData.get('storage_region'), // COS配置 cos_bucket: formData.get('cos_bucket'), cos_region: formData.get('cos_region'), cos_path: formData.get('cos_path'), // 防盗链配置 sign_key: formData.get('sign_key') }; try { const response = await fetch(`${API_BASE}/videos/vod-config`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, credentials: 'include', body: JSON.stringify(data) }); const result = await response.json(); if (result.success) { showToast('VOD配置更新成功', 'success'); } else { showToast(result.message || '配置更新失败', 'error'); } } catch (error) { console.error('Update VOD config error:', error); showToast('配置更新失败', 'error'); } } // ==================== 视频上传 ==================== let currentUploadTask = null; // 重置上传表单 function resetVideoUploadForm() { document.getElementById('addVideoForm').reset(); document.getElementById('coverPreview').style.display = 'none'; document.getElementById('uploadProgress').style.display = 'none'; document.getElementById('uploadVideoBtn').style.display = 'inline-flex'; document.getElementById('cancelUploadBtn').style.display = 'none'; } // 封面预览 function handleCoverPreview(e) { const file = e.target.files[0]; if (file) { const reader = new FileReader(); reader.onload = function(event) { document.getElementById('coverPreviewImg').src = event.target.result; document.getElementById('coverPreview').style.display = 'block'; }; reader.readAsDataURL(file); } } // 处理视频上传 async function handleVideoUpload(e) { e.preventDefault(); const titleCn = document.getElementById('videoTitleCn').value; const titleEn = document.getElementById('videoTitleEn').value; const descCn = document.getElementById('videoDescCn').value; const descEn = document.getElementById('videoDescEn').value; const coverFile = document.getElementById('videoCover').files[0]; const videoFile = document.getElementById('videoFile').files[0]; if (!videoFile) { showToast('请选择视频文件', 'error'); return; } try { // 1. 获取上传签名 showToast('正在获取上传凭证...', 'info'); const sigResponse = await fetch(`${API_BASE}/videos/upload-signature`, { method: 'POST', credentials: 'include' }); const sigResult = await sigResponse.json(); if (!sigResult.success) { showToast(sigResult.message || '获取上传凭证失败', 'error'); return; } // 2. 先创建视频记录(可选上传封面) const formData = new FormData(); formData.append('title_cn', titleCn); formData.append('title_en', titleEn); formData.append('desc_cn', descCn); formData.append('desc_en', descEn); if (coverFile) { formData.append('cover', coverFile); } formData.append('status', 2); // Processing const videoRecordResponse = await fetch(`${API_BASE}/videos`, { method: 'POST', credentials: 'include', body: formData }); const videoRecordResult = await videoRecordResponse.json(); if (!videoRecordResult.success) { showToast(videoRecordResult.message || '创建视频记录失败', 'error'); return; } const videoId = videoRecordResult.data.id; // 3. 使用腾讯云VOD SDK上传视频 showUploadProgress(videoFile.name); const tcVod = new TcVod.default({ getSignature: () => sigResult.data.signature }); const uploader = tcVod.upload({ mediaFile: videoFile, }); // 监听上传进度 uploader.on('media_progress', (info) => { updateUploadProgress(info.percent * 100, info.speed); }); // 上传完成 uploader.done().then(async (doneResult) => { console.log('Upload complete:', doneResult); // 更新视频记录 const updateData = new FormData(); updateData.append('file_id', doneResult.fileId); updateData.append('video_url', doneResult.video.url); updateData.append('upload_status', 'success'); updateData.append('status', 1); // Active await fetch(`${API_BASE}/videos/${videoId}`, { method: 'PUT', credentials: 'include', body: updateData }); showToast('视频上传成功!', 'success'); setTimeout(() => { navigateToPage('video-list'); }, 1500); }).catch(async (error) => { console.error('Upload failed:', error); // 更新失败状态 const updateData = new FormData(); updateData.append('upload_status', 'failed'); await fetch(`${API_BASE}/videos/${videoId}`, { method: 'PUT', credentials: 'include', body: updateData }); showToast('视频上传失败: ' + error.message, 'error'); document.getElementById('uploadVideoBtn').style.display = 'inline-flex'; document.getElementById('cancelUploadBtn').style.display = 'none'; }); currentUploadTask = uploader; document.getElementById('uploadVideoBtn').style.display = 'none'; document.getElementById('cancelUploadBtn').style.display = 'inline-flex'; // 取消上传按钮 document.getElementById('cancelUploadBtn').onclick = () => { if (currentUploadTask) { currentUploadTask.cancel(); showToast('上传已取消', 'warning'); resetVideoUploadForm(); } }; } catch (error) { console.error('Video upload error:', error); showToast('上传失败: ' + error.message, 'error'); } } // 显示上传进度 function showUploadProgress(fileName) { document.getElementById('uploadProgress').style.display = 'block'; document.getElementById('uploadFileName').textContent = fileName; document.getElementById('uploadPercent').textContent = '0%'; document.getElementById('progressFill').style.width = '0%'; document.getElementById('uploadStatus').textContent = '正在上传...'; document.getElementById('uploadSpeed').textContent = ''; } // 更新上传进度 function updateUploadProgress(percent, speed) { const percentInt = Math.floor(percent); document.getElementById('uploadPercent').textContent = percentInt + '%'; document.getElementById('progressFill').style.width = percentInt + '%'; if (speed) { const speedText = formatSpeed(speed); document.getElementById('uploadSpeed').textContent = speedText; } } // 格式化速度 function formatSpeed(bytesPerSecond) { if (bytesPerSecond < 1024) { return bytesPerSecond.toFixed(2) + ' B/s'; } else if (bytesPerSecond < 1024 * 1024) { return (bytesPerSecond / 1024).toFixed(2) + ' KB/s'; } else { return (bytesPerSecond / (1024 * 1024)).toFixed(2) + ' MB/s'; } } // ==================== 修改密码功能 ==================== // 处理修改密码 async function handleChangePassword(e) { e.preventDefault(); const currentPassword = document.getElementById('currentPassword').value; const newPassword = document.getElementById('newPassword').value; const confirmPassword = document.getElementById('confirmPassword').value; // 验证新密码 if (newPassword.length < 6) { showToast('新密码长度至少6位', 'error'); return; } // 验证两次密码是否一致 if (newPassword !== confirmPassword) { showToast('两次输入的新密码不一致', 'error'); return; } try { const response = await fetch(`${API_BASE}/auth/change-password`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, credentials: 'include', body: JSON.stringify({ current_password: currentPassword, new_password: newPassword }) }); const result = await response.json(); if (result.success) { showToast('密码修改成功,请重新登录', 'success'); document.getElementById('changePasswordForm').reset(); // 2秒后自动退出登录 setTimeout(() => { handleLogout(); }, 2000); } else { showToast(result.message || '密码修改失败', 'error'); } } catch (error) { console.error('Change password error:', error); showToast('密码修改失败', 'error'); } }