1088 lines
38 KiB
HTML
1088 lines
38 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="zh-CN">
|
|
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
|
|
<title>甜圈短剧</title>
|
|
<style>
|
|
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
|
html, body {
|
|
margin: 0;
|
|
min-height: 100%;
|
|
background: transparent;
|
|
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
|
color: #fff;
|
|
overflow-x: hidden;
|
|
}
|
|
html:not(.fm-native), html:not(.fm-native) body { background: #1a1a2e; }
|
|
|
|
:root {
|
|
--panel-rgb: 30, 30, 46;
|
|
--panel: rgba(var(--panel-rgb), .72);
|
|
--panel-soft: rgba(var(--panel-rgb), .48);
|
|
--panel-strong: rgba(var(--panel-rgb), .88);
|
|
--line: rgba(255, 255, 255, .12);
|
|
--accent: #ff6b9d;
|
|
--accent-hover: #ff8fab;
|
|
--text-primary: #ffffff;
|
|
--text-secondary: rgba(255,255,255,.7);
|
|
--text-muted: rgba(255,255,255,.5);
|
|
--card-radius: 10px;
|
|
--focus-color: #ff6b9d;
|
|
}
|
|
|
|
.app { min-height: 100vh; background: transparent; position: relative; }
|
|
|
|
.header {
|
|
position: sticky;
|
|
top: 0;
|
|
z-index: 100;
|
|
background: var(--panel-strong);
|
|
backdrop-filter: blur(20px);
|
|
border-bottom: 1px solid var(--line);
|
|
padding: 12px 16px;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 12px;
|
|
}
|
|
.logo {
|
|
font-size: 20px;
|
|
font-weight: 700;
|
|
color: var(--accent);
|
|
white-space: nowrap;
|
|
flex-shrink: 0;
|
|
}
|
|
.search-box {
|
|
flex: 1;
|
|
position: relative;
|
|
max-width: 400px;
|
|
}
|
|
.search-box input {
|
|
width: 100%;
|
|
padding: 10px 40px 10px 16px;
|
|
border-radius: 24px;
|
|
border: 1px solid var(--line);
|
|
background: var(--panel-soft);
|
|
color: var(--text-primary);
|
|
font-size: 14px;
|
|
outline: none;
|
|
}
|
|
.search-btn {
|
|
position: absolute;
|
|
right: 8px;
|
|
top: 50%;
|
|
transform: translateY(-50%);
|
|
background: var(--accent);
|
|
border: none;
|
|
color: #fff;
|
|
width: 32px;
|
|
height: 32px;
|
|
border-radius: 50%;
|
|
cursor: pointer;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
font-size: 16px;
|
|
}
|
|
.header-actions {
|
|
display: flex;
|
|
gap: 8px;
|
|
flex-shrink: 0;
|
|
}
|
|
.header-btn {
|
|
padding: 8px 16px;
|
|
border-radius: 20px;
|
|
border: 1px solid var(--line);
|
|
background: var(--panel-soft);
|
|
color: var(--text-secondary);
|
|
font-size: 13px;
|
|
cursor: pointer;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.category-bar {
|
|
display: flex;
|
|
gap: 8px;
|
|
padding: 12px 16px;
|
|
overflow-x: auto;
|
|
scrollbar-width: none;
|
|
background: transparent;
|
|
}
|
|
.category-bar::-webkit-scrollbar { display: none; }
|
|
.category-tab {
|
|
padding: 8px 20px;
|
|
border-radius: 20px;
|
|
border: 1px solid var(--line);
|
|
background: var(--panel-soft);
|
|
color: var(--text-secondary);
|
|
font-size: 14px;
|
|
cursor: pointer;
|
|
white-space: nowrap;
|
|
flex-shrink: 0;
|
|
}
|
|
.category-tab.active {
|
|
background: var(--accent);
|
|
color: #fff;
|
|
border-color: var(--accent);
|
|
}
|
|
|
|
.content { padding: 0 16px 24px; }
|
|
.section-title {
|
|
font-size: 18px;
|
|
font-weight: 600;
|
|
margin: 20px 0 12px;
|
|
color: var(--text-primary);
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
}
|
|
.section-title::before {
|
|
content: "";
|
|
width: 4px;
|
|
height: 18px;
|
|
background: var(--accent);
|
|
border-radius: 2px;
|
|
}
|
|
|
|
.video-grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
|
|
gap: 12px;
|
|
}
|
|
@media (min-width: 768px) {
|
|
.video-grid { grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); gap: 16px; }
|
|
}
|
|
@media (min-width: 1200px) {
|
|
.video-grid { grid-template-columns: repeat(auto-fill, minmax(160px, 1fr)); }
|
|
}
|
|
|
|
.video-card {
|
|
background: var(--panel);
|
|
border-radius: var(--card-radius);
|
|
overflow: hidden;
|
|
border: 1px solid var(--line);
|
|
cursor: pointer;
|
|
position: relative;
|
|
}
|
|
.video-poster {
|
|
width: 100%;
|
|
aspect-ratio: 9/16;
|
|
object-fit: cover;
|
|
display: block;
|
|
background: #333;
|
|
}
|
|
.video-info {
|
|
padding: 10px;
|
|
}
|
|
.video-title {
|
|
font-size: 13px;
|
|
font-weight: 500;
|
|
color: var(--text-primary);
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
display: -webkit-box;
|
|
-webkit-line-clamp: 2;
|
|
-webkit-box-orient: vertical;
|
|
line-height: 1.4;
|
|
min-height: 36px;
|
|
}
|
|
.video-meta {
|
|
font-size: 11px;
|
|
color: var(--text-muted);
|
|
margin-top: 4px;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
white-space: nowrap;
|
|
}
|
|
.video-remarks {
|
|
position: absolute;
|
|
top: 8px;
|
|
right: 8px;
|
|
background: var(--accent);
|
|
color: #fff;
|
|
font-size: 10px;
|
|
padding: 2px 8px;
|
|
border-radius: 4px;
|
|
font-weight: 500;
|
|
}
|
|
|
|
.loading, .empty, .error {
|
|
text-align: center;
|
|
padding: 40px 20px;
|
|
color: var(--text-muted);
|
|
font-size: 14px;
|
|
}
|
|
.spinner {
|
|
width: 40px;
|
|
height: 40px;
|
|
border: 3px solid var(--line);
|
|
border-top-color: var(--accent);
|
|
border-radius: 50%;
|
|
animation: spin 1s linear infinite;
|
|
margin: 0 auto 16px;
|
|
}
|
|
@keyframes spin { to { transform: rotate(360deg); } }
|
|
|
|
.pagination {
|
|
display: flex;
|
|
justify-content: center;
|
|
align-items: center;
|
|
gap: 8px;
|
|
padding: 24px 0;
|
|
}
|
|
.page-btn {
|
|
padding: 8px 16px;
|
|
border-radius: 8px;
|
|
border: 1px solid var(--line);
|
|
background: var(--panel-soft);
|
|
color: var(--text-secondary);
|
|
font-size: 14px;
|
|
cursor: pointer;
|
|
}
|
|
.page-btn:disabled {
|
|
opacity: .4;
|
|
cursor: not-allowed;
|
|
}
|
|
.page-info {
|
|
font-size: 14px;
|
|
color: var(--text-secondary);
|
|
padding: 0 12px;
|
|
}
|
|
|
|
.detail-sheet {
|
|
position: fixed;
|
|
inset: 0;
|
|
z-index: 200;
|
|
background: transparent;
|
|
display: none;
|
|
overflow-y: auto;
|
|
}
|
|
.detail-sheet.active { display: block; }
|
|
.detail-backdrop {
|
|
position: fixed;
|
|
inset: 0;
|
|
background: rgba(0,0,0,.6);
|
|
backdrop-filter: blur(8px);
|
|
}
|
|
.detail-content {
|
|
position: relative;
|
|
z-index: 1;
|
|
max-width: 800px;
|
|
margin: 0 auto;
|
|
padding: 20px;
|
|
}
|
|
.detail-header {
|
|
display: flex;
|
|
gap: 20px;
|
|
margin-bottom: 24px;
|
|
background: var(--panel);
|
|
border-radius: var(--card-radius);
|
|
padding: 20px;
|
|
border: 1px solid var(--line);
|
|
}
|
|
.detail-poster {
|
|
width: 140px;
|
|
border-radius: var(--card-radius);
|
|
flex-shrink: 0;
|
|
aspect-ratio: 9/16;
|
|
object-fit: cover;
|
|
}
|
|
.detail-info { flex: 1; min-width: 0; }
|
|
.detail-title {
|
|
font-size: 22px;
|
|
font-weight: 700;
|
|
margin-bottom: 12px;
|
|
color: var(--text-primary);
|
|
}
|
|
.detail-meta-row {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
gap: 12px;
|
|
margin-bottom: 12px;
|
|
font-size: 14px;
|
|
color: var(--text-secondary);
|
|
}
|
|
.detail-meta-row span {
|
|
background: var(--panel-soft);
|
|
padding: 4px 12px;
|
|
border-radius: 4px;
|
|
border: 1px solid var(--line);
|
|
}
|
|
.detail-desc {
|
|
font-size: 14px;
|
|
line-height: 1.8;
|
|
color: var(--text-secondary);
|
|
max-height: 120px;
|
|
overflow: hidden;
|
|
}
|
|
.detail-desc.expanded { max-height: none; }
|
|
.detail-desc-toggle {
|
|
color: var(--accent);
|
|
cursor: pointer;
|
|
font-size: 13px;
|
|
margin-top: 4px;
|
|
display: inline-block;
|
|
}
|
|
.detail-actions {
|
|
display: flex;
|
|
gap: 12px;
|
|
margin-top: 16px;
|
|
}
|
|
.detail-play-btn {
|
|
padding: 12px 32px;
|
|
border-radius: 8px;
|
|
border: none;
|
|
background: var(--accent);
|
|
color: #fff;
|
|
font-size: 16px;
|
|
font-weight: 600;
|
|
cursor: pointer;
|
|
}
|
|
.detail-close {
|
|
position: fixed;
|
|
top: 16px;
|
|
right: 16px;
|
|
z-index: 201;
|
|
width: 40px;
|
|
height: 40px;
|
|
border-radius: 50%;
|
|
border: 1px solid var(--line);
|
|
background: var(--panel-strong);
|
|
color: var(--text-primary);
|
|
font-size: 20px;
|
|
cursor: pointer;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
}
|
|
|
|
.episode-section {
|
|
background: var(--panel);
|
|
border-radius: var(--card-radius);
|
|
padding: 16px;
|
|
border: 1px solid var(--line);
|
|
margin-bottom: 16px;
|
|
}
|
|
.episode-section-title {
|
|
font-size: 16px;
|
|
font-weight: 600;
|
|
margin-bottom: 12px;
|
|
color: var(--text-primary);
|
|
}
|
|
.episode-grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fill, minmax(80px, 1fr));
|
|
gap: 8px;
|
|
}
|
|
.episode-btn {
|
|
padding: 10px;
|
|
border-radius: 6px;
|
|
border: 1px solid var(--line);
|
|
background: var(--panel-soft);
|
|
color: var(--text-secondary);
|
|
font-size: 13px;
|
|
cursor: pointer;
|
|
text-align: center;
|
|
}
|
|
|
|
.settings-panel {
|
|
position: fixed;
|
|
top: 60px;
|
|
right: 16px;
|
|
z-index: 150;
|
|
background: var(--panel-strong);
|
|
border: 1px solid var(--line);
|
|
border-radius: var(--card-radius);
|
|
padding: 16px;
|
|
min-width: 280px;
|
|
display: none;
|
|
box-shadow: 0 8px 32px rgba(0,0,0,.4);
|
|
}
|
|
.settings-panel.active { display: block; }
|
|
.settings-row {
|
|
margin-bottom: 12px;
|
|
}
|
|
.settings-row label {
|
|
display: block;
|
|
font-size: 13px;
|
|
color: var(--text-secondary);
|
|
margin-bottom: 6px;
|
|
}
|
|
.settings-row input {
|
|
width: 100%;
|
|
padding: 8px 12px;
|
|
border-radius: 6px;
|
|
border: 1px solid var(--line);
|
|
background: var(--panel-soft);
|
|
color: var(--text-primary);
|
|
font-size: 14px;
|
|
outline: none;
|
|
}
|
|
.settings-save {
|
|
width: 100%;
|
|
padding: 10px;
|
|
border-radius: 6px;
|
|
border: none;
|
|
background: var(--accent);
|
|
color: #fff;
|
|
font-size: 14px;
|
|
cursor: pointer;
|
|
margin-top: 8px;
|
|
}
|
|
|
|
::-webkit-scrollbar { width: 6px; height: 6px; }
|
|
::-webkit-scrollbar-track { background: transparent; }
|
|
::-webkit-scrollbar-thumb { background: var(--line); border-radius: 3px; }
|
|
</style>
|
|
</head>
|
|
|
|
<body>
|
|
<div class="app" id="app">
|
|
<header class="header">
|
|
<div class="logo">🍩 甜圈短剧</div>
|
|
<div class="search-box">
|
|
<input type="text" id="searchInput" placeholder="搜索短剧...">
|
|
<button class="search-btn" id="searchBtn">🔍</button>
|
|
</div>
|
|
<div class="header-actions">
|
|
<button class="header-btn" id="historyBtn">📜 历史</button>
|
|
<button class="header-btn" id="settingsBtn">⚙️ 设置</button>
|
|
</div>
|
|
</header>
|
|
|
|
<div class="category-bar" id="categoryBar"></div>
|
|
<main class="content" id="content">
|
|
<div class="loading">
|
|
<div class="spinner"></div>正在加载...
|
|
</div>
|
|
</main>
|
|
<div class="pagination" id="pagination" style="display:none;"></div>
|
|
</div>
|
|
|
|
<div class="detail-sheet" id="detailSheet">
|
|
<div class="detail-backdrop" id="detailBackdrop"></div>
|
|
<button class="detail-close" id="detailClose">✕</button>
|
|
<div class="detail-content" id="detailContent"></div>
|
|
</div>
|
|
|
|
<div class="settings-panel" id="settingsPanel">
|
|
<div class="settings-row">
|
|
<label>API 基础地址</label>
|
|
<input type="text" id="apiBaseInput" value="https://api.apibdzy.com" placeholder="https://api.apibdzy.com">
|
|
</div>
|
|
<div class="settings-row">
|
|
<label>每页数量</label>
|
|
<input type="number" id="pageSizeInput" value="24" min="6" max="50">
|
|
</div>
|
|
<button class="settings-save" id="settingsSave">保存设置</button>
|
|
</div>
|
|
|
|
<script>
|
|
var CONFIG = {
|
|
apiBase: 'https://api.apibdzy.com',
|
|
pageSize: 24,
|
|
cacheKey: 'tianquan_config_v3'
|
|
};
|
|
|
|
var state = {
|
|
currentCategory: '',
|
|
currentPage: 1,
|
|
totalPages: 1,
|
|
categories: [],
|
|
videos: [],
|
|
isSearch: false,
|
|
searchKeyword: ''
|
|
};
|
|
|
|
var $ = function(sel) { return document.querySelector(sel); };
|
|
|
|
function loadConfig() {
|
|
try {
|
|
var saved = localStorage.getItem(CONFIG.cacheKey);
|
|
if (saved) {
|
|
var parsed = JSON.parse(saved);
|
|
if (parsed.apiBase) CONFIG.apiBase = parsed.apiBase;
|
|
if (parsed.pageSize) CONFIG.pageSize = parsed.pageSize;
|
|
}
|
|
} catch (e) { console.log('配置加载失败', e); }
|
|
}
|
|
|
|
function saveConfig() {
|
|
var apiBase = $('#apiBaseInput').value.trim() || 'https://api.apibdzy.com';
|
|
var pageSize = parseInt($('#pageSizeInput').value) || 24;
|
|
CONFIG.apiBase = apiBase;
|
|
CONFIG.pageSize = pageSize;
|
|
localStorage.setItem(CONFIG.cacheKey, JSON.stringify({ apiBase: apiBase, pageSize: pageSize }));
|
|
$('#settingsPanel').classList.remove('active');
|
|
showToast('设置已保存');
|
|
loadHome();
|
|
}
|
|
|
|
function showToast(msg) {
|
|
var div = document.createElement('div');
|
|
div.style.cssText = 'position:fixed;bottom:80px;left:50%;transform:translateX(-50%);background:rgba(30,30,46,0.95);color:#fff;padding:10px 20px;border-radius:20px;font-size:14px;z-index:999;border:1px solid rgba(255,255,255,0.12);';
|
|
div.textContent = msg;
|
|
document.body.appendChild(div);
|
|
setTimeout(function() { div.remove(); }, 2000);
|
|
}
|
|
|
|
function buildUrl(path, params) {
|
|
var base = CONFIG.apiBase;
|
|
if (base.charAt(base.length - 1) === '/') {
|
|
base = base.substring(0, base.length - 1);
|
|
}
|
|
var url = base + path;
|
|
var qs = [];
|
|
for (var key in params) {
|
|
if (params[key] !== '' && params[key] !== undefined && params[key] !== null) {
|
|
qs.push(key + '=' + encodeURIComponent(params[key]));
|
|
}
|
|
}
|
|
if (qs.length > 0) {
|
|
url = url + '?' + qs.join('&');
|
|
}
|
|
return url;
|
|
}
|
|
|
|
function apiRequest(url, callback) {
|
|
console.log('请求URL:', url);
|
|
|
|
if (typeof req !== 'undefined') {
|
|
try {
|
|
var res = req(url, { method: 'GET', timeout: 15000 });
|
|
console.log('req 返回类型:', typeof res);
|
|
if (res) {
|
|
var text = '';
|
|
if (typeof res === 'string') {
|
|
text = res;
|
|
} else if (res.content && typeof res.content === 'string') {
|
|
text = res.content;
|
|
} else if (res.content) {
|
|
text = JSON.stringify(res.content);
|
|
} else {
|
|
text = JSON.stringify(res);
|
|
}
|
|
console.log('req 返回内容前100字:', text.substring(0, 100));
|
|
try {
|
|
var data = JSON.parse(text);
|
|
callback(null, data);
|
|
return;
|
|
} catch(e) {
|
|
callback(new Error('JSON解析失败: ' + e.message + ', 内容:' + text.substring(0, 50)), null);
|
|
return;
|
|
}
|
|
}
|
|
} catch (e) {
|
|
console.log('req 失败:', e.message || e);
|
|
}
|
|
} else {
|
|
console.log('req 未定义');
|
|
}
|
|
|
|
if (typeof http !== 'undefined') {
|
|
try {
|
|
http(url, { method: 'GET', timeout: 15000 }).then(function(res) {
|
|
if (res) {
|
|
var text = '';
|
|
if (typeof res === 'string') {
|
|
text = res;
|
|
} else if (res.content && typeof res.content === 'string') {
|
|
text = res.content;
|
|
} else if (res.content) {
|
|
text = JSON.stringify(res.content);
|
|
} else {
|
|
text = JSON.stringify(res);
|
|
}
|
|
try {
|
|
var data = JSON.parse(text);
|
|
callback(null, data);
|
|
} catch(e) {
|
|
callback(new Error('JSON解析失败: ' + e.message), null);
|
|
}
|
|
} else {
|
|
callback(new Error('http 返回空'), null);
|
|
}
|
|
}).catch(function(e) {
|
|
callback(e, null);
|
|
});
|
|
return;
|
|
} catch (e) {
|
|
console.log('http 调用失败:', e.message || e);
|
|
}
|
|
} else {
|
|
console.log('http 未定义');
|
|
}
|
|
|
|
if (typeof fetch !== 'undefined') {
|
|
try {
|
|
fetch(url).then(function(res) {
|
|
return res.json();
|
|
}).then(function(data) {
|
|
callback(null, data);
|
|
}).catch(function(e) {
|
|
callback(e, null);
|
|
});
|
|
return;
|
|
} catch (e) {
|
|
console.log('fetch 失败:', e.message || e);
|
|
}
|
|
} else {
|
|
console.log('fetch 未定义');
|
|
}
|
|
|
|
callback(new Error('无可用的请求方式(req/http/fetch都未定义)'), null);
|
|
}
|
|
|
|
function getPosterUrl(url) {
|
|
if (!url) return '';
|
|
if (typeof fm !== 'undefined' && fm.res) {
|
|
return fm.res(url, { headers: { Referer: CONFIG.apiBase + '/' } });
|
|
}
|
|
return url;
|
|
}
|
|
|
|
function loadCategories() {
|
|
var url = buildUrl('/api.php/provide/vod/', { ac: 'list', pg: '1' });
|
|
apiRequest(url, function(err, data) {
|
|
if (err) {
|
|
console.error('分类加载失败:', err.message || err);
|
|
state.categories = [
|
|
{ type_id: '1', type_name: '电影' },
|
|
{ type_id: '2', type_name: '电视剧' },
|
|
{ type_id: '3', type_name: '综艺' },
|
|
{ type_id: '4', type_name: '动漫' },
|
|
{ type_id: '54', type_name: '爽文短剧' }
|
|
];
|
|
} else if (data && data.class && data.class.length > 0) {
|
|
state.categories = data.class;
|
|
} else {
|
|
console.log('分类数据异常:', data);
|
|
state.categories = [
|
|
{ type_id: '1', type_name: '电影' },
|
|
{ type_id: '2', type_name: '电视剧' },
|
|
{ type_id: '3', type_name: '综艺' },
|
|
{ type_id: '4', type_name: '动漫' }
|
|
];
|
|
}
|
|
renderCategories();
|
|
});
|
|
}
|
|
|
|
function loadHome() {
|
|
state.isSearch = false;
|
|
state.searchKeyword = '';
|
|
state.currentPage = 1;
|
|
$('#searchInput').value = '';
|
|
|
|
$('#content').innerHTML = '<div class="loading"><div class="spinner"></div>正在加载...</div>';
|
|
|
|
var url = buildUrl('/api.php/provide/vod/', { ac: 'list', pg: '1' });
|
|
apiRequest(url, function(err, data) {
|
|
if (err) {
|
|
console.error('首页加载失败:', err.message || err);
|
|
$('#content').innerHTML = '<div class="error">加载失败: ' + (err.message || err) + '<br><small>当前API: ' + CONFIG.apiBase + '</small><br><small>请检查网络或更换API地址</small></div>';
|
|
return;
|
|
}
|
|
console.log('首页数据:', data ? 'code=' + data.code + ', list长度=' + (data.list ? data.list.length : 0) : '空');
|
|
state.videos = data.list || [];
|
|
state.totalPages = parseInt(data.pagecount) || 1;
|
|
state.currentPage = parseInt(data.page) || 1;
|
|
renderVideos(state.videos, '🔥 热门推荐');
|
|
renderPagination();
|
|
});
|
|
}
|
|
|
|
function loadCategory(typeId, page) {
|
|
page = page || 1;
|
|
state.isSearch = false;
|
|
state.searchKeyword = '';
|
|
state.currentCategory = typeId;
|
|
state.currentPage = page;
|
|
$('#searchInput').value = '';
|
|
|
|
$('#content').innerHTML = '<div class="loading"><div class="spinner"></div>正在加载...</div>';
|
|
|
|
var url = buildUrl('/api.php/provide/vod/', { ac: 'list', t: typeId, pg: String(page) });
|
|
apiRequest(url, function(err, data) {
|
|
if (err) {
|
|
$('#content').innerHTML = '<div class="error">加载失败: ' + (err.message || err) + '</div>';
|
|
return;
|
|
}
|
|
state.videos = data.list || [];
|
|
state.totalPages = parseInt(data.pagecount) || 1;
|
|
state.currentPage = parseInt(data.page) || 1;
|
|
var catName = '';
|
|
for (var i = 0; i < state.categories.length; i++) {
|
|
if (state.categories[i].type_id == typeId) {
|
|
catName = state.categories[i].type_name;
|
|
break;
|
|
}
|
|
}
|
|
renderVideos(state.videos, catName || '影片列表');
|
|
renderPagination();
|
|
window.scrollTo(0, 0);
|
|
});
|
|
}
|
|
|
|
function searchVideos(keyword, page) {
|
|
page = page || 1;
|
|
if (!keyword.trim()) return;
|
|
state.isSearch = true;
|
|
state.searchKeyword = keyword;
|
|
state.currentPage = page;
|
|
|
|
$('#content').innerHTML = '<div class="loading"><div class="spinner"></div>搜索中...</div>';
|
|
|
|
var url = buildUrl('/api.php/provide/vod/', { ac: 'list', wd: keyword, pg: String(page) });
|
|
apiRequest(url, function(err, data) {
|
|
if (err) {
|
|
$('#content').innerHTML = '<div class="error">搜索失败: ' + (err.message || err) + '</div>';
|
|
return;
|
|
}
|
|
state.videos = data.list || [];
|
|
state.totalPages = parseInt(data.pagecount) || 1;
|
|
state.currentPage = parseInt(data.page) || 1;
|
|
renderVideos(state.videos, '「' + keyword + '」搜索结果');
|
|
renderPagination();
|
|
window.scrollTo(0, 0);
|
|
});
|
|
}
|
|
|
|
function loadDetail(vodId) {
|
|
var url = buildUrl('/api.php/provide/vod/', { ac: 'detail', ids: vodId });
|
|
apiRequest(url, function(err, data) {
|
|
if (err) {
|
|
showToast('加载详情失败: ' + (err.message || err));
|
|
return;
|
|
}
|
|
if (data.list && data.list.length > 0) {
|
|
renderDetail(data.list[0]);
|
|
} else {
|
|
showToast('未找到详情数据');
|
|
}
|
|
});
|
|
}
|
|
|
|
function renderCategories() {
|
|
var bar = $('#categoryBar');
|
|
var html = '<div class="category-tab active" data-type="">全部</div>';
|
|
for (var i = 0; i < state.categories.length; i++) {
|
|
var c = state.categories[i];
|
|
html += '<div class="category-tab" data-type="' + c.type_id + '">' + c.type_name + '</div>';
|
|
}
|
|
bar.innerHTML = html;
|
|
|
|
var tabs = bar.querySelectorAll('.category-tab');
|
|
for (var j = 0; j < tabs.length; j++) {
|
|
tabs[j].addEventListener('click', function() {
|
|
var typeId = this.dataset.type;
|
|
if (typeId === '') {
|
|
loadHome();
|
|
} else {
|
|
loadCategory(typeId);
|
|
}
|
|
for (var k = 0; k < tabs.length; k++) {
|
|
tabs[k].classList.remove('active');
|
|
}
|
|
this.classList.add('active');
|
|
});
|
|
}
|
|
}
|
|
|
|
function renderVideos(videos, title) {
|
|
var content = $('#content');
|
|
if (!videos || videos.length === 0) {
|
|
content.innerHTML = '<div class="empty">暂无内容</div>';
|
|
return;
|
|
}
|
|
|
|
var html = '<div class="section-title">' + title + '</div><div class="video-grid" id="videoGrid">';
|
|
for (var i = 0; i < videos.length; i++) {
|
|
var v = videos[i];
|
|
var remarks = v.vod_remarks ? '<span class="video-remarks">' + v.vod_remarks + '</span>' : '';
|
|
var year = v.vod_year ? '· ' + v.vod_year : '';
|
|
html += '<div class="video-card" data-id="' + v.vod_id + '">' +
|
|
'<img class="video-poster" src="' + getPosterUrl(v.vod_pic) + '" alt="' + v.vod_name + '" loading="lazy" onerror="this.style.display='none'">' +
|
|
remarks +
|
|
'<div class="video-info">' +
|
|
'<div class="video-title">' + v.vod_name + '</div>' +
|
|
'<div class="video-meta">' + (v.type_name || '') + year + '</div>' +
|
|
'</div></div>';
|
|
}
|
|
html += '</div>';
|
|
content.innerHTML = html;
|
|
|
|
var cards = $('#videoGrid').querySelectorAll('.video-card');
|
|
for (var j = 0; j < cards.length; j++) {
|
|
cards[j].addEventListener('click', function() {
|
|
var id = this.dataset.id;
|
|
var name = this.querySelector('.video-title').textContent;
|
|
var pic = this.querySelector('.video-poster').src;
|
|
if (typeof fm !== 'undefined' && fm.vod) {
|
|
fm.vod('tianquan', id, name, pic);
|
|
} else {
|
|
openDetail(id);
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
function renderPagination() {
|
|
var pag = $('#pagination');
|
|
if (state.totalPages <= 1) {
|
|
pag.style.display = 'none';
|
|
return;
|
|
}
|
|
pag.style.display = 'flex';
|
|
var prevDisabled = state.currentPage <= 1 ? 'disabled' : '';
|
|
var nextDisabled = state.currentPage >= state.totalPages ? 'disabled' : '';
|
|
pag.innerHTML =
|
|
'<button class="page-btn" id="prevBtn" ' + prevDisabled + '>上一页</button>' +
|
|
'<span class="page-info">' + state.currentPage + ' / ' + state.totalPages + '</span>' +
|
|
'<button class="page-btn" id="nextBtn" ' + nextDisabled + '>下一页</button>';
|
|
|
|
$('#prevBtn').addEventListener('click', function() {
|
|
if (state.currentPage > 1) {
|
|
if (state.isSearch) {
|
|
searchVideos(state.searchKeyword, state.currentPage - 1);
|
|
} else if (state.currentCategory) {
|
|
loadCategory(state.currentCategory, state.currentPage - 1);
|
|
} else {
|
|
loadHomePage(state.currentPage - 1);
|
|
}
|
|
}
|
|
});
|
|
|
|
$('#nextBtn').addEventListener('click', function() {
|
|
if (state.currentPage < state.totalPages) {
|
|
if (state.isSearch) {
|
|
searchVideos(state.searchKeyword, state.currentPage + 1);
|
|
} else if (state.currentCategory) {
|
|
loadCategory(state.currentCategory, state.currentPage + 1);
|
|
} else {
|
|
loadHomePage(state.currentPage + 1);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
function loadHomePage(page) {
|
|
$('#content').innerHTML = '<div class="loading"><div class="spinner"></div>正在加载...</div>';
|
|
var url = buildUrl('/api.php/provide/vod/', { ac: 'list', pg: String(page) });
|
|
apiRequest(url, function(err, data) {
|
|
if (err) {
|
|
$('#content').innerHTML = '<div class="error">加载失败: ' + (err.message || err) + '</div>';
|
|
return;
|
|
}
|
|
state.videos = data.list || [];
|
|
state.totalPages = parseInt(data.pagecount) || 1;
|
|
state.currentPage = parseInt(data.page) || 1;
|
|
renderVideos(state.videos, '🔥 热门推荐');
|
|
renderPagination();
|
|
window.scrollTo(0, 0);
|
|
});
|
|
}
|
|
|
|
function renderDetail(vod) {
|
|
var sheet = $('#detailSheet');
|
|
var content = $('#detailContent');
|
|
|
|
var playFrom = vod.vod_play_from ? vod.vod_play_from.split('$$$') : [];
|
|
var playUrl = vod.vod_play_url ? vod.vod_play_url.split('$$$') : [];
|
|
|
|
var episodesHtml = '';
|
|
for (var idx = 0; idx < playFrom.length; idx++) {
|
|
var from = playFrom[idx];
|
|
var urls = playUrl[idx] ? playUrl[idx].split('#') : [];
|
|
if (urls.length > 0) {
|
|
episodesHtml += '<div class="episode-section"><div class="episode-section-title">' + from + '</div><div class="episode-grid">';
|
|
for (var i = 0; i < urls.length; i++) {
|
|
var parts = urls[i].split('$');
|
|
var name = parts[0] || ('第' + (i+1) + '集');
|
|
var url = parts[1] || urls[i];
|
|
episodesHtml += '<button class="episode-btn" data-url="' + url + '" data-name="' + name + '">' + name + '</button>';
|
|
}
|
|
episodesHtml += '</div></div>';
|
|
}
|
|
}
|
|
|
|
var metaHtml = '';
|
|
if (vod.vod_year) metaHtml += '<span>' + vod.vod_year + '</span>';
|
|
if (vod.type_name) metaHtml += '<span>' + vod.type_name + '</span>';
|
|
if (vod.vod_area) metaHtml += '<span>' + vod.vod_area + '</span>';
|
|
if (vod.vod_remarks) metaHtml += '<span>' + vod.vod_remarks + '</span>';
|
|
|
|
var directorHtml = vod.vod_director ? '<div class="detail-meta-row"><span>导演: ' + vod.vod_director + '</span></div>' : '';
|
|
var actorHtml = vod.vod_actor ? '<div class="detail-meta-row"><span>主演: ' + vod.vod_actor + '</span></div>' : '';
|
|
var descToggle = (vod.vod_content && vod.vod_content.length > 100) ? '<span class="detail-desc-toggle" id="descToggle">展开更多</span>' : '';
|
|
|
|
content.innerHTML =
|
|
'<div class="detail-header">' +
|
|
'<img class="detail-poster" src="' + getPosterUrl(vod.vod_pic) + '" alt="' + vod.vod_name + '" onerror="this.style.display='none'">' +
|
|
'<div class="detail-info">' +
|
|
'<div class="detail-title">' + vod.vod_name + '</div>' +
|
|
'<div class="detail-meta-row">' + metaHtml + '</div>' +
|
|
directorHtml + actorHtml +
|
|
'<div class="detail-desc" id="detailDesc">' + (vod.vod_content || '暂无简介') + '</div>' +
|
|
descToggle +
|
|
'<div class="detail-actions"><button class="detail-play-btn" id="detailPlayBtn">▶ 立即播放</button></div>' +
|
|
'</div></div>' + episodesHtml;
|
|
|
|
sheet.classList.add('active');
|
|
|
|
$('#detailClose').addEventListener('click', closeDetail);
|
|
$('#detailBackdrop').addEventListener('click', closeDetail);
|
|
|
|
var desc = $('#detailDesc');
|
|
var toggle = $('#descToggle');
|
|
if (toggle) {
|
|
toggle.addEventListener('click', function() {
|
|
desc.classList.toggle('expanded');
|
|
toggle.textContent = desc.classList.contains('expanded') ? '收起' : '展开更多';
|
|
});
|
|
}
|
|
|
|
$('#detailPlayBtn').addEventListener('click', function() {
|
|
if (playUrl.length > 0 && playUrl[0]) {
|
|
var firstEp = playUrl[0].split('#')[0];
|
|
var parts = firstEp.split('$');
|
|
var name = parts[0] || '第1集';
|
|
var url = parts[1] || firstEp;
|
|
playVideo(url, vod.vod_name + ' - ' + name);
|
|
}
|
|
});
|
|
|
|
var epBtns = content.querySelectorAll('.episode-btn');
|
|
for (var j = 0; j < epBtns.length; j++) {
|
|
epBtns[j].addEventListener('click', function() {
|
|
playVideo(this.dataset.url, vod.vod_name + ' - ' + this.dataset.name);
|
|
});
|
|
}
|
|
}
|
|
|
|
function closeDetail() {
|
|
$('#detailSheet').classList.remove('active');
|
|
}
|
|
|
|
function playVideo(url, title) {
|
|
if (!url) { showToast('播放地址无效'); return; }
|
|
if (url.charAt(0) === '/') {
|
|
var base = CONFIG.apiBase;
|
|
if (base.charAt(base.length - 1) === '/') {
|
|
base = base.substring(0, base.length - 1);
|
|
}
|
|
url = base + url;
|
|
}
|
|
if (typeof fm !== 'undefined' && fm.play) {
|
|
try {
|
|
fm.play(url, title, { headers: { Referer: CONFIG.apiBase + '/' } });
|
|
} catch (e) {
|
|
showToast('播放失败: ' + (e.message || e));
|
|
}
|
|
} else {
|
|
showToast('App环境才可播放');
|
|
console.log('播放地址:', url);
|
|
}
|
|
}
|
|
|
|
function openDetail(vodId) {
|
|
loadDetail(vodId);
|
|
}
|
|
|
|
function handleSearch() {
|
|
var keyword = $('#searchInput').value.trim();
|
|
if (keyword) {
|
|
searchVideos(keyword);
|
|
}
|
|
}
|
|
|
|
function showHistory() {
|
|
if (typeof fm !== 'undefined' && fm.history) {
|
|
try {
|
|
fm.history().then(function(history) {
|
|
if (history && history.length > 0) {
|
|
state.videos = [];
|
|
for (var i = 0; i < history.length; i++) {
|
|
var h = history[i];
|
|
state.videos.push({
|
|
vod_id: h.key,
|
|
vod_name: h.vodName,
|
|
vod_pic: h.vodPic,
|
|
vod_remarks: h.vodRemarks,
|
|
type_name: h.vodFlag
|
|
});
|
|
}
|
|
renderVideos(state.videos, '📜 最近观看');
|
|
$('#pagination').style.display = 'none';
|
|
} else {
|
|
$('#content').innerHTML = '<div class="empty">暂无观看记录</div>';
|
|
}
|
|
}).catch(function(e) {
|
|
showToast('获取历史记录失败');
|
|
});
|
|
} catch (e) {
|
|
showToast('获取历史记录失败');
|
|
}
|
|
} else {
|
|
showToast('请在App内查看历史记录');
|
|
}
|
|
}
|
|
|
|
function bindEvents() {
|
|
$('#searchBtn').addEventListener('click', handleSearch);
|
|
$('#searchInput').addEventListener('keypress', function(e) {
|
|
if (e.key === 'Enter') handleSearch();
|
|
});
|
|
|
|
$('#historyBtn').addEventListener('click', showHistory);
|
|
|
|
$('#settingsBtn').addEventListener('click', function() {
|
|
$('#settingsPanel').classList.toggle('active');
|
|
});
|
|
|
|
$('#settingsSave').addEventListener('click', saveConfig);
|
|
|
|
document.addEventListener('click', function(e) {
|
|
if (!e.target.closest('#settingsPanel') && !e.target.closest('#settingsBtn')) {
|
|
$('#settingsPanel').classList.remove('active');
|
|
}
|
|
});
|
|
}
|
|
|
|
function init() {
|
|
console.log('=== 甜圈短剧初始化 ===');
|
|
console.log('环境检查:');
|
|
console.log('- req 定义:', typeof req !== 'undefined');
|
|
console.log('- http 定义:', typeof http !== 'undefined');
|
|
console.log('- fetch 定义:', typeof fetch !== 'undefined');
|
|
console.log('- fm 定义:', typeof fm !== 'undefined');
|
|
console.log('- localStorage 定义:', typeof localStorage !== 'undefined');
|
|
|
|
loadConfig();
|
|
$('#apiBaseInput').value = CONFIG.apiBase;
|
|
$('#pageSizeInput').value = CONFIG.pageSize;
|
|
|
|
bindEvents();
|
|
loadCategories();
|
|
loadHome();
|
|
|
|
if (typeof fongmiBridge !== 'undefined' || typeof fm !== 'undefined' || typeof fongmi !== 'undefined') {
|
|
document.documentElement.classList.add('fm-native');
|
|
}
|
|
|
|
window.addEventListener('fmsdk', function() {
|
|
console.log('FongMi SDK 已就绪');
|
|
document.documentElement.classList.add('fm-native');
|
|
});
|
|
}
|
|
|
|
if (document.readyState === 'loading') {
|
|
document.addEventListener('DOMContentLoaded', init);
|
|
} else {
|
|
init();
|
|
}
|
|
</script>
|
|
</body>
|
|
|
|
</html> |