update
This commit is contained in:
parent
0a8b633539
commit
b17e9dea28
@ -3,102 +3,20 @@ package main
|
|||||||
import (
|
import (
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"sort"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sort"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
||||||
// 重命名电影的处理函数
|
// GetCategoryList 获取分类列表
|
||||||
func PostRename(c *gin.Context) {
|
|
||||||
response := gin.H{
|
|
||||||
"code": http.StatusInternalServerError,
|
|
||||||
"message": "An unexpected error occurred.",
|
|
||||||
}
|
|
||||||
|
|
||||||
// 定义请求结构体
|
|
||||||
type RenameRequest struct {
|
|
||||||
OldName string `json:"old_name" binding:"required"`
|
|
||||||
NewName string `json:"new_name" binding:"required"`
|
|
||||||
}
|
|
||||||
|
|
||||||
var req RenameRequest
|
|
||||||
if err := c.ShouldBindJSON(&req); err != nil {
|
|
||||||
response["code"] = http.StatusBadRequest
|
|
||||||
response["message"] = "Invalid request body: " + err.Error()
|
|
||||||
c.JSON(http.StatusBadRequest, response)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
oldName := req.OldName
|
|
||||||
newName := req.NewName
|
|
||||||
|
|
||||||
if oldName == "" || newName == "" {
|
|
||||||
response["code"] = http.StatusBadRequest
|
|
||||||
response["message"] = "Old name and new name are required."
|
|
||||||
c.JSON(http.StatusBadRequest, response)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if oldName == newName {
|
|
||||||
response["code"] = http.StatusBadRequest
|
|
||||||
response["message"] = "Old name and new name cannot be the same."
|
|
||||||
c.JSON(http.StatusBadRequest, response)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
MovieDictLock.Lock()
|
|
||||||
defer MovieDictLock.Unlock()
|
|
||||||
|
|
||||||
// 检查新名称是否已存在
|
|
||||||
if _, exists := MovieDict[newName]; exists {
|
|
||||||
response["code"] = http.StatusConflict
|
|
||||||
response["message"] = "New name already exists."
|
|
||||||
c.JSON(http.StatusConflict, response)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// 查找旧名称对应的电影
|
|
||||||
movie, exists := MovieDict[oldName]
|
|
||||||
if exists {
|
|
||||||
// 更新电影信息
|
|
||||||
err := movie.RenameVideo(newName) // 重命名视频文件
|
|
||||||
if err != nil {
|
|
||||||
response["code"] = http.StatusInternalServerError
|
|
||||||
response["message"] = "Failed to rename video file."
|
|
||||||
c.JSON(http.StatusInternalServerError, response)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
delete(MovieDict, oldName) // 删除旧名称的条目
|
|
||||||
MovieDict[newName] = movie // 添加新名称的条目
|
|
||||||
|
|
||||||
response["code"] = http.StatusOK
|
|
||||||
response["message"] = "Movie renamed successfully."
|
|
||||||
response["data"] = gin.H{"old_name": oldName, "new_name": newName}
|
|
||||||
c.JSON(http.StatusOK, response)
|
|
||||||
log.Printf("Movie renamed from %s to %s successfully", oldName, newName)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// 如果旧名称不存在,返回404错误
|
|
||||||
response["code"] = http.StatusNotFound
|
|
||||||
response["message"] = "Movie not found."
|
|
||||||
c.JSON(http.StatusNotFound, response)
|
|
||||||
log.Printf("Movie rename failed: %s not found", oldName)
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetCategoryList(c *gin.Context) {
|
func GetCategoryList(c *gin.Context) {
|
||||||
response := gin.H{
|
response := gin.H{
|
||||||
"code": http.StatusInternalServerError,
|
"code": http.StatusOK,
|
||||||
"message": "An unexpected error occurred.",
|
"message": "Success",
|
||||||
"data": []string{},
|
"data": Categories,
|
||||||
}
|
}
|
||||||
|
|
||||||
response["code"] = http.StatusOK
|
|
||||||
response["message"] = "Success"
|
|
||||||
response["data"] = Categories
|
|
||||||
c.JSON(http.StatusOK, response)
|
c.JSON(http.StatusOK, response)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -227,3 +145,55 @@ func MovieList(c *gin.Context) {
|
|||||||
response["data"] = responseData
|
response["data"] = responseData
|
||||||
c.JSON(http.StatusOK, response)
|
c.JSON(http.StatusOK, response)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PostRename 重命名文件
|
||||||
|
func PostRename(c *gin.Context) {
|
||||||
|
var requestData struct {
|
||||||
|
OldName string `json:"old_name"`
|
||||||
|
NewName string `json:"new_name"`
|
||||||
|
}
|
||||||
|
|
||||||
|
response := gin.H{
|
||||||
|
"code": http.StatusOK,
|
||||||
|
"message": "Success",
|
||||||
|
"data": nil,
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := c.ShouldBindJSON(&requestData); err != nil {
|
||||||
|
response["code"] = http.StatusBadRequest
|
||||||
|
response["message"] = "Invalid request data: " + err.Error()
|
||||||
|
c.JSON(http.StatusBadRequest, response)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if requestData.OldName == "" || requestData.NewName == "" {
|
||||||
|
response["code"] = http.StatusBadRequest
|
||||||
|
response["message"] = "Both old_name and new_name are required"
|
||||||
|
c.JSON(http.StatusBadRequest, response)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查找要重命名的电影
|
||||||
|
movie, exists := MovieDict[requestData.OldName]
|
||||||
|
if !exists {
|
||||||
|
response["code"] = http.StatusNotFound
|
||||||
|
response["message"] = "Movie not found"
|
||||||
|
c.JSON(http.StatusNotFound, response)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 执行重命名
|
||||||
|
if err := movie.RenameVideo(requestData.NewName); err != nil {
|
||||||
|
response["code"] = http.StatusInternalServerError
|
||||||
|
response["message"] = "Failed to rename video: " + err.Error()
|
||||||
|
c.JSON(http.StatusInternalServerError, response)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新全局字典
|
||||||
|
delete(MovieDict, requestData.OldName)
|
||||||
|
MovieDict[requestData.NewName] = movie
|
||||||
|
|
||||||
|
response["message"] = "Rename successful"
|
||||||
|
c.JSON(http.StatusOK, response)
|
||||||
|
}
|
@ -10,6 +10,9 @@ import (
|
|||||||
func main() {
|
func main() {
|
||||||
initMovie()
|
initMovie()
|
||||||
|
|
||||||
|
// 设置为发布模式
|
||||||
|
gin.SetMode(gin.ReleaseMode)
|
||||||
|
|
||||||
eg := gin.Default()
|
eg := gin.Default()
|
||||||
eg.Use(Cors())
|
eg.Use(Cors())
|
||||||
eg.Static("/res", "movie/")
|
eg.Static("/res", "movie/")
|
||||||
|
@ -75,7 +75,7 @@ var Movies []*Movie // 存储所有电影信息的全局切
|
|||||||
var MovieDict = make(map[string]*Movie) // 存储需要处理缩略图的视频字典
|
var MovieDict = make(map[string]*Movie) // 存储需要处理缩略图的视频字典
|
||||||
var MovieDictLock sync.Mutex // 保护MovieDict的并发访问
|
var MovieDictLock sync.Mutex // 保护MovieDict的并发访问
|
||||||
|
|
||||||
var IsRemakePNG = true // 是否重新生成所有PNG缩略图
|
var IsRemakePNG = false // 是否重新生成所有PNG缩略图
|
||||||
var Categories = []string{ // 分类
|
var Categories = []string{ // 分类
|
||||||
"15min", "30min", "60min", "大于60min", "最新添加"}
|
"15min", "30min", "60min", "大于60min", "最新添加"}
|
||||||
|
|
||||||
|
59
src/Main.jsx
59
src/Main.jsx
@ -1,9 +1,10 @@
|
|||||||
|
// ...existing code...
|
||||||
import React, { useState, useEffect, useContext, useCallback, useRef } from 'react';
|
import React, { useState, useEffect, useContext, useCallback, useRef } from 'react';
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import Container from '@mui/material/Container';
|
import Container from '@mui/material/Container';
|
||||||
import Grid from '@mui/material/Grid';
|
import Grid from '@mui/material/Grid';
|
||||||
import Pagination from '@mui/material/Pagination';
|
import Pagination from '@mui/material/Pagination';
|
||||||
import { Link } from 'react-router-dom'; // useLocation removed as not directly used for this fix
|
import { Link } from 'react-router-dom';
|
||||||
import CircularProgress from '@mui/material/CircularProgress';
|
import CircularProgress from '@mui/material/CircularProgress';
|
||||||
import TextField from '@mui/material/TextField';
|
import TextField from '@mui/material/TextField';
|
||||||
import InputAdornment from '@mui/material/InputAdornment';
|
import InputAdornment from '@mui/material/InputAdornment';
|
||||||
@ -11,9 +12,10 @@ import IconButton from '@mui/material/IconButton';
|
|||||||
import SearchIcon from '@mui/icons-material/Search';
|
import SearchIcon from '@mui/icons-material/Search';
|
||||||
import ClearIcon from '@mui/icons-material/Clear';
|
import ClearIcon from '@mui/icons-material/Clear';
|
||||||
|
|
||||||
import ConfigContext from './Config'; // Ensure this path is correct
|
import ConfigContext from './Config';
|
||||||
import MovieCard from './components/MovieCard'; // Ensure this path is correct
|
import MovieCard from './components/MovieCard';
|
||||||
import CategoryNav from './components/CategoryNav'; // Ensure this path is correct
|
import CategoryNav from './components/CategoryNav';
|
||||||
|
// ...existing code...
|
||||||
|
|
||||||
// 分类配置
|
// 分类配置
|
||||||
const categories = [
|
const categories = [
|
||||||
@ -71,9 +73,9 @@ const Main = () => {
|
|||||||
]),
|
]),
|
||||||
});
|
});
|
||||||
|
|
||||||
const isMounted = useRef(false);
|
|
||||||
const isPopStateNav = useRef(false);
|
|
||||||
const scrollTimeoutRef = useRef(null);
|
const scrollTimeoutRef = useRef(null);
|
||||||
|
const isPopStateNav = useRef(false);
|
||||||
|
const isMounted = useRef(false);
|
||||||
|
|
||||||
const fetchMovies = useCallback(async (category, page, search = '', options = {}) => {
|
const fetchMovies = useCallback(async (category, page, search = '', options = {}) => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
@ -114,7 +116,7 @@ const Main = () => {
|
|||||||
isPopStateNav.current = false;
|
isPopStateNav.current = false;
|
||||||
}
|
}
|
||||||
// IMPORTANT: Removed duplicated scroll logic block that was here
|
// IMPORTANT: Removed duplicated scroll logic block that was here
|
||||||
}, []); // No dependencies that change frequently, setPersistedParams is stable
|
}, [isPopStateNav]);
|
||||||
|
|
||||||
const navigateAndFetch = useCallback((newCategory, newPage, newSearchQuery = '', options = {}) => {
|
const navigateAndFetch = useCallback((newCategory, newPage, newSearchQuery = '', options = {}) => {
|
||||||
const { replace = false, preserveScroll = false, isPopStatePaging = false } = options;
|
const { replace = false, preserveScroll = false, isPopStatePaging = false } = options;
|
||||||
@ -122,33 +124,25 @@ const Main = () => {
|
|||||||
setActiveCategory(newCategory);
|
setActiveCategory(newCategory);
|
||||||
setCurrentPage(newPage);
|
setCurrentPage(newPage);
|
||||||
setActiveSearchQuery(newSearchQuery);
|
setActiveSearchQuery(newSearchQuery);
|
||||||
|
|
||||||
if (newCategory === SEARCH_CATEGORY) {
|
if (newCategory === SEARCH_CATEGORY) {
|
||||||
setSearchInput(newSearchQuery);
|
setSearchInput(newSearchQuery);
|
||||||
} else {
|
} else {
|
||||||
setSearchInput('');
|
setSearchInput('');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Determine scroll position for history state
|
const scrollForHistory = preserveScroll
|
||||||
// If preserving scroll, use current window.scrollY or a remembered scroll for the target category if available
|
? (window.history.state?.appState?.scrollPos || window.scrollY)
|
||||||
// Otherwise, new navigations (not popstate) typically scroll to 0 unless specified.
|
: 0;
|
||||||
let scrollForHistory = 0;
|
|
||||||
if (preserveScroll) {
|
|
||||||
// For general preserveScroll (like back/fwd), use current scrollY.
|
|
||||||
// For specific category change restorations, this might be overridden by options.restoreScrollPos in fetchMovies.
|
|
||||||
scrollForHistory = window.history.state?.appState?.scrollPos || window.scrollY;
|
|
||||||
}
|
|
||||||
// If navigating to a category with a known scroll position, that should be prioritized for restoration.
|
|
||||||
// This is handled by passing restoreScrollPos to fetchMovies. For history state, use `scrollForHistory`.
|
|
||||||
|
|
||||||
|
|
||||||
const historyState = {
|
const historyState = {
|
||||||
category: newCategory,
|
category: newCategory,
|
||||||
page: newPage,
|
page: newPage,
|
||||||
searchQuery: newSearchQuery,
|
searchQuery: newSearchQuery,
|
||||||
scrollPos: scrollForHistory, // This is the scroll position *at the moment of navigation*
|
scrollPos: scrollForHistory,
|
||||||
};
|
};
|
||||||
|
|
||||||
const url = window.location.pathname; // Keep URL simple, no query params in URL itself for now
|
const url = window.location.pathname;
|
||||||
const browserHistoryState = window.history.state?.appState;
|
const browserHistoryState = window.history.state?.appState;
|
||||||
const needsPush = !browserHistoryState ||
|
const needsPush = !browserHistoryState ||
|
||||||
browserHistoryState.category !== newCategory ||
|
browserHistoryState.category !== newCategory ||
|
||||||
@ -168,14 +162,14 @@ const Main = () => {
|
|||||||
if (oldCategoryState && oldCategoryState !== newCategory) {
|
if (oldCategoryState && oldCategoryState !== newCategory) {
|
||||||
newCategoryHistory[oldCategoryState] = {
|
newCategoryHistory[oldCategoryState] = {
|
||||||
...newCategoryHistory[oldCategoryState],
|
...newCategoryHistory[oldCategoryState],
|
||||||
scrollPos: window.scrollY, // Save scroll of category being left
|
scrollPos: window.scrollY,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
newCategoryHistory[newCategory] = {
|
newCategoryHistory[newCategory] = {
|
||||||
...newCategoryHistory[newCategory],
|
...newCategoryHistory[newCategory],
|
||||||
lastPage: newPage,
|
lastPage: newPage,
|
||||||
scrollPos: scrollForHistory, // Store the scroll we intend to be at for the new state
|
scrollPos: scrollForHistory,
|
||||||
...(newCategory === SEARCH_CATEGORY && { searchQuery: newSearchQuery }),
|
...(newCategory === SEARCH_CATEGORY && { searchQuery: newSearchQuery }),
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -185,20 +179,11 @@ const Main = () => {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
// Determine scroll position for fetching movies
|
const scrollPosForFetch = preserveScroll ? scrollForHistory : 0;
|
||||||
// If preserving scroll, it means we want to restore to where we were or a specific point
|
|
||||||
let scrollPosForFetch = scrollForHistory; // Default to the scrollForHistory calculated
|
|
||||||
if (!preserveScroll) { // If not preserving, new main navigations usually go to top
|
|
||||||
scrollPosForFetch = 0;
|
|
||||||
}
|
|
||||||
// If navigating to a category and it has a specific remembered scroll, that should be preferred
|
|
||||||
// This part becomes complex if we want category changes to always restore *their* scroll.
|
|
||||||
// For now, `preserveScroll` will try to keep `window.scrollY`, otherwise `0`.
|
|
||||||
// PopState correctly restores its specific scroll.
|
|
||||||
|
|
||||||
fetchMovies(newCategory, newPage, newSearchQuery, {
|
fetchMovies(newCategory, newPage, newSearchQuery, {
|
||||||
restoreScrollPos: scrollPosForFetch,
|
restoreScrollPos: scrollPosForFetch,
|
||||||
skipScrollRestore: false, // Allow fetchMovies to handle scroll unless explicitly told otherwise later
|
skipScrollRestore: false,
|
||||||
isPopStatePaging: isPopStatePaging,
|
isPopStatePaging: isPopStatePaging,
|
||||||
});
|
});
|
||||||
}, [fetchMovies, setPersistedParams]);
|
}, [fetchMovies, setPersistedParams]);
|
||||||
@ -268,7 +253,7 @@ const Main = () => {
|
|||||||
clearTimeout(scrollTimeoutRef.current);
|
clearTimeout(scrollTimeoutRef.current);
|
||||||
};
|
};
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [navigateAndFetch, setPersistedParams]); // Removed fetchMovies as navigateAndFetch calls it.
|
}, [navigateAndFetch, setPersistedParams]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const handleScroll = () => {
|
const handleScroll = () => {
|
||||||
@ -306,7 +291,7 @@ const Main = () => {
|
|||||||
window.removeEventListener('scroll', handleScroll);
|
window.removeEventListener('scroll', handleScroll);
|
||||||
clearTimeout(scrollTimeoutRef.current);
|
clearTimeout(scrollTimeoutRef.current);
|
||||||
};
|
};
|
||||||
}, [loading, movies.length, setPersistedParams]);
|
}, [loading, movies.length, setPersistedParams, isPopStateNav, scrollTimeoutRef]);
|
||||||
|
|
||||||
|
|
||||||
const handleCategoryChange = useCallback((newCategory) => {
|
const handleCategoryChange = useCallback((newCategory) => {
|
||||||
@ -422,7 +407,7 @@ const Main = () => {
|
|||||||
setActiveSearchQuery('');
|
setActiveSearchQuery('');
|
||||||
setPersistedParams(prev => ({ ...prev, lastKnownState: { ...prev.lastKnownState, searchQuery: '' } }));
|
setPersistedParams(prev => ({ ...prev, lastKnownState: { ...prev.lastKnownState, searchQuery: '' } }));
|
||||||
}
|
}
|
||||||
}, [activeCategory, navigateAndFetch, persistedParams.categoryHistory, setPersistedParams, fetchMovies]);
|
}, [activeCategory, persistedParams.categoryHistory, setPersistedParams, fetchMovies]);
|
||||||
|
|
||||||
|
|
||||||
// MODIFIED handleRename
|
// MODIFIED handleRename
|
||||||
|
@ -122,30 +122,30 @@ const VideoPlayer = () => {
|
|||||||
file: {
|
file: {
|
||||||
attributes: {
|
attributes: {
|
||||||
playsInline: true, // 重要:移动端内联播放
|
playsInline: true, // 重要:移动端内联播放
|
||||||
preload: 'metadata'
|
preload: 'metadata',
|
||||||
|
disablePictureInPicture: true // 禁用画中画模式
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
style={{
|
style={{
|
||||||
maxWidth: '100%',
|
maxWidth: '100%',
|
||||||
maxHeight: '100%'
|
maxHeight: '100%',
|
||||||
|
margin: 'auto' // 居中显示
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
{/* 底部信息(仅桌面端显示) */}
|
{/* 底部信息(移动端显示简化提示) */}
|
||||||
{!isMobile && (
|
|
||||||
<Box sx={{
|
<Box sx={{
|
||||||
p: 2,
|
p: isMobile ? 1 : 2,
|
||||||
color: 'rgba(255,255,255,0.7)',
|
color: 'rgba(255,255,255,0.7)',
|
||||||
textAlign: 'center',
|
textAlign: 'center',
|
||||||
fontSize: '0.8rem'
|
fontSize: '0.8rem'
|
||||||
}}>
|
}}>
|
||||||
<Typography variant="caption">
|
<Typography variant="caption">
|
||||||
提示:双击视频可全屏播放
|
{isMobile ? '双击可全屏' : '提示:双击视频可全屏播放'}
|
||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
)}
|
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
Loading…
x
Reference in New Issue
Block a user