From fe911592e0db2df5de884eeecd27d1f762612135 Mon Sep 17 00:00:00 2001 From: Codex Agent Date: Wed, 4 Feb 2026 13:51:20 +0800 Subject: [PATCH] fix: resolve server deployment image 404 errors and enhance admin UI MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add dedicated image serving API endpoint (/admin/images/) with security whitelist - Update image paths from /static/ to /fs-ai-asistant/api/workflow/lawrisk/admin/images/ - Add permit import sample file download endpoint - Enhance import wizard UI with template/sample preview section - Add risk count column to unbound permits table - Filter out "不涉及" (not applicable) theme from theme list - Improve permit import UX with better visual organization This ensures images load correctly in server deployments (nginx, gunicorn) by using the same API prefix as other admin resources, avoiding static file routing issues. Co-Authored-By: Claude Sonnet 4.5 --- lawrisk/api/v2.py | 40 +++++++++++ lawrisk/services/template_service.py | 6 ++ static/db_admin.html | 104 +++++++++++++++++---------- 3 files changed, 113 insertions(+), 37 deletions(-) diff --git a/lawrisk/api/v2.py b/lawrisk/api/v2.py index f30eaab..383c8d5 100644 --- a/lawrisk/api/v2.py +++ b/lawrisk/api/v2.py @@ -56,6 +56,7 @@ from lawrisk.services.auth_service import ( ) from lawrisk.services.template_service import ( get_permit_template_path, + get_permit_sample_path, get_permit_template_metadata, overwrite_permit_template, ) @@ -1078,6 +1079,45 @@ def admin_permit_import_template(): return jsonify({"success": False, "message": "模板文件暂时无法下载"}), 500 +@v2_bp.route('/admin/permit-import/sample', methods=['GET']) +def admin_permit_import_sample(): + """Provide the Excel import sample file for download.""" + sample_path = get_permit_sample_path() + + if not os.path.exists(sample_path): + return jsonify({"success": False, "message": "样表文件不存在,请联系管理员"}), 404 + + try: + return send_file( + sample_path, + as_attachment=True, + download_name='风险提示表(仅销售预包装食品备案,市场监管部门)(样表).xlsx', + ) + except Exception as exc: + print(f"admin_permit_import_sample error: {exc}") + return jsonify({"success": False, "message": "样表文件暂时无法下载"}), 500 + + +@v2_bp.route('/admin/images/', methods=['GET']) +def admin_image(filename): + """Serve admin UI image files.""" + # 安全检查:只允许特定的文件名,防止路径遍历攻击 + allowed_files = {'empty_table.png', 'sample_table.png'} + if filename not in allowed_files: + return jsonify({"success": False, "message": "文件不存在"}), 404 + + image_path = os.path.join(_project_root(), 'static', 'images', filename) + + if not os.path.exists(image_path): + return jsonify({"success": False, "message": f"图片文件 {filename} 不存在"}), 404 + + try: + return send_file(image_path, mimetype='image/png') + except Exception as exc: + print(f"admin_image error for {filename}: {exc}") + return jsonify({"success": False, "message": "图片文件暂时无法加载"}), 500 + + def _build_import_preview_response(session_token: str): """Internal helper to build preview response JSON.""" try: diff --git a/lawrisk/services/template_service.py b/lawrisk/services/template_service.py index 8deef8f..a097668 100644 --- a/lawrisk/services/template_service.py +++ b/lawrisk/services/template_service.py @@ -10,6 +10,7 @@ from typing import Any, Dict PROJECT_ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..")) TEMPLATE_DIR = os.path.join(PROJECT_ROOT, "data", "template") PERMIT_TEMPLATE_FILENAME = "风险提示表 模板.xlsx" +PERMIT_SAMPLE_FILENAME = "风险提示表(仅销售预包装食品备案,市场监管部门)(样表).xlsx" TEMPLATE_META_FILENAME = "template_meta.json" @@ -18,6 +19,11 @@ def get_permit_template_path() -> str: return os.path.join(TEMPLATE_DIR, PERMIT_TEMPLATE_FILENAME) +def get_permit_sample_path() -> str: + """Return the absolute path to the permit import sample file.""" + return os.path.join(TEMPLATE_DIR, PERMIT_SAMPLE_FILENAME) + + def _meta_path() -> str: return os.path.join(TEMPLATE_DIR, TEMPLATE_META_FILENAME) diff --git a/static/db_admin.html b/static/db_admin.html index 048307d..5467b78 100644 --- a/static/db_admin.html +++ b/static/db_admin.html @@ -2770,6 +2770,63 @@ 📥 许可导入 + + +
+
+
+ 💡 + 导入模板与样表示例 +
+ +
+
+
+
+ 1 + 空表 (空模板) +
+
+ 空表 +
+
+
+
+ 2 + 样表示例 (含填报说明) +
+
+ 样表示例 +
+
+
+
+
📄
@@ -4495,13 +4552,6 @@ html += '

📄 上传 Excel

'; html += '
'; html += ''; - html += ''; if (state.sessionId) { const sheetCount = state.sheetSummaries ? state.sheetSummaries.length : 0; html += `
当前会话:${escapeHtml(state.filename || '(未命名)')} | Sheet ${sheetCount} 个 | 风险 ${state.totalRows || 0} 条
`; @@ -4578,36 +4628,6 @@ html += '
正在准备预览数据...
'; } - // 添加样表示例 - html += '
'; - html += '
💡 导入模板与样表示例
'; - html += getSampleExcelHtml(); - html += '
'; - - function getSampleExcelHtml() { - return ` -
-
-
- 1 - 空表 (空模板) -
-
- 空表 -
-
-
-
- 2 - 样表示例 (含填报说明) -
-
- 样表示例 -
-
-
`; - } - const nextDisabled = !state.sessionId || state.selectedSheets.size === 0 || state.uploading || state.previewLoading; html += '
'; html += '
提示:系统将根据配置规则自动匹配主题,进入下一步预览结果。
'; @@ -7659,6 +7679,7 @@ 许可(备案)事项名称 实施层级 + 提示条款数量 @@ -7676,10 +7697,19 @@ ? "background: #e0f2fe; color: #0369a1;" // Blue for Municipal : "background: #fee2e2; color: #b91c1c;"; // Red for District + // Add risk count display logic + const riskCount = typeof p.risk_count === 'number' ? p.risk_count : 0; + const riskBadgeStyle = riskCount > 0 + ? "background: #fef3c7; color: #92400e;" // Amber - has risks + : "background: #f3f4f6; color: #6b7280;"; // Gray - no risks + html += ` ${escapeHtml(p.permit_name)} ${implLevel} + + ${riskCount} + `; });