feat: 优化事项过滤逻辑与后台管理界面交互

This commit is contained in:
Codex Agent 2025-12-26 09:26:10 +08:00
parent ea55825080
commit b532c46dc1
3 changed files with 192 additions and 17 deletions

View File

@ -932,20 +932,43 @@ def admin_permit_import_upload():
filename = file_storage.filename or 'import.xlsx'
file_bytes = file_storage.read()
user = get_current_user() or {}
user_role = user.get("role")
is_super_admin = (user_role == "admin")
uploaded_by = user.get("display_name") or user.get("username") or user.get("id")
content_type = file_storage.mimetype or "application/octet-stream"
binding_mode = (request.form.get("binding_mode") or "auto").strip().lower()
if binding_mode not in {"none", "department", "auto", "municipal", "district"}:
binding_mode = "auto"
bound_department_id = (request.form.get("bound_department_id") or "").strip() or None
if not bound_department_id:
bound_department_id = (user.get("department") or {}).get("id")
if bound_department_id and binding_mode == "auto":
# 只要用户绑定了部门,导入时就强制绑定到该部门所属的区域
# 避免市级管理员上传的数据根据 Sheet 名称意外流向其他区划
binding_mode = "department"
uploader_department_id = (user.get("department") or {}).get("id")
if not is_super_admin:
# Non-super admins are forced to use their own department's region
if not uploader_department_id:
return jsonify({"success": False, "message": "该账号未绑定服务部门,无法执行导入"}), 403
bound_department_id = uploader_department_id
binding_mode = "department"
else:
# Super admins default to their own department if none specified, but can use 'auto'
if not bound_department_id:
bound_department_id = uploader_department_id
# If a super admin manually chose a department OR has one and left it as auto,
# but they didn't explicitly ask for 'auto', we might want to default to department.
# However, the user said "super admins can manually specify", so 'auto' vs 'department'
# should probably be respected if they chose it.
# Existing logic forced 'department' if ANY bound_dept was present.
# We relax this for super admins to allow 'auto'.
if bound_department_id and binding_mode == "auto":
# If the super admin didn't explicitly request 'auto' in the form,
# maybe we still want to default to department?
# Actually, usually 'auto' is the frontend default.
# To allow super admin to use 'auto', we only force 'department' if THEY didn't send 'auto'.
# But the form usually sends 'auto'.
pass
if not file_bytes:
return jsonify({"success": False, "message": "上传的文件为空"}), 400
@ -1114,10 +1137,29 @@ def admin_reimport_permit_file(file_id: str):
return jsonify({"success": False, "message": "file_id 不能为空"}), 400
user = get_current_user() or {}
user_role = user.get("role")
is_super_admin = (user_role == "admin")
requested_by = user.get("display_name") or user.get("username") or user.get("id")
uploader_department_id = (user.get("department") or {}).get("id")
binding_mode = "auto"
bound_department_id = None
if not is_super_admin:
if not uploader_department_id:
return jsonify({"success": False, "message": "该账号未绑定服务部门,无法执行重新导入"}), 403
bound_department_id = uploader_department_id
binding_mode = "department"
try:
data = start_import_session_from_file(file_id, requested_by=requested_by)
data = start_import_session_from_file(
file_id,
requested_by=requested_by,
uploader_department_id=uploader_department_id,
bound_department_id=bound_department_id,
binding_mode=binding_mode
)
return jsonify({"success": True, "data": data})
except ValueError as exc:
return jsonify({"success": False, "message": str(exc)}), 404

View File

@ -1779,6 +1779,26 @@ def describe_permit_import_session(session_id: str) -> Dict[str, Any]:
sheet_risk_total += len(permit_rows)
total_risks += len(permit_rows)
permit_risks = []
common_meta = {
"responsible_contact": "",
"jurisdiction_scope": "",
"unit_name": "",
"permit_status": ""
}
for row in permit_rows:
permit_risks.append({
"serial_number": row.get("serial_number"),
"risk_content": row.get("risk_content"),
"legal_basis": row.get("legal_basis"),
"summary": row.get("summary"),
"remark": row.get("remark"),
})
for key in common_meta:
if not common_meta[key] and row.get(key):
common_meta[key] = row.get(key)
# Try to resolve themes via rules (Base Table Logic)
resolved_theme_names = _resolve_themes_for_permit(conn, permit_name)
@ -1791,6 +1811,8 @@ def describe_permit_import_session(session_id: str) -> Dict[str, Any]:
"is_new": permit_name in new_permits,
"default_theme_names": [],
"resolved_theme_names": resolved_theme_names,
"risks": permit_risks,
"common_meta": common_meta,
"sample_serial": permit_rows[0].get("serial_number") if permit_rows else None,
"sample_risk": permit_rows[0].get("risk_content") if permit_rows else "",
}
@ -4124,7 +4146,14 @@ def delete_stored_permit_file(file_id: str) -> bool:
return cur.rowcount > 0
def start_import_session_from_file(file_id: str, *, requested_by: Optional[str] = None) -> Dict[str, Any]:
def start_import_session_from_file(
file_id: str,
*,
requested_by: Optional[str] = None,
uploader_department_id: Optional[str] = None,
bound_department_id: Optional[str] = None,
binding_mode: str = "auto",
) -> Dict[str, Any]:
"""Create a fresh import session using an archived permit file."""
normalized = _clean_text(file_id)
if not normalized:
@ -4160,6 +4189,9 @@ def start_import_session_from_file(file_id: str, *, requested_by: Optional[str]
filename=filename or "许可导入.xlsx",
content_type=content_type or "application/octet-stream",
uploaded_by=effective_uploader,
uploader_department_id=uploader_department_id,
bound_department_id=bound_department_id,
binding_mode=binding_mode,
)

