feat: 优化事项过滤逻辑与后台管理界面交互
This commit is contained in:
parent
ea55825080
commit
b532c46dc1
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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>';
|
||||
|
|
|
|||
Loading…
Reference in New Issue