mirror of
https://github.com/MoonTechLab/LunaTV.git
synced 2026-02-23 11:35:17 +08:00
feat: douban url
This commit is contained in:
@@ -5,6 +5,7 @@ import { DoubanItem, DoubanResult } from '@/lib/types';
|
|||||||
|
|
||||||
interface DoubanApiResponse {
|
interface DoubanApiResponse {
|
||||||
subjects: Array<{
|
subjects: Array<{
|
||||||
|
id: string;
|
||||||
title: string;
|
title: string;
|
||||||
cover: string;
|
cover: string;
|
||||||
rate: string;
|
rate: string;
|
||||||
@@ -95,6 +96,7 @@ export async function GET(request: Request) {
|
|||||||
|
|
||||||
// 转换数据格式
|
// 转换数据格式
|
||||||
const list: DoubanItem[] = doubanData.subjects.map((item) => ({
|
const list: DoubanItem[] = doubanData.subjects.map((item) => ({
|
||||||
|
id: item.id,
|
||||||
title: item.title,
|
title: item.title,
|
||||||
poster: item.cover,
|
poster: item.cover,
|
||||||
rate: item.rate,
|
rate: item.rate,
|
||||||
@@ -149,21 +151,23 @@ function handleTop250(pageStart: number) {
|
|||||||
// 获取 HTML 内容
|
// 获取 HTML 内容
|
||||||
const html = await fetchResponse.text();
|
const html = await fetchResponse.text();
|
||||||
|
|
||||||
// 使用正则表达式提取电影信息
|
// 通过正则同时捕获影片 id、标题、封面以及评分
|
||||||
const moviePattern =
|
const moviePattern =
|
||||||
/<div class="item">[\s\S]*?<img[^>]+alt="([^"]+)"[^>]*src="([^"]+)"[\s\S]*?<span class="rating_num"[^>]*>([^<]+)<\/span>[\s\S]*?<\/div>/g;
|
/<div class="item">[\s\S]*?<a[^>]+href="https?:\/\/movie\.douban\.com\/subject\/(\d+)\/"[\s\S]*?<img[^>]+alt="([^"]+)"[^>]*src="([^"]+)"[\s\S]*?<span class="rating_num"[^>]*>([^<]*)<\/span>[\s\S]*?<\/div>/g;
|
||||||
const movies: DoubanItem[] = [];
|
const movies: DoubanItem[] = [];
|
||||||
let match;
|
let match;
|
||||||
|
|
||||||
while ((match = moviePattern.exec(html)) !== null) {
|
while ((match = moviePattern.exec(html)) !== null) {
|
||||||
const title = match[1];
|
const id = match[1];
|
||||||
const cover = match[2];
|
const title = match[2];
|
||||||
const rate = match[3] || '';
|
const cover = match[3];
|
||||||
|
const rate = match[4] || '';
|
||||||
|
|
||||||
// 处理图片 URL,确保使用 HTTPS
|
// 处理图片 URL,确保使用 HTTPS
|
||||||
const processedCover = cover.replace(/^http:/, 'https:');
|
const processedCover = cover.replace(/^http:/, 'https:');
|
||||||
|
|
||||||
movies.push({
|
movies.push({
|
||||||
|
id: id,
|
||||||
title: title,
|
title: title,
|
||||||
poster: processedCover,
|
poster: processedCover,
|
||||||
rate: rate,
|
rate: rate,
|
||||||
|
|||||||
@@ -199,6 +199,7 @@ function DoubanPageClient() {
|
|||||||
doubanData.map((item, index) => (
|
doubanData.map((item, index) => (
|
||||||
<div key={`${item.title}-${index}`} className='w-full'>
|
<div key={`${item.title}-${index}`} className='w-full'>
|
||||||
<DemoCard
|
<DemoCard
|
||||||
|
id={item.id}
|
||||||
title={item.title}
|
title={item.title}
|
||||||
poster={item.poster}
|
poster={item.poster}
|
||||||
rate={item.rate}
|
rate={item.rate}
|
||||||
|
|||||||
@@ -193,6 +193,7 @@ function HomeClient() {
|
|||||||
className='min-w-[96px] w-24 sm:min-w-[180px] sm:w-44'
|
className='min-w-[96px] w-24 sm:min-w-[180px] sm:w-44'
|
||||||
>
|
>
|
||||||
<DemoCard
|
<DemoCard
|
||||||
|
id={movie.id}
|
||||||
title={movie.title}
|
title={movie.title}
|
||||||
poster={movie.poster}
|
poster={movie.poster}
|
||||||
rate={movie.rate}
|
rate={movie.rate}
|
||||||
@@ -237,6 +238,7 @@ function HomeClient() {
|
|||||||
className='min-w-[96px] w-24 sm:min-w-[180px] sm:w-44'
|
className='min-w-[96px] w-24 sm:min-w-[180px] sm:w-44'
|
||||||
>
|
>
|
||||||
<DemoCard
|
<DemoCard
|
||||||
|
id={show.id}
|
||||||
title={show.title}
|
title={show.title}
|
||||||
poster={show.poster}
|
poster={show.poster}
|
||||||
rate={show.rate}
|
rate={show.rate}
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
import { Search } from 'lucide-react';
|
import { Link as LinkIcon, Search } from 'lucide-react';
|
||||||
import Image from 'next/image';
|
import Image from 'next/image';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
|
|
||||||
interface DemoCardProps {
|
interface DemoCardProps {
|
||||||
|
id: string;
|
||||||
title: string;
|
title: string;
|
||||||
poster: string;
|
poster: string;
|
||||||
rate?: string;
|
rate?: string;
|
||||||
@@ -53,7 +54,7 @@ function SearchCircle({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const DemoCard = ({ title, poster, rate }: DemoCardProps) => {
|
const DemoCard = ({ id, title, poster, rate }: DemoCardProps) => {
|
||||||
const [hover, setHover] = useState(false);
|
const [hover, setHover] = useState(false);
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
@@ -98,6 +99,18 @@ const DemoCard = ({ title, poster, rate }: DemoCardProps) => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{/* 顶部左侧 🔗 链接按钮 */}
|
||||||
|
<a
|
||||||
|
href={`https://movie.douban.com/subject/${id}`}
|
||||||
|
target='_blank'
|
||||||
|
rel='noopener noreferrer'
|
||||||
|
onClick={(e) => e.stopPropagation()}
|
||||||
|
className='absolute top-2 left-2 opacity-0 group-hover:opacity-100 transition-opacity duration-200'
|
||||||
|
>
|
||||||
|
<div className='w-8 h-8 sm:w-9 sm:h-9 rounded-full bg-gray-600/60 flex items-center justify-center transition-all duration-200 hover:bg-green-500 hover:scale-110'>
|
||||||
|
<LinkIcon className='w-4 h-4 text-white' strokeWidth={2} />
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
{/* 信息层 */}
|
{/* 信息层 */}
|
||||||
<div className='absolute top-[calc(100%+0.2rem)] left-0 right-0'>
|
<div className='absolute top-[calc(100%+0.2rem)] left-0 right-0'>
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ export interface SearchResult {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface DoubanItem {
|
export interface DoubanItem {
|
||||||
|
id: string;
|
||||||
title: string;
|
title: string;
|
||||||
poster: string;
|
poster: string;
|
||||||
rate: string;
|
rate: string;
|
||||||
|
|||||||
Reference in New Issue
Block a user