feat: 新增平台
This commit is contained in:
@@ -201,6 +201,73 @@
|
||||
@keyframes spin {
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
/* 输入框按钮组 */
|
||||
.input-group {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.input-group .url-input {
|
||||
flex: 1;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.input-btn {
|
||||
padding: 0 1.25rem;
|
||||
font-size: 0.875rem;
|
||||
font-weight: 500;
|
||||
border-radius: 12px;
|
||||
border: 2px solid var(--primary-200);
|
||||
background: white;
|
||||
color: var(--primary-600);
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.input-btn:hover {
|
||||
background: var(--primary-50);
|
||||
border-color: var(--primary-400);
|
||||
}
|
||||
|
||||
.input-btn:active {
|
||||
transform: scale(0.96);
|
||||
}
|
||||
|
||||
/* 进度条 */
|
||||
.progress-container {
|
||||
display: none;
|
||||
margin-top: 1.5rem;
|
||||
}
|
||||
|
||||
.progress-container.show {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.progress-label {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 0.5rem;
|
||||
font-size: 0.875rem;
|
||||
color: var(--secondary-600);
|
||||
}
|
||||
|
||||
.progress-bar {
|
||||
height: 8px;
|
||||
background: var(--secondary-100);
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.progress-fill {
|
||||
height: 100%;
|
||||
background: linear-gradient(90deg, #4f46e5 0%, #7c3aed 100%);
|
||||
border-radius: 4px;
|
||||
width: 0%;
|
||||
transition: width 0.3s ease;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
@@ -217,22 +284,36 @@
|
||||
|
||||
<div class="main-card">
|
||||
<div class="input-wrapper">
|
||||
<input type="text" class="url-input" id="videoUrl" placeholder="在此粘贴 抖音/TikTok/B站 视频链接...">
|
||||
<div class="input-group">
|
||||
<input type="text" class="url-input" id="videoUrl" placeholder="在此粘贴 抖音/TikTok/B站/快手/皮皮虾/微博 视频链接...">
|
||||
<button class="input-btn" onclick="pasteUrl()">粘贴</button>
|
||||
<button class="input-btn" onclick="clearAll()">清空</button>
|
||||
</div>
|
||||
</div>
|
||||
<button class="action-btn" onclick="parseVideo()" id="parseBtn">
|
||||
<span class="btn-text">立即解析</span>
|
||||
<div class="loading-spinner"></div>
|
||||
</button>
|
||||
|
||||
<div class="progress-container" id="progressContainer">
|
||||
<div class="progress-label">
|
||||
<span id="progressText">正在解析...</span>
|
||||
<span id="progressPercent">0%</span>
|
||||
</div>
|
||||
<div class="progress-bar">
|
||||
<div class="progress-fill" id="progressFill"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="result" class="result-card">
|
||||
<video id="videoPlayer" class="video-preview" controls></video>
|
||||
<div class="result-content">
|
||||
<h3 class="video-title" id="title"></h3>
|
||||
<p class="video-meta" id="description"></p>
|
||||
<div class="download-actions">
|
||||
<a id="videoLink" class="ui-btn ui-btn-primary" href="" download target="_blank">
|
||||
<button class="ui-btn ui-btn-primary" onclick="downloadVideo()">
|
||||
下载视频
|
||||
</a>
|
||||
</button>
|
||||
<button class="ui-btn ui-btn-primary" onclick="downloadCover()">
|
||||
下载封面
|
||||
</button>
|
||||
@@ -240,12 +321,14 @@
|
||||
复制链接
|
||||
</button>
|
||||
</div>
|
||||
<input type="hidden" id="videoLink" value="">
|
||||
<a id="coverLink" href="" style="display: none;"></a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="nav-links" id="navLinks">
|
||||
<a href="/api-docs" class="nav-link">API 文档</a>
|
||||
<a href="/auth/login" class="nav-link">登录账号</a>
|
||||
<a href="/auth/register" class="nav-link">注册新用户</a>
|
||||
</div>
|
||||
@@ -258,13 +341,103 @@
|
||||
|
||||
<script src="/static/js/ui-components.js"></script>
|
||||
<script>
|
||||
// 粘贴按钮
|
||||
async function pasteUrl() {
|
||||
try {
|
||||
const text = await navigator.clipboard.readText();
|
||||
document.getElementById('videoUrl').value = text;
|
||||
UI.notify('已粘贴', 'success');
|
||||
} catch (e) {
|
||||
UI.notify('无法访问剪贴板,请手动粘贴', 'warning');
|
||||
}
|
||||
}
|
||||
|
||||
// 清空按钮
|
||||
function clearAll() {
|
||||
document.getElementById('videoUrl').value = '';
|
||||
document.getElementById('result').classList.remove('show');
|
||||
document.getElementById('progressContainer').classList.remove('show');
|
||||
document.getElementById('videoPlayer').src = '';
|
||||
document.getElementById('videoPlayer').poster = '';
|
||||
UI.notify('已清空', 'info');
|
||||
}
|
||||
|
||||
// 进度条控制
|
||||
let progressInterval = null;
|
||||
|
||||
function startProgress() {
|
||||
const container = document.getElementById('progressContainer');
|
||||
const fill = document.getElementById('progressFill');
|
||||
const text = document.getElementById('progressText');
|
||||
const percent = document.getElementById('progressPercent');
|
||||
|
||||
container.classList.add('show');
|
||||
let progress = 0;
|
||||
const steps = [
|
||||
{ p: 15, t: '正在连接服务器...' },
|
||||
{ p: 35, t: '正在解析链接...' },
|
||||
{ p: 55, t: '正在获取视频信息...' },
|
||||
{ p: 75, t: '正在提取无水印地址...' },
|
||||
{ p: 90, t: '即将完成...' }
|
||||
];
|
||||
let stepIndex = 0;
|
||||
|
||||
progressInterval = setInterval(() => {
|
||||
if (stepIndex < steps.length && progress >= steps[stepIndex].p - 10) {
|
||||
text.textContent = steps[stepIndex].t;
|
||||
stepIndex++;
|
||||
}
|
||||
if (progress < 90) {
|
||||
progress += Math.random() * 8 + 2;
|
||||
if (progress > 90) progress = 90;
|
||||
fill.style.width = progress + '%';
|
||||
percent.textContent = Math.round(progress) + '%';
|
||||
}
|
||||
}, 300);
|
||||
}
|
||||
|
||||
function completeProgress() {
|
||||
if (progressInterval) {
|
||||
clearInterval(progressInterval);
|
||||
progressInterval = null;
|
||||
}
|
||||
const fill = document.getElementById('progressFill');
|
||||
const text = document.getElementById('progressText');
|
||||
const percent = document.getElementById('progressPercent');
|
||||
|
||||
fill.style.width = '100%';
|
||||
percent.textContent = '100%';
|
||||
text.textContent = '解析完成!';
|
||||
|
||||
setTimeout(() => {
|
||||
document.getElementById('progressContainer').classList.remove('show');
|
||||
fill.style.width = '0%';
|
||||
}, 500);
|
||||
}
|
||||
|
||||
function resetProgress() {
|
||||
if (progressInterval) {
|
||||
clearInterval(progressInterval);
|
||||
progressInterval = null;
|
||||
}
|
||||
const container = document.getElementById('progressContainer');
|
||||
const fill = document.getElementById('progressFill');
|
||||
container.classList.remove('show');
|
||||
fill.style.width = '0%';
|
||||
}
|
||||
|
||||
function extractVideoUrl(text) {
|
||||
const patterns = [
|
||||
/https?:\/\/v\.douyin\.com\/[A-Za-z0-9_-]+/,
|
||||
/https?:\/\/www\.douyin\.com\/video\/\d+/,
|
||||
/https?:\/\/(?:www\.)?bilibili\.com\/video\/[A-Za-z0-9?&=]+/,
|
||||
/https?:\/\/b23\.tv\/[A-Za-z0-9]+/,
|
||||
/https?:\/\/(?:www\.)?tiktok\.com\/@[^\/]+\/video\/\d+/
|
||||
/https?:\/\/(?:www\.)?tiktok\.com\/@[^\/]+\/video\/\d+/,
|
||||
/https?:\/\/(?:www\.)?kuaishou\.com\/[A-Za-z0-9\/_-]+/,
|
||||
/https?:\/\/h5\.pipix\.com\/s\/[A-Za-z0-9_-]+\/?/,
|
||||
/https?:\/\/(?:www\.)?pipix\.com\/[A-Za-z0-9\/_-]+/,
|
||||
/https?:\/\/(?:video\.)?weibo\.com\/show\?fid=[0-9:]+/,
|
||||
/https?:\/\/(?:www\.)?weibo\.com\/[A-Za-z0-9\/]+/
|
||||
];
|
||||
|
||||
for (const pattern of patterns) {
|
||||
@@ -295,6 +468,7 @@
|
||||
resultCard.classList.remove('show');
|
||||
btn.disabled = true;
|
||||
btn.classList.add('loading');
|
||||
startProgress();
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/parse', {
|
||||
@@ -318,6 +492,7 @@
|
||||
UI.notify(error.message, 'error');
|
||||
btn.disabled = false;
|
||||
btn.classList.remove('loading');
|
||||
resetProgress();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -331,6 +506,7 @@
|
||||
UI.notify('解析超时,请稍后重试', 'error');
|
||||
btn.disabled = false;
|
||||
btn.classList.remove('loading');
|
||||
resetProgress();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -350,6 +526,7 @@
|
||||
UI.notify(error.message, 'error');
|
||||
btn.disabled = false;
|
||||
btn.classList.remove('loading');
|
||||
resetProgress();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -360,14 +537,31 @@
|
||||
const btn = document.getElementById('parseBtn');
|
||||
const resultCard = document.getElementById('result');
|
||||
|
||||
completeProgress();
|
||||
|
||||
document.getElementById('title').textContent = data.title || '无标题';
|
||||
document.getElementById('description').textContent = data.description || '';
|
||||
|
||||
const player = document.getElementById('videoPlayer');
|
||||
player.src = data.video_url;
|
||||
player.poster = data.cover;
|
||||
// 先停止并清空当前视频
|
||||
player.pause();
|
||||
player.removeAttribute('src');
|
||||
player.load();
|
||||
|
||||
document.getElementById('videoLink').href = data.video_url;
|
||||
// 使用代理播放视频,绕过防盗链
|
||||
const proxyVideoUrl = '/proxy/download?url=' + encodeURIComponent(data.video_url);
|
||||
player.src = proxyVideoUrl;
|
||||
|
||||
// 封面也使用代理
|
||||
if (data.cover) {
|
||||
const proxyCoverUrl = '/proxy/download?url=' + encodeURIComponent(data.cover);
|
||||
player.poster = proxyCoverUrl;
|
||||
}
|
||||
|
||||
// 强制重新加载视频
|
||||
player.load();
|
||||
|
||||
document.getElementById('videoLink').value = data.video_url;
|
||||
document.getElementById('coverLink').href = data.cover;
|
||||
|
||||
resultCard.classList.add('show');
|
||||
@@ -377,6 +571,18 @@
|
||||
UI.notify('解析成功', 'success');
|
||||
}
|
||||
|
||||
function downloadVideo() {
|
||||
const videoUrl = document.getElementById('videoLink').value;
|
||||
if (!videoUrl) {
|
||||
UI.notify('视频链接无效', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
// 使用代理下载
|
||||
const proxyUrl = '/proxy/download?url=' + encodeURIComponent(videoUrl);
|
||||
window.open(proxyUrl, '_blank');
|
||||
}
|
||||
|
||||
async function downloadCover() {
|
||||
const coverUrl = document.getElementById('coverLink').href;
|
||||
if (!coverUrl) {
|
||||
@@ -434,6 +640,7 @@
|
||||
const data = await response.json();
|
||||
if (data.success) {
|
||||
document.getElementById('navLinks').innerHTML = `
|
||||
<a href="/api-docs" class="nav-link">API 文档</a>
|
||||
<a href="/auth/profile" class="nav-link">个人中心</a>
|
||||
<a href="#" class="nav-link" onclick="logout(); return false;">退出登录</a>
|
||||
`;
|
||||
|
||||
Reference in New Issue
Block a user