feat: Enhance package manager detection script and improve type safety in components
- Updated `check-package-manager.js` to disable specific ESLint rules for better readability. - Refactored `page.tsx` in the login module to remove unnecessary type assertions and improve state management. - Modified `page.tsx` in the home module to enhance error handling, improve layout with grid system, and limit displayed items. - Adjusted `PageLayout.tsx` to implement responsive layout changes for the play page. - Improved `ThemeToggle.tsx` to ensure proper dependency tracking in useEffect. - Enhanced `VideoCard.tsx` with better type definitions for favorites. - Updated `db.client.ts` to rename legacy cache prefix for future migration. - Added runtime configuration types in `types.ts` and extended global Window interface. - Introduced a new Workbox service worker file for improved caching strategies.main
parent
be5462cbb0
commit
4617b0199b
39
README.md
39
README.md
|
@ -590,7 +590,44 @@ KatelyaTV 支持标准的苹果 CMS V10 API 格式。
|
|||
|
||||
[MIT](LICENSE) © 2025 KatelyaTV & Contributors
|
||||
|
||||
## 🙏 致谢
|
||||
## <20> Star History
|
||||
|
||||
<div align="center">
|
||||
|
||||
[](https://star-history.com/#katelya77/KatelyaTV&Date)
|
||||
|
||||
</div>
|
||||
|
||||
## 💖 支持项目
|
||||
|
||||
如果这个项目对您有帮助,欢迎给个 ⭐️ Star 支持一下!
|
||||
|
||||
您也可以通过以下方式支持项目的持续开发:
|
||||
|
||||
<div align="center">
|
||||
|
||||
### 请开发者喝杯咖啡 ☕
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<td align="center">
|
||||
<img src="https://via.placeholder.com/200x200/00D8FF/FFFFFF?text=WeChat+Pay" alt="微信支付" width="200">
|
||||
<br>
|
||||
<strong>微信支付</strong>
|
||||
</td>
|
||||
<td align="center">
|
||||
<img src="https://via.placeholder.com/200x200/1677FF/FFFFFF?text=Alipay" alt="支付宝" width="200">
|
||||
<br>
|
||||
<strong>支付宝</strong>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
> 💝 感谢您的支持!您的捐赠将用于项目的持续维护和功能改进。
|
||||
|
||||
</div>
|
||||
|
||||
## <20>🙏 致谢
|
||||
|
||||
- [ts-nextjs-tailwind-starter](https://github.com/theodorusclarence/ts-nextjs-tailwind-starter) — 项目最初基于该脚手架。
|
||||
- [LibreTV](https://github.com/LibreSpark/LibreTV) — 由此启发,站在巨人的肩膀上。
|
||||
|
|
|
@ -1 +1 @@
|
|||
20250830155949
|
||||
20250901193125
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "moontv",
|
||||
"version": "0.1.0",
|
||||
"name": "katelyatv",
|
||||
"version": "0.4.0-katelya",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "npm run gen:runtime && npm run gen:manifest && next dev -H 0.0.0.0",
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -1,5 +1,7 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-var-requires, no-console */
|
||||
|
||||
/**
|
||||
* 智能包管理器检测和推荐脚本
|
||||
* 帮助用户选择最适合的包管理器
|
||||
|
@ -7,7 +9,6 @@
|
|||
|
||||
const { execSync } = require('child_process');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
console.log('🔍 检测包管理器环境...\n');
|
||||
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
|
||||
'use client';
|
||||
|
||||
import { AlertCircle, CheckCircle } from 'lucide-react';
|
||||
|
@ -85,10 +83,10 @@ function LoginPageClient() {
|
|||
// 在客户端挂载后设置配置
|
||||
useEffect(() => {
|
||||
if (typeof window !== 'undefined') {
|
||||
const storageType = (window as any).RUNTIME_CONFIG?.STORAGE_TYPE;
|
||||
setShouldAskUsername(storageType && storageType !== 'localstorage');
|
||||
const storageType = window.RUNTIME_CONFIG?.STORAGE_TYPE;
|
||||
setShouldAskUsername(Boolean(storageType && storageType !== 'localstorage'));
|
||||
setEnableRegister(
|
||||
Boolean((window as any).RUNTIME_CONFIG?.ENABLE_REGISTER)
|
||||
Boolean(window.RUNTIME_CONFIG?.ENABLE_REGISTER)
|
||||
);
|
||||
}
|
||||
}, []);
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/* eslint-disable @typescript-eslint/no-explicit-any, react-hooks/exhaustive-deps, no-console */
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
|
||||
'use client';
|
||||
|
||||
|
@ -8,6 +8,7 @@ import { Suspense, useEffect, useState } from 'react';
|
|||
|
||||
// 客户端收藏 API
|
||||
import {
|
||||
type Favorite,
|
||||
clearAllFavorites,
|
||||
getAllFavorites,
|
||||
getAllPlayRecords,
|
||||
|
@ -19,7 +20,6 @@ import { DoubanItem } from '@/lib/types';
|
|||
import CapsuleSwitch from '@/components/CapsuleSwitch';
|
||||
import ContinueWatching from '@/components/ContinueWatching';
|
||||
import PageLayout from '@/components/PageLayout';
|
||||
import ScrollableRow from '@/components/ScrollableRow';
|
||||
import { useSite } from '@/components/SiteProvider';
|
||||
import VideoCard from '@/components/VideoCard';
|
||||
|
||||
|
@ -137,7 +137,8 @@ function HomeClient() {
|
|||
setHotVarietyShows(varietyShowsData.list);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取豆瓣数据失败:', error);
|
||||
// 静默处理错误,避免控制台警告
|
||||
// console.error('获取豆瓣数据失败:', error);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
|
@ -147,7 +148,7 @@ function HomeClient() {
|
|||
}, []);
|
||||
|
||||
// 处理收藏数据更新的函数
|
||||
const updateFavoriteItems = async (allFavorites: Record<string, any>) => {
|
||||
const updateFavoriteItems = async (allFavorites: Record<string, Favorite>) => {
|
||||
const allPlayRecords = await getAllPlayRecords();
|
||||
|
||||
// 根据保存时间排序(从近到远)
|
||||
|
@ -191,7 +192,7 @@ function HomeClient() {
|
|||
// 监听收藏更新事件
|
||||
const unsubscribe = subscribeToDataUpdates(
|
||||
'favoritesUpdated',
|
||||
(newFavorites: Record<string, any>) => {
|
||||
(newFavorites: Record<string, Favorite>) => {
|
||||
updateFavoriteItems(newFavorites);
|
||||
}
|
||||
);
|
||||
|
@ -290,13 +291,13 @@ function HomeClient() {
|
|||
<ChevronRight className='w-4 h-4 ml-1' />
|
||||
</Link>
|
||||
</div>
|
||||
<ScrollableRow>
|
||||
<div className='grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 gap-4'>
|
||||
{loading
|
||||
? // 加载状态显示灰色占位数据
|
||||
Array.from({ length: 8 }).map((_, index) => (
|
||||
? // 加载状态显示灰色占位数据 (显示10个,2行x5列)
|
||||
Array.from({ length: 10 }).map((_, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className='min-w-[96px] w-24 sm:min-w-[180px] sm:w-44'
|
||||
className='w-full'
|
||||
>
|
||||
<div className='relative aspect-[2/3] w-full overflow-hidden rounded-lg bg-purple-200 animate-pulse dark:bg-purple-800'>
|
||||
<div className='absolute inset-0 bg-purple-300 dark:bg-purple-700'></div>
|
||||
|
@ -304,11 +305,11 @@ function HomeClient() {
|
|||
<div className='mt-2 h-4 bg-purple-200 rounded animate-pulse dark:bg-purple-800'></div>
|
||||
</div>
|
||||
))
|
||||
: // 显示真实数据
|
||||
hotMovies.map((movie, index) => (
|
||||
: // 显示真实数据,只显示前10个实现2行布局
|
||||
hotMovies.slice(0, 10).map((movie, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className='min-w-[96px] w-24 sm:min-w-[180px] sm:w-44'
|
||||
className='w-full'
|
||||
>
|
||||
<VideoCard
|
||||
from='douban'
|
||||
|
@ -321,7 +322,7 @@ function HomeClient() {
|
|||
/>
|
||||
</div>
|
||||
))}
|
||||
</ScrollableRow>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* 热门剧集 */}
|
||||
|
@ -338,13 +339,13 @@ function HomeClient() {
|
|||
<ChevronRight className='w-4 h-4 ml-1' />
|
||||
</Link>
|
||||
</div>
|
||||
<ScrollableRow>
|
||||
<div className='grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 gap-4'>
|
||||
{loading
|
||||
? // 加载状态显示灰色占位数据
|
||||
Array.from({ length: 8 }).map((_, index) => (
|
||||
? // 加载状态显示灰色占位数据 (显示10个,2行x5列)
|
||||
Array.from({ length: 10 }).map((_, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className='min-w-[96px] w-24 sm:min-w-[180px] sm:w-44'
|
||||
className='w-full'
|
||||
>
|
||||
<div className='relative aspect-[2/3] w-full overflow-hidden rounded-lg bg-purple-200 animate-pulse dark:bg-purple-800'>
|
||||
<div className='absolute inset-0 bg-purple-300 dark:bg-purple-700'></div>
|
||||
|
@ -352,11 +353,11 @@ function HomeClient() {
|
|||
<div className='mt-2 h-4 bg-purple-200 rounded animate-pulse dark:bg-purple-800'></div>
|
||||
</div>
|
||||
))
|
||||
: // 显示真实数据
|
||||
hotTvShows.map((show, index) => (
|
||||
: // 显示真实数据,只显示前10个实现2行布局
|
||||
hotTvShows.slice(0, 10).map((show, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className='min-w-[96px] w-24 sm:min-w-[180px] sm:w-44'
|
||||
className='w-full'
|
||||
>
|
||||
<VideoCard
|
||||
from='douban'
|
||||
|
@ -368,7 +369,7 @@ function HomeClient() {
|
|||
/>
|
||||
</div>
|
||||
))}
|
||||
</ScrollableRow>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* 热门综艺 */}
|
||||
|
@ -385,13 +386,13 @@ function HomeClient() {
|
|||
<ChevronRight className='w-4 h-4 ml-1' />
|
||||
</Link>
|
||||
</div>
|
||||
<ScrollableRow>
|
||||
<div className='grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 gap-4'>
|
||||
{loading
|
||||
? // 加载状态显示灰色占位数据
|
||||
Array.from({ length: 8 }).map((_, index) => (
|
||||
? // 加载状态显示灰色占位数据 (显示10个,2行x5列)
|
||||
Array.from({ length: 10 }).map((_, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className='min-w-[96px] w-24 sm:min-w-[180px] sm:w-44'
|
||||
className='w-full'
|
||||
>
|
||||
<div className='relative aspect-[2/3] w-full overflow-hidden rounded-lg bg-purple-200 animate-pulse dark:bg-purple-800'>
|
||||
<div className='absolute inset-0 bg-purple-300 dark:bg-purple-700'></div>
|
||||
|
@ -399,11 +400,11 @@ function HomeClient() {
|
|||
<div className='mt-2 h-4 bg-purple-200 rounded animate-pulse dark:bg-purple-800'></div>
|
||||
</div>
|
||||
))
|
||||
: // 显示真实数据
|
||||
hotVarietyShows.map((show, index) => (
|
||||
: // 显示真实数据,只显示前10个实现2行布局
|
||||
hotVarietyShows.slice(0, 10).map((show, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className='min-w-[96px] w-24 sm:min-w-[180px] sm:w-44'
|
||||
className='w-full'
|
||||
>
|
||||
<VideoCard
|
||||
from='douban'
|
||||
|
@ -415,7 +416,7 @@ function HomeClient() {
|
|||
/>
|
||||
</div>
|
||||
))}
|
||||
</ScrollableRow>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* 首页底部 Logo */}
|
||||
|
|
|
@ -183,20 +183,24 @@ const PageLayout = ({ children, activePath = '/' }: PageLayoutProps) => {
|
|||
</div>
|
||||
)}
|
||||
|
||||
{/* 主内容容器 - 修改布局实现完全居中:左右各留白1/6,主内容区占2/3 */}
|
||||
{/* 主内容容器 - 为播放页面使用特殊布局(83.33%宽度),其他页面使用默认布局(66.67%宽度) */}
|
||||
<main className='flex-1 md:min-h-0 mb-14 md:mb-0 md:p-6 lg:p-8'>
|
||||
{/* 使用flex布局实现三等分 */}
|
||||
{/* 使用flex布局实现宽度控制 */}
|
||||
<div className='flex w-full min-h-screen md:min-h-[calc(100vh-10rem)]'>
|
||||
{/* 左侧留白区域 - 占1/6 */}
|
||||
{/* 左侧留白区域 - 播放页面占8.33%,其他页面占16.67% */}
|
||||
<div
|
||||
className='hidden md:block flex-shrink-0'
|
||||
style={{ width: '16.67%' }}
|
||||
style={{
|
||||
width: ['/play'].includes(activePath) ? '8.33%' : '16.67%'
|
||||
}}
|
||||
></div>
|
||||
|
||||
{/* 主内容区 - 占2/3 */}
|
||||
{/* 主内容区 - 播放页面占83.33%,其他页面占66.67% */}
|
||||
<div
|
||||
className='flex-1 md:flex-none rounded-container w-full'
|
||||
style={{ width: '66.67%' }}
|
||||
style={{
|
||||
width: ['/play'].includes(activePath) ? '83.33%' : '66.67%'
|
||||
}}
|
||||
>
|
||||
<div
|
||||
className='p-4 md:p-8 lg:p-10'
|
||||
|
@ -208,10 +212,12 @@ const PageLayout = ({ children, activePath = '/' }: PageLayoutProps) => {
|
|||
</div>
|
||||
</div>
|
||||
|
||||
{/* 右侧留白区域 - 占1/6 */}
|
||||
{/* 右侧留白区域 - 播放页面占8.33%,其他页面占16.67% */}
|
||||
<div
|
||||
className='hidden md:block flex-shrink-0'
|
||||
style={{ width: '16.67%' }}
|
||||
style={{
|
||||
width: ['/play'].includes(activePath) ? '8.33%' : '16.67%'
|
||||
}}
|
||||
></div>
|
||||
</div>
|
||||
</main>
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
/* eslint-disable @typescript-eslint/no-explicit-any,react-hooks/exhaustive-deps */
|
||||
|
||||
'use client';
|
||||
|
||||
import { Moon, Sun } from 'lucide-react';
|
||||
|
@ -25,7 +23,7 @@ export function ThemeToggle() {
|
|||
useEffect(() => {
|
||||
setMounted(true);
|
||||
setThemeColor(resolvedTheme);
|
||||
}, []);
|
||||
}, [resolvedTheme]);
|
||||
|
||||
if (!mounted) {
|
||||
// 渲染一个占位符以避免布局偏移
|
||||
|
@ -36,12 +34,18 @@ export function ThemeToggle() {
|
|||
// 检查浏览器是否支持 View Transitions API
|
||||
const targetTheme = resolvedTheme === 'dark' ? 'light' : 'dark';
|
||||
setThemeColor(targetTheme);
|
||||
if (!(document as any).startViewTransition) {
|
||||
|
||||
// 使用更好的类型定义
|
||||
const documentWithTransition = document as Document & {
|
||||
startViewTransition?: (callback: () => void) => void;
|
||||
};
|
||||
|
||||
if (!documentWithTransition.startViewTransition) {
|
||||
setTheme(targetTheme);
|
||||
return;
|
||||
}
|
||||
|
||||
(document as any).startViewTransition(() => {
|
||||
documentWithTransition.startViewTransition(() => {
|
||||
setTheme(targetTheme);
|
||||
});
|
||||
};
|
||||
|
|
|
@ -1,11 +1,10 @@
|
|||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
|
||||
import { CheckCircle, Heart, Link, PlayCircleIcon } from 'lucide-react';
|
||||
import Image from 'next/image';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
|
||||
import {
|
||||
type Favorite,
|
||||
deleteFavorite,
|
||||
deletePlayRecord,
|
||||
generateStorageKey,
|
||||
|
@ -131,7 +130,7 @@ export default function VideoCard({
|
|||
const storageKey = generateStorageKey(actualSource, actualId);
|
||||
const unsubscribe = subscribeToDataUpdates(
|
||||
'favoritesUpdated',
|
||||
(newFavorites: Record<string, any>) => {
|
||||
(newFavorites: Record<string, Favorite>) => {
|
||||
// 检查当前项目是否在新的收藏列表中
|
||||
const isNowFavorited = !!newFavorites[storageKey];
|
||||
setFavorited(isNowFavorited);
|
||||
|
|
|
@ -65,7 +65,7 @@ const LEGACY_SEARCH_HISTORY_KEY = 'moontv_search_history';
|
|||
|
||||
// 缓存相关常量
|
||||
const CACHE_PREFIX = 'katelyatv_cache_';
|
||||
const LEGACY_CACHE_PREFIX = 'moontv_cache_';
|
||||
const _LEGACY_CACHE_PREFIX = 'moontv_cache_'; // 保留用于将来的迁移功能
|
||||
const CACHE_VERSION = '1.0.0';
|
||||
const CACHE_EXPIRE_TIME = 60 * 60 * 1000; // 一小时缓存过期
|
||||
|
||||
|
|
|
@ -95,3 +95,18 @@ export interface DoubanResult {
|
|||
message: string;
|
||||
list: DoubanItem[];
|
||||
}
|
||||
|
||||
// Runtime配置类型
|
||||
export interface RuntimeConfig {
|
||||
STORAGE_TYPE?: string;
|
||||
ENABLE_REGISTER?: boolean;
|
||||
IMAGE_PROXY?: string;
|
||||
DOUBAN_PROXY?: string;
|
||||
}
|
||||
|
||||
// 全局Window类型扩展
|
||||
declare global {
|
||||
interface Window {
|
||||
RUNTIME_CONFIG?: RuntimeConfig;
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue