Merge branch 'senshinya:main' into main

This commit is contained in:
ououmm
2025-07-03 23:09:37 +08:00
committed by GitHub
5 changed files with 97 additions and 77 deletions

View File

@@ -10,12 +10,22 @@ export const runtime = 'edge';
const HISTORY_LIMIT = 20;
/**
* GET /api/searchhistory
* GET /api/searchhistory?user=<username>
* 返回 string[]
*/
export async function GET() {
export async function GET(request: NextRequest) {
try {
const history = await db.getSearchHistory();
const { searchParams } = new URL(request.url);
const user = searchParams.get('user')?.trim();
if (!user) {
return NextResponse.json(
{ error: 'User parameter is required' },
{ status: 400 }
);
}
const history = await db.getSearchHistory(user);
return NextResponse.json(history, { status: 200 });
} catch (err) {
console.error('获取搜索历史失败', err);
@@ -28,12 +38,14 @@ export async function GET() {
/**
* POST /api/searchhistory
* body: { keyword: string }
* body: { keyword: string, user: string }
*/
export async function POST(request: NextRequest) {
try {
const body = await request.json();
const keyword: string = body.keyword?.trim();
const user: string = body.user?.trim();
if (!keyword) {
return NextResponse.json(
{ error: 'Keyword is required' },
@@ -41,10 +53,17 @@ export async function POST(request: NextRequest) {
);
}
await db.addSearchHistory(keyword);
if (!user) {
return NextResponse.json(
{ error: 'User parameter is required' },
{ status: 400 }
);
}
await db.addSearchHistory(user, keyword);
// 再次获取最新列表,确保客户端与服务端同步
const history = await db.getSearchHistory();
const history = await db.getSearchHistory(user);
return NextResponse.json(history.slice(0, HISTORY_LIMIT), { status: 200 });
} catch (err) {
console.error('添加搜索历史失败', err);
@@ -56,7 +75,7 @@ export async function POST(request: NextRequest) {
}
/**
* DELETE /api/searchhistory
* DELETE /api/searchhistory?user=<username>&keyword=<kw>
*
* 1. 不带 keyword -> 清空全部搜索历史
* 2. 带 keyword=<kw> -> 删除单条关键字
@@ -64,9 +83,17 @@ export async function POST(request: NextRequest) {
export async function DELETE(request: NextRequest) {
try {
const { searchParams } = new URL(request.url);
const user = searchParams.get('user')?.trim();
const kw = searchParams.get('keyword')?.trim();
await db.deleteSearchHistory(kw || undefined);
if (!user) {
return NextResponse.json(
{ error: 'User parameter is required' },
{ status: 400 }
);
}
await db.deleteSearchHistory(user, kw || undefined);
return NextResponse.json({ success: true }, { status: 200 });
} catch (err) {

View File

@@ -149,7 +149,7 @@ export default function VideoCard({
};
const hideCheckCircle = from === 'favorites' || from === 'search';
const alwaysShowHeart = from === 'favorites';
const alwaysShowHeart = from !== 'favorites';
return (
<Link
@@ -254,7 +254,7 @@ export default function VideoCard({
</div>
)}
{/* 搜索非聚合 - 集数圆形展示框 */}
{from === 'search' && (
{episodes && episodes > 1 && !currentEpisode && (
<div className='absolute top-2 right-2 w-4 h-4 sm:w-7 sm:h-7 rounded-full bg-green-500/90 dark:bg-green-600/90 flex items-center justify-center shadow-md text-[0.55rem] sm:text-xs'>
<span className='text-white font-bold leading-none'>
{episodes}

View File

@@ -206,7 +206,10 @@ export async function getSearchHistory(): Promise<string[]> {
// 如果配置为使用数据库,则从后端 API 获取
if (STORAGE_TYPE !== 'localstorage') {
try {
return fetchFromApi<string[]>('/api/searchhistory');
const user = getUsername();
return fetchFromApi<string[]>(
`/api/searchhistory?user=${encodeURIComponent(user ?? '')}`
);
} catch (err) {
console.error('获取搜索历史失败:', err);
return [];
@@ -240,12 +243,13 @@ export async function addSearchHistory(keyword: string): Promise<void> {
// 数据库模式
if (STORAGE_TYPE !== 'localstorage') {
try {
const user = getUsername();
await fetch('/api/searchhistory', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ keyword: trimmed }),
body: JSON.stringify({ keyword: trimmed, user: user ?? '' }),
});
} catch (err) {
console.error('保存搜索历史失败:', err);
@@ -276,7 +280,8 @@ export async function clearSearchHistory(): Promise<void> {
// 数据库模式
if (STORAGE_TYPE !== 'localstorage') {
try {
await fetch('/api/searchhistory', {
const user = getUsername();
await fetch(`/api/searchhistory?user=${encodeURIComponent(user ?? '')}`, {
method: 'DELETE',
});
} catch (err) {
@@ -300,9 +305,15 @@ export async function deleteSearchHistory(keyword: string): Promise<void> {
// 数据库模式
if (STORAGE_TYPE !== 'localstorage') {
try {
await fetch(`/api/searchhistory?keyword=${encodeURIComponent(trimmed)}`, {
method: 'DELETE',
});
const user = getUsername();
await fetch(
`/api/searchhistory?user=${encodeURIComponent(
user ?? ''
)}&keyword=${encodeURIComponent(trimmed)}`,
{
method: 'DELETE',
}
);
} catch (err) {
console.error('删除搜索历史失败:', err);
}

View File

@@ -58,9 +58,9 @@ export interface IStorage {
checkUserExist(userName: string): Promise<boolean>;
// 搜索历史相关
getSearchHistory(): Promise<string[]>;
addSearchHistory(keyword: string): Promise<void>;
deleteSearchHistory(keyword?: string): Promise<void>;
getSearchHistory(userName: string): Promise<string[]>;
addSearchHistory(userName: string, keyword: string): Promise<void>;
deleteSearchHistory(userName: string, keyword?: string): Promise<void>;
// 用户列表
getAllUsers(): Promise<string[]>;
@@ -184,26 +184,30 @@ class RedisStorage implements IStorage {
}
// ---------- 搜索历史 ----------
private shKey = 'moontv:search_history';
async getSearchHistory(): Promise<string[]> {
return (await this.client.lRange(this.shKey, 0, -1)) as string[];
private shKey(user: string) {
return `u:${user}:sh`; // u:username:sh
}
async addSearchHistory(keyword: string): Promise<void> {
async getSearchHistory(userName: string): Promise<string[]> {
return (await this.client.lRange(this.shKey(userName), 0, -1)) as string[];
}
async addSearchHistory(userName: string, keyword: string): Promise<void> {
const key = this.shKey(userName);
// 先去重
await this.client.lRem(this.shKey, 0, keyword);
await this.client.lRem(key, 0, keyword);
// 插入到最前
await this.client.lPush(this.shKey, keyword);
await this.client.lPush(key, keyword);
// 限制最大长度
await this.client.lTrim(this.shKey, 0, SEARCH_HISTORY_LIMIT - 1);
await this.client.lTrim(key, 0, SEARCH_HISTORY_LIMIT - 1);
}
async deleteSearchHistory(keyword?: string): Promise<void> {
async deleteSearchHistory(userName: string, keyword?: string): Promise<void> {
const key = this.shKey(userName);
if (keyword) {
await this.client.lRem(this.shKey, 0, keyword);
await this.client.lRem(key, 0, keyword);
} else {
await this.client.del(this.shKey);
await this.client.del(key);
}
}
@@ -371,16 +375,16 @@ export class DbManager {
}
// ---------- 搜索历史 ----------
async getSearchHistory(): Promise<string[]> {
return this.storage.getSearchHistory();
async getSearchHistory(userName: string): Promise<string[]> {
return this.storage.getSearchHistory(userName);
}
async addSearchHistory(keyword: string): Promise<void> {
await this.storage.addSearchHistory(keyword);
async addSearchHistory(userName: string, keyword: string): Promise<void> {
await this.storage.addSearchHistory(userName, keyword);
}
async deleteSearchHistory(keyword?: string): Promise<void> {
await this.storage.deleteSearchHistory(keyword);
async deleteSearchHistory(userName: string, keyword?: string): Promise<void> {
await this.storage.deleteSearchHistory(userName, keyword);
}
// 获取全部用户名