From b0ecaa4122b87ab22d036d272b8284a1b67e03a8 Mon Sep 17 00:00:00 2001 From: eeuse Date: Mon, 28 Sep 2026 22:34:54 +0800 Subject: [PATCH] =?UTF-8?q?AI=20=E7=94=9F=E6=88=90:=20=E4=B8=80=E4=B8=AA?= =?UTF-8?q?=E4=BB=BF=20aptv.pro=20=E7=9A=84=E8=A7=86=E9=A2=91=E7=AB=99?= =?UTF-8?q?=E7=82=B9=EF=BC=8C=E4=BD=BF=E7=94=A8=20Python=20Flask=20?= =?UTF-8?q?=E6=9E=84=E5=BB=BA=EF=BC=8C=E5=8C=85=E5=90=AB=E8=A7=86=E9=A2=91?= =?UTF-8?q?=E5=88=97=E8=A1=A8=E3=80=81=E5=88=86=E7=B1=BB=E3=80=81=E6=90=9C?= =?UTF-8?q?=E7=B4=A2=E5=92=8C=E8=A7=86=E9=A2=91=E6=92=AD=E6=94=BE=E9=A1=B5?= =?UTF-8?q?=E9=9D=A2=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 37 ++++++ app.py | 62 +++++++++ data/videos.json | 122 ++++++++++++++++++ requirements.txt | 1 + static/css/style.css | 293 ++++++++++++++++++++++++++++++++++++++++++ static/js/main.js | 30 +++++ templates/base.html | 39 ++++++ templates/detail.html | 50 +++++++ templates/index.html | 39 ++++++ 9 files changed, 673 insertions(+) create mode 100644 README.md create mode 100644 app.py create mode 100644 data/videos.json create mode 100644 requirements.txt create mode 100644 static/css/style.css create mode 100644 static/js/main.js create mode 100644 templates/base.html create mode 100644 templates/detail.html create mode 100644 templates/index.html diff --git a/README.md b/README.md new file mode 100644 index 0000000..8acea87 --- /dev/null +++ b/README.md @@ -0,0 +1,37 @@ +# APTV Clone + +仿 aptv.pro 的视频站点,使用 Python Flask 构建。 + +## 功能 + +- 首页视频列表(卡片式布局) +- 按分类筛选视频 +- 搜索视频 +- 视频详情页(内嵌播放器) +- 响应式设计 + +## 目录结构 + +``` +aptv-clone/ +├── app.py # Flask 主应用 +├── requirements.txt # 依赖 +├── data/ +│ └── videos.json # 视频数据 +├── static/ +│ ├── css/style.css # 样式 +│ └── js/main.js # 前端脚本 +└── templates/ + ├── base.html # 基础模板 + ├── index.html # 首页 + └── detail.html # 详情页 +``` + +## 运行 + +```bash +pip install -r requirements.txt +python app.py +``` + +访问 http://127.0.0.1:5000 diff --git a/app.py b/app.py new file mode 100644 index 0000000..a964a86 --- /dev/null +++ b/app.py @@ -0,0 +1,62 @@ +import json +import os +from flask import Flask, render_template, request, abort + +app = Flask(__name__) + +DATA_FILE = os.path.join(os.path.dirname(__file__), 'data', 'videos.json') + + +def load_videos(): + """加载视频数据""" + with open(DATA_FILE, 'r', encoding='utf-8') as f: + return json.load(f) + + +@app.route('/') +def index(): + """首页:支持分类筛选和搜索""" + videos = load_videos() + category = request.args.get('category', '').strip() + keyword = request.args.get('q', '').strip() + + # 分类筛选 + if category: + videos = [v for v in videos if v.get('category') == category] + + # 关键词搜索 + if keyword: + kw = keyword.lower() + videos = [v for v in videos if kw in v.get('title', '').lower() + or kw in v.get('description', '').lower()] + + # 所有分类 + all_videos = load_videos() + categories = sorted({v.get('category', '') for v in all_videos if v.get('category')}) + + return render_template('index.html', + videos=videos, + categories=categories, + current_category=category, + keyword=keyword) + + +@app.route('/video/') +def detail(vid): + """视频详情页""" + videos = load_videos() + video = next((v for v in videos if v.get('id') == vid), None) + if not video: + abort(404) + # 推荐:同分类的其他视频 + related = [v for v in videos if v.get('category') == video.get('category') and v.get('id') != vid][:6] + return render_template('detail.html', video=video, related=related) + + +@app.errorhandler(404) +def not_found(e): + return render_template('base.html', not_found=True), 404 + + +if __name__ == '__main__': + app.run(debug=True, host='0.0.0.0', port=5000) diff --git a/data/videos.json b/data/videos.json new file mode 100644 index 0000000..efa85e5 --- /dev/null +++ b/data/videos.json @@ -0,0 +1,122 @@ +[ + { + "id": 1, + "title": "星际穿越", + "category": "科幻", + "year": 2014, + "rating": 9.4, + "cover": "https://picsum.photos/seed/interstellar/400/560", + "description": "一支探险队穿越虫洞,寻找人类新的家园。", + "url": "https://www.w3schools.com/html/mov_bbb.mp4" + }, + { + "id": 2, + "title": "盗梦空间", + "category": "科幻", + "year": 2010, + "rating": 9.3, + "cover": "https://picsum.photos/seed/inception/400/560", + "description": "一位盗梦者接受任务,在梦境中植入意念。", + "url": "https://www.w3schools.com/html/mov_bbb.mp4" + }, + { + "id": 3, + "title": "肖申克的救赎", + "category": "剧情", + "year": 1994, + "rating": 9.7, + "cover": "https://picsum.photos/seed/shawshank/400/560", + "description": "一位银行家被冤入狱,用希望与坚持追寻自由。", + "url": "https://www.w3schools.com/html/mov_bbb.mp4" + }, + { + "id": 4, + "title": "教父", + "category": "犯罪", + "year": 1972, + "rating": 9.3, + "cover": "https://picsum.photos/seed/godfather/400/560", + "description": "一个黑手党家族的兴衰史。", + "url": "https://www.w3schools.com/html/mov_bbb.mp4" + }, + { + "id": 5, + "title": "阿甘正传", + "category": "剧情", + "year": 1994, + "rating": 9.5, + "cover": "https://picsum.photos/seed/gump/400/560", + "description": "一个智商不高的男人,却跑出了不平凡的人生。", + "url": "https://www.w3schools.com/html/mov_bbb.mp4" + }, + { + "id": 6, + "title": "千与千寻", + "category": "动画", + "year": 2001, + "rating": 9.4, + "cover": "https://picsum.photos/seed/spirited/400/560", + "description": "少女千寻误入神灵世界,努力拯救父母。", + "url": "https://www.w3schools.com/html/mov_bbb.mp4" + }, + { + "id": 7, + "title": "黑客帝国", + "category": "科幻", + "year": 1999, + "rating": 9.1, + "cover": "https://picsum.photos/seed/matrix/400/560", + "description": "程序员尼奥发现现实世界只是虚拟的矩阵。", + "url": "https://www.w3schools.com/html/mov_bbb.mp4" + }, + { + "id": 8, + "title": "狮子王", + "category": "动画", + "year": 1994, + "rating": 9.1, + "cover": "https://picsum.photos/seed/lionking/400/560", + "description": "小狮子辛巴成长为草原之王的故事。", + "url": "https://www.w3schools.com/html/mov_bbb.mp4" + }, + { + "id": 9, + "title": "无间道", + "category": "犯罪", + "year": 2002, + "rating": 9.3, + "cover": "https://picsum.photos/seed/infernal/400/560", + "description": "警察与黑帮卧底身份交错的对决。", + "url": "https://www.w3schools.com/html/mov_bbb.mp4" + }, + { + "id": 10, + "title": "楚门的世界", + "category": "剧情", + "year": 1998, + "rating": 9.4, + "cover": "https://picsum.photos/seed/truman/400/560", + "description": "一个人的一生竟然是一场巨大的真人秀。", + "url": "https://www.w3schools.com/html/mov_bbb.mp4" + }, + { + "id": 11, + "title": "大话西游", + "category": "喜剧", + "year": 1995, + "rating": 9.2, + "cover": "https://picsum.photos/seed/west/400/560", + "description": "至尊宝穿越时空,只为拯救心中所爱。", + "url": "https://www.w3schools.com/html/mov_bbb.mp4" + }, + { + "id": 12, + "title": "疯狂动物城", + "category": "动画", + "year": 2016, + "rating": 9.2, + "cover": "https://picsum.photos/seed/zootopia/400/560", + "description": "兔子警官与狐狸搭档侦破神秘案件。", + "url": "https://www.w3schools.com/html/mov_bbb.mp4" + } +] diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..047e950 --- /dev/null +++ b/requirements.txt @@ -0,0 +1 @@ +Flask==3.0.0 diff --git a/static/css/style.css b/static/css/style.css new file mode 100644 index 0000000..ac62b75 --- /dev/null +++ b/static/css/style.css @@ -0,0 +1,293 @@ +* { margin: 0; padding: 0; box-sizing: border-box; } + +:root { + --bg: #0f1115; + --bg-soft: #171a21; + --card: #1c2029; + --text: #eaeaea; + --muted: #9aa3b2; + --accent: #ff5c39; + --accent-2: #ff8a5c; + --radius: 10px; +} + +body { + background: var(--bg); + color: var(--text); + font-family: -apple-system, "PingFang SC", "Microsoft YaHei", Helvetica, Arial, sans-serif; + line-height: 1.6; + min-height: 100vh; + display: flex; + flex-direction: column; +} + +.container { + width: 100%; + max-width: 1200px; + margin: 0 auto; + padding: 0 20px; +} + +/* Header */ +.header { + background: rgba(15, 17, 21, .92); + backdrop-filter: blur(8px); + border-bottom: 1px solid #232733; + position: sticky; + top: 0; + z-index: 50; +} + +.header-inner { + display: flex; + align-items: center; + justify-content: space-between; + height: 64px; + gap: 16px; +} + +.logo { + font-size: 22px; + font-weight: 800; + color: var(--text); + text-decoration: none; + letter-spacing: .5px; +} + +.logo span { color: var(--accent); } + +.search-form { + display: flex; + gap: 8px; +} + +.search-form input { + background: var(--bg-soft); + border: 1px solid #2a2f3b; + color: var(--text); + padding: 8px 14px; + border-radius: var(--radius); + outline: none; + width: 220px; + transition: border-color .2s; +} + +.search-form input:focus { border-color: var(--accent); } + +.search-form button { + background: var(--accent); + color: #fff; + border: none; + padding: 8px 16px; + border-radius: var(--radius); + cursor: pointer; + font-weight: 600; + transition: background .2s; +} + +.search-form button:hover { background: var(--accent-2); } + +/* Main */ +main.container { + flex: 1; + padding-top: 24px; + padding-bottom: 40px; +} + +/* Categories */ +.categories { + display: flex; + flex-wrap: wrap; + gap: 10px; + margin-bottom: 24px; +} + +.cat { + color: var(--muted); + text-decoration: none; + padding: 6px 14px; + border-radius: 999px; + background: var(--bg-soft); + border: 1px solid #2a2f3b; + font-size: 14px; + transition: all .2s; +} + +.cat:hover { color: var(--text); border-color: var(--accent); } + +.cat.active { + background: var(--accent); + color: #fff; + border-color: var(--accent); +} + +.result-tip { color: var(--muted); margin-bottom: 16px; font-size: 14px; } + +/* Grid */ +.grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)); + gap: 20px; +} + +.card { + background: var(--card); + border-radius: var(--radius); + overflow: hidden; + text-decoration: none; + color: var(--text); + transition: transform .2s, box-shadow .2s; + display: block; +} + +.card:hover { + transform: translateY(-4px); + box-shadow: 0 10px 24px rgba(0,0,0,.5); +} + +.cover { + position: relative; + aspect-ratio: 5 / 7; + overflow: hidden; + background: #222; +} + +.cover img { + width: 100%; + height: 100%; + object-fit: cover; + display: block; + transition: transform .3s; +} + +.card:hover .cover img { transform: scale(1.05); } + +.rating { + position: absolute; + top: 8px; + right: 8px; + background: rgba(0,0,0,.7); + color: #ffd166; + font-size: 12px; + font-weight: 700; + padding: 2px 8px; + border-radius: 6px; +} + +.card-body { padding: 10px 12px 14px; } + +.card-title { + font-size: 15px; + font-weight: 600; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.card-meta { font-size: 12px; color: var(--muted); margin-top: 4px; } + +/* Empty */ +.empty { + text-align: center; + padding: 80px 20px; + color: var(--muted); +} + +.empty h2 { color: var(--text); margin-bottom: 10px; } + +.empty a { color: var(--accent); text-decoration: none; } + +/* Detail */ +.detail { display: flex; flex-direction: column; gap: 24px; } + +.player-wrap { + background: #000; + border-radius: var(--radius); + overflow: hidden; + aspect-ratio: 16 / 9; +} + +.player { width: 100%; height: 100%; display: block; background: #000; } + +.detail-info { + display: flex; + gap: 24px; + background: var(--card); + padding: 20px; + border-radius: var(--radius); +} + +.detail-cover { + flex: 0 0 160px; + border-radius: var(--radius); + overflow: hidden; +} + +.detail-cover img { width: 100%; display: block; } + +.detail-text { flex: 1; } + +.detail-text h1 { font-size: 26px; margin-bottom: 12px; } + +.detail-meta { + display: flex; + gap: 14px; + align-items: center; + color: var(--muted); + font-size: 14px; + margin-bottom: 14px; + flex-wrap: wrap; +} + +.tag { + background: var(--accent); + color: #fff; + padding: 2px 10px; + border-radius: 6px; + font-size: 12px; +} + +.score { color: #ffd166; font-weight: 700; } + +.detail-desc { color: #c9d1dc; margin-bottom: 18px; } + +.back-btn { + display: inline-block; + color: var(--text); + text-decoration: none; + background: var(--bg-soft); + border: 1px solid #2a2f3b; + padding: 8px 16px; + border-radius: var(--radius); + font-size: 14px; + transition: all .2s; +} + +.back-btn:hover { border-color: var(--accent); color: var(--accent); } + +.section-title { + font-size: 18px; + margin-bottom: 14px; + padding-left: 10px; + border-left: 3px solid var(--accent); +} + +/* Footer */ +.footer { + border-top: 1px solid #232733; + padding: 20px 0; + color: var(--muted); + font-size: 13px; + text-align: center; +} + +/* Responsive */ +@media (max-width: 640px) { + .header-inner { flex-wrap: wrap; height: auto; padding: 12px 0; } + .search-form { width: 100%; } + .search-form input { flex: 1; width: auto; } + .grid { grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); gap: 14px; } + .detail-info { flex-direction: column; } + .detail-cover { flex: none; max-width: 180px; } + .detail-text h1 { font-size: 20px; } +} diff --git a/static/js/main.js b/static/js/main.js new file mode 100644 index 0000000..00bc9e1 --- /dev/null +++ b/static/js/main.js @@ -0,0 +1,30 @@ +// 前端交互脚本 +(function () { + 'use strict'; + + // 卡片点击时添加轻微反馈 + document.addEventListener('click', function (e) { + const card = e.target.closest('.card'); + if (!card) return; + card.style.transform = 'scale(0.98)'; + setTimeout(function () { card.style.transform = ''; }, 150); + }); + + // 搜索框自动聚焦(当 URL 带 q 参数时) + const params = new URLSearchParams(window.location.search); + if (params.get('q')) { + const input = document.querySelector('.search-form input'); + if (input) { + input.focus(); + input.setSelectionRange(input.value.length, input.value.length); + } + } + + // 视频播放错误提示 + const player = document.querySelector('.player'); + if (player) { + player.addEventListener('error', function () { + console.warn('视频加载失败,请检查视频源。'); + }); + } +})(); diff --git a/templates/base.html b/templates/base.html new file mode 100644 index 0000000..9807cdb --- /dev/null +++ b/templates/base.html @@ -0,0 +1,39 @@ + + + + + + {% block title %}APTV - 在线影视{% endblock %} + + + +
+
+ +
+ + +
+
+
+ +
+ {% if not_found %} +
+

