This commit is contained in:
@@ -25,7 +25,9 @@
|
||||
"Bash(git -C /d/3.Project/HTY1024/ai-app-database commit -m 'fix\\(ci\\): 将 Secret 名从 GITEA_TOKEN 改为 REGISTRY_TOKEN:*)",
|
||||
"Bash(git -C /d/3.Project/HTY1024/ai-app-database log --oneline -3)",
|
||||
"Bash(git -C /d/3.Project/HTY1024/ai-app-database add scripts/ .gitattributes)",
|
||||
"Bash(git -C /d/3.Project/HTY1024/ai-app-database log --oneline -4)"
|
||||
"Bash(git -C /d/3.Project/HTY1024/ai-app-database log --oneline -4)",
|
||||
"Bash(xargs ls:*)",
|
||||
"Bash(find /d/3.Project/HTY1024/ai-app-database/app -name \"__init__.py\" -exec head -30 {} \\\\;)"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
+32
-1
@@ -404,7 +404,7 @@ def progress(resource_id):
|
||||
@resources_bp.route('/<int:resource_id>/preview')
|
||||
@login_required
|
||||
def preview(resource_id):
|
||||
"""直接返回文件内容(用于 iframe 文本预览)"""
|
||||
"""直接返回文件内容(用于文本/Word 文档预览)"""
|
||||
res = db.get_or_404(Resource, resource_id)
|
||||
if res.user_id != current_user.id and not current_user.is_admin:
|
||||
abort(403)
|
||||
@@ -413,6 +413,37 @@ def preview(resource_id):
|
||||
abs_path = os.path.join(current_app.root_path, 'static', res.file_path)
|
||||
if not os.path.exists(abs_path):
|
||||
abort(404)
|
||||
|
||||
ext = (res.original_name or res.filename or '').rsplit('.', 1)[-1].lower()
|
||||
|
||||
# Word 文档(.docx)使用 mammoth 转换为 HTML
|
||||
if ext == 'docx':
|
||||
try:
|
||||
import mammoth
|
||||
with open(abs_path, 'rb') as f:
|
||||
result = mammoth.convert_to_html(f)
|
||||
return Response(result.value, mimetype='text/html; charset=utf-8')
|
||||
except ImportError:
|
||||
return Response(
|
||||
'<div style="padding:1rem;color:#dc3545">'
|
||||
'服务器未安装 mammoth 库,无法预览 .docx 文件。请运行 '
|
||||
'<code>pip install mammoth</code> 后重试。</div>',
|
||||
mimetype='text/html; charset=utf-8'
|
||||
)
|
||||
except Exception as e:
|
||||
return Response(
|
||||
f'<div style="padding:1rem;color:#dc3545">无法解析 Word 文档:{e}</div>',
|
||||
mimetype='text/html; charset=utf-8'
|
||||
)
|
||||
|
||||
# 旧版 .doc 二进制格式不支持在线预览
|
||||
if ext == 'doc':
|
||||
return Response(
|
||||
'<div style="padding:1rem;color:#6c757d">'
|
||||
'不支持在线预览旧版 .doc 文件,请下载后查看,或将文件转换为 .docx 格式。</div>',
|
||||
mimetype='text/html; charset=utf-8'
|
||||
)
|
||||
|
||||
try:
|
||||
with open(abs_path, 'r', encoding='utf-8', errors='replace') as f:
|
||||
content = f.read()
|
||||
|
||||
@@ -155,6 +155,20 @@
|
||||
</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 '' -%}
|
||||
{% if _ext in ('doc', 'docx') %}
|
||||
<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>
|
||||
@@ -165,6 +179,7 @@
|
||||
<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">
|
||||
@@ -195,6 +210,30 @@
|
||||
<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 '' -%}
|
||||
{% if _ext in ('doc', 'docx') %}
|
||||
// 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 => {
|
||||
@@ -222,6 +261,7 @@ function toggleWrap() {
|
||||
pre.style.whiteSpace = pre.style.whiteSpace === 'pre' ? 'pre-wrap' : 'pre';
|
||||
}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
// ── 下载进度轮询 ────────────────────────────────────────────────────────────
|
||||
{% if resource.download_status in ('pending','downloading') %}
|
||||
|
||||
@@ -15,6 +15,8 @@ MIME_TYPE_MAP = {
|
||||
'text/xml': 'text',
|
||||
'application/json': 'text',
|
||||
'application/xml': 'text',
|
||||
'application/msword': 'text',
|
||||
'application/vnd.openxmlformats-officedocument.wordprocessingml.document': 'text',
|
||||
# 图片
|
||||
'image/jpeg': 'image',
|
||||
'image/png': 'image',
|
||||
@@ -45,6 +47,7 @@ MIME_TYPE_MAP = {
|
||||
EXT_TYPE_MAP = {
|
||||
'txt': 'text', 'md': 'text', 'csv': 'text', 'json': 'text',
|
||||
'xml': 'text', 'log': 'text', 'html': 'text', 'htm': 'text',
|
||||
'doc': 'text', 'docx': 'text',
|
||||
'jpg': 'image', 'jpeg': 'image', 'png': 'image', 'gif': 'image',
|
||||
'webp': 'image', 'bmp': 'image', 'svg': 'image', 'ico': 'image',
|
||||
'mp3': 'audio', 'wav': 'audio', 'ogg': 'audio', 'flac': 'audio',
|
||||
|
||||
@@ -29,7 +29,7 @@ class Config:
|
||||
WTF_CSRF_TIME_LIMIT = 3600
|
||||
|
||||
# 允许的文件类型
|
||||
ALLOWED_TEXT_EXT = {'txt', 'md', 'csv', 'json', 'xml', 'log', 'html', 'htm'}
|
||||
ALLOWED_TEXT_EXT = {'txt', 'md', 'csv', 'json', 'xml', 'log', 'html', 'htm', 'doc', 'docx'}
|
||||
ALLOWED_IMAGE_EXT = {'jpg', 'jpeg', 'png', 'gif', 'webp', 'bmp', 'svg', 'ico'}
|
||||
ALLOWED_AUDIO_EXT = {'mp3', 'wav', 'ogg', 'flac', 'm4a', 'aac', 'wma'}
|
||||
ALLOWED_VIDEO_EXT = {'mp4', 'webm', 'avi', 'mkv', 'mov', 'wmv', 'flv', 'm4v'}
|
||||
|
||||
@@ -13,5 +13,6 @@ requests==2.32.3
|
||||
python-dotenv==1.0.1
|
||||
python-magic-bin==0.4.14; sys_platform == 'win32'
|
||||
python-magic==0.4.27; sys_platform != 'win32'
|
||||
mammoth==1.8.0
|
||||
# WSGI 服务器(容器生产环境)
|
||||
gunicorn==23.0.0
|
||||
|
||||
Reference in New Issue
Block a user