Files
music-platform/public/js/admin.js
hz4th_coder 595670b6ce feat: 音乐展示平台 v1.0.0
- 响应式设计,支持桌面端和手机端自适应
- 首页展示轮播图、推荐歌单、热门歌曲、新碟上架、热门歌手
- 歌曲/歌手/专辑/歌单详情页
- 底部播放器:播放/暂停、上下首、进度拖动、音量控制
- 歌词同步滚动显示
- 搜索功能
- 后台管理 /admin
- JSON文件存储
2026-07-07 23:57:31 +08:00

908 lines
29 KiB
JavaScript

// ============ 后台管理 JS ============
// 当前页面
let currentPage = 'dashboard';
// API 工具
const api = {
async get(url) {
const res = await fetch(url);
return res.json();
},
async post(url, data) {
const res = await fetch(url, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
});
return res.json();
},
async put(url, data) {
const res = await fetch(url, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
});
return res.json();
},
async delete(url) {
const res = await fetch(url, { method: 'DELETE' });
return res.json();
},
async upload(url, formData) {
const res = await fetch(url, {
method: 'POST',
body: formData
});
return res.json();
}
};
// 通知
function toast(msg, type = 'info') {
const el = document.createElement('div');
el.className = `toast ${type}`;
el.textContent = msg;
document.body.appendChild(el);
setTimeout(() => el.remove(), 3000);
}
// 模态框
const modal = {
open(title, bodyHtml) {
document.getElementById('modalTitle').textContent = title;
document.getElementById('modalBody').innerHTML = bodyHtml;
document.getElementById('modal').style.display = 'flex';
},
close() {
document.getElementById('modal').style.display = 'none';
}
};
// ============ 页面渲染 ============
const pages = {
// 仪表盘
async dashboard() {
const content = document.getElementById('adminContent');
content.innerHTML = '<div class="loading">加载中...</div>';
try {
const stats = await api.get('/api/admin/stats');
content.innerHTML = `
<div class="stats-grid">
<div class="stat-card">
<div class="icon blue"><i class="fas fa-music"></i></div>
<div class="value">${stats.songCount}</div>
<div class="label">歌曲总数</div>
</div>
<div class="stat-card">
<div class="icon green"><i class="fas fa-user"></i></div>
<div class="value">${stats.artistCount}</div>
<div class="label">歌手数量</div>
</div>
<div class="stat-card">
<div class="icon orange"><i class="fas fa-compact-disc"></i></div>
<div class="value">${stats.albumCount}</div>
<div class="label">专辑数量</div>
</div>
<div class="stat-card">
<div class="icon purple"><i class="fas fa-list"></i></div>
<div class="value">${stats.playlistCount}</div>
<div class="label">歌单数量</div>
</div>
<div class="stat-card">
<div class="icon red"><i class="fas fa-play-circle"></i></div>
<div class="value">${formatNumber(stats.totalPlays)}</div>
<div class="label">总播放次数</div>
</div>
</div>
<div class="table-container">
<div class="table-header">
<h3>快捷操作</h3>
</div>
<div style="padding: 20px; display: flex; gap: 12px; flex-wrap: wrap;">
<button class="btn btn-primary" onclick="pages.songs()"><i class="fas fa-music"></i> 管理歌曲</button>
<button class="btn btn-primary" onclick="pages.artists()"><i class="fas fa-user"></i> 管理歌手</button>
<button class="btn btn-primary" onclick="pages.albums()"><i class="fas fa-compact-disc"></i> 管理专辑</button>
<button class="btn btn-primary" onclick="pages.playlists()"><i class="fas fa-list"></i> 管理歌单</button>
</div>
</div>
`;
} catch (err) {
content.innerHTML = '<div class="empty-state"><i class="fas fa-exclamation-circle"></i><p>加载失败</p></div>';
}
},
// 歌曲管理
async songs() {
currentPage = 'songs';
document.getElementById('pageTitle').textContent = '歌曲管理';
updateNav();
const content = document.getElementById('adminContent');
content.innerHTML = '<div class="loading">加载中...</div>';
try {
const songs = await api.get('/api/admin/songs');
const artists = await api.get('/api/artists');
const albums = await api.get('/api/albums');
let html = `
<div class="table-container">
<div class="table-header">
<h3>歌曲列表 (${songs.length})</h3>
<button class="btn btn-primary" onclick="songForm.open()"><i class="fas fa-plus"></i> 添加歌曲</button>
</div>
<table>
<thead>
<tr>
<th>封面</th>
<th>歌曲名</th>
<th>歌手</th>
<th>专辑</th>
<th>时长</th>
<th>播放量</th>
<th>操作</th>
</tr>
</thead>
<tbody>
`;
if (songs.length === 0) {
html += '<tr><td colspan="7" style="text-align:center;padding:40px;color:#999">暂无歌曲</td></tr>';
} else {
songs.forEach(song => {
html += `
<tr>
<td><img class="cover-thumb" src="${song.cover || ''}" onerror="this.style.display='none'" alt=""></td>
<td>${song.title}</td>
<td>${song.artist_name || '-'}</td>
<td>${song.album_title || '-'}</td>
<td>${formatTime(song.duration)}</td>
<td>${formatNumber(song.play_count)}</td>
<td class="actions-cell">
<button class="btn btn-secondary btn-sm" onclick='songForm.edit(${JSON.stringify(song)})'>编辑</button>
<button class="btn btn-danger btn-sm" onclick="songForm.delete(${song.id})">删除</button>
</td>
</tr>
`;
});
}
html += '</tbody></table></div>';
content.innerHTML = html;
// 保存歌手和专辑数据供表单使用
window._artists = artists;
window._albums = albums;
} catch (err) {
content.innerHTML = '<div class="empty-state"><i class="fas fa-exclamation-circle"></i><p>加载失败</p></div>';
}
},
// 歌手管理
async artists() {
currentPage = 'artists';
document.getElementById('pageTitle').textContent = '歌手管理';
updateNav();
const content = document.getElementById('adminContent');
content.innerHTML = '<div class="loading">加载中...</div>';
try {
const artists = await api.get('/api/admin/artists');
let html = `
<div class="table-container">
<div class="table-header">
<h3>歌手列表 (${artists.length})</h3>
<button class="btn btn-primary" onclick="artistForm.open()"><i class="fas fa-plus"></i> 添加歌手</button>
</div>
<table>
<thead>
<tr>
<th>头像</th>
<th>姓名</th>
<th>简介</th>
<th>操作</th>
</tr>
</thead>
<tbody>
`;
if (artists.length === 0) {
html += '<tr><td colspan="4" style="text-align:center;padding:40px;color:#999">暂无歌手</td></tr>';
} else {
artists.forEach(artist => {
html += `
<tr>
<td><img class="cover-thumb" src="${artist.avatar || ''}" onerror="this.style.display='none'" alt="" style="border-radius:50%"></td>
<td>${artist.name}</td>
<td>${artist.bio || '-'}</td>
<td class="actions-cell">
<button class="btn btn-secondary btn-sm" onclick='artistForm.edit(${JSON.stringify(artist)})'>编辑</button>
<button class="btn btn-danger btn-sm" onclick="artistForm.delete(${artist.id})">删除</button>
</td>
</tr>
`;
});
}
html += '</tbody></table></div>';
content.innerHTML = html;
} catch (err) {
content.innerHTML = '<div class="empty-state"><i class="fas fa-exclamation-circle"></i><p>加载失败</p></div>';
}
},
// 专辑管理
async albums() {
currentPage = 'albums';
document.getElementById('pageTitle').textContent = '专辑管理';
updateNav();
const content = document.getElementById('adminContent');
content.innerHTML = '<div class="loading">加载中...</div>';
try {
const albums = await api.get('/api/admin/albums');
const artists = await api.get('/api/artists');
let html = `
<div class="table-container">
<div class="table-header">
<h3>专辑列表 (${albums.length})</h3>
<button class="btn btn-primary" onclick="albumForm.open()"><i class="fas fa-plus"></i> 添加专辑</button>
</div>
<table>
<thead>
<tr>
<th>封面</th>
<th>专辑名</th>
<th>歌手</th>
<th>发行日期</th>
<th>操作</th>
</tr>
</thead>
<tbody>
`;
if (albums.length === 0) {
html += '<tr><td colspan="5" style="text-align:center;padding:40px;color:#999">暂无专辑</td></tr>';
} else {
albums.forEach(album => {
html += `
<tr>
<td><img class="cover-thumb" src="${album.cover || ''}" onerror="this.style.display='none'" alt=""></td>
<td>${album.title}</td>
<td>${album.artist_name || '-'}</td>
<td>${album.release_date || '-'}</td>
<td class="actions-cell">
<button class="btn btn-secondary btn-sm" onclick='albumForm.edit(${JSON.stringify(album)})'>编辑</button>
<button class="btn btn-danger btn-sm" onclick="albumForm.delete(${album.id})">删除</button>
</td>
</tr>
`;
});
}
html += '</tbody></table></div>';
content.innerHTML = html;
window._artists = artists;
} catch (err) {
content.innerHTML = '<div class="empty-state"><i class="fas fa-exclamation-circle"></i><p>加载失败</p></div>';
}
},
// 歌单管理
async playlists() {
currentPage = 'playlists';
document.getElementById('pageTitle').textContent = '歌单管理';
updateNav();
const content = document.getElementById('adminContent');
content.innerHTML = '<div class="loading">加载中...</div>';
try {
const playlists = await api.get('/api/admin/playlists');
let html = `
<div class="table-container">
<div class="table-header">
<h3>歌单列表 (${playlists.length})</h3>
<button class="btn btn-primary" onclick="playlistForm.open()"><i class="fas fa-plus"></i> 添加歌单</button>
</div>
<table>
<thead>
<tr>
<th>封面</th>
<th>歌单名</th>
<th>描述</th>
<th>推荐</th>
<th>操作</th>
</tr>
</thead>
<tbody>
`;
if (playlists.length === 0) {
html += '<tr><td colspan="5" style="text-align:center;padding:40px;color:#999">暂无歌单</td></tr>';
} else {
playlists.forEach(pl => {
html += `
<tr>
<td><img class="cover-thumb" src="${pl.cover || ''}" onerror="this.style.display='none'" alt=""></td>
<td>${pl.title}</td>
<td>${pl.description || '-'}</td>
<td>${pl.is_featured ? '✅' : '-'}</td>
<td class="actions-cell">
<button class="btn btn-secondary btn-sm" onclick='playlistForm.edit(${JSON.stringify(pl)})'>编辑</button>
<button class="btn btn-danger btn-sm" onclick="playlistForm.delete(${pl.id})">删除</button>
</td>
</tr>
`;
});
}
html += '</tbody></table></div>';
content.innerHTML = html;
} catch (err) {
content.innerHTML = '<div class="empty-state"><i class="fas fa-exclamation-circle"></i><p>加载失败</p></div>';
}
},
// 轮播图管理
async banners() {
currentPage = 'banners';
document.getElementById('pageTitle').textContent = '轮播图管理';
updateNav();
const content = document.getElementById('adminContent');
content.innerHTML = '<div class="loading">加载中...</div>';
try {
const banners = await api.get('/api/admin/banners');
let html = `
<div class="table-container">
<div class="table-header">
<h3>轮播图列表 (${banners.length})</h3>
<button class="btn btn-primary" onclick="bannerForm.open()"><i class="fas fa-plus"></i> 添加轮播图</button>
</div>
<table>
<thead>
<tr>
<th>图片</th>
<th>标题</th>
<th>链接</th>
<th>排序</th>
<th>状态</th>
<th>操作</th>
</tr>
</thead>
<tbody>
`;
if (banners.length === 0) {
html += '<tr><td colspan="6" style="text-align:center;padding:40px;color:#999">暂无轮播图</td></tr>';
} else {
banners.forEach(b => {
html += `
<tr>
<td><img class="cover-thumb" src="${b.image || ''}" onerror="this.style.display='none'" alt="" style="width:80px;height:40px"></td>
<td>${b.title}</td>
<td>${b.link || '-'}</td>
<td>${b.sort_order}</td>
<td>${b.is_active ? '启用' : '禁用'}</td>
<td class="actions-cell">
<button class="btn btn-secondary btn-sm" onclick='bannerForm.edit(${JSON.stringify(b)})'>编辑</button>
<button class="btn btn-danger btn-sm" onclick="bannerForm.delete(${b.id})">删除</button>
</td>
</tr>
`;
});
}
html += '</tbody></table></div>';
content.innerHTML = html;
} catch (err) {
content.innerHTML = '<div class="empty-state"><i class="fas fa-exclamation-circle"></i><p>加载失败</p></div>';
}
}
};
// ============ 表单模块 ============
// 歌曲表单
const songForm = {
open(song = null) {
const artists = window._artists || [];
const albums = window._albums || [];
let artistOptions = '<option value="">选择歌手</option>';
artists.forEach(a => {
artistOptions += `<option value="${a.id}" ${song && song.artist_id == a.id ? 'selected' : ''}>${a.name}</option>`;
});
let albumOptions = '<option value="">选择专辑</option>';
albums.forEach(a => {
albumOptions += `<option value="${a.id}" ${song && song.album_id == a.id ? 'selected' : ''}>${a.title}</option>`;
});
const html = `
<form onsubmit="songForm.submit(event, ${song ? song.id : 'null'})">
<div class="form-group">
<label>歌曲名 *</label>
<input type="text" name="title" value="${song ? song.title : ''}" required>
</div>
<div class="form-group">
<label>歌手</label>
<select name="artist_id">${artistOptions}</select>
</div>
<div class="form-group">
<label>专辑</label>
<select name="album_id">${albumOptions}</select>
</div>
<div class="form-group">
<label>音乐文件 *</label>
<input type="file" name="music_file" accept="audio/*">
${song ? `<small>当前: ${song.file_path}</small>` : ''}
<input type="hidden" name="file_path" value="${song ? song.file_path : ''}">
</div>
<div class="form-group">
<label>封面图片</label>
<input type="file" name="cover_file" accept="image/*">
${song ? `<small>当前: ${song.cover || '无'}</small>` : ''}
<input type="hidden" name="cover" value="${song ? song.cover : ''}">
</div>
<div class="form-group">
<label>时长 (秒)</label>
<input type="number" name="duration" value="${song ? song.duration : '0'}" step="0.1">
</div>
<div class="form-group">
<label>歌词 (LRC格式)</label>
<textarea name="lyrics" rows="6">${song ? song.lyrics || '' : ''}</textarea>
</div>
<div class="form-actions">
<button type="button" class="btn btn-secondary" onclick="modal.close()">取消</button>
<button type="submit" class="btn btn-primary">保存</button>
</div>
</form>
`;
modal.open(song ? '编辑歌曲' : '添加歌曲', html);
// 处理文件上传
this.setupFileUpload('music_file', 'file_path', '/api/admin/upload/music');
this.setupFileUpload('cover_file', 'cover', '/api/admin/upload/cover');
},
edit(song) {
this.open(song);
},
async submit(e, id) {
e.preventDefault();
const form = e.target;
const data = {
title: form.title.value,
artist_id: form.artist_id.value || null,
album_id: form.album_id.value || null,
file_path: form.file_path.value,
cover: form.cover.value,
duration: parseFloat(form.duration.value) || 0,
lyrics: form.lyrics.value
};
if (!data.file_path) {
toast('请上传音乐文件', 'error');
return;
}
try {
if (id) {
await api.put(`/api/admin/songs/${id}`, data);
toast('歌曲已更新', 'success');
} else {
await api.post('/api/admin/songs', data);
toast('歌曲已添加', 'success');
}
modal.close();
pages.songs();
} catch (err) {
toast('操作失败', 'error');
}
},
async delete(id) {
if (!confirm('确定要删除这首歌曲吗?')) return;
try {
await api.delete(`/api/admin/songs/${id}`);
toast('歌曲已删除', 'success');
pages.songs();
} catch (err) {
toast('删除失败', 'error');
}
},
setupFileUpload(inputName, hiddenName, uploadUrl) {
const input = document.querySelector(`input[name="${inputName}"]`);
if (!input) return;
input.addEventListener('change', async (e) => {
const file = e.target.files[0];
if (!file) return;
const formData = new FormData();
formData.append(inputName.replace('_file', ''), file);
try {
const result = await api.upload(uploadUrl, formData);
document.querySelector(`input[name="${hiddenName}"]`).value = result.path;
toast('文件已上传', 'success');
} catch (err) {
toast('上传失败', 'error');
}
});
}
};
// 歌手表单
const artistForm = {
open(artist = null) {
const html = `
<form onsubmit="artistForm.submit(event, ${artist ? artist.id : 'null'})">
<div class="form-group">
<label>歌手名 *</label>
<input type="text" name="name" value="${artist ? artist.name : ''}" required>
</div>
<div class="form-group">
<label>头像</label>
<input type="file" name="avatar_file" accept="image/*">
${artist ? `<small>当前: ${artist.avatar || '无'}</small>` : ''}
<input type="hidden" name="avatar" value="${artist ? artist.avatar : ''}">
</div>
<div class="form-group">
<label>简介</label>
<textarea name="bio" rows="4">${artist ? artist.bio || '' : ''}</textarea>
</div>
<div class="form-actions">
<button type="button" class="btn btn-secondary" onclick="modal.close()">取消</button>
<button type="submit" class="btn btn-primary">保存</button>
</div>
</form>
`;
modal.open(artist ? '编辑歌手' : '添加歌手', html);
songForm.setupFileUpload('avatar_file', 'avatar', '/api/admin/upload/cover');
},
edit(artist) {
this.open(artist);
},
async submit(e, id) {
e.preventDefault();
const form = e.target;
const data = {
name: form.name.value,
avatar: form.avatar.value,
bio: form.bio.value
};
try {
if (id) {
await api.put(`/api/admin/artists/${id}`, data);
toast('歌手已更新', 'success');
} else {
await api.post('/api/admin/artists', data);
toast('歌手已添加', 'success');
}
modal.close();
pages.artists();
} catch (err) {
toast('操作失败', 'error');
}
},
async delete(id) {
if (!confirm('确定要删除这个歌手吗?')) return;
try {
await api.delete(`/api/admin/artists/${id}`);
toast('歌手已删除', 'success');
pages.artists();
} catch (err) {
toast('删除失败', 'error');
}
}
};
// 专辑表单
const albumForm = {
open(album = null) {
const artists = window._artists || [];
let artistOptions = '<option value="">选择歌手</option>';
artists.forEach(a => {
artistOptions += `<option value="${a.id}" ${album && album.artist_id == a.id ? 'selected' : ''}>${a.name}</option>`;
});
const html = `
<form onsubmit="albumForm.submit(event, ${album ? album.id : 'null'})">
<div class="form-group">
<label>专辑名 *</label>
<input type="text" name="title" value="${album ? album.title : ''}" required>
</div>
<div class="form-group">
<label>歌手</label>
<select name="artist_id">${artistOptions}</select>
</div>
<div class="form-group">
<label>封面</label>
<input type="file" name="cover_file" accept="image/*">
${album ? `<small>当前: ${album.cover || '无'}</small>` : ''}
<input type="hidden" name="cover" value="${album ? album.cover : ''}">
</div>
<div class="form-group">
<label>描述</label>
<textarea name="description" rows="3">${album ? album.description || '' : ''}</textarea>
</div>
<div class="form-group">
<label>发行日期</label>
<input type="date" name="release_date" value="${album ? album.release_date || '' : ''}">
</div>
<div class="form-actions">
<button type="button" class="btn btn-secondary" onclick="modal.close()">取消</button>
<button type="submit" class="btn btn-primary">保存</button>
</div>
</form>
`;
modal.open(album ? '编辑专辑' : '添加专辑', html);
songForm.setupFileUpload('cover_file', 'cover', '/api/admin/upload/cover');
},
edit(album) {
this.open(album);
},
async submit(e, id) {
e.preventDefault();
const form = e.target;
const data = {
title: form.title.value,
artist_id: form.artist_id.value || null,
cover: form.cover.value,
description: form.description.value,
release_date: form.release_date.value
};
try {
if (id) {
await api.put(`/api/admin/albums/${id}`, data);
toast('专辑已更新', 'success');
} else {
await api.post('/api/admin/albums', data);
toast('专辑已添加', 'success');
}
modal.close();
pages.albums();
} catch (err) {
toast('操作失败', 'error');
}
},
async delete(id) {
if (!confirm('确定要删除这个专辑吗?')) return;
try {
await api.delete(`/api/admin/albums/${id}`);
toast('专辑已删除', 'success');
pages.albums();
} catch (err) {
toast('删除失败', 'error');
}
}
};
// 歌单表单
const playlistForm = {
open(playlist = null) {
const html = `
<form onsubmit="playlistForm.submit(event, ${playlist ? playlist.id : 'null'})">
<div class="form-group">
<label>歌单名 *</label>
<input type="text" name="title" value="${playlist ? playlist.title : ''}" required>
</div>
<div class="form-group">
<label>封面</label>
<input type="file" name="cover_file" accept="image/*">
${playlist ? `<small>当前: ${playlist.cover || '无'}</small>` : ''}
<input type="hidden" name="cover" value="${playlist ? playlist.cover : ''}">
</div>
<div class="form-group">
<label>描述</label>
<textarea name="description" rows="3">${playlist ? playlist.description || '' : ''}</textarea>
</div>
<div class="form-group">
<label>
<input type="checkbox" name="is_featured" ${playlist && playlist.is_featured ? 'checked' : ''}> 首页推荐
</label>
</div>
<div class="form-actions">
<button type="button" class="btn btn-secondary" onclick="modal.close()">取消</button>
<button type="submit" class="btn btn-primary">保存</button>
</div>
</form>
`;
modal.open(playlist ? '编辑歌单' : '添加歌单', html);
songForm.setupFileUpload('cover_file', 'cover', '/api/admin/upload/cover');
},
edit(playlist) {
this.open(playlist);
},
async submit(e, id) {
e.preventDefault();
const form = e.target;
const data = {
title: form.title.value,
cover: form.cover.value,
description: form.description.value,
is_featured: form.is_featured.checked ? 1 : 0
};
try {
if (id) {
await api.put(`/api/admin/playlists/${id}`, data);
toast('歌单已更新', 'success');
} else {
await api.post('/api/admin/playlists', data);
toast('歌单已添加', 'success');
}
modal.close();
pages.playlists();
} catch (err) {
toast('操作失败', 'error');
}
},
async delete(id) {
if (!confirm('确定要删除这个歌单吗?')) return;
try {
await api.delete(`/api/admin/playlists/${id}`);
toast('歌单已删除', 'success');
pages.playlists();
} catch (err) {
toast('删除失败', 'error');
}
}
};
// 轮播图表单
const bannerForm = {
open(banner = null) {
const html = `
<form onsubmit="bannerForm.submit(event, ${banner ? banner.id : 'null'})">
<div class="form-group">
<label>标题 *</label>
<input type="text" name="title" value="${banner ? banner.title : ''}" required>
</div>
<div class="form-group">
<label>图片 *</label>
<input type="file" name="image_file" accept="image/*">
${banner ? `<small>当前: ${banner.image || '无'}</small>` : ''}
<input type="hidden" name="image" value="${banner ? banner.image : ''}">
</div>
<div class="form-group">
<label>链接</label>
<input type="text" name="link" value="${banner ? banner.link || '' : ''}" placeholder="/song/1">
</div>
<div class="form-group">
<label>排序</label>
<input type="number" name="sort_order" value="${banner ? banner.sort_order : '0'}">
</div>
<div class="form-group">
<label>
<input type="checkbox" name="is_active" ${!banner || banner.is_active ? 'checked' : ''}> 启用
</label>
</div>
<div class="form-actions">
<button type="button" class="btn btn-secondary" onclick="modal.close()">取消</button>
<button type="submit" class="btn btn-primary">保存</button>
</div>
</form>
`;
modal.open(banner ? '编辑轮播图' : '添加轮播图', html);
songForm.setupFileUpload('image_file', 'image', '/api/admin/upload/cover');
},
edit(banner) {
this.open(banner);
},
async submit(e, id) {
e.preventDefault();
const form = e.target;
const data = {
title: form.title.value,
image: form.image.value,
link: form.link.value,
sort_order: parseInt(form.sort_order.value) || 0,
is_active: form.is_active.checked ? 1 : 0
};
if (!data.image) {
toast('请上传图片', 'error');
return;
}
try {
if (id) {
await api.put(`/api/admin/banners/${id}`, data);
toast('轮播图已更新', 'success');
} else {
await api.post('/api/admin/banners', data);
toast('轮播图已添加', 'success');
}
modal.close();
pages.banners();
} catch (err) {
toast('操作失败', 'error');
}
},
async delete(id) {
if (!confirm('确定要删除这个轮播图吗?')) return;
try {
await api.delete(`/api/admin/banners/${id}`);
toast('轮播图已删除', 'success');
pages.banners();
} catch (err) {
toast('删除失败', 'error');
}
}
};
// ============ 辅助函数 ============
function formatTime(seconds) {
if (!seconds || isNaN(seconds)) return '0:00';
const min = Math.floor(seconds / 60);
const sec = Math.floor(seconds % 60);
return `${min}:${sec.toString().padStart(2, '0')}`;
}
function formatNumber(num) {
if (!num) return '0';
if (num >= 10000) return (num / 10000).toFixed(1) + '万';
return num.toString();
}
function updateNav() {
document.querySelectorAll('.nav-item').forEach(item => {
item.classList.toggle('active', item.dataset.page === currentPage);
});
}
// ============ 初始化 ============
document.addEventListener('DOMContentLoaded', () => {
// 导航点击
document.querySelectorAll('.nav-item[data-page]').forEach(item => {
item.addEventListener('click', (e) => {
e.preventDefault();
const page = item.dataset.page;
if (pages[page]) {
pages[page]();
}
});
});
// 默认显示仪表盘
pages.dashboard();
});