Files
video-platform/public/admin-panel.html

392 lines
17 KiB
HTML

<!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 rel="stylesheet" href="/css/style.css">
</head>
<body class="admin-page">
<!-- 后台顶部栏 -->
<header class="admin-header">
<div class="admin-header-content">
<div class="logo">
<svg viewBox="0 0 24 24" width="32" height="32">
<path fill="#ff2442" d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z"/>
</svg>
<span>后台管理</span>
</div>
<div class="admin-user">
<span id="adminUsername">管理员</span>
<button onclick="logout()" class="logout-btn">退出</button>
</div>
</div>
</header>
<div class="admin-container">
<!-- 侧边栏 -->
<aside class="admin-sidebar">
<nav class="admin-nav">
<a href="#" class="nav-item active" data-tab="videos">
<svg viewBox="0 0 24 24" width="20" height="20">
<path fill="currentColor" d="M4 6H2v14c0 1.1.9 2 2 2h14v-2H4V6zm16-4H8c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm-8 12.5v-9l6 4.5-6 4.5z"/>
</svg>
<span>视频管理</span>
</a>
<a href="#" class="nav-item" data-tab="stats">
<svg viewBox="0 0 24 24" width="20" height="20">
<path fill="currentColor" d="M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zM9 17H7v-7h2v7zm4 0h-2V7h2v10zm4 0h-2v-4h2v4z"/>
</svg>
<span>数据统计</span>
</a>
</nav>
</aside>
<!-- 主内容区 -->
<main class="admin-main">
<!-- 视频管理 -->
<div id="videosTab" class="admin-tab active">
<div class="tab-header">
<h2>视频管理</h2>
<button class="btn-primary" onclick="showAddModal()">添加视频</button>
</div>
<div class="video-table-wrapper">
<table class="video-table">
<thead>
<tr>
<th>ID</th>
<th>封面</th>
<th>标题</th>
<th>分类</th>
<th>作者</th>
<th>播放量</th>
<th>点赞</th>
<th>上传时间</th>
<th>操作</th>
</tr>
</thead>
<tbody id="videoTableBody">
<!-- 动态加载 -->
</tbody>
</table>
</div>
</div>
<!-- 数据统计 -->
<div id="statsTab" class="admin-tab">
<div class="tab-header">
<h2>数据统计</h2>
</div>
<div class="stats-grid">
<div class="stat-card">
<div class="stat-icon">
<svg viewBox="0 0 24 24" width="32" height="32">
<path fill="currentColor" d="M4 6H2v14c0 1.1.9 2 2 2h14v-2H4V6zm16-4H8c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm-8 12.5v-9l6 4.5-6 4.5z"/>
</svg>
</div>
<div class="stat-info">
<div class="stat-value" id="totalVideos">0</div>
<div class="stat-label">视频总数</div>
</div>
</div>
<div class="stat-card">
<div class="stat-icon views-icon">
<svg viewBox="0 0 24 24" width="32" height="32">
<path fill="currentColor" d="M12 4.5C7 4.5 2.73 7.61 1 12c1.73 4.39 6 7.5 11 7.5s9.27-3.11 11-7.5c-1.73-4.39-6-7.5-11-7.5zM12 17c-2.76 0-5-2.24-5-5s2.24-5 5-5 5 2.24 5 5-2.24 5-5 5zm0-8c-1.66 0-3 1.34-3 3s1.34 3 3 3 3-1.34 3-3-1.34-3-3-3z"/>
</svg>
</div>
<div class="stat-info">
<div class="stat-value" id="totalViews">0</div>
<div class="stat-label">总播放量</div>
</div>
</div>
<div class="stat-card">
<div class="stat-icon likes-icon">
<svg viewBox="0 0 24 24" width="32" height="32">
<path fill="currentColor" d="M1 21h4V9H1v12zm22-11c0-1.1-.9-2-2-2h-6.31l.95-4.57.03-.32c0-.41-.17-.79-.44-1.06L14.17 1 7.59 7.59C7.22 7.95 7 8.45 7 9v10c0 1.1.9 2 2 2h9c.83 0 1.54-.5 1.84-1.22l3.02-7.05c.09-.23.14-.47.14-.73v-2z"/>
</svg>
</div>
<div class="stat-info">
<div class="stat-value" id="totalLikes">0</div>
<div class="stat-label">总点赞数</div>
</div>
</div>
</div>
<div class="chart-placeholder">
<p>📊 数据可视化图表(可扩展)</p>
</div>
</div>
</main>
</div>
<!-- 添加/编辑视频模态框 -->
<div class="modal" id="videoModal">
<div class="modal-content">
<div class="modal-header">
<h3 id="modalTitle">添加视频</h3>
<button class="modal-close" onclick="closeModal()">&times;</button>
</div>
<form id="videoForm" class="video-form">
<input type="hidden" id="videoId">
<div class="form-group">
<label for="videoTitle">视频标题 *</label>
<input type="text" id="videoTitleInput" name="title" required placeholder="请输入视频标题">
</div>
<div class="form-group">
<label for="videoUrl">视频地址 *</label>
<input type="url" id="videoUrl" name="url" required placeholder="视频文件URL地址">
</div>
<div class="form-group">
<label for="videoCover">封面地址</label>
<input type="url" id="videoCover" name="cover" placeholder="封面图片URL地址">
</div>
<div class="form-row">
<div class="form-group">
<label for="videoCategory">分类 *</label>
<select id="videoCategory" name="category" required>
<option value="推荐">推荐</option>
<option value="热门">热门</option>
<option value="萌宠">萌宠</option>
<option value="美食">美食</option>
<option value="旅行">旅行</option>
<option value="科技">科技</option>
<option value="风景">风景</option>
<option value="其他">其他</option>
</select>
</div>
<div class="form-group">
<label for="videoDuration">时长(秒)</label>
<input type="number" id="videoDuration" name="duration" placeholder="视频时长">
</div>
</div>
<div class="form-group">
<label for="videoAuthor">作者</label>
<input type="text" id="videoAuthor" name="author" placeholder="视频作者">
</div>
<div class="form-group">
<label for="videoDescription">描述</label>
<textarea id="videoDescription" name="description" rows="3" placeholder="视频描述"></textarea>
</div>
<div class="form-actions">
<button type="button" class="btn-secondary" onclick="closeModal()">取消</button>
<button type="submit" class="btn-primary">保存</button>
</div>
</form>
</div>
</div>
<script>
// 检查登录状态
async function checkAuth() {
try {
const response = await fetch('/api/admin/check');
const data = await response.json();
if (!data.success) {
window.location.href = '/admin';
} else {
document.getElementById('adminUsername').textContent = data.user.username;
loadVideos();
}
} catch (error) {
window.location.href = '/admin';
}
}
// 登出
async function logout() {
try {
await fetch('/api/admin/logout', { method: 'POST' });
window.location.href = '/admin';
} catch (error) {
console.error('登出失败:', error);
}
}
// 加载视频列表
async function loadVideos() {
try {
const response = await fetch('/api/videos');
const data = await response.json();
if (data.success) {
renderVideoTable(data.data);
updateStats(data.data);
}
} catch (error) {
console.error('加载视频失败:', error);
}
}
// 渲染视频表格
function renderVideoTable(videos) {
const tbody = document.getElementById('videoTableBody');
tbody.innerHTML = videos.map(video => `
<tr>
<td>${video.id}</td>
<td><img src="${video.cover || 'https://via.placeholder.com/80x45'}" alt="${video.title}" class="table-cover"></td>
<td class="video-title-cell">${video.title}</td>
<td><span class="category-tag">${video.category}</span></td>
<td>${video.author || '未知'}</td>
<td>${formatNumber(video.views)}</td>
<td>${formatNumber(video.likes)}</td>
<td>${video.createTime}</td>
<td class="action-cell">
<button onclick="editVideo(${video.id})" class="btn-edit">编辑</button>
<button onclick="deleteVideo(${video.id})" class="btn-delete">删除</button>
</td>
</tr>
`).join('');
}
// 更新统计数据
function updateStats(videos) {
const totalVideos = videos.length;
const totalViews = videos.reduce((sum, v) => sum + (v.views || 0), 0);
const totalLikes = videos.reduce((sum, v) => sum + (v.likes || 0), 0);
document.getElementById('totalVideos').textContent = totalVideos;
document.getElementById('totalViews').textContent = formatNumber(totalViews);
document.getElementById('totalLikes').textContent = formatNumber(totalLikes);
}
// 格式化数字
function formatNumber(num) {
if (num >= 10000) {
return (num / 10000).toFixed(1) + '万';
}
return num || 0;
}
// 显示添加模态框
function showAddModal() {
document.getElementById('modalTitle').textContent = '添加视频';
document.getElementById('videoForm').reset();
document.getElementById('videoId').value = '';
document.getElementById('videoModal').classList.add('active');
}
// 编辑视频
async function editVideo(id) {
try {
const response = await fetch(`/api/videos/${id}`);
const data = await response.json();
if (data.success) {
const video = data.data;
document.getElementById('modalTitle').textContent = '编辑视频';
document.getElementById('videoId').value = video.id;
document.getElementById('videoTitleInput').value = video.title;
document.getElementById('videoUrl').value = video.url;
document.getElementById('videoCover').value = video.cover || '';
document.getElementById('videoCategory').value = video.category;
document.getElementById('videoDuration').value = video.duration || '';
document.getElementById('videoAuthor').value = video.author || '';
document.getElementById('videoDescription').value = video.description || '';
document.getElementById('videoModal').classList.add('active');
}
} catch (error) {
console.error('加载视频失败:', error);
}
}
// 删除视频
async function deleteVideo(id) {
if (!confirm('确定要删除这个视频吗?')) return;
try {
const response = await fetch(`/api/admin/videos/${id}`, {
method: 'DELETE'
});
const data = await response.json();
if (data.success) {
loadVideos();
} else {
alert('删除失败: ' + data.message);
}
} catch (error) {
console.error('删除视频失败:', error);
}
}
// 关闭模态框
function closeModal() {
document.getElementById('videoModal').classList.remove('active');
}
// 提交表单
document.getElementById('videoForm').addEventListener('submit', async (e) => {
e.preventDefault();
const formData = {
title: document.getElementById('videoTitleInput').value,
url: document.getElementById('videoUrl').value,
cover: document.getElementById('videoCover').value,
category: document.getElementById('videoCategory').value,
duration: parseInt(document.getElementById('videoDuration').value) || 10,
author: document.getElementById('videoAuthor').value || '管理员',
description: document.getElementById('videoDescription').value
};
const videoId = document.getElementById('videoId').value;
const isEdit = !!videoId;
try {
const url = isEdit ? `/api/admin/videos/${videoId}` : '/api/admin/videos';
const method = isEdit ? 'PUT' : 'POST';
const response = await fetch(url, {
method,
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(formData)
});
const data = await response.json();
if (data.success) {
closeModal();
loadVideos();
} else {
alert('保存失败: ' + data.message);
}
} catch (error) {
console.error('保存视频失败:', error);
alert('保存失败,请重试');
}
});
// 切换标签
document.querySelectorAll('.admin-nav .nav-item').forEach(item => {
item.addEventListener('click', (e) => {
e.preventDefault();
const tab = item.dataset.tab;
document.querySelectorAll('.admin-nav .nav-item').forEach(i => i.classList.remove('active'));
document.querySelectorAll('.admin-tab').forEach(t => t.classList.remove('active'));
item.classList.add('active');
document.getElementById(tab + 'Tab').classList.add('active');
});
});
// 初始化
document.addEventListener('DOMContentLoaded', checkAuth);
</script>
</body>
</html>