301 lines
12 KiB
HTML
301 lines
12 KiB
HTML
{% extends 'base.html' %}
|
|
{% block title %}{{ resource.title }}{% endblock %}
|
|
|
|
{% block breadcrumb %}
|
|
<ol class="breadcrumb mb-0">
|
|
<li class="breadcrumb-item"><a href="{{ url_for('resources.list_resources') }}">我的资源</a></li>
|
|
<li class="breadcrumb-item active text-truncate" style="max-width:200px">{{ resource.title }}</li>
|
|
</ol>
|
|
{% endblock %}
|
|
|
|
{% block content %}
|
|
<div class="row g-4">
|
|
|
|
<!-- ── 左:信息面板 ── -->
|
|
<div class="col-lg-4 col-xl-3">
|
|
<div class="card shadow-sm mb-3">
|
|
<div class="card-body">
|
|
<h5 class="card-title">{{ resource.title }}</h5>
|
|
{% if resource.description %}
|
|
<p class="text-muted small">{{ resource.description }}</p>
|
|
{% endif %}
|
|
|
|
<table class="table table-sm table-borderless small mb-0">
|
|
<tr><td class="text-muted">类型</td>
|
|
<td><span class="badge bg-{{ resource.type_badge_color }}">
|
|
{{ resource.resource_type }}</span></td></tr>
|
|
<tr><td class="text-muted">来源</td>
|
|
<td>{{ resource.source_type }}</td></tr>
|
|
{% if resource.original_name %}
|
|
<tr><td class="text-muted">文件名</td>
|
|
<td class="text-truncate" style="max-width:140px"
|
|
title="{{ resource.original_name }}">
|
|
{{ resource.original_name }}</td></tr>
|
|
{% endif %}
|
|
{% if resource.file_size %}
|
|
<tr><td class="text-muted">大小</td>
|
|
<td>{{ resource.file_size | filesize }}</td></tr>
|
|
{% endif %}
|
|
{% if resource.mime_type %}
|
|
<tr><td class="text-muted">MIME</td>
|
|
<td class="text-truncate" style="max-width:140px">
|
|
{{ resource.mime_type }}</td></tr>
|
|
{% endif %}
|
|
<tr><td class="text-muted">上传时间</td>
|
|
<td>{{ resource.created_at | datetime_fmt }}</td></tr>
|
|
</table>
|
|
|
|
{% if resource.tag_list %}
|
|
<div class="mt-2">
|
|
{% for tag in resource.tag_list %}
|
|
<span class="badge bg-light text-dark border me-1">{{ tag }}</span>
|
|
{% endfor %}
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
<div class="card-footer d-flex gap-2 flex-wrap">
|
|
{% if resource.file_path and resource.download_status in ('na','done') %}
|
|
<a href="{{ url_for('resources.download', resource_id=resource.id) }}"
|
|
class="btn btn-sm btn-outline-primary">
|
|
<i class="bi bi-download me-1"></i>下载
|
|
</a>
|
|
{% endif %}
|
|
<a href="{{ url_for('resources.edit', resource_id=resource.id) }}"
|
|
class="btn btn-sm btn-outline-secondary">
|
|
<i class="bi bi-pencil me-1"></i>编辑
|
|
</a>
|
|
<form action="{{ url_for('resources.delete', resource_id=resource.id) }}"
|
|
method="POST" class="d-inline"
|
|
onsubmit="return confirm('确认删除此资源?')">
|
|
{{ csrf_token_input() }}
|
|
<button class="btn btn-sm btn-outline-danger">
|
|
<i class="bi bi-trash me-1"></i>删除
|
|
</button>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 来源信息 -->
|
|
{% if resource.source_url %}
|
|
<div class="card shadow-sm">
|
|
<div class="card-body small">
|
|
<div class="text-muted mb-1">来源链接</div>
|
|
<div class="text-break" style="word-break:break-all;font-size:0.75rem">
|
|
{{ resource.source_url | truncate_mid(80) }}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
|
|
<!-- ── 右:预览区 ── -->
|
|
<div class="col-lg-8 col-xl-9">
|
|
<div class="card shadow-sm">
|
|
<div class="card-header d-flex justify-content-between align-items-center">
|
|
<span><i class="bi bi-{{ resource.type_icon }} me-2"></i>预览</span>
|
|
{% if resource.download_status in ('pending','downloading') %}
|
|
<span class="badge bg-info" id="statusBadge">
|
|
<i class="bi bi-arrow-down-circle me-1"></i>
|
|
下载中 <span id="progressPct">{{ resource.download_progress }}</span>%
|
|
</span>
|
|
{% elif resource.download_status == 'failed' %}
|
|
<span class="badge bg-danger">
|
|
<i class="bi bi-exclamation-triangle me-1"></i>下载失败
|
|
</span>
|
|
{% endif %}
|
|
</div>
|
|
<div class="card-body p-0 preview-container">
|
|
|
|
{% if resource.download_status in ('pending','downloading') %}
|
|
<!-- 下载进度 -->
|
|
<div class="d-flex flex-column align-items-center justify-content-center p-5" id="downloadingView">
|
|
<div class="spinner-border text-primary mb-3"></div>
|
|
<p class="text-muted mb-2">正在下载,请稍候…</p>
|
|
<div class="progress w-50" style="height:8px">
|
|
<div class="progress-bar progress-bar-striped progress-bar-animated"
|
|
id="progressBar" style="width:{{ resource.download_progress }}%"></div>
|
|
</div>
|
|
<p class="text-muted small mt-2" id="downloadError"></p>
|
|
</div>
|
|
|
|
{% elif resource.download_status == 'failed' %}
|
|
<div class="d-flex flex-column align-items-center justify-content-center p-5 text-danger">
|
|
<i class="bi bi-exclamation-triangle fs-1 mb-2"></i>
|
|
<p>下载失败</p>
|
|
{% if resource.download_error %}
|
|
<p class="text-muted small">{{ resource.download_error }}</p>
|
|
{% endif %}
|
|
</div>
|
|
|
|
{% elif resource.file_path %}
|
|
{%- set file_url = url_for('static', filename=resource.file_path) -%}
|
|
|
|
{% if resource.resource_type == 'image' %}
|
|
<div class="text-center p-3 bg-checkerboard">
|
|
<img src="{{ file_url }}" class="img-fluid rounded" style="max-height:70vh"
|
|
alt="{{ resource.title }}">
|
|
</div>
|
|
|
|
{% elif resource.resource_type == 'audio' %}
|
|
<div class="d-flex flex-column align-items-center justify-content-center p-5">
|
|
<i class="bi bi-music-note-beamed text-warning mb-3" style="font-size:5rem"></i>
|
|
<p class="fw-medium mb-3">{{ resource.original_name or resource.title }}</p>
|
|
<audio controls class="w-100" style="max-width:600px">
|
|
<source src="{{ file_url }}" type="{{ resource.mime_type or 'audio/mpeg' }}">
|
|
您的浏览器不支持 HTML5 音频播放器
|
|
</audio>
|
|
</div>
|
|
|
|
{% elif resource.resource_type == 'video' %}
|
|
<div class="ratio ratio-16x9">
|
|
<video controls class="rounded-bottom">
|
|
<source src="{{ file_url }}" type="{{ resource.mime_type or 'video/mp4' }}">
|
|
您的浏览器不支持 HTML5 视频播放器
|
|
</video>
|
|
</div>
|
|
|
|
{% elif resource.resource_type == 'text' %}
|
|
{%- set _name = (resource.original_name or resource.filename or '') -%}
|
|
{%- set _ext = _name.rsplit('.', 1)[-1].lower() if '.' in _name else '' -%}
|
|
{%- set _mime = (resource.mime_type or '')|lower -%}
|
|
{%- set _is_word = _ext in ('doc', 'docx')
|
|
or 'wordprocessingml' in _mime
|
|
or _mime == 'application/msword' -%}
|
|
{% if _is_word %}
|
|
<div class="text-preview-toolbar p-2 border-bottom d-flex gap-2 align-items-center">
|
|
<i class="bi bi-file-earmark-word text-primary"></i>
|
|
<span class="small text-muted">Word 文档预览</span>
|
|
</div>
|
|
<div class="word-preview p-4 bg-white" id="textContent"
|
|
style="max-height:75vh;overflow:auto;line-height:1.7">
|
|
<div class="text-center py-4 text-muted">
|
|
<div class="spinner-border spinner-border-sm me-2"></div>加载中…
|
|
</div>
|
|
</div>
|
|
{% else %}
|
|
<div class="text-preview-toolbar p-2 border-bottom d-flex gap-2">
|
|
<button class="btn btn-sm btn-outline-secondary" onclick="changeFont(1)">A+</button>
|
|
<button class="btn btn-sm btn-outline-secondary" onclick="changeFont(-1)">A-</button>
|
|
<button class="btn btn-sm btn-outline-secondary" onclick="toggleWrap()">换行</button>
|
|
</div>
|
|
<div class="text-preview p-4" id="textContent">
|
|
<div class="text-center py-4 text-muted">
|
|
<div class="spinner-border spinner-border-sm me-2"></div>加载中…
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
|
|
{% else %}
|
|
<div class="d-flex flex-column align-items-center justify-content-center p-5 text-muted">
|
|
<i class="bi bi-file-earmark fs-1 mb-2"></i>
|
|
<p>该文件类型暂不支持在线预览</p>
|
|
<a href="{{ url_for('resources.download', resource_id=resource.id) }}"
|
|
class="btn btn-primary mt-2">
|
|
<i class="bi bi-download me-1"></i>下载文件
|
|
</a>
|
|
</div>
|
|
{% endif %}
|
|
|
|
{% else %}
|
|
<div class="d-flex flex-column align-items-center justify-content-center p-5 text-muted">
|
|
<i class="bi bi-hourglass fs-1 mb-2"></i>
|
|
<p>文件尚未就绪</p>
|
|
</div>
|
|
{% endif %}
|
|
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
{% endblock %}
|
|
|
|
{% block scripts %}
|
|
<script>
|
|
// ── 文本内容加载 ────────────────────────────────────────────────────────────
|
|
{% if resource.resource_type == 'text' and resource.file_path and resource.download_status in ('na','done') %}
|
|
{%- set _name = (resource.original_name or resource.filename or '') -%}
|
|
{%- set _ext = _name.rsplit('.', 1)[-1].lower() if '.' in _name else '' -%}
|
|
{%- set _mime = (resource.mime_type or '')|lower -%}
|
|
{%- set _is_word = _ext in ('doc', 'docx')
|
|
or 'wordprocessingml' in _mime
|
|
or _mime == 'application/msword' -%}
|
|
{% if _is_word %}
|
|
// Word 文档:服务端已转换为 HTML,直接渲染
|
|
fetch("{{ url_for('resources.preview', resource_id=resource.id) }}")
|
|
.then(r => r.text())
|
|
.then(html => {
|
|
const container = document.getElementById('textContent');
|
|
container.innerHTML = html;
|
|
// 让文档内的图片自适应宽度
|
|
container.querySelectorAll('img').forEach(img => {
|
|
img.style.maxWidth = '100%';
|
|
img.style.height = 'auto';
|
|
});
|
|
// 表格基础样式
|
|
container.querySelectorAll('table').forEach(t => {
|
|
t.classList.add('table', 'table-bordered');
|
|
});
|
|
})
|
|
.catch(() => {
|
|
document.getElementById('textContent').innerHTML =
|
|
'<div class="text-danger p-3">文档加载失败</div>';
|
|
});
|
|
{% else %}
|
|
fetch("{{ url_for('resources.preview', resource_id=resource.id) }}")
|
|
.then(r => r.text())
|
|
.then(text => {
|
|
const pre = document.createElement('pre');
|
|
pre.id = 'textPre';
|
|
pre.style.cssText = 'margin:0;font-family:monospace;font-size:14px;white-space:pre-wrap;word-break:break-all';
|
|
pre.textContent = text;
|
|
document.getElementById('textContent').innerHTML = '';
|
|
document.getElementById('textContent').appendChild(pre);
|
|
})
|
|
.catch(() => {
|
|
document.getElementById('textContent').innerHTML =
|
|
'<div class="text-danger p-3">文件加载失败</div>';
|
|
});
|
|
|
|
let fontSize = 14;
|
|
function changeFont(delta) {
|
|
fontSize = Math.max(10, Math.min(24, fontSize + delta));
|
|
const pre = document.getElementById('textPre');
|
|
if (pre) pre.style.fontSize = fontSize + 'px';
|
|
}
|
|
function toggleWrap() {
|
|
const pre = document.getElementById('textPre');
|
|
if (!pre) return;
|
|
pre.style.whiteSpace = pre.style.whiteSpace === 'pre' ? 'pre-wrap' : 'pre';
|
|
}
|
|
{% endif %}
|
|
{% endif %}
|
|
|
|
// ── 下载进度轮询 ────────────────────────────────────────────────────────────
|
|
{% if resource.download_status in ('pending','downloading') %}
|
|
(function poll() {
|
|
fetch("{{ url_for('resources.progress', resource_id=resource.id) }}")
|
|
.then(r => r.json())
|
|
.then(data => {
|
|
const bar = document.getElementById('progressBar');
|
|
const pct = document.getElementById('progressPct');
|
|
if (bar) bar.style.width = data.progress + '%';
|
|
if (pct) pct.textContent = data.progress;
|
|
|
|
if (data.status === 'done') {
|
|
location.reload();
|
|
} else if (data.status === 'failed') {
|
|
const errEl = document.getElementById('downloadError');
|
|
if (errEl) errEl.textContent = data.error || '下载失败';
|
|
const badge = document.getElementById('statusBadge');
|
|
if (badge) { badge.className = 'badge bg-danger'; badge.textContent = '下载失败'; }
|
|
} else {
|
|
setTimeout(poll, 2000);
|
|
}
|
|
})
|
|
.catch(() => setTimeout(poll, 3000));
|
|
})();
|
|
{% endif %}
|
|
</script>
|
|
{% endblock %}
|