├── .npmrc ├── public ├── dark.png ├── icp.png ├── og.png ├── gongan.png ├── light.png ├── logo.svg ├── douban-movic.svg ├── weread.svg ├── baidutieba.svg ├── zhihu.svg ├── douyin.svg ├── kuaishou.svg ├── toutiao.svg ├── weibo.svg ├── netease-music.svg ├── bilibili.svg ├── lol.svg ├── history-today.svg ├── baidu.svg ├── netease.svg ├── juejin.svg ├── thepaper.svg └── qq.svg ├── src ├── app │ ├── favicon.ico │ ├── favicon-16x16.png │ ├── apple-touch-icon.png │ ├── sitemap.ts │ ├── robots.ts │ ├── hero.ts │ ├── Providers.tsx │ ├── not-found.tsx │ ├── globals.css │ ├── api │ │ ├── netease │ │ │ └── route.ts │ │ ├── history-today │ │ │ └── route.ts │ │ ├── baidutieba │ │ │ └── route.ts │ │ ├── baidu │ │ │ └── route.ts │ │ ├── qq │ │ │ └── route.ts │ │ ├── thepaper │ │ │ └── route.ts │ │ ├── juejin │ │ │ └── route.ts │ │ ├── zhihu │ │ │ └── route.ts │ │ ├── douyin │ │ │ └── route.ts │ │ ├── toutiao │ │ │ └── route.ts │ │ ├── weread │ │ │ └── route.ts │ │ ├── lol │ │ │ └── route.ts │ │ ├── netease-music │ │ │ └── route.ts │ │ ├── bilibili │ │ │ └── route.ts │ │ ├── kuaishou │ │ │ └── route.ts │ │ ├── weibo │ │ │ └── route.ts │ │ └── douban-movic │ │ │ └── route.ts │ ├── page.tsx │ └── layout.tsx ├── hooks │ └── useIsMobile.ts ├── components │ ├── FullLoading │ │ └── index.tsx │ ├── Analytics │ │ └── index.tsx │ ├── BackTop │ │ └── index.tsx │ ├── OverflowDetector │ │ └── index.tsx │ ├── HotSettings │ │ └── index.tsx │ ├── ThemeSwitcher │ │ └── index.tsx │ ├── Header │ │ └── index.tsx │ ├── Footer │ │ └── index.tsx │ └── HotCard │ │ └── index.tsx └── lib │ ├── type.ts │ ├── constant.ts │ └── utils.ts ├── postcss.config.mjs ├── next.config.ts ├── CHANGELOG.md ├── .gitignore ├── tsconfig.json ├── eslint.config.mjs ├── package.json ├── .release-it.json └── README.md /.npmrc: -------------------------------------------------------------------------------- 1 | public-hoist-pattern[]=*@heroui/* -------------------------------------------------------------------------------- /public/dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/baiwumm/next-daily-hot/HEAD/public/dark.png -------------------------------------------------------------------------------- /public/icp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/baiwumm/next-daily-hot/HEAD/public/icp.png -------------------------------------------------------------------------------- /public/og.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/baiwumm/next-daily-hot/HEAD/public/og.png -------------------------------------------------------------------------------- /public/gongan.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/baiwumm/next-daily-hot/HEAD/public/gongan.png -------------------------------------------------------------------------------- /public/light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/baiwumm/next-daily-hot/HEAD/public/light.png -------------------------------------------------------------------------------- /src/app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/baiwumm/next-daily-hot/HEAD/src/app/favicon.ico -------------------------------------------------------------------------------- /src/app/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/baiwumm/next-daily-hot/HEAD/src/app/favicon-16x16.png -------------------------------------------------------------------------------- /src/app/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/baiwumm/next-daily-hot/HEAD/src/app/apple-touch-icon.png -------------------------------------------------------------------------------- /postcss.config.mjs: -------------------------------------------------------------------------------- 1 | const config = { 2 | plugins: { 3 | "@tailwindcss/postcss": {}, 4 | }, 5 | }; 6 | 7 | export default config; 8 | -------------------------------------------------------------------------------- /next.config.ts: -------------------------------------------------------------------------------- 1 | import type { NextConfig } from "next"; 2 | 3 | const nextConfig: NextConfig = { 4 | typescript: { 5 | ignoreBuildErrors: true, 6 | }, 7 | eslint: { 8 | ignoreDuringBuilds: true, 9 | }, 10 | }; 11 | 12 | export default nextConfig; 13 | -------------------------------------------------------------------------------- /src/app/sitemap.ts: -------------------------------------------------------------------------------- 1 | import type { MetadataRoute } from 'next'; 2 | 3 | export default function sitemap(): MetadataRoute.Sitemap { 4 | return [ 5 | { 6 | url: process.env.SITE_URL || '', 7 | lastModified: new Date(), 8 | changeFrequency: 'daily', 9 | priority: 1, 10 | }, 11 | ]; 12 | } 13 | -------------------------------------------------------------------------------- /src/app/robots.ts: -------------------------------------------------------------------------------- 1 | import type { MetadataRoute } from 'next'; 2 | 3 | export default function robots(): MetadataRoute.Robots { 4 | return { 5 | rules: { 6 | userAgent: '*', 7 | allow: '/', 8 | disallow: '/private/', 9 | }, 10 | sitemap: `${process.env.SITE_URL}sitemap.xml`, 11 | }; 12 | } 13 | -------------------------------------------------------------------------------- /src/hooks/useIsMobile.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: 白雾茫茫丶 3 | * @Date: 2025-11-20 14:40:51 4 | * @LastEditors: 白雾茫茫丶 5 | * @LastEditTime: 2025-11-20 14:45:18 6 | * @Description: 判断是否移动端 7 | */ 8 | import { useResponsive } from 'ahooks'; 9 | 10 | export const useIsMobile = () => { 11 | const responsive = useResponsive(); 12 | return responsive.xs; 13 | }; -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## [2.1.0](///compare/2.0.0...2.1.0) (2025-11-21) 4 | 5 | ### Features 6 | 7 | * 安装配置 Hero UI a56d6ed 8 | * 完成顶部和底部布局和相应交互逻辑 4ac9d70 9 | * 完成热榜卡片内容的开发 fe8b370 10 | * 完成主体内容、热榜卡片布局 308c32c 11 | * 细节优化 087b0e0 12 | * 新增 BackTop 回到顶部组件 74d7c77 13 | * 新增 NotFound 页面 de91aa6 14 | * 新增release-it @release-it/conventional-changelog 包 72d92bd 15 | * **metadata:** 完善网站 Meta 信息 a498986 16 | * update README.md a5d5168 17 | * update README.md 86dca7c 18 | 19 | ## [2.0.0](///compare/1.6.6...2.0.0) (2025-11-19) 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.* 7 | .yarn/* 8 | !.yarn/patches 9 | !.yarn/plugins 10 | !.yarn/releases 11 | !.yarn/versions 12 | 13 | # testing 14 | /coverage 15 | 16 | # next.js 17 | /.next/ 18 | /out/ 19 | 20 | # production 21 | /build 22 | 23 | # misc 24 | .DS_Store 25 | *.pem 26 | 27 | # debug 28 | npm-debug.log* 29 | yarn-debug.log* 30 | yarn-error.log* 31 | .pnpm-debug.log* 32 | 33 | # env files (can opt-in for committing if needed) 34 | .env* 35 | 36 | # vercel 37 | .vercel 38 | 39 | # typescript 40 | *.tsbuildinfo 41 | next-env.d.ts 42 | -------------------------------------------------------------------------------- /src/app/hero.ts: -------------------------------------------------------------------------------- 1 | import { heroui } from "@heroui/react"; 2 | export default heroui({ 3 | layout: { 4 | radius: { 5 | small: "6px", // rounded-small 6 | medium: "8px", // rounded-medium 7 | large: "10px", // rounded-large 8 | }, 9 | borderWidth: { 10 | small: "0.5px", // border-small 11 | medium: "1px", // border-medium (default) 12 | large: "2px", // border-large 13 | } 14 | }, 15 | themes: { 16 | light: { 17 | colors: { 18 | primary: { 19 | DEFAULT: '#F82006' 20 | } 21 | } 22 | }, 23 | dark: { 24 | colors: { 25 | primary: { 26 | DEFAULT: '#F82006' 27 | } 28 | } 29 | } 30 | } 31 | }); -------------------------------------------------------------------------------- /public/logo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/app/Providers.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | import 'dayjs/locale/zh-cn'; 3 | 4 | import { HeroUIProvider, ToastProvider } from '@heroui/react'; 5 | import dayjs from 'dayjs'; 6 | import relativeTime from 'dayjs/plugin/relativeTime'; 7 | import timezone from 'dayjs/plugin/timezone'; 8 | import utc from 'dayjs/plugin/utc'; 9 | import { type ReactNode } from 'react'; 10 | 11 | dayjs.extend(utc); 12 | dayjs.extend(timezone); 13 | // 引入处理相对时间的插件 14 | dayjs.extend(relativeTime); 15 | dayjs.locale('zh-cn'); 16 | 17 | type ProvidersProps = { 18 | children: ReactNode; 19 | } 20 | 21 | export function Providers({ children }: ProvidersProps) { 22 | return ( 23 | 24 | 25 | {children} 26 | 27 | ) 28 | } -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2017", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "strict": true, 8 | "noEmit": true, 9 | "esModuleInterop": true, 10 | "module": "esnext", 11 | "moduleResolution": "bundler", 12 | "resolveJsonModule": true, 13 | "isolatedModules": true, 14 | "jsx": "react-jsx", 15 | "incremental": true, 16 | "plugins": [ 17 | { 18 | "name": "next" 19 | } 20 | ], 21 | "paths": { 22 | "@/*": ["./src/*"] 23 | } 24 | }, 25 | "include": [ 26 | "next-env.d.ts", 27 | "**/*.ts", 28 | "**/*.tsx", 29 | ".next/types/**/*.ts", 30 | ".next/dev/types/**/*.ts", 31 | "**/*.mts" 32 | ], 33 | "exclude": ["node_modules"] 34 | } 35 | -------------------------------------------------------------------------------- /eslint.config.mjs: -------------------------------------------------------------------------------- 1 | import { defineConfig, globalIgnores } from "eslint/config"; 2 | import nextVitals from "eslint-config-next/core-web-vitals"; 3 | import nextTs from "eslint-config-next/typescript"; 4 | import simpleImportSort from 'eslint-plugin-simple-import-sort'; 5 | 6 | const eslintConfig = defineConfig([ 7 | ...nextVitals, 8 | ...nextTs, 9 | // Override default ignores of eslint-config-next. 10 | globalIgnores([ 11 | // Default ignores of eslint-config-next: 12 | ".next/**", 13 | "out/**", 14 | "build/**", 15 | "next-env.d.ts", 16 | ]), 17 | { 18 | files: ['**/*.{js,mjs,cjs,ts,jsx,tsx}'], 19 | plugins: { 20 | 'simple-import-sort': simpleImportSort, 21 | }, 22 | rules: { 23 | 'simple-import-sort/imports': 'error', // import排序 npm包需在引入最顶部排序规则 24 | 'simple-import-sort/exports': 'error', // export排序规则 25 | }, 26 | }, 27 | { ignores: ['**/node_modules'] }, 28 | ]); 29 | 30 | export default eslintConfig; 31 | -------------------------------------------------------------------------------- /src/components/FullLoading/index.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: 白雾茫茫丶 3 | * @Date: 2025-11-20 09:05:12 4 | * @LastEditors: 白雾茫茫丶 5 | * @LastEditTime: 2025-11-20 09:06:45 6 | * @Description: 全屏加载 7 | */ 8 | 'use client'; 9 | import { Spinner } from "@heroui/react"; 10 | import { useEffect, useState } from 'react'; 11 | 12 | const FullLoading = () => { 13 | const [mounted, setMounted] = useState(false); 14 | 15 | useEffect(() => { 16 | const timer = setTimeout(() => setMounted(true), 0); 17 | return () => clearTimeout(timer); 18 | }, []); 19 | 20 | // 判断组件是否挂载 21 | if (!mounted) { 22 | return ( 23 |
24 | 25 |
26 | ); 27 | } 28 | return null; 29 | }; 30 | export default FullLoading; -------------------------------------------------------------------------------- /src/app/not-found.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: 白雾茫茫丶 3 | * @Date: 2025-11-20 14:00:11 4 | * @LastEditors: 白雾茫茫丶 5 | * @LastEditTime: 2025-11-20 14:08:48 6 | * @Description: 404 页面 7 | */ 8 | 'use client' 9 | import { Button } from "@heroui/react"; 10 | import Link from 'next/link'; 11 | import { type FC } from 'react'; 12 | 13 | const NotFound: FC = () => { 14 | return ( 15 |
16 |
17 |

404

18 |

看来这个页面去环球旅行了,还没寄明信片回来。

19 |
20 | 21 |
22 |
23 |
24 | ) 25 | } 26 | export default NotFound; -------------------------------------------------------------------------------- /public/douban-movic.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/app/globals.css: -------------------------------------------------------------------------------- 1 | @import "tailwindcss"; 2 | @plugin './hero.ts'; 3 | /* Note: You may need to change the path to fit your project structure */ 4 | @source '../../node_modules/@heroui/theme/dist/**/*.{js,ts,jsx,tsx}'; 5 | @custom-variant dark (&:is(.dark *)); 6 | 7 | :root { 8 | font-family: 'Maple Mono CN', system-ui, Avenir, Helvetica, Arial, sans-serif; 9 | } 10 | 11 | /* 修改滚动条样式 */ 12 | *::-webkit-scrollbar { 13 | width: 4px; 14 | height: 4px; 15 | } 16 | 17 | *::-webkit-scrollbar-thumb { 18 | background-color: hsl(var(--heroui-default)/.65); 19 | border-radius: 2.5px; 20 | transition: .35s background-color; 21 | } 22 | 23 | *::-webkit-scrollbar-thumb:hover { 24 | background-color: hsl(var(--heroui-default)/.45); 25 | } 26 | 27 | *::-webkit-scrollbar-track { 28 | background-color: transparent; 29 | } 30 | 31 | /* 暗黑模式过渡动画 */ 32 | ::view-transition-old(root), 33 | ::view-transition-new(root) { 34 | animation: none; 35 | mix-blend-mode: normal; 36 | } 37 | 38 | ::view-transition-old(root), 39 | .dark::view-transition-new(root) { 40 | z-index: 1; 41 | } 42 | 43 | ::view-transition-new(root), 44 | .dark::view-transition-old(root) { 45 | z-index: 9999; 46 | } 47 | -------------------------------------------------------------------------------- /public/weread.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/lib/type.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @description: 榜单类型 3 | */ 4 | export type HotTypes = 5 | | 'weibo' 6 | | 'bilibili' 7 | | 'douyin' 8 | | 'toutiao' 9 | | 'zhihu' 10 | | 'baidu' 11 | | 'baidutieba' 12 | | 'qq' 13 | | 'juejin' 14 | | 'netease' 15 | | 'lol' 16 | | 'thepaper' 17 | | 'kuaishou' 18 | | 'history-today' 19 | | 'weread' 20 | | 'douban-movic' 21 | | 'netease-music'; 22 | 23 | /** 24 | * @description: 热榜子项 25 | */ 26 | export type HotListItem = { 27 | id: string; // 唯一 key 28 | title: string; // 标题 29 | desc?: string; // 描述 30 | pic?: string; // 封面图 31 | hot: number | string; // 热度 32 | tip?: string; // 如果不显示热度,显示其他信息 33 | url: string; // 地址 34 | mobileUrl: string; // 移动端地址 35 | label?: string; // 标签(微博) 36 | }; 37 | 38 | /** 39 | * @description: 接口返回数据格式 40 | */ 41 | export type IResponse = { 42 | code: number; 43 | msg: string; 44 | data?: HotListItem[]; 45 | timestamp: number; 46 | }; 47 | 48 | /** 49 | * @description: 更新时间 50 | */ 51 | export type UpdateTime = Partial>; 52 | 53 | /** 54 | * @description: 榜单配置 55 | */ 56 | export type HotListConfig = { 57 | value: HotTypes; 58 | label: string; 59 | tip: string; 60 | prefix?: string; // 前缀 61 | suffix?: string; // 后缀 62 | }; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "next-daily-hot", 3 | "version": "2.1.0", 4 | "private": true, 5 | "author": { 6 | "name": "baiwumm", 7 | "email": "me@baiwumm.com", 8 | "url": "https://baiwumm.com" 9 | }, 10 | "scripts": { 11 | "dev": "next dev -p 5173", 12 | "build": "next build", 13 | "start": "next start", 14 | "lint": "eslint", 15 | "release": "release-it" 16 | }, 17 | "dependencies": { 18 | "@heroui/react": "^2.8.5", 19 | "@next/third-parties": "^16.0.3", 20 | "@remixicon/react": "^4.7.0", 21 | "@vercel/analytics": "^1.5.0", 22 | "ahooks": "^3.9.6", 23 | "cheerio": "^1.1.2", 24 | "crypto-js": "^4.2.0", 25 | "dayjs": "^1.11.19", 26 | "framer-motion": "^12.23.24", 27 | "lunar-typescript": "^1.8.6", 28 | "next": "16.0.3", 29 | "next-themes": "^0.4.6", 30 | "react": "19.2.0", 31 | "react-dom": "19.2.0" 32 | }, 33 | "devDependencies": { 34 | "@release-it/conventional-changelog": "^10.0.2", 35 | "@tailwindcss/postcss": "^4", 36 | "@types/crypto-js": "^4.2.2", 37 | "@types/node": "^20", 38 | "@types/react": "^19", 39 | "@types/react-dom": "^19", 40 | "eslint": "^9", 41 | "eslint-config-next": "16.0.3", 42 | "release-it": "^19.0.6", 43 | "tailwindcss": "^4", 44 | "typescript": "^5" 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /public/baidutieba.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.release-it.json: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": { 3 | "@release-it/conventional-changelog": { 4 | "preset": { 5 | "name": "conventionalcommits", 6 | "types": [ 7 | { "type": "feat", "section": "✨ Features | 新功能" }, 8 | { "type": "fix", "section": "🐛 Bug Fixes | Bug 修复" }, 9 | { "type": "chore", "section": "🎫 Chores | 其他更新" }, 10 | { "type": "docs", "section": "📝 Documentation | 文档" }, 11 | { "type": "style", "section": "💄 Styles | 风格" }, 12 | { "type": "refactor", "section": "♻ Code Refactoring | 代码重构" }, 13 | { "type": "perf", "section": "⚡ Performance Improvements | 性能优化" }, 14 | { "type": "test", "section": "✅ Tests | 测试" }, 15 | { "type": "revert", "section": "⏪ Reverts | 回退" }, 16 | { "type": "build", "section": "👷‍ Build System | 构建" }, 17 | { "type": "ci", "section": "🔧 Continuous Integration | CI 配置" }, 18 | { "type": "config", "section": "🔨 CONFIG | 配置" } 19 | ] 20 | }, 21 | "infile": "CHANGELOG.md", 22 | "ignoreRecommendedBump": true, 23 | "strictSemVer": true 24 | } 25 | }, 26 | "git": { 27 | "commitMessage": "chore: Release v${version}" 28 | }, 29 | "github": { 30 | "release": true, 31 | "draft": false 32 | } 33 | } -------------------------------------------------------------------------------- /src/app/api/netease/route.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: 白雾茫茫丶 3 | * @Date: 2024-05-14 09:50:55 4 | * @LastEditors: 白雾茫茫丶 5 | * @LastEditTime: 2025-11-20 15:09:53 6 | * @Description: 网易新闻-热榜 7 | */ 8 | import { NextResponse } from 'next/server'; 9 | 10 | import { REQUEST_STATUS_TEXT } from '@/lib/constant'; 11 | import type { HotListItem } from '@/lib/type'; 12 | import { responseError, responseSuccess } from '@/lib/utils'; 13 | 14 | export async function GET() { 15 | // 官方 url 16 | const url = 'https://m.163.com/fe/api/hot/news/flow'; 17 | try { 18 | // 请求数据 19 | const response = await fetch(url); 20 | if (!response.ok) { 21 | // 如果请求失败,抛出错误,不进行缓存 22 | throw new Error(`${REQUEST_STATUS_TEXT.ERROR}:网易新闻-热榜`); 23 | } 24 | // 得到请求体 25 | const responseBody = await response.json(); 26 | // 处理数据 27 | if (responseBody.msg === 'success') { 28 | const result: HotListItem[] = responseBody.data.list.map((v) => { 29 | return { 30 | id: v.skipID, 31 | title: v.title, 32 | desc: v._keyword, 33 | pic: v.imgsrc, 34 | url: `https://www.163.com/dy/article/${v.skipID}.html`, 35 | mobileUrl: v.url, 36 | }; 37 | }); 38 | return NextResponse.json(responseSuccess(result)); 39 | } 40 | return NextResponse.json(responseSuccess()); 41 | } catch { 42 | return NextResponse.json(responseError); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/app/api/history-today/route.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: 白雾茫茫丶 3 | * @Date: 2024-05-14 10:25:47 4 | * @LastEditors: 白雾茫茫丶 5 | * @LastEditTime: 2025-11-20 15:10:35 6 | * @Description: 百度百科-历史上的今天 7 | */ 8 | import { NextResponse } from 'next/server'; 9 | 10 | import { REQUEST_STATUS_TEXT } from '@/lib/constant'; 11 | import type { HotListItem } from '@/lib/type'; 12 | import { responseError, responseSuccess } from '@/lib/utils'; 13 | 14 | export async function GET() { 15 | // 获取月份 16 | const month = (new Date().getMonth() + 1).toString().padStart(2, '0'); 17 | // 获取天数 18 | const day = new Date().getDate().toString().padStart(2, '0'); 19 | const url = `https://baike.baidu.com/cms/home/eventsOnHistory/${month}.json`; 20 | try { 21 | // 请求数据 22 | const response = await fetch(url); 23 | if (!response.ok) { 24 | // 如果请求失败,抛出错误,不进行缓存 25 | throw new Error(`${REQUEST_STATUS_TEXT.ERROR}:百度百科-历史上的今天`); 26 | } 27 | // 得到请求体 28 | const responseBody = await response.json(); 29 | // 处理数据 30 | const result: HotListItem[] = responseBody[month][month + day].map((v, index: number) => { 31 | return { 32 | id: index, 33 | title: v.title.replace(/<[^>]+>/g, ''), 34 | tip: v.year, 35 | type: v.type, 36 | url: v.link, 37 | mobileUrl: v.link, 38 | }; 39 | }); 40 | return NextResponse.json(responseSuccess(result)); 41 | } catch { 42 | return NextResponse.json(responseError); 43 | } 44 | } -------------------------------------------------------------------------------- /src/app/api/baidutieba/route.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: 白雾茫茫丶 3 | * @Date: 2024-05-14 09:38:02 4 | * @LastEditors: 白雾茫茫丶 5 | * @LastEditTime: 2025-11-20 15:01:25 6 | * @Description: 百度贴吧-热议榜 7 | */ 8 | import { NextResponse } from 'next/server'; 9 | 10 | import { REQUEST_STATUS_TEXT } from '@/lib/constant'; 11 | import type { HotListItem } from '@/lib/type'; 12 | import { responseError, responseSuccess } from '@/lib/utils'; 13 | 14 | export async function GET() { 15 | // 官方 url 16 | const url = 'https://tieba.baidu.com/hottopic/browse/topicList'; 17 | try { 18 | // 请求数据 19 | const response = await fetch(url); 20 | if (!response.ok) { 21 | // 如果请求失败,抛出错误,不进行缓存 22 | throw new Error(`${REQUEST_STATUS_TEXT.ERROR}:百度贴吧-热议榜`); 23 | } 24 | // 得到请求体 25 | const responseBody = await response.json(); 26 | // 处理数据 27 | if (responseBody.errmsg === 'success') { 28 | const result: HotListItem[] = responseBody.data.bang_topic.topic_list.map((v) => { 29 | return { 30 | id: v.topic_id.toString(), 31 | title: v.topic_name, 32 | desc: v.topic_desc, 33 | pic: v.topic_pic, 34 | hot: v.discuss_num, 35 | url: v.topic_url, 36 | mobileUrl: v.topic_url, 37 | }; 38 | }); 39 | return NextResponse.json(responseSuccess(result)); 40 | } 41 | return NextResponse.json(responseSuccess()); 42 | } catch { 43 | return NextResponse.json(responseError); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/app/api/baidu/route.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: 白雾茫茫丶 3 | * @Date: 2024-05-14 09:33:19 4 | * @LastEditors: 白雾茫茫丶 5 | * @LastEditTime: 2025-11-20 15:11:15 6 | * @Description: 百度-热搜榜 7 | */ 8 | import { NextResponse } from 'next/server'; 9 | 10 | import { REQUEST_STATUS_TEXT } from '@/lib/constant'; 11 | import type { HotListItem } from '@/lib/type'; 12 | import { responseError, responseSuccess } from '@/lib/utils'; 13 | 14 | export async function GET() { 15 | // 官方 url 16 | const url = 'https://top.baidu.com/api/board?platform=wise&tab=realtime'; 17 | try { 18 | // 请求数据 19 | const response = await fetch(url); 20 | if (!response.ok) { 21 | // 如果请求失败,抛出错误,不进行缓存 22 | throw new Error(`${REQUEST_STATUS_TEXT.ERROR}:百度-热搜榜`); 23 | } 24 | // 得到请求体 25 | const responseBody = await response.json(); 26 | // 处理数据 27 | if (responseBody.success) { 28 | const result: HotListItem[] = responseBody.data.cards[0].content.map((v, index: number) => { 29 | return { 30 | id: index + v.hotScore, 31 | title: v.word, 32 | desc: v.desc, 33 | pic: v.img, 34 | hot: Number(v.hotScore), 35 | url: `https://www.baidu.com/s?wd=${encodeURIComponent(v.query)}`, 36 | mobileUrl: v.url, 37 | }; 38 | }); 39 | return NextResponse.json(responseSuccess(result)); 40 | } 41 | return NextResponse.json(responseSuccess()); 42 | } catch { 43 | return NextResponse.json(responseError); 44 | } 45 | } -------------------------------------------------------------------------------- /src/app/api/qq/route.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: 白雾茫茫丶 3 | * @Date: 2024-05-14 09:42:24 4 | * @LastEditors: 白雾茫茫丶 5 | * @LastEditTime: 2025-11-20 15:09:30 6 | * @Description: 腾讯新闻-热点榜 7 | */ 8 | import { NextResponse } from 'next/server'; 9 | 10 | import { REQUEST_STATUS_TEXT } from '@/lib/constant'; 11 | import type { HotListItem } from '@/lib/type'; 12 | import { responseError, responseSuccess } from '@/lib/utils'; 13 | 14 | export async function GET() { 15 | // 官方 url 16 | const url = 'https://r.inews.qq.com/gw/event/hot_ranking_list'; 17 | try { 18 | // 请求数据 19 | const response = await fetch(url); 20 | if (!response.ok) { 21 | // 如果请求失败,抛出错误,不进行缓存 22 | throw new Error(`${REQUEST_STATUS_TEXT.ERROR}:腾讯新闻-热点榜`); 23 | } 24 | // 得到请求体 25 | const responseBody = await response.json(); 26 | // 处理数据 27 | if (responseBody.ret === 0) { 28 | const result: HotListItem[] = responseBody.idlist[0].newslist.slice(1).map((v) => { 29 | return { 30 | id: v.id, 31 | title: v.title, 32 | desc: v.abstract, 33 | pic: v.miniProShareImage, 34 | hot: v.readCount, 35 | url: `https://new.qq.com/rain/a/${v.id}`, 36 | mobileUrl: `https://view.inews.qq.com/a/${v.id}`, 37 | }; 38 | }); 39 | return NextResponse.json(responseSuccess(result)); 40 | } 41 | return NextResponse.json(responseSuccess()); 42 | } catch { 43 | return NextResponse.json(responseError); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/app/api/thepaper/route.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: 白雾茫茫丶 3 | * @Date: 2024-05-14 10:12:17 4 | * @LastEditors: 白雾茫茫丶 5 | * @LastEditTime: 2025-11-20 15:09:20 6 | * @Description: 澎湃新闻-热榜 7 | */ 8 | import { NextResponse } from 'next/server'; 9 | 10 | import { REQUEST_STATUS_TEXT } from '@/lib/constant'; 11 | import type { HotListItem } from '@/lib/type'; 12 | import { responseError, responseSuccess } from '@/lib/utils'; 13 | 14 | export async function GET() { 15 | // 官方 url 16 | const url = 'https://cache.thepaper.cn/contentapi/wwwIndex/rightSidebar'; 17 | try { 18 | // 请求数据 19 | const response = await fetch(url); 20 | if (!response.ok) { 21 | // 如果请求失败,抛出错误,不进行缓存 22 | throw new Error(`${REQUEST_STATUS_TEXT.ERROR}:澎湃新闻-热榜`); 23 | } 24 | // 得到请求体 25 | const responseBody = await response.json(); 26 | // 处理数据 27 | if (responseBody.resultCode === 1) { 28 | const result: HotListItem[] = responseBody.data.hotNews.map((v) => { 29 | return { 30 | id: v.contId, 31 | title: v.name, 32 | pic: v.pic, 33 | hot: v.praiseTimes, 34 | url: `https://www.thepaper.cn/newsDetail_forward_${v.contId}`, 35 | mobileUrl: `https://m.thepaper.cn/newsDetail_forward_${v.contId}`, 36 | }; 37 | }); 38 | return NextResponse.json(responseSuccess(result)); 39 | } 40 | return NextResponse.json(responseSuccess()); 41 | } catch { 42 | return NextResponse.json(responseError); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/app/api/juejin/route.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: 白雾茫茫丶 3 | * @Date: 2024-05-14 09:47:41 4 | * @LastEditors: 白雾茫茫丶 5 | * @LastEditTime: 2025-11-20 15:10:21 6 | * @Description: 稀土掘金-热榜 7 | */ 8 | import { NextResponse } from 'next/server'; 9 | 10 | import { REQUEST_STATUS_TEXT } from '@/lib/constant'; 11 | import type { HotListItem } from '@/lib/type'; 12 | import { responseError, responseSuccess } from '@/lib/utils'; 13 | 14 | export async function GET() { 15 | // 官方 url 16 | const url = 'https://api.juejin.cn/content_api/v1/content/article_rank?category_id=1&type=hot'; 17 | try { 18 | // 请求数据 19 | const response = await fetch(url); 20 | if (!response.ok) { 21 | // 如果请求失败,抛出错误,不进行缓存 22 | throw new Error(`${REQUEST_STATUS_TEXT.ERROR}:稀土掘金-热榜`); 23 | } 24 | // 得到请求体 25 | const responseBody = await response.json(); 26 | // 处理数据 27 | if (responseBody.err_msg === 'success') { 28 | const result: HotListItem[] = responseBody.data.map((v) => { 29 | return { 30 | id: v.content.content_id, 31 | title: v.content.title, 32 | hot: v.content_counter.hot_rank, 33 | url: `https://juejin.cn/post/${v.content.content_id}`, 34 | mobileUrl: `https://juejin.cn/post/${v.content.content_id}`, 35 | }; 36 | }); 37 | return NextResponse.json(responseSuccess(result)); 38 | } 39 | return NextResponse.json(responseSuccess()); 40 | } catch { 41 | return NextResponse.json(responseError); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/app/api/zhihu/route.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: 白雾茫茫丶 3 | * @Date: 2024-05-14 09:28:41 4 | * @LastEditors: 白雾茫茫丶 5 | * @LastEditTime: 2025-11-20 15:08:00 6 | * @Description: 知乎-热榜 7 | */ 8 | import { NextResponse } from 'next/server'; 9 | 10 | import { REQUEST_STATUS_TEXT } from '@/lib/constant'; 11 | import type { HotListItem } from '@/lib/type'; 12 | import { responseError, responseSuccess } from '@/lib/utils'; 13 | 14 | export async function GET() { 15 | // 官方 url 16 | const url = 'https://api.zhihu.com/topstory/hot-list'; 17 | try { 18 | // 请求数据 19 | const response = await fetch(url); 20 | if (!response.ok) { 21 | // 如果请求失败,抛出错误,不进行缓存 22 | throw new Error(`${REQUEST_STATUS_TEXT.ERROR}:知乎-热榜`); 23 | } 24 | // 得到请求体 25 | const responseBody = await response.json(); 26 | // 处理数据 27 | if (responseBody.data) { 28 | const result: HotListItem[] = responseBody.data.map((v) => { 29 | return { 30 | id: v.id, 31 | title: v.target.title, 32 | pic: v.children[0].thumbnail, 33 | hot: parseInt(v.detail_text.replace(/[^\d]/g, '')) * 10000, 34 | url: `https://www.zhihu.com/question/${v.card_id.replace('Q_', '')}`, 35 | mobileUrl: `https://www.zhihu.com/question/${v.card_id.replace('Q_', '')}`, 36 | }; 37 | }); 38 | return NextResponse.json(responseSuccess(result)); 39 | } 40 | return NextResponse.json(responseSuccess()); 41 | } catch { 42 | return NextResponse.json(responseError); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/app/api/douyin/route.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: 白雾茫茫丶 3 | * @Date: 2024-05-14 09:14:07 4 | * @LastEditors: 白雾茫茫丶 5 | * @LastEditTime: 2025-11-20 15:10:45 6 | * @Description: 抖音-热点榜 7 | */ 8 | import { NextResponse } from 'next/server'; 9 | 10 | import { REQUEST_STATUS_TEXT } from '@/lib/constant'; 11 | import type { HotListItem } from '@/lib/type'; 12 | import { responseError, responseSuccess } from '@/lib/utils'; 13 | 14 | export async function GET() { 15 | // 官方 url 16 | const url = 'https://aweme.snssdk.com/aweme/v1/hot/search/list/'; 17 | try { 18 | // 请求数据 19 | const response = await fetch(url); 20 | if (!response.ok) { 21 | // 如果请求失败,抛出错误,不进行缓存 22 | throw new Error(`${REQUEST_STATUS_TEXT.ERROR}:抖音-热点榜`); 23 | } 24 | // 得到请求体 25 | const responseBody = await response.json(); 26 | // 处理数据 27 | if (responseBody.status_code === 0) { 28 | const result: HotListItem[] = responseBody.data.word_list.map((v) => { 29 | return { 30 | id: v.group_id, 31 | title: v.word, 32 | pic: `${v.word_cover.url_list[0]}`, 33 | hot: Number(v.hot_value), 34 | url: `https://www.douyin.com/hot/${encodeURIComponent(v.sentence_id)}`, 35 | mobileUrl: `https://www.douyin.com/hot/${encodeURIComponent(v.sentence_id)}`, 36 | }; 37 | }); 38 | return NextResponse.json(responseSuccess(result)); 39 | } 40 | return NextResponse.json(responseSuccess()); 41 | } catch { 42 | return NextResponse.json(responseError); 43 | } 44 | } 45 | 46 | -------------------------------------------------------------------------------- /src/app/api/toutiao/route.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: 白雾茫茫丶 3 | * @Date: 2024-05-14 09:19:59 4 | * @LastEditors: 白雾茫茫丶 5 | * @LastEditTime: 2025-11-20 15:09:07 6 | * @Description: 今日头条-热榜 7 | */ 8 | import { NextResponse } from 'next/server'; 9 | 10 | import { REQUEST_STATUS_TEXT } from '@/lib/constant'; 11 | import type { HotListItem } from '@/lib/type'; 12 | import { responseError, responseSuccess } from '@/lib/utils'; 13 | 14 | export async function GET() { 15 | // 官方 url 16 | const url = 'https://www.toutiao.com/hot-event/hot-board/?origin=toutiao_pc'; 17 | try { 18 | // 请求数据 19 | const response = await fetch(url); 20 | if (!response.ok) { 21 | // 如果请求失败,抛出错误,不进行缓存 22 | throw new Error(`${REQUEST_STATUS_TEXT.ERROR}:今日头条-热榜`); 23 | } 24 | // 得到请求体 25 | const responseBody = await response.json(); 26 | // 处理数据 27 | if (responseBody.status === 'success') { 28 | const result: HotListItem[] = responseBody.data.map((v) => { 29 | return { 30 | id: v.ClusterId, 31 | title: v.Title, 32 | pic: v.Image.url, 33 | hot: v.HotValue, 34 | url: `https://www.toutiao.com/trending/${v.ClusterIdStr}/`, 35 | mobileUrl: `https://api.toutiaoapi.com/feoffline/amos_land/new/html/main/index.html?topic_id=${v.ClusterIdStr}`, 36 | }; 37 | }); 38 | return NextResponse.json(responseSuccess(result)); 39 | } 40 | return NextResponse.json(responseSuccess()); 41 | } catch { 42 | return NextResponse.json(responseError); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /public/zhihu.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/app/api/weread/route.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: 白雾茫茫丶 3 | * @Date: 2024-05-14 11:27:32 4 | * @LastEditors: 白雾茫茫丶 5 | * @LastEditTime: 2024-05-14 16:57:16 6 | * @Description: 微信读书-飙升榜 7 | */ 8 | import { NextResponse } from 'next/server'; 9 | 10 | import { REQUEST_STATUS_TEXT } from '@/lib/constant'; 11 | import type { HotListItem } from '@/lib/type'; 12 | import { getWereadID, responseError, responseSuccess } from '@/lib/utils'; 13 | 14 | export async function GET() { 15 | // 官方 url 16 | const url = 'https://weread.qq.com/web/bookListInCategory/rising?rank=1'; 17 | try { 18 | // 请求数据 19 | const response = await fetch(url); 20 | if (!response.ok) { 21 | // 如果请求失败,抛出错误,不进行缓存 22 | throw new Error(`${REQUEST_STATUS_TEXT.ERROR}:微信读书-飙升榜`); 23 | } 24 | // 得到请求体 25 | const responseBody = await response.json(); 26 | // 处理数据 27 | if (responseBody.books) { 28 | const result: HotListItem[] = responseBody.books.map((v) => { 29 | const info = v.bookInfo; 30 | return { 31 | id: info.bookId, 32 | title: info.title, 33 | hot: v.readingCount, 34 | pic: info.cover.replace('s_', 't9_'), 35 | url: `https://weread.qq.com/web/bookDetail/${getWereadID(info.bookId)}`, 36 | mobileUrl: `https://weread.qq.com/web/bookDetail/${getWereadID(info.bookId)}`, 37 | }; 38 | }); 39 | return NextResponse.json(responseSuccess(result)); 40 | } 41 | return NextResponse.json(responseSuccess()); 42 | } catch { 43 | return NextResponse.json(responseError); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/app/api/lol/route.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: 白雾茫茫丶 3 | * @Date: 2024-05-14 09:54:58 4 | * @LastEditors: 白雾茫茫丶 5 | * @LastEditTime: 2025-11-20 15:05:57 6 | * @Description: 英雄联盟-更新公告 7 | */ 8 | import { NextResponse } from 'next/server'; 9 | 10 | import { REQUEST_STATUS_TEXT } from '@/lib/constant'; 11 | import type { HotListItem } from '@/lib/type'; 12 | import { responseError, responseSuccess } from '@/lib/utils'; 13 | 14 | export async function GET() { 15 | // 官方 url 16 | const url = 'https://apps.game.qq.com/cmc/zmMcnTargetContentList?page=1&num=50&target=24&source=web_pc'; 17 | try { 18 | // 请求数据 19 | const response = await fetch(url); 20 | if (!response.ok) { 21 | // 如果请求失败,抛出错误,不进行缓存 22 | throw new Error(`${REQUEST_STATUS_TEXT.ERROR}:英雄联盟-更新公告`); 23 | } 24 | // 得到请求体 25 | const responseBody = await response.json(); 26 | // 处理数据 27 | if (responseBody.status === 1) { 28 | const result: HotListItem[] = responseBody.data.result.map((v) => { 29 | return { 30 | id: v.iDocID, 31 | title: v.sTitle, 32 | desc: v.sAuthor, 33 | pic: v.sIMG, 34 | hot: Number(v.iTotalPlay), 35 | url: `https://lol.qq.com/news/detail.shtml?docid=${encodeURIComponent(v.iDocID)}`, 36 | mobileUrl: `https://lol.qq.com/news/detail.shtml?docid=${encodeURIComponent(v.iDocID)}`, 37 | }; 38 | }); 39 | return NextResponse.json(responseSuccess(result)); 40 | } 41 | return NextResponse.json(responseSuccess()); 42 | } catch { 43 | return NextResponse.json(responseError); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/app/api/netease-music/route.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: 白雾茫茫丶 3 | * @Date: 2024-05-14 14:13:34 4 | * @LastEditors: 白雾茫茫丶 5 | * @LastEditTime: 2024-05-14 16:54:08 6 | * @Description: 网易云音乐-新歌榜 7 | */ 8 | import { NextResponse } from 'next/server'; 9 | 10 | import { REQUEST_STATUS_TEXT } from '@/lib/constant'; 11 | import type { HotListItem } from '@/lib/type'; 12 | import { convertMillisecondsToTime, responseError, responseSuccess } from '@/lib/utils'; 13 | 14 | export async function GET() { 15 | // 官方 url 16 | const url = 'https://music.163.com/api/playlist/detail?id=3778678'; 17 | try { 18 | // 请求数据 19 | const response = await fetch(url, { 20 | headers: { 21 | authority: 'music.163.com', 22 | referer: 'https://music.163.com/', 23 | }, 24 | }); 25 | if (!response.ok) { 26 | // 如果请求失败,抛出错误,不进行缓存 27 | throw new Error(`${REQUEST_STATUS_TEXT.ERROR}:网易云音乐-新歌榜`); 28 | } 29 | // 得到请求体 30 | const responseBody = await response.json(); 31 | // 处理数据 32 | if (responseBody.code === 200) { 33 | const result: HotListItem[] = responseBody.result.tracks.map((v) => { 34 | return { 35 | id: v.id, 36 | title: v.name, 37 | author: v.artists.map((item: { name: string }) => item.name).join('/'), 38 | pic: v.album.picUrl, 39 | tip: convertMillisecondsToTime(v.duration), 40 | url: `https://music.163.com/#/song?id=${v.id}`, 41 | mobileUrl: `https://music.163.com/m/song?id=${v.id}`, 42 | }; 43 | }); 44 | return NextResponse.json(responseSuccess(result)); 45 | } 46 | return NextResponse.json(responseSuccess()); 47 | } catch { 48 | return NextResponse.json(responseError); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/lib/constant.ts: -------------------------------------------------------------------------------- 1 | import type { HotListConfig } from './type' 2 | 3 | /** 4 | * @description: 主题 5 | */ 6 | export const THEME_MODE = { 7 | LIGHT: 'light', 8 | DARK: 'dark', 9 | SYSTEM: 'system' 10 | } as const; 11 | 12 | /** 13 | * @description: 请求状态码 14 | */ 15 | export const REQUEST_STATUS = { 16 | SUCCESS: 200, // 成功 17 | ERROR: 500, // 失败 18 | } as const; 19 | 20 | /** 21 | * @description: 请求状态对应的文案 22 | */ 23 | export const REQUEST_STATUS_TEXT = { 24 | SUCCESS: '请求成功', 25 | ERROR: '请求失败', 26 | } as const; 27 | 28 | /** 29 | * @description: localstorage key 30 | */ 31 | export const LOCAL_KEY = { 32 | UPDATETIME: 'update-time', // 热榜更新时间 33 | HOTHIDDEN: 'hot-hidden-values', // 隐藏不显示的热榜列表 34 | } as const; 35 | 36 | /** 37 | * @description: 热榜配置 38 | */ 39 | export const hotCardConfig: HotListConfig[] = [ 40 | { value: 'weibo', label: '微博', tip: '热搜榜' }, 41 | { value: 'bilibili', label: '哔哩哔哩', tip: '热门榜' }, 42 | { value: 'douyin', label: '抖音', tip: '热点榜' }, 43 | { value: 'toutiao', label: '今日头条', tip: '热榜' }, 44 | { value: 'zhihu', label: '知乎', tip: '热榜' }, 45 | { value: 'baidu', label: '百度', tip: '热搜榜' }, 46 | { value: 'baidutieba', label: '百度贴吧', tip: '热议榜' }, 47 | { value: 'qq', label: '腾讯新闻', tip: '热点榜' }, 48 | { value: 'juejin', label: '稀土掘金', tip: '热榜' }, 49 | { value: 'netease', label: '网易新闻', tip: '热榜' }, 50 | { value: 'lol', label: '英雄联盟', tip: '更新公告' }, 51 | { value: 'thepaper', label: '澎湃新闻', tip: '热榜' }, 52 | { value: 'kuaishou', label: '快手', tip: '热榜' }, 53 | { value: 'history-today', label: '百度百科', tip: '历史上的今天', suffix: '年' }, 54 | { value: 'weread', label: '微信读书', tip: '飙升榜' }, 55 | { value: 'douban-movic', label: '豆瓣电影', tip: '新片榜' }, 56 | { value: 'netease-music', label: '网易云音乐', tip: '热歌榜' }, 57 | ]; -------------------------------------------------------------------------------- /src/app/api/bilibili/route.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: 白雾茫茫丶 3 | * @Date: 2024-05-13 16:25:11 4 | * @LastEditors: 白雾茫茫丶 5 | * @LastEditTime: 2025-11-20 15:11:00 6 | * @Description: 哔哩哔哩-热门榜 7 | */ 8 | import { NextResponse } from 'next/server'; 9 | 10 | import { REQUEST_STATUS_TEXT } from '@/lib/constant'; 11 | import type { HotListItem } from '@/lib/type'; 12 | import { responseError, responseSuccess } from '@/lib/utils'; 13 | 14 | export async function GET() { 15 | // 官方 url 16 | const url = 'https://api.bilibili.com/x/web-interface/ranking/v2'; 17 | try { 18 | // 请求数据 19 | const response = await fetch(url, { 20 | headers: { 21 | Referer: `https://www.bilibili.com/ranking/all`, 22 | 'User-Agent': 23 | 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36', 24 | }, 25 | }); 26 | if (!response.ok) { 27 | // 如果请求失败,抛出错误,不进行缓存 28 | throw new Error(`${REQUEST_STATUS_TEXT.ERROR}:哔哩哔哩-热门榜`); 29 | } 30 | // 得到请求体 31 | const responseBody = await response.json(); 32 | const data = responseBody?.data?.realtime || responseBody?.data?.list; 33 | if (!data) { 34 | return NextResponse.json(responseSuccess()); 35 | } 36 | const result: HotListItem[] = data.map((v) => { 37 | return { 38 | id: v.bvid, 39 | title: v.title, 40 | desc: v.desc, 41 | pic: v.pic.replace(/http:/, 'https:'), 42 | hot: v.stat.view, 43 | url: v.short_link_v2 || `https://b23.tv/${v.bvid}`, 44 | mobileUrl: `https://m.bilibili.com/video/${v.bvid}`, 45 | }; 46 | }); 47 | return NextResponse.json(responseSuccess(result)); 48 | } catch { 49 | return NextResponse.json(responseError); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/app/page.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: 白雾茫茫丶 3 | * @Date: 2025-11-19 15:55:09 4 | * @LastEditors: 白雾茫茫丶 5 | * @LastEditTime: 2025-11-20 15:42:05 6 | * @Description: 首页 7 | */ 8 | 'use client'; 9 | 10 | import { useLocalStorageState } from 'ahooks'; 11 | import { AnimatePresence, motion } from 'framer-motion'; 12 | import { useMemo } from 'react'; 13 | 14 | import HotCard from '@/components/HotCard'; 15 | import { hotCardConfig, LOCAL_KEY } from '@/lib/constant'; 16 | import type { HotTypes } from '@/lib/type'; 17 | 18 | export default function Home() { 19 | const [hiddenHotList] = useLocalStorageState( 20 | LOCAL_KEY.HOTHIDDEN, 21 | { 22 | defaultValue: [], 23 | listenStorageChange: true, 24 | } 25 | ); 26 | 27 | const visibleConfigs = useMemo(() => { 28 | const hiddenSet = new Set(hiddenHotList || []); 29 | return hotCardConfig.filter((config) => !hiddenSet.has(config.value)); 30 | }, [hiddenHotList]); 31 | 32 | return ( 33 | // 👇 父容器必须是 motion.div 并开启 layout 34 | 39 | 40 | {visibleConfigs.map((config) => ( 41 | // 👇 每个子项也必须是 motion.div + layout 42 | 50 | 51 | 52 | ))} 53 | 54 | 55 | ); 56 | } 57 | -------------------------------------------------------------------------------- /public/douyin.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/app/api/kuaishou/route.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: 白雾茫茫丶 3 | * @Date: 2024-05-14 10:16:28 4 | * @LastEditors: 白雾茫茫丶 5 | * @LastEditTime: 2025-11-20 15:10:10 6 | * @Description: 快手-热榜 7 | */ 8 | import { NextResponse } from 'next/server'; 9 | 10 | import { REQUEST_STATUS_TEXT } from '@/lib/constant'; 11 | import type { HotListItem } from '@/lib/type'; 12 | import { responseError, responseSuccess } from '@/lib/utils'; 13 | 14 | export async function GET() { 15 | // 官方 url 16 | const url = 'https://www.kuaishou.com/?isHome=1'; 17 | try { 18 | // 请求数据 19 | const response = await fetch(url); 20 | if (!response.ok) { 21 | // 如果请求失败,抛出错误,不进行缓存 22 | throw new Error(`${REQUEST_STATUS_TEXT.ERROR}:快手-热榜`); 23 | } 24 | // 得到请求体 25 | const responseBody = await response.text(); 26 | // 处理数据 27 | const result: HotListItem[] = []; 28 | const pattern = /window.__APOLLO_STATE__=(.*);\(function\(\)/s; 29 | const idPattern = /clientCacheKey=([A-Za-z0-9]+)/s; 30 | const matchResult = responseBody.match(pattern); 31 | const jsonObject = matchResult ? JSON.parse(matchResult[1])['defaultClient'] : []; 32 | 33 | // 获取所有分类 34 | const allItems = jsonObject['$ROOT_QUERY.visionHotRank({"page":"home"})']['items']; 35 | // 遍历所有分类 36 | allItems.forEach((v) => { 37 | // 基础数据 38 | const image = jsonObject[v.id]['poster']; 39 | const id = image.match(idPattern)[1]; 40 | // 数据处理 41 | result.push({ 42 | id, 43 | title: jsonObject[v.id]['name'], 44 | hot: jsonObject[v.id]['hotValue']?.replace('万', '') * 10000, 45 | url: `https://www.kuaishou.com/short-video/${id}`, 46 | mobileUrl: `https://www.kuaishou.com/short-video/${id}`, 47 | }); 48 | }); 49 | return NextResponse.json(responseSuccess(result)); 50 | } catch { 51 | return NextResponse.json(responseError); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/app/api/weibo/route.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: 白雾茫茫丶 3 | * @Date: 2024-05-11 14:37:26 4 | * @LastEditors: 白雾茫茫丶 5 | * @LastEditTime: 2025-11-20 15:08:28 6 | * @Description: 微博-热搜榜 7 | */ 8 | import { NextResponse } from 'next/server'; 9 | 10 | import { REQUEST_STATUS_TEXT } from '@/lib/constant'; 11 | import type { HotListItem } from '@/lib/type'; 12 | import { responseError, responseSuccess } from '@/lib/utils'; 13 | 14 | export async function GET() { 15 | // 官方 url 16 | const url = 'https://weibo.com/ajax/side/hotSearch'; 17 | try { 18 | // 请求数据 19 | const response = await fetch(url, { 20 | headers: { 21 | 'User-Agent': 22 | 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36', 23 | Referer: 'https://weibo.com/', 24 | Accept: 'application/json', 25 | }, 26 | }); 27 | if (!response.ok) { 28 | // 如果请求失败,抛出错误,不进行缓存 29 | throw new Error(`${REQUEST_STATUS_TEXT.ERROR}:微博-热搜榜`); 30 | } 31 | // 得到请求体 32 | const responseBody = await response.json(); 33 | // 处理数据 34 | if (responseBody.ok === 1) { 35 | const result: HotListItem[] = responseBody.data.realtime.map((v) => { 36 | const key = v.word_scheme ? v.word_scheme : `#${v.word}`; 37 | return { 38 | id: v.mid, 39 | title: v.word, 40 | desc: key, 41 | hot: v.raw_hot, 42 | label: v.label_name, 43 | url: `https://s.weibo.com/weibo?q=${encodeURIComponent(key)}&t=31&band_rank=1&Refer=top`, 44 | mobileUrl: `https://s.weibo.com/weibo?q=${encodeURIComponent(key)}&t=31&band_rank=1&Refer=top`, 45 | }; 46 | }); 47 | return NextResponse.json(responseSuccess(result)); 48 | } 49 | return NextResponse.json(responseSuccess()); 50 | } catch { 51 | return NextResponse.json(responseError); 52 | } 53 | } 54 | 55 | -------------------------------------------------------------------------------- /src/app/api/douban-movic/route.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: 白雾茫茫丶 3 | * @Date: 2024-05-14 14:02:04 4 | * @LastEditors: 白雾茫茫丶 5 | * @LastEditTime: 2025-11-20 15:03:47 6 | * @Description: 豆瓣电影-新片榜 7 | */ 8 | import * as cheerio from 'cheerio'; 9 | import { NextResponse } from 'next/server'; 10 | 11 | import { REQUEST_STATUS_TEXT } from '@/lib/constant'; 12 | import type { HotListItem } from '@/lib/type'; 13 | import { responseError, responseSuccess } from '@/lib/utils'; 14 | 15 | export async function GET() { 16 | // 官方 url 17 | const url = 'https://movie.douban.com/chart/'; 18 | try { 19 | // 请求数据 20 | const response = await fetch(url); 21 | if (!response.ok) { 22 | // 如果请求失败,抛出错误,不进行缓存 23 | throw new Error(`${REQUEST_STATUS_TEXT.ERROR}:豆瓣电影-新片榜`); 24 | } 25 | // 得到请求体 26 | const responseBody = await response.text(); 27 | // 处理数据 28 | const getNumbers = (text: string | undefined) => { 29 | if (!text) return 10000000; 30 | const regex = /\d+/; 31 | const match = text.match(regex); 32 | if (match) { 33 | return Number(match[0]); 34 | } else { 35 | return 10000000; 36 | } 37 | }; 38 | const $ = cheerio.load(responseBody); 39 | const listDom = $('.article tr.item'); 40 | const result: HotListItem[] = listDom.toArray().map((item) => { 41 | const dom = $(item); 42 | const url = dom.find('a').attr('href') || ''; 43 | const score = dom.find('.rating_nums').text() ?? '0.0'; 44 | return { 45 | id: String(getNumbers(url)), 46 | title: `${dom.find('.pl2 a').text().replace(/\s+/g, ' ').trim().replace(/\n/g, '')}`, 47 | desc: dom.find('p.pl').text(), 48 | hot: getNumbers(dom.find('span.pl').text()), 49 | score: Number(score), 50 | url, 51 | mobileUrl: `https://m.douban.com/movie/subject/${getNumbers(url)}/`, 52 | }; 53 | }); 54 | return NextResponse.json(responseSuccess(result)); 55 | } catch { 56 | return NextResponse.json(responseError); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/components/Analytics/index.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: 白雾茫茫丶 3 | * @Date: 2025-11-21 09:16:01 4 | * @LastEditors: 白雾茫茫丶 5 | * @LastEditTime: 2025-11-21 09:23:19 6 | * @Description: 统计代码 7 | */ 8 | import { GoogleAnalytics } from '@next/third-parties/google'; 9 | import Script from 'next/script'; 10 | 11 | /** 12 | * @description: Umami 统计 13 | */ 14 | export const UmamiAnalytics = () => { 15 | return process.env.NEXT_PUBLIC_UMAMI_ID && process.env.NODE_ENV === 'production' ? ( 16 |