import React, { useState, useEffect } from 'react'; import { Search, Upload, Download, Trash2, Settings, TrendingUp, Database, Pin, X, AlertCircle } from 'lucide-react'; import Papa from 'papaparse'; const MovieLinkSite = () => { // API 配置 const API_BASE_URL = window.location.origin; // 使用当前域名 const [isAdmin, setIsAdmin] = useState(false); const [adminPassword, setAdminPassword] = useState(''); const [currentPage, setCurrentPage] = useState('home'); const [loading, setLoading] = useState(false); const [error, setError] = useState(''); // 两个独立的数据库 const [moviesDB, setMoviesDB] = useState([]); const [searchStatsDB, setSearchStatsDB] = useState({}); const [searchQuery, setSearchQuery] = useState(''); const [searchResults, setSearchResults] = useState([]); const [topKeywords, setTopKeywords] = useState([]); const [pinnedKeywords, setPinnedKeywords] = useState([]); const [currentUserID, setCurrentUserID] = useState(''); // 配置 const [displayCount, setDisplayCount] = useState(4); const [timeWindow, setTimeWindow] = useState(168); const [backupInterval, setBackupInterval] = useState(24); const [confirmText, setConfirmText] = useState(''); // ==================== Cookie 工具函数 ==================== const getCookie = (name) => { const value = `; ${document.cookie}`; const parts = value.split(`; ${name}=`); if (parts.length === 2) return parts.pop().split(';').shift(); return null; }; const setCookie = (name, value, days) => { const date = new Date(); date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000)); const expires = `expires=${date.toUTCString()}`; document.cookie = `${name}=${value};${expires};path=/`; }; // ==================== API 调用函数 ==================== // 获取资源列表 const fetchMovies = async () => { try { const response = await fetch(`${API_BASE_URL}/api/movies`); const data = await response.json(); return data.movies || []; } catch (error) { console.error('获取资源列表失败:', error); return []; } }; // 上传资源列表(管理员) const uploadMovies = async (movies) => { try { const response = await fetch(`${API_BASE_URL}/api/movies`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${adminPassword}`, }, body: JSON.stringify({ movies }), }); const data = await response.json(); if (response.status === 401) { throw new Error('管理员密码错误'); } return data; } catch (error) { throw error; } }; // 记录搜索行为 const recordSearch = async (keyword, userID) => { try { const timestamp = new Date().toISOString().slice(0, 19).replace('T', ' '); await fetch(`${API_BASE_URL}/api/search-stats`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ keyword, userID, timestamp }), }); } catch (error) { console.error('记录搜索失败:', error); } }; // 获取搜索统计 const fetchSearchStats = async () => { try { const response = await fetch(`${API_BASE_URL}/api/search-stats`); const data = await response.json(); return data.stats || {}; } catch (error) { console.error('获取搜索统计失败:', error); return {}; } }; // 清空搜索统计(管理员) const clearSearchStatsAPI = async () => { try { const response = await fetch(`${API_BASE_URL}/api/search-stats`, { method: 'DELETE', headers: { 'Authorization': `Bearer ${adminPassword}`, }, }); const data = await response.json(); if (response.status === 401) { throw new Error('管理员密码错误'); } return data; } catch (error) { throw error; } }; // 获取配置 const fetchConfig = async () => { try { const response = await fetch(`${API_BASE_URL}/api/config`); const data = await response.json(); return data.config || {}; } catch (error) { console.error('获取配置失败:', error); return {}; } }; // 更新配置(管理员) const updateConfigAPI = async (config) => { try { const response = await fetch(`${API_BASE_URL}/api/config`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${adminPassword}`, }, body: JSON.stringify(config), }); const data = await response.json(); if (response.status === 401) { throw new Error('管理员密码错误'); } return data; } catch (error) { throw error; } }; // 备份数据(管理员) const backupDataAPI = async () => { try { const response = await fetch(`${API_BASE_URL}/api/backup`, { headers: { 'Authorization': `Bearer ${adminPassword}`, }, }); const data = await response.json(); if (response.status === 401) { throw new Error('管理员密码错误'); } return data; } catch (error) { throw error; } }; // ==================== 初始化 ==================== useEffect(() => { // 获取或生成用户ID(使用 Cookie) let userID = getCookie('movie_site_uid'); if (!userID) { userID = 'user_' + Date.now().toString(36) + Math.random().toString(36).substr(2, 9); setCookie('movie_site_uid', userID, 365); } setCurrentUserID(userID); // 加载数据 loadInitialData(); }, []); const loadInitialData = async () => { setLoading(true); try { const [movies, stats, config] = await Promise.all([ fetchMovies(), fetchSearchStats(), fetchConfig() ]); setMoviesDB(movies); setSearchStatsDB(stats); if (config.pinnedKeywords) setPinnedKeywords(config.pinnedKeywords); if (config.displayCount) setDisplayCount(config.displayCount); if (config.timeWindow) setTimeWindow(config.timeWindow); if (config.backupInterval) setBackupInterval(config.backupInterval); updateTopKeywords(stats, config.pinnedKeywords || [], config.timeWindow || 168, config.displayCount || 4); } catch (error) { console.error('加载初始数据失败:', error); setError('数据加载失败,请刷新页面重试'); } setLoading(false); }; // 更新热门关键词(基于时间窗口和用户数统计) const updateTopKeywords = (stats, pinned, windowHours, count) => { const now = new Date(); const cutoffTime = new Date(now.getTime() - windowHours * 60 * 60 * 1000); const keywordCounts = Object.entries(stats).map(([keyword, users]) => { const validUsers = Object.values(users).filter(timestamp => new Date(timestamp) >= cutoffTime ); return { keyword, count: validUsers.length }; }).filter(item => item.count > 0); const topN = keywordCounts .sort((a, b) => b.count - a.count) .slice(0, count) .map(item => item.keyword); const combined = [...new Set([...pinned, ...topN])].slice(0, count); setTopKeywords(combined); }; // 管理员登录 const handleAdminLogin = () => { if (!adminPassword) { alert('请输入管理员密码'); return; } // 密码验证会在实际 API 调用时进行 setIsAdmin(true); setCurrentPage('admin'); }; // 处理 CSV 上传(资源数据库) const handleCSVUpload = (e) => { const file = e.target.files[0]; if (!file) return; setLoading(true); Papa.parse(file, { header: true, skipEmptyLines: true, dynamicTyping: false, complete: async (results) => { try { if (!results.data || results.data.length === 0) { alert('❌ CSV 文件为空或格式错误'); setLoading(false); return; } const headers = Object.keys(results.data[0]); if (!headers[0] || headers[0].trim() !== '标题') { alert('❌ CSV 格式错误:第一列必须是"标题"'); setLoading(false); return; } const movieList = results.data.map(row => { const cleanRow = {}; headers.forEach(header => { cleanRow[header.trim()] = row[header] ? row[header].toString().trim() : ''; }); return cleanRow; }).filter(row => row['标题']); // 上传到服务器 await uploadMovies(movieList); setMoviesDB(movieList); alert(`✅ 成功导入 ${movieList.length} 条影视资源\n列名:${headers.join(', ')}`); } catch (error) { alert('❌ 上传失败:' + error.message); } setLoading(false); }, error: (error) => { alert('❌ 读取 CSV 文件失败:' + error.message); setLoading(false); } }); }; // 搜索功能 const handleSearch = async (keyword) => { if (!keyword.trim()) { setSearchResults([]); return; } const results = moviesDB.filter(movie => movie['标题'] && movie['标题'].toLowerCase().includes(keyword.toLowerCase()) ); setSearchResults(results); // 记录搜索行为到服务器 const normalizedKeyword = keyword.trim(); await recordSearch(normalizedKeyword, currentUserID); // 更新本地状态 const now = new Date().toISOString().slice(0, 19).replace('T', ' '); setSearchStatsDB(prev => { const newStats = { ...prev }; if (!newStats[normalizedKeyword]) { newStats[normalizedKeyword] = {}; } newStats[normalizedKeyword][currentUserID] = now; updateTopKeywords(newStats, pinnedKeywords, timeWindow, displayCount); return newStats; }); }; // 导出资源数据库(CSV) const exportMoviesDB = () => { if (moviesDB.length === 0) { alert('没有资源数据可导出'); return; } const csv = Papa.unparse(moviesDB); const blob = new Blob(['\uFEFF' + csv], { type: 'text/csv;charset=utf-8;' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `影视资源_${new Date().toISOString().slice(0, 10)}.csv`; a.click(); URL.revokeObjectURL(url); }; // 导出搜索统计(CSV) const exportSearchStats = () => { const now = new Date(); const cutoffTime = new Date(now.getTime() - timeWindow * 60 * 60 * 1000); const data = Object.entries(searchStatsDB).map(([keyword, users]) => { const windowUsers = Object.entries(users).filter(([_, timestamp]) => new Date(timestamp) >= cutoffTime ); const totalUsers = Object.keys(users).length; const lastSearches = Object.values(users).sort((a, b) => new Date(b) - new Date(a) ); return { 关键词: keyword, 窗口内用户数: windowUsers.length, 历史总用户数: totalUsers, 最后搜索时间: lastSearches[0] || '', 用户详情: Object.entries(users) .sort((a, b) => new Date(b[1]) - new Date(a[1])) .map(([uid, time]) => `${uid}:${time}`) .join('; ') }; }).sort((a, b) => b.窗口内用户数 - a.窗口内用户数); const csv = Papa.unparse(data); const blob = new Blob(['\uFEFF' + csv], { type: 'text/csv;charset=utf-8;' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `搜索统计_${new Date().toISOString().slice(0, 10)}.csv`; a.click(); URL.revokeObjectURL(url); }; // 清空搜索统计 const clearSearchStats = async () => { if (confirmText !== '确认') { alert('❌ 请输入"确认"以清空数据'); return; } setLoading(true); try { await clearSearchStatsAPI(); setSearchStatsDB({}); setTopKeywords([]); setConfirmText(''); alert('✅ 搜索统计已清空'); } catch (error) { alert('❌ 清空失败:' + error.message); } setLoading(false); }; // 添加置顶关键词 const addPinnedKeyword = async (keyword) => { if (!keyword || pinnedKeywords.includes(keyword)) return; const newPinned = [...pinnedKeywords, keyword]; setPinnedKeywords(newPinned); updateTopKeywords(searchStatsDB, newPinned, timeWindow, displayCount); // 保存配置到服务器 try { await updateConfigAPI({ pinnedKeywords: newPinned, displayCount, timeWindow, backupInterval }); } catch (error) { console.error('保存配置失败:', error); } }; // 移除置顶关键词 const removePinnedKeyword = async (keyword) => { const newPinned = pinnedKeywords.filter(k => k !== keyword); setPinnedKeywords(newPinned); updateTopKeywords(searchStatsDB, newPinned, timeWindow, displayCount); try { await updateConfigAPI({ pinnedKeywords: newPinned, displayCount, timeWindow, backupInterval }); } catch (error) { console.error('保存配置失败:', error); } }; // 更新配置 const saveConfig = async () => { setLoading(true); try { await updateConfigAPI({ pinnedKeywords, displayCount, timeWindow, backupInterval }); updateTopKeywords(searchStatsDB, pinnedKeywords, timeWindow, displayCount); alert('✅ 配置已保存'); } catch (error) { alert('❌ 保存失败:' + error.message); } setLoading(false); }; // 备份所有数据 const backupAllData = async () => { setLoading(true); try { const result = await backupDataAPI(); // 下载备份文件 const dataStr = JSON.stringify(result.backup, null, 2); const blob = new Blob([dataStr], { type: 'application/json' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `完整备份_${new Date().toISOString().slice(0, 19).replace(/:/g, '-')}.json`; a.click(); URL.revokeObjectURL(url); alert('✅ 数据备份成功'); } catch (error) { alert('❌ 备份失败:' + error.message); } setLoading(false); }; // 渲染首页 const renderHomePage = () => (
未找到相关资源
📊 当前资源: {moviesDB.length} 条
📈 统计关键词: {Object.keys(searchStatsDB).length} 个
👥 独立用户数:
{new Set(Object.values(searchStatsDB).flatMap(users => Object.keys(users))).size}
人
📊 总搜索记录:
{Object.values(searchStatsDB).reduce((sum, users) => sum + Object.keys(users).length, 0)}
次
暂无置顶关键词
) : ( pinnedKeywords.map((keyword, idx) => (💾 备份内容包括:资源数据库、行为数据库、配置信息
暂无搜索统计数据
) : (| 排名 | 关键词 | 窗口内用户数 | 历史总用户数 | 最后搜索时间 |
|---|---|---|---|---|
| {idx + 1} | {item.keyword} | {item.windowCount} | {item.totalCount} | {item.lastSearch} |