Spaces:
Running
Running
| """ | |
| 叶兴阳双语音标有声读物 PDF 下载站 | |
| Flask 路由器 — 干净 URL + SPA 回退 + API 接口 | |
| """ | |
| from flask import Flask, send_file, jsonify, request, abort, send_from_directory | |
| import os | |
| import json | |
| app = Flask(__name__, static_folder='.', static_url_path='') | |
| BASE_DIR = os.path.dirname(os.path.abspath(__file__)) | |
| # 预读索引,加快 /api/sources 响应 | |
| with open(os.path.join(BASE_DIR, 'data', 'index.json'), 'r', encoding='utf-8') as f: | |
| INDEX = json.load(f) | |
| # 构建 id → source 的快速查找 | |
| ID_MAP = {s['id']: s for s in INDEX['sources']} | |
| # ─── 页面路由 ──────────────────────────────────────── | |
| def home(): | |
| return send_file('index.html') | |
| def reader(): | |
| return send_file('reader.html') | |
| def category(cat_id): | |
| """干净 URL 分类页 — 回退到 index.html,前端 JS 识别路由""" | |
| if cat_id not in ID_MAP: | |
| abort(404) | |
| return send_file('index.html') | |
| # ─── 数据 API ──────────────────────────────────────── | |
| def api_sources(): | |
| """返回所有分类的基本信息(不含具体文件)""" | |
| return jsonify(INDEX) | |
| def api_source(cat_id): | |
| """返回单个分类的元信息""" | |
| if cat_id not in ID_MAP: | |
| return jsonify({'error': '分类不存在'}), 404 | |
| src = dict(ID_MAP[cat_id]) | |
| return jsonify(src) | |
| def api_files(cat_id): | |
| """返回某个分类的全部文件列表(按需合并分片)""" | |
| if cat_id not in ID_MAP: | |
| return jsonify({'error': '分类不存在'}), 404 | |
| src = ID_MAP[cat_id] | |
| all_files = [] | |
| for chunk in src['chunks']: | |
| chunk_path = os.path.join(BASE_DIR, 'data', chunk['f']) | |
| if os.path.isfile(chunk_path): | |
| with open(chunk_path, 'r', encoding='utf-8') as f: | |
| all_files.extend(json.load(f)) | |
| return jsonify(all_files) | |
| def api_search(cat_id): | |
| """搜索指定分类的文件""" | |
| q = request.args.get('q', '').lower().strip() | |
| if cat_id not in ID_MAP: | |
| return jsonify({'error': '分类不存在'}), 404 | |
| src = ID_MAP[cat_id] | |
| results = [] | |
| for chunk in src['chunks']: | |
| chunk_path = os.path.join(BASE_DIR, 'data', chunk['f']) | |
| if os.path.isfile(chunk_path): | |
| with open(chunk_path, 'r', encoding='utf-8') as f: | |
| items = json.load(f) | |
| results.extend([item for item in items if q in item[0].lower()]) | |
| return jsonify(results) | |
| def api_health(): | |
| return jsonify({'status': 'ok', 'sources': len(INDEX['sources'])}) | |
| # ─── 静态文件 ──────────────────────────────────────── | |
| def serve_data(filename): | |
| """托管 data/ 目录下的 JSON 数据文件""" | |
| data_dir = os.path.join(BASE_DIR, 'data') | |
| return send_from_directory(data_dir, filename) | |
| def favicon(): | |
| return '', 204 | |
| # ─── 404 ──────────────────────────────────────────── | |
| def not_found(e): | |
| return ''' | |
| <!DOCTYPE html> | |
| <html lang="zh-CN"> | |
| <head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1.0"> | |
| <title>404 - 页面不存在</title> | |
| <style> | |
| *{margin:0;padding:0;box-sizing:border-box} | |
| body{font-family:-apple-system,BlinkMacSystemFont,"Microsoft YaHei",sans-serif; | |
| background:#f8fafc;display:flex;align-items:center;justify-content:center;min-height:100vh;color:#1e293b} | |
| .box{text-align:center;padding:3rem} | |
| .box h1{font-size:5rem;font-weight:800;color:#6366f1;line-height:1} | |
| .box p{color:#64748b;margin:1rem 0 1.5rem;font-size:1rem} | |
| .box a{display:inline-block;padding:0.65rem 2rem;background:linear-gradient(135deg,#6366f1,#4f46e5); | |
| color:white;text-decoration:none;border-radius:12px;font-weight:600;font-size:0.95rem;transition:all .15s} | |
| .box a:hover{transform:translateY(-1px);box-shadow:0 4px 16px rgba(99,102,241,0.3)} | |
| </style></head> | |
| <body><div class="box"> | |
| <h1>404</h1><p>页面不存在</p> | |
| <a href="/">← 返回首页</a> | |
| </div></body></html> | |
| ''', 404 | |
| # ─── 启动 ──────────────────────────────────────────── | |
| if __name__ == '__main__': | |
| print(f'Server starting at http://0.0.0.0:7860') | |
| print(f'Routes: / /category/<id> /reader /api/sources /api/files/<id> /api/search/<id>?q=') | |
| app.run(host='0.0.0.0', port=7860, debug=False) | |