404 - 页面不存在

+

返回首页

+
+ {% else %} + {% block content %}{% endblock %} + {% endif %} +
+ +
+
+

© 2024 APTV.pro Clone · 仅供学习交流

+
+
+ + + + diff --git a/templates/detail.html b/templates/detail.html new file mode 100644 index 0000000..0b53daf --- /dev/null +++ b/templates/detail.html @@ -0,0 +1,50 @@ +{% extends "base.html" %} + +{% block title %}{{ video.title }} - APTV{% endblock %} + +{% block content %} +
+
+ +
+ +
+
+ {{ video.title }} +
+
+

{{ video.title }}

+

+ {{ video.category }} + {{ video.year }} + 评分 {{ video.rating }} +

+

{{ video.description }}

+ ← 返回首页 +
+
+ + {% if related %} + + {% endif %} +
+{% endblock %} diff --git a/templates/index.html b/templates/index.html new file mode 100644 index 0000000..6cf1496 --- /dev/null +++ b/templates/index.html @@ -0,0 +1,39 @@ +{% extends "base.html" %} + +{% block title %}APTV - 首页{% endblock %} + +{% block content %} + + + {% if keyword %} +

搜索 “{{ keyword }}” 共找到 {{ videos|length }} 条结果

+ {% endif %} + + {% if videos %} + + {% else %} +
+

没有找到相关影片

+

试试其他关键词或分类吧

+
+ {% endif %} +{% endblock %}