Files
ai-app-database/app/templates/user/detail.html
T
huty 66ffa9679d
CI — Docker Build & Push / Build & Push Image (push) Failing after 2m28s
修复 word 文件预览问题
2026-04-28 12:45:54 +09:00

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 %}