View File

@ -563,6 +563,79 @@
color: #b91c1c;
}
.preview-permit-details-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 6px 14px;
background: #f8fafc;
padding: 10px;
border-radius: 8px;
font-size: 13px;
}
.detail-item {
color: #475569;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.detail-item span {
color: #64748b;
font-weight: 500;
}
.preview-risk-scroll-area {
max-height: 240px;
overflow-y: auto;
border: 1px solid #f1f5f9;
border-radius: 8px;
background: #fff;
padding: 2px;
}
.preview-risk-item {
padding: 10px;
border-bottom: 1px solid #f8fafc;
display: flex;
gap: 12px;
font-size: 13px;
line-height: 1.5;
}
.preview-risk-item:last-child {
border-bottom: none;
}
.risk-serial {
flex-shrink: 0;
width: 24px;
height: 24px;
background: #f1f5f9;
color: #64748b;
border-radius: 6px;
display: flex;
align-items: center;
justify-content: center;
font-size: 11px;
font-weight: 600;
}
.risk-body {
flex: 1;
}
.risk-text {
color: #1e293b;
margin-bottom: 4px;
}
.risk-basis {
font-size: 12px;
color: #64748b;
font-style: italic;
}
.preview-permit-meta {
font-size: 12px;
color: #4b5563;
@ -1569,7 +1642,7 @@
.preview-permit-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
grid-template-columns: repeat(auto-fit, minmax(380px, 1fr));
gap: 16px;
}
@ -4509,20 +4582,48 @@
const badgeClass = permit.is_duplicate ? 'duplicate' : (permit.is_new ? 'new' : '');
const badgeLabel = permit.is_duplicate ? '覆盖现有' : (permit.is_new ? '新增事项' : '已有事项');
const meta = permit.common_meta || {};
const risks = permit.risks || [];
html += `<div class="preview-permit-card ${badgeClass}">`;
html += '<div class="preview-permit-header">';
html += `<div style="display:flex;justify-content:space-between;align-items:center;gap:8px;"><strong>${escapeHtml(permit.permit_name)}</strong><span class="permit-badge ${badgeClass}">${badgeLabel}</span></div>`;
html += `<span class="muted-text">风险 ${permit.risk_count || 0} 条</span>`;
// Excel themes display removed as they are no longer in the template
if (permit.sample_risk) {
html += `<span class="muted-text">示例风险:${escapeHtml(truncateText(permit.sample_risk, 80))}</span>`;
}
html += `<div style="display:flex;justify-content:space-between;align-items:center;gap:8px;margin-bottom:4px;">
<strong style="font-size:15px; color:#1e293b;">${escapeHtml(permit.permit_name)}</strong>
<span class="permit-badge ${badgeClass}">${badgeLabel}</span>
</div>`;
// Detailed Metadata
html += '<div class="preview-permit-details-grid">';
if (meta.unit_name) html += `<div class="detail-item"><span>单位:</span>${escapeHtml(meta.unit_name)}</div>`;
if (meta.responsible_contact) html += `<div class="detail-item"><span>联系人:</span>${escapeHtml(meta.responsible_contact)}</div>`;
if (meta.permit_status) html += `<div class="detail-item"><span>许可情况:</span>${escapeHtml(meta.permit_status)}</div>`;
if (meta.jurisdiction_scope) html += `<div class="detail-item" style="grid-column: span 2;"><span>管辖范围:</span>${escapeHtml(meta.jurisdiction_scope)}</div>`;
html += '</div>';
html += `<div class="muted-text" style="margin-top:8px;">共 ${permit.risk_count || 0} 条风险提示</div>`;
html += '</div>'; // end preview-permit-header
// Risk List
if (risks.length > 0) {
html += '<div class="preview-risk-scroll-area">';
risks.forEach((r, idx) => {
html += `
<div class="preview-risk-item">
<div class="risk-serial">${escapeHtml(r.serial_number || (idx + 1))}</div>
<div class="risk-body">
<div class="risk-text">${escapeHtml(r.risk_content)}</div>
${r.legal_basis ? `<div class="risk-basis">依据:${escapeHtml(r.legal_basis)}</div>` : ''}
</div>
</div>
`;
});
html += '</div>';
}
if (selectedThemes.length) {
html += `<div class="preview-permit-meta"><strong>预定绑定主题:</strong>${selectedThemes.map(t => `<span class="item-tag">${escapeHtml(t)}</span>`).join('')}</div>`;
html += `<div class="preview-permit-meta"><strong>绑定主题:</strong>${selectedThemes.map(t => `<span class="item-tag">${escapeHtml(t)}</span>`).join('')}</div>`;
} else {
html += '<div class="preview-permit-meta"><span class="import-error" style="padding:4px 8px;">尚未配置自动绑定规则,系统将默认设为“不涉及”。</span></div>';
html += '<div class="preview-permit-meta"><span class="import-error" style="padding:4px 8px; font-size:12px; margin-top:0;">尚未配置自动绑定规则,默认设为“不涉及”。</span></div>';
}
html += '</div